赞
踩
ArcGIS Maps SDK for JavaScript 从 4.29
开始增加 RenderNode
类,可以添加数据以及操作 FBO(ManagedFBO)
;
通过操作 FBO,可以通过后处理实现很多效果,官方提供了几个示例,感兴趣可以看看。
本文介绍一下通过 FBO,实现高斯模糊
效果。
本文包括核心代码、完整代码以及在线示例。
现在各种算法已经非常成熟,本文通过高斯模糊实现效果,具体详见注释。
// The fragment shader program applying a greyscsale conversion const fshader = `#version 300 es // 定义精度 precision highp float; // 输出颜色 out lowp vec4 fragColor; // 输入纹理坐标 in vec2 uv; // 纹理采样器 uniform sampler2D colorTex; void main() { // 定义一个常量偏移量 const float offset = 1.0 / 300.0; // 定义一个包含9个偏移量的数组,用于采样纹理的9个不同位置 vec2 offsets[9] = vec2[]( vec2(-offset, offset), // 左上 vec2( 0.0f, offset), // 正上 vec2( offset, offset), // 右上 vec2(-offset, 0.0f), // 左 vec2( 0.0f, 0.0f), // 中 vec2( offset, 0.0f), // 右 vec2(-offset, -offset), // 左下 vec2( 0.0f, -offset), // 正下 vec2( offset, -offset) // 右下 ); // 定义一个包含9个权重的数组,用于加权采样结果 float kernel[9] = float[]( 1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, 2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0 ); // 定义一个数组来存储9个采样结果 vec3 sampleTex[9]; // 循环遍历每个偏移量,采样纹理并存储结果 for(int i = 0; i < 9; i++) { sampleTex[i] = vec3(texture(colorTex, uv + offsets[i])); } // 初始化颜色 vec3 col = vec3(0.0); // 循环遍历每个权重,将采样结果与对应的权重相乘并累加到最终颜色中 for(int i = 0; i < 9; i++){ col += sampleTex[i] * kernel[i]; } // 将最终颜色转换为纹理输出 fragColor = vec4(col, 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"], function ( Map, SceneView, RenderNode, ) { const view = new SceneView({ container: "viewDiv", map: new Map({basemap: "satellite"}) }); // 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 lowp vec4 fragColor; // 输入纹理坐标 in vec2 uv; // 纹理采样器 uniform sampler2D colorTex; void main() { // 定义一个常量偏移量 const float offset = 1.0 / 300.0; // 定义一个包含9个偏移量的数组,用于采样纹理的9个不同位置 vec2 offsets[9] = vec2[]( vec2(-offset, offset), // 左上 vec2( 0.0f, offset), // 正上 vec2( offset, offset), // 右上 vec2(-offset, 0.0f), // 左 vec2( 0.0f, 0.0f), // 中 vec2( offset, 0.0f), // 右 vec2(-offset, -offset), // 左下 vec2( 0.0f, -offset), // 正下 vec2( offset, -offset) // 右下 ); // 定义一个包含9个权重的数组,用于加权采样结果 float kernel[9] = float[]( 1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, 2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0 ); // 定义一个数组来存储9个采样结果 vec3 sampleTex[9]; // 循环遍历每个偏移量,采样纹理并存储结果 for(int i = 0; i < 9; i++) { sampleTex[i] = vec3(texture(colorTex, uv + offsets[i])); } // 初始化颜色 vec3 col = vec3(0.0); // 循环遍历每个权重,将采样结果与对应的权重相乘并累加到最终颜色中 for(int i = 0; i < 9; i++){ col += sampleTex[i] * kernel[i]; } // 将最终颜色转换为纹理输出 fragColor = vec4(col, 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 版权所有,并保留所有权利。