赞
踩
ArcGIS Maps SDK for JavaScript 从 4.29
开始增加 RenderNode
类,可以添加数据以及操作 FBO(ManagedFBO)
;
通过操作 FBO,可以通过后处理实现很多效果,官方提供了几个示例,感兴趣可以看看。
本文介绍一下通过 FBO,实现鼠标探测效果。
本文包括核心代码、完整代码以及在线示例。
原理也比较容易,即获取鼠标位置,转为 WebGL 位置,计算圆形范围,
经过判断,范围内外显示不同颜色。
需要注意的地方:鼠标位置归一化,y 轴位置翻转以及纠正圆形。
具体介绍详见代码注释。
// 监听鼠标离开图形事件 view.on("pointer-move", function (event) { // 获取视口尺寸 const viewWidth = view.width; const viewHeight = view.height; // 计算中心点,这里归一化位置 const centerX = event.x / viewWidth; // 翻转 y 轴 const centerY = 1 - event.y / viewHeight; luminanceRenderNode.center = [centerX,centerY]; }); // 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; // 圆参数 uniform vec3 u_center; // 计算宽高比,纠正圆形 vec2 calculateAspectRatio(vec2 size) { return vec2(size[1] / size[0], 1.0); } // 是否在圆内 bool isInsideCircle(vec2 uv_) { // 纹理尺寸 vec2 size = vec2(textureSize(colorTex, 0)); // 纠正范围 uv_ = (uv_ - u_center.rg)/calculateAspectRatio(size); // 计算UV坐标到圆心的距离 float distance = length(uv_); // 判断距离是否小于圆的半径 // 这里给一个最小圆形 return distance < (u_center.b >= 0.3 ? 0.3: u_center.b); } void main() { vec4 color = texture(colorTex, uv); if(isInsideCircle(uv)){ // 圆内高亮 fragColor = vec4(color.rgb * 1.2, color.a); }else{ // 灰度化 fragColor = vec4(vec3(dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)))*0.7, color.a); } }`;
<!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 - Color Spy | Sample | ArcGIS Maps SDK for JavaScript 4.29</title> <link rel="stylesheet" href="https://js.arcgis.com/4.29/esri/themes/light/main.css"/> <script src="https://js.arcgis.com/4.29/"></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/widgets/Slider","esri/geometry/Point", "esri/layers/IntegratedMesh3DTilesLayer", ], function ( Map, SceneView, RenderNode, Slider, Point, 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); view.when(() => { 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: { // 修改中心点 center: { set: function (value) { // Setting produces to null disables the render node this.viewCenter[0] = value[0]; this.viewCenter[1] = value[1]; this.requestRender(); } }, // 修改半径 radius: { set: function (value = 0.1) { // Setting produces to null disables the render node this.viewCenter[2] = value; this.requestRender(); } }, // 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); // console.log(color.glName) // 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.textureUniformCenter, this.viewCenter); // 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, // 默认圆参数 viewCenter: new Float32Array([0,0,0.1]), // 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; // 圆参数 uniform vec3 u_center; // 计算宽高比,纠正圆形 vec2 calculateAspectRatio(vec2 size) { return vec2(size[1] / size[0], 1.0); } // 是否在圆内 bool isInsideCircle(vec2 uv_) { // 纹理尺寸 vec2 size = vec2(textureSize(colorTex, 0)); // 纠正范围 uv_ = (uv_ - u_center.rg)/calculateAspectRatio(size); // 计算UV坐标到圆心的距离 float distance = length(uv_); // 判断距离是否小于圆的半径 return distance < (u_center.b >= 0.3 ? 0.3: u_center.b); } void main() { vec4 color = texture(colorTex, uv); if(isInsideCircle(uv)){ // 圆内高亮 fragColor = vec4(color.rgb * 1.2, color.a); }else{ fragColor = vec4(vec3(dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)))*0.7, color.a); } }`; this.program = createProgram(gl, vshader, fshader); this.textureUniformLocation = gl.getUniformLocation(this.program, "colorTex"); this.textureUniformCenter = gl.getUniformLocation(this.program, "u_center"); 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.on("pointer-move", function (event) { // 获取视口尺寸 const viewWidth = view.width; const viewHeight = view.height; // 计算中心点 const centerX = event.x / viewWidth; const centerY = 1 - event.y / viewHeight; luminanceRenderNode.center = [centerX,centerY]; }); const slider = new Slider({ container: "sliderDiv", min: 0, max: 0.3, values: [ 0.1 ], snapOnClickEnabled: false, visibleElements: { labels: false, rangeLabels: true } }); slider.on("thumb-drag", (event) => { // 修改半径 luminanceRenderNode.radius = slider.values[0]; }); view.ui.add("renderNodeUI", "top-right"); view.ui.add(slider, { position: "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> <div id="sliderDiv"></div> </body> </html>
ArcGIS Maps SDK for JavaScript 在线示例:探测效果(地图探测)
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。