赞
踩
ArcGIS Maps SDK for JavaScript 从 4.29
开始增加 RenderNode
类,可以添加数据以及操作 FBO(ManagedFBO)
;
通过操作 FBO,可以通过后处理实现很多效果,官方提供了几个示例,感兴趣可以看看。
本文介绍一下通过 FBO,实现锐化效果。
锐化效果应用还是比较广的,本文实现锐化效果初衷是偶然发现,同样是加载 3dtile 数据,
Cesium 效果会比 ArcGIS Maps SDK for JavaScript 清晰一些,初步认为是锐化。
经过测试发现,基本确认是锐化处理的原因。
本文包括核心代码、完整代码以及在线示例。
现在各种算法已经非常成熟,本文先通过高斯模糊,在经过锐化宣发实现效果,具体详见注释。
// The fragment shader program applying a greyscsale conversion const fshader = `#version 300 es precision highp float; out highp vec4 fragColor; in vec2 uv; uniform sampler2D u_Texture; // 锐化强度参数,范围通常是 0 到 1 const float u_Sharpness = 0.5; // 调整亮度,通常跟锐化强度相关 const float brightness = 2.0; // 先模糊后锐化,效果会比较柔和 void main() { // 获取纹理大小 vec2 u_Resolution = vec2(textureSize(u_Texture, 0)); vec4 color = texture(u_Texture, uv); // 高斯模糊内核 vec3 kernelRow1 = vec3(1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0); vec3 kernelRow2 = vec3(2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0); vec3 kernelRow3 = vec3(1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0); // 应用高斯内核进行模糊 vec3 blurredColor = texture(u_Texture, uv).rgb * kernelRow2[1]; // 中心权重 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i != 1 || j != 1) { // 跳过中心 vec2 offset = vec2(i - 1, j - 1) / u_Resolution; blurredColor += texture(u_Texture, uv + offset).rgb * (i == 0 ? kernelRow1[j] : (i == 1 ? kernelRow2[j] : kernelRow3[j])); } } } // 拉普拉斯算子内核 vec3 laplacianKernelRow1 = vec3(0.0, -1.0, 0.0); vec3 laplacianKernelRow2 = vec3(-1.0, 4.0, -1.0); vec3 laplacianKernelRow3 = vec3(0.0, -1.0, 0.0); // 应用拉普拉斯内核 // 中心权重 vec3 laplacianColor = texture(u_Texture, uv).rgb * laplacianKernelRow2[1]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i != 1 || j != 1) { // 跳过中心 vec2 offset = vec2(i - 1, j - 1) / u_Resolution; laplacianColor += texture(u_Texture, uv + offset).rgb * (i == 0 ? laplacianKernelRow1[j] : (i == 1 ? laplacianKernelRow2[j] : laplacianKernelRow3[j])); } } } // 锐化:从原始颜色中减去拉普拉斯模糊的颜色 vec3 sharpenedColor = color.rgb - (blurredColor - laplacianColor) * u_Sharpness; // 限制颜色值并应用 sharpenedColor = clamp(sharpenedColor, 0.0, 1.0); fragColor = vec4(sharpenedColor*brightness, 1.0); } `;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/> <title>Custom RenderNode - 锐化效果 | Sample | ArcGIS Maps SDK for JavaScript 4.29</title> <link rel="stylesheet" href="./4.29/esri/themes/light/main.css"/> <script src="./4.29/init.js"></script> <script type="module" src="https://js.arcgis.com/calcite-components/2.5.1/calcite.esm.js"></script> <link rel="stylesheet" type="text/css" href="https://js.arcgis.com/calcite-components/2.5.1/calcite.css"/> <style> html, body, #viewDiv { padding: 0; margin: 0; height: 100%; width: 100%; } </style> <script> require(["esri/Map", "esri/views/SceneView", "esri/views/3d/webgl/RenderNode", "esri/layers/IntegratedMesh3DTilesLayer"], function ( Map, SceneView, RenderNode, IntegratedMesh3DTilesLayer ) { const view = new SceneView({ container: "viewDiv", map: new Map({basemap: "satellite"}) }); const layer = new IntegratedMesh3DTilesLayer({ url: "http://openlayers.vip/cesium/3dtile/xianggang_1.1/tileset.json", title: "Utrecht Integrated Mesh 3D Tiles" }); view.map.add(layer); layer.when(function () { view.extent = layer.fullExtent; }); // Create and compile WebGL shader objects function createShader(gl, src, type) { const shader = gl.createShader(type); gl.shaderSource(shader, src); gl.compileShader(shader); return shader; } // Create and link WebGL program object function createProgram(gl, vsSource, fsSource) { const program = gl.createProgram(); if (!program) { console.error("Failed to create program"); } const vertexShader = createShader(gl, vsSource, gl.VERTEX_SHADER); const fragmentShader = createShader(gl, fsSource, gl.FRAGMENT_SHADER); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); const success = gl.getProgramParameter(program, gl.LINK_STATUS); if (!success) { // covenience console output to help debugging shader code console.error(`Failed to link program: error ${gl.getError()}, info log: ${gl.getProgramInfoLog(program)}, vertex: ${gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)}, fragment: ${gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)} vertex info log: ${gl.getShaderInfoLog(vertexShader)}, fragment info log: ${gl.getShaderInfoLog(fragmentShader)}`); } return program; } // Derive a new subclass from RenderNode called LuminanceRenderNode const LuminanceRenderNode = RenderNode.createSubclass({ constructor: function () { // consumes and produces define the location of the the render node in the render pipeline this.consumes = {required: ["composite-color"]}; this.produces = "composite-color"; }, // Ensure resources are cleaned up when render node is removed destroy() { if (this.program) { this.gl?.deleteProgram(this.program); } if (this.positionBuffer) { this.gl?.deleteBuffer(this.positionBuffer); } if (this.vao) { this.gl?.deleteVertexArray(this.vao); } }, properties: { // Define getter and setter for class member enabled enabled: { get: function () { return this.produces != null; }, set: function (value) { // Setting produces to null disables the render node this.produces = value ? "composite-color" : null; this.requestRender(); } } }, render(inputs) { // The field input contains all available framebuffer objects // We need color texture from the composite render target const input = inputs.find(({name}) => name === "composite-color"); const color = input.getTexture(); // Acquire the composite framebuffer object, and bind framebuffer as current target const output = this.acquireOutputFramebuffer(); const gl = this.gl; // Clear newly acquired framebuffer gl.clearColor(0, 0, 0, 1); gl.colorMask(true, true, true, true); gl.clear(gl.COLOR_BUFFER_BIT); // Prepare custom shaders and geometry for screenspace rendering this.ensureShader(this.gl); this.ensureScreenSpacePass(gl); // Bind custom program gl.useProgram(this.program); // Use composite-color render target to be modified in the shader gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, color.glName); gl.uniform1i(this.textureUniformLocation, 0); // Issue the render call for a screen space render pass gl.bindVertexArray(this.vao); gl.drawArrays(gl.TRIANGLES, 0, 3); // use depth from input on output framebuffer output.attachDepth(input.getAttachment(gl.DEPTH_STENCIL_ATTACHMENT)); return output; }, program: null, textureUniformLocation: null, positionLocation: null, vao: null, positionBuffer: null, // Setup screen space filling triangle ensureScreenSpacePass(gl) { if (this.vao) { return; } this.vao = gl.createVertexArray(); gl.bindVertexArray(this.vao); this.positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); const vertices = new Float32Array([-1.0, -1.0, 3.0, -1.0, -1.0, 3.0]); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); gl.vertexAttribPointer(this.positionLocation, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.positionLocation); gl.bindVertexArray(null); }, // Setup custom shader programs ensureShader(gl) { if (this.program != null) { return; } // The vertex shader program // Sets position from 0..1 for fragment shader // Forwards texture coordinates to fragment shader const vshader = `#version 300 es in vec2 position; out vec2 uv; void main() { gl_Position = vec4(position, 0.0, 1.0); uv = position * 0.5 + vec2(0.5); }`; // The fragment shader program applying a greyscsale conversion const fshader = `#version 300 es precision highp float; out highp vec4 fragColor; in vec2 uv; uniform sampler2D u_Texture; // 锐化强度参数,范围通常是 0 到 1 const float u_Sharpness = 0.5; // 调整亮度,通常跟锐化强度相关 const float brightness = 2.0; // 先模糊后锐化,效果会比较柔和 void main() { // 获取纹理大小 vec2 u_Resolution = vec2(textureSize(u_Texture, 0)); vec4 color = texture(u_Texture, uv); // 高斯模糊内核 vec3 kernelRow1 = vec3(1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0); vec3 kernelRow2 = vec3(2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0); vec3 kernelRow3 = vec3(1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0); // 应用高斯内核进行模糊 vec3 blurredColor = texture(u_Texture, uv).rgb * kernelRow2[1]; // 中心权重 for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i != 1 || j != 1) { // 跳过中心 vec2 offset = vec2(i - 1, j - 1) / u_Resolution; blurredColor += texture(u_Texture, uv + offset).rgb * (i == 0 ? kernelRow1[j] : (i == 1 ? kernelRow2[j] : kernelRow3[j])); } } } // 拉普拉斯算子内核 vec3 laplacianKernelRow1 = vec3(0.0, -1.0, 0.0); vec3 laplacianKernelRow2 = vec3(-1.0, 4.0, -1.0); vec3 laplacianKernelRow3 = vec3(0.0, -1.0, 0.0); // 应用拉普拉斯内核 // 中心权重 vec3 laplacianColor = texture(u_Texture, uv).rgb * laplacianKernelRow2[1]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (i != 1 || j != 1) { // 跳过中心 vec2 offset = vec2(i - 1, j - 1) / u_Resolution; laplacianColor += texture(u_Texture, uv + offset).rgb * (i == 0 ? laplacianKernelRow1[j] : (i == 1 ? laplacianKernelRow2[j] : laplacianKernelRow3[j])); } } } // 锐化:从原始颜色中减去拉普拉斯模糊的颜色 vec3 sharpenedColor = color.rgb - (blurredColor - laplacianColor) * u_Sharpness; // 限制颜色值并应用 sharpenedColor = clamp(sharpenedColor, 0.0, 1.0); fragColor = vec4(sharpenedColor*brightness, 1.0); } `; this.program = createProgram(gl, vshader, fshader); this.textureUniformLocation = gl.getUniformLocation(this.program, "colorTex"); this.positionLocation = gl.getAttribLocation(this.program, "position"); } }); // Initializes the new custom render node and connects to SceneView const luminanceRenderNode = new LuminanceRenderNode({view}); // Toggle button to enable/disable the custom render node const renderNodeToggle = document.getElementById("renderNodeToggle"); renderNodeToggle.addEventListener("calciteSwitchChange", () => { luminanceRenderNode.enabled = !luminanceRenderNode.enabled; }); view.ui.add("renderNodeUI", "top-right"); }); </script> </head> <body> <calcite-block open heading="Toggle Render Node" id="renderNodeUI"> <calcite-label layout="inline"> Color <calcite-switch id="renderNodeToggle" checked></calcite-switch> Grayscale </calcite-label> </calcite-block> <div id="viewDiv"></div> </body> </html>
ArcGIS Maps SDK for JavaScript 在线示例:锐化效果
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。