赞
踩
ArcGIS Maps SDK for JavaScript 从 4.29
开始增加 RenderNode
类,可以添加数据以及操作 FBO(ManagedFBO)
;
通过操作 FBO,可以通过后处理实现很多效果,官方提供了几个示例,感兴趣可以看看。
本文介绍一下通过 FBO,实现自定义范围后处理效果(自定义三角形范围)。
本文包括核心代码、完整代码以及在线示例。
首先介绍一下原理:通过地图构建三角形数据,转为 WebGL 内部坐标,即世界坐标;
顶点着色器中,传递顶点数据;
片元着色器中,根据顶点数据以及三角形顶点数据,判断是否在三角形内,三角形内外显示不同颜色。
// 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 vec4 position_out; // uv 取样 out vec2 uv; void main() { // 绘制顶点 gl_Position = vec4(position, 0.0, 1.0); position_out = gl_Position; // uv 调整中心 uv = position * 0.5 + vec2(0.5); }`; // The fragment shader program applying a greyscsale conversion const fshader = `#version 300 es precision mediump float; out mediump vec4 fragColor; // 相机矩阵 uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; // 三角形顶点 uniform vec3 u_triangle_out[3]; in vec2 uv; // 当前片元位置 in vec4 position_out; // 颜色纹理 uniform sampler2D colorTex; // 判断是否在三角形中 bool isPointInTriangle(vec3 a, vec3 b, vec3 c, vec3 p) { float signOfTrig = (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); float signOfAB = (b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x); float signOfCA = (a.x - c.x)*(p.y - c.y) - (a.y - c.y)*(p.x - c.x); float signOfBC = (c.x - b.x)*(p.y - c.y) - (c.y - b.y)*(p.x - c.x); bool d1 = (signOfAB<=0.0&&signOfTrig<=0.0) || (signOfAB>=0.0&&signOfTrig>=0.0); bool d2 = (signOfCA<=0.0&&signOfTrig<=0.0) || (signOfCA>=0.0&&signOfTrig>=0.0); bool d3 = (signOfBC<=0.0&&signOfTrig<=0.0) || (signOfBC>=0.0&&signOfTrig>=0.0); return d1 && d2 && d3; } void main() { vec4 color = texture(colorTex, uv); // 调整亮度 color = color * 1.7; vec4 triangle_out1 = vec4(u_triangle_out[0], 1.0); vec4 triangle_out2 = vec4(u_triangle_out[1], 1.0); vec4 triangle_out3 = vec4(u_triangle_out[2], 1.0); // 转换三角形顶点数据 vec4 temp1 = u_projectionMatrix * u_viewMatrix * triangle_out1; triangle_out1 = vec4(temp1.xyz/temp1.w, 1.0); vec4 temp2 = u_projectionMatrix * u_viewMatrix * triangle_out2; triangle_out2 = vec4(temp2.xyz/temp2.w, 1.0); vec4 temp3 = u_projectionMatrix * u_viewMatrix * triangle_out3; triangle_out3 = vec4(temp3.xyz/temp3.w, 1.0); // 三个点都在地球背面,则不显示 if ( triangle_out1.z/triangle_out1.w < 0.99911 || triangle_out2.z/triangle_out2.w < 0.99911 || triangle_out3.z/triangle_out3.w < 0.99911 ) { // 三角形范围 if (!isPointInTriangle( triangle_out1.xyz, triangle_out2.xyz, triangle_out3.xyz, position_out.xyz )){ fragColor = color; } else { fragColor = vec4(vec3(dot(color.rgb, vec3(0.2126, 0.7152, 0.0722))), color.a); } } else { fragColor = color; } } `;
<!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 src="./renderCommon.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/Graphic", "esri/views/3d/webgl", "esri/geometry/SpatialReference", "esri/widgets/Home", ], function ( Map, SceneView, RenderNode, Graphic, webgl, SpatialReference, Home ) { const {map, view} = initMap({Map, SceneView, Home}); // Create a polygon geometry const polygon = { type: "polygon", // autocasts as new Polygon() hasZ: false, rings: [ [91.70200480682539, 40.50378397539653, 0], [124.54786035613671, 48.316773528052515, 0], [114.78476104211637, 24.59876952891829, 0], [91.70200480682539, 40.50378397539653, 0] ] }; // Add the geometry and symbol to a new graphic const polygonGraphic = new Graphic({ geometry: polygon, }); const points = polygonGraphic.geometry.rings[0].flat(); let localOriginRender; view.when(() => { // Calculate local origin in render coordinates with 32bit precision // 经纬度坐标转为世界坐标 localOriginRender = webgl.toRenderCoordinates( view, points, 0, SpatialReference.WGS84, new Float32Array(points.length - 3), 0, (points.length - 3) / 3, ); // 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() { this.shaderProgram && this.gl?.deleteProgram(this.shaderProgram); this.positionBuffer && this.gl?.deleteBuffer(this.positionBuffer); 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); // gl.disable(gl.CULL_FACE); // 禁用面剔除 // Prepare custom shaders and geometry for screenspace rendering // 初始化着色器 this.ensureShader(gl); // 初始化屏幕数据 this.ensureScreenSpacePass(gl); // Bind custom program gl.useProgram(this.shaderProgram); // 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); // 传入三角形顶点 gl.uniform3fv(this.textureUniformTriangleExtent,localOriginRender); // 激活相机矩阵 activeMatrix(this); // 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)); this.requestRender(); return output; }, // 着色器程序 shaderProgram: null, // 纹理 textureUniformLocation: null, // 顶点位置 positionLocation: null, // 顶点数组 vao: null, // 顶点缓冲区 positionBuffer: null, // used to avoid allocating objects in each frame. // 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.shaderProgram != 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 vec4 position_out; // uv 取样 out vec2 uv; void main() { // 绘制顶点 gl_Position = vec4(position, 0.0, 1.0); position_out = gl_Position; // uv 调整中心 uv = position * 0.5 + vec2(0.5); }`; // The fragment shader program applying a greyscsale conversion const fshader = `#version 300 es precision mediump float; out mediump vec4 fragColor; // 相机矩阵 uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; // 三角形顶点 uniform vec3 u_triangle_out[3]; in vec2 uv; // 当前片元位置 in vec4 position_out; // 颜色纹理 uniform sampler2D colorTex; // 判断是否在三角形中 bool isPointInTriangle(vec3 a, vec3 b, vec3 c, vec3 p) { float signOfTrig = (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); float signOfAB = (b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x); float signOfCA = (a.x - c.x)*(p.y - c.y) - (a.y - c.y)*(p.x - c.x); float signOfBC = (c.x - b.x)*(p.y - c.y) - (c.y - b.y)*(p.x - c.x); bool d1 = (signOfAB<=0.0&&signOfTrig<=0.0) || (signOfAB>=0.0&&signOfTrig>=0.0); bool d2 = (signOfCA<=0.0&&signOfTrig<=0.0) || (signOfCA>=0.0&&signOfTrig>=0.0); bool d3 = (signOfBC<=0.0&&signOfTrig<=0.0) || (signOfBC>=0.0&&signOfTrig>=0.0); return d1 && d2 && d3; } void main() { vec4 color = texture(colorTex, uv); // 调整亮度 color = color * 1.7; vec4 triangle_out1 = vec4(u_triangle_out[0], 1.0); vec4 triangle_out2 = vec4(u_triangle_out[1], 1.0); vec4 triangle_out3 = vec4(u_triangle_out[2], 1.0); // 转换三角形顶点数据 vec4 temp1 = u_projectionMatrix * u_viewMatrix * triangle_out1; triangle_out1 = vec4(temp1.xyz/temp1.w, 1.0); vec4 temp2 = u_projectionMatrix * u_viewMatrix * triangle_out2; triangle_out2 = vec4(temp2.xyz/temp2.w, 1.0); vec4 temp3 = u_projectionMatrix * u_viewMatrix * triangle_out3; triangle_out3 = vec4(temp3.xyz/temp3.w, 1.0); // 三个点都在地球背面,则不显示 if ( triangle_out1.z/triangle_out1.w < 0.99911 || triangle_out2.z/triangle_out2.w < 0.99911 || triangle_out3.z/triangle_out3.w < 0.99911 ) { // 三角形范围 if (!isPointInTriangle( triangle_out1.xyz, triangle_out2.xyz, triangle_out3.xyz, position_out.xyz )){ fragColor = color; } else { fragColor = vec4(vec3(dot(color.rgb, vec3(0.2126, 0.7152, 0.0722))), color.a); } } else { fragColor = color; } } `; this.shaderProgram = initWebgl2Shaders(gl, vshader, fshader); this.textureUniformLocation = gl.getUniformLocation(this.shaderProgram, "colorTex"); // 三角形顶点位置 this.textureUniformTriangleExtent = gl.getUniformLocation( this.shaderProgram, "u_triangle_out"); this.positionLocation = gl.getAttribLocation(this.shaderProgram, "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; }); }); }); </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 版权所有,并保留所有权利。