当前位置: 首页 > news >正文

ArcGIS JSAPI 高级教程 - ArcGIS Maps SDK for JavaScript - 原生代码实现动态扩散效果

ArcGIS JSAPI 高级教程 - ArcGIS Maps SDK for JavaScript - 原生代码实现动态扩散效果

    • 核心代码
    • 完整代码:
    • 在线示例

在这里插入图片描述

ArcGIS Maps SDK for JavaScript 从 4.29 开始增加 RenderNode 类,可以添加数据以及操作 FBO(ManagedFBO)

通过操作 FBO,可以通过后处理实现很多效果,官方提供了几个示例,感兴趣可以看看。

本文介绍一下通过 FBO,实现动态扩散效果效果。

本文包括核心代码、完整代码以及在线示例。


核心代码

介绍一下原理:首先通过深度值和 uv,获取世界坐标,定义中心点,

将世界坐标投影到中心点的平面上,并根据点到平面的距离与指定半径的关系决定是否应用混合效果,

混合因子依据距离与半径的差值计算,实现平滑过渡,实现扩散效果。

并且控制混合效果的半径由 u_radius 变量决定,混合颜色由 test_color 变量定义。

#version 300 esprecision mediump float;
out mediump vec4 fragColor;in vec2 uv;// 页面颜色纹理
uniform sampler2D colorTex;
// 深度纹理
uniform sampler2D depthTex;// 相机视图矩阵
uniform mat4 u_viewMatrix;
// 相机投影矩阵
uniform mat4 u_projectionMatrix;
// 相机逆投影矩阵
uniform mat4 u_inverseProjectionMatrix;// 相机远近截面
uniform vec2 nearFar;// 动态颜色
uniform vec4 test_color;
// 扫描半径
uniform float u_radius;// 定义光线和观察者的世界位置
uniform vec3[2] u_lightWorldPosition;// 线性化深度
float linearizeDepth(float depth) {float depthNdc = depth * 2.0 - 1.0;return (2.0 * nearFar[0] * nearFar[1]) / (depthNdc * (nearFar[1] - nearFar[0]) - (nearFar[1] + nearFar[0]));
}// 获取深度值
float linearDepth(vec2 uv) {ivec2 iuv = ivec2(uv * vec2(textureSize(depthTex, 0)));return texelFetch(depthTex, iuv, 0).r;
}// 深度值获取坐标
vec4 getPositionByDepth(vec2 uv) {// 获取深度值float depth = linearDepth(uv);// 将深度值转换为视图空间中的Z值float viewZ = linearizeDepth(depth);// 计算裁剪空间中的W值float clipW = u_projectionMatrix[2][3] * viewZ + u_projectionMatrix[3][3];// 将纹理坐标和深度值转换为NDC坐标vec3 ndcPosition = vec3(uv, depth) * 2.0 - 1.0;// 将NDC坐标转换为裁剪坐标vec4 clipPosition = vec4(ndcPosition, 1.0) * clipW;// 将裁剪坐标变换回视图坐标vec4 viewPos = u_inverseProjectionMatrix * clipPosition;// 进行透视除法,将视图坐标转换为齐次坐标viewPos /= viewPos.w;// 返回视图空间中的位置return viewPos;
}// 将一个点投影到一个平面上。
vec3 pointProjectOnPlane(in vec3 planeNormal, in vec3 planeOrigin, in vec3 point) {// 计算从平面原点到点的向量vec3 v01 = point - planeOrigin;// 计算该向量与平面法线的点积float d = dot(planeNormal, v01);// 将点投影到平面上return (point - planeNormal * d);
}void main() {// 从颜色纹理中获取当前像素的颜色vec4 color = texture(colorTex, uv);// 将获取的颜色设置为片段颜色fragColor = color;// 转换坐标 start ========================================================================// 计算第一个光源位置在视图空间中的位置vec4 center0 = u_viewMatrix * vec4(u_lightWorldPosition[0], 1.0);center0 /= center0.w;// 计算第二个光源位置在视图空间中的位置vec4 center500 = u_viewMatrix * vec4(u_lightWorldPosition[1], 1.0);center500 /= center500.w;// 计算两个光源之间的法线,用于后续的投影计算vec3 circleNormal = normalize(center500.xyz - center0.xyz);// 转换坐标 end ========================================================================// 获取当前像素在视图空间中的位置vec4 viewPos = getPositionByDepth(uv);// 将视图空间中的点投影到一个平面上// 这个平面由扫描平面法线和扫描中心定义vec3 prjOnPlane = pointProjectOnPlane(circleNormal,center0.xyz,viewPos.xyz);// 计算投影点到扫描中心的距离float dis = length(prjOnPlane.xyz - center0.xyz);// 如果距离小于指定的半径,应用混合效果if (dis < u_radius) {// 计算混合因子float f = 1.0 - abs(u_radius - dis) / u_radius;f = pow(f, 4.0);// 根据混合因子混合当前颜色和测试颜色fragColor.rgb = mix(color.rgb, test_color.rgb/255.0, f);}
}

完整代码:

<!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><script type="text/javascript" src="https://openlayers.vip/examples/resources/dat_gui/dat.gui.js"></script><link rel="stylesheet" type="text/css" href="https://openlayers.vip/examples/resources/dat_gui/dat.gui.css"/><link rel="stylesheet" href="https://openlayers.vip/arcgis_api/4.30/esri/themes/light/main.css"/><script src="https://openlayers.vip/arcgis_api/4.30/init.js"></script><script src="https://openlayers.vip/examples/resources/renderCommon.js"></script><script type="module" src="https://openlayers.vip/arcgis_api/calcite-components/2.8.1/calcite.esm.js"></script><link rel="stylesheet" type="text/css" href="https://openlayers.vip/arcgis_api/calcite-components/2.8.1/calcite.css" /><style>html,body,#viewDiv {padding: 0;margin: 0;height: 100%;width: 100%;}</style>
</head>
<body>
<script>require(["esri/Map", "esri/views/SceneView", "esri/views/3d/webgl/RenderNode","esri/Graphic", "esri/views/3d/webgl","esri/geometry/SpatialReference","esri/widgets/Home","esri/layers/IntegratedMesh3DTilesLayer",], function (Map,SceneView,RenderNode,Graphic,webgl,SpatialReference,Home,IntegratedMesh3DTilesLayer,) {// 地面点const point = {type: "point", // autocasts as new Point()x: 114.17494271319552,y: 22.29474643685136,z: 1,viewH: 3000,};// 空中点const pointSky = {type: "point", // autocasts as new Point()x: 114.17494271319552,y: 22.29474643685136,z: 500};const {map, view} = initMap({Map, SceneView, Home},undefined,point);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);const markerSymbol = {type: "simple-marker", // autocasts as new SimpleMarkerSymbol()color: [226, 119, 40],outline: {// autocasts as new SimpleLineSymbol()color: [255, 255, 255],width: 2}};// 创建点对象const pointGraphic = new Graphic({geometry: point,symbol: markerSymbol});view.graphics.add(pointGraphic);// 平铺点数据const points = [point.x, point.y, point.z,pointSky.x, pointSky.y, pointSky.z,];view.when(() => {// 世界坐标const localOriginRender = webgl.toRenderCoordinates(view,points,0,SpatialReference.WGS84,new Float32Array(points.length),0,(points.length) / 3,);// Derive a new subclass from RenderNode called LuminanceRenderNodeconst LuminanceRenderNode = RenderNode.createSubclass({constructor: function (param) {param = {...param}// consumes and produces define the location of the the render node in the render pipelinethis.consumes = {required: ["composite-color"]};this.produces = "composite-color";// 半径this.test_float = 500;// 持续时间this.test_brightness = 2000;// 颜色this.test_color = [0, 255, 255, 0.1];// 记录时间this._time = (new Date()).getTime();this.point = param.point;},// Ensure resources are cleaned up when render node is removeddestroy() {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 enabledenabled: {get: function () {return this.produces != null;},set: function (value) {// Setting produces to null disables the render nodethis.produces = value ? "composite-color" : null;this.requestRender();}}},render(inputs) {// We need color texture from the composite render targetconst input = inputs.find(({name}) => name === "composite-color");const color = input.getTexture();// Acquire the composite framebuffer object, and bind framebuffer as current targetconst output = this.acquireOutputFramebuffer();const gl = this.gl;const depth = input.getTexture(gl.DEPTH_STENCIL_ATTACHMENT);// Clear newly acquired framebuffergl.clearColor(0, 0, 0, 0);gl.colorMask(true, true, true, true);gl.clear(gl.COLOR_BUFFER_BIT);// 激活透明activeOpacity(gl);// Prepare custom shaders and geometry for screenspace renderingthis.ensureShader(gl);this.ensureScreenSpacePass(gl);// Bind custom programgl.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.activeTexture(gl.TEXTURE2);gl.bindTexture(gl.TEXTURE_2D, depth.glName);gl.uniform1i(this.depthTextureUniformLocation, 2);gl.uniform2fv(this.nearFarUniformLocation, [this.camera.near, this.camera.far]);gl.uniform1f(this.scanRadiusUniformLocation,this.test_float * (((new Date()).getTime() - this._time) % this.test_brightness) / this.test_brightness);gl.uniform4fv(this.textureUniformTestColor, this.test_color);gl.uniform3fv(this.textureUniformLightWorldPosition, this.point);// 激活相机矩阵activeMatrix(this);// Issue the render call for a screen space render passgl.bindVertexArray(this.vao);gl.drawArrays(gl.TRIANGLES, 0, 3);// use depth from input on output framebufferoutput.attachDepth(input.getAttachment(gl.DEPTH_STENCIL_ATTACHMENT));this.requestRender();return output;},shaderProgram: null,textureUniformLocation: null,depthTextureUniformLocation: null,positionLocation: null,vao: null,positionBuffer: null,// Setup screen space filling triangleensureScreenSpacePass(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 programsensureShader(gl) {if (this.shaderProgram != null) {return;}// The vertex shader programconst vshader = `#version 300 esin vec2 position;out vec2 uv;void main() {uv = position * 0.5 + vec2(0.5);gl_Position = vec4(position, 0.0, 1.0);}`;// The fragment shader program applying a greyscsale conversionconst fshader = `#version 300 esprecision mediump float;out mediump vec4 fragColor;in vec2 uv;// 页面颜色纹理uniform sampler2D colorTex;// 深度纹理uniform sampler2D depthTex;// 相机视图矩阵uniform mat4 u_viewMatrix;// 相机投影矩阵uniform mat4 u_projectionMatrix;// 相机逆投影矩阵uniform mat4 u_inverseProjectionMatrix;// 相机远近截面uniform vec2 nearFar;// 动态颜色uniform vec4 test_color;// 扫描半径uniform float u_radius;// 定义光线和观察者的世界位置uniform vec3[2] u_lightWorldPosition;// 线性化深度float linearizeDepth(float depth) {float depthNdc = depth * 2.0 - 1.0;return (2.0 * nearFar[0] * nearFar[1]) / (depthNdc * (nearFar[1] - nearFar[0]) - (nearFar[1] + nearFar[0]));}// 获取深度值float linearDepth(vec2 uv) {ivec2 iuv = ivec2(uv * vec2(textureSize(depthTex, 0)));return texelFetch(depthTex, iuv, 0).r;}// 深度值获取坐标vec4 getPositionByDepth(vec2 uv) {// 获取深度值float depth = linearDepth(uv);// 将深度值转换为视图空间中的Z值float viewZ = linearizeDepth(depth);// 计算裁剪空间中的W值float clipW = u_projectionMatrix[2][3] * viewZ + u_projectionMatrix[3][3];// 将纹理坐标和深度值转换为NDC坐标vec3 ndcPosition = vec3(uv, depth) * 2.0 - 1.0;// 将NDC坐标转换为裁剪坐标vec4 clipPosition = vec4(ndcPosition, 1.0) * clipW;// 将裁剪坐标变换回视图坐标vec4 viewPos = u_inverseProjectionMatrix * clipPosition;// 进行透视除法,将视图坐标转换为齐次坐标viewPos /= viewPos.w;// 返回视图空间中的位置return viewPos;}// 将一个点投影到一个平面上。vec3 pointProjectOnPlane(in vec3 planeNormal, in vec3 planeOrigin, in vec3 point) {// 计算从平面原点到点的向量vec3 v01 = point - planeOrigin;// 计算该向量与平面法线的点积float d = dot(planeNormal, v01);// 将点投影到平面上return (point - planeNormal * d);}void main() {// 从颜色纹理中获取当前像素的颜色vec4 color = texture(colorTex, uv);// 将获取的颜色设置为片段颜色fragColor = color;// 转换坐标 start ========================================================================// 计算第一个光源位置在视图空间中的位置vec4 center0 = u_viewMatrix * vec4(u_lightWorldPosition[0], 1.0);center0 /= center0.w;// 计算第二个光源位置在视图空间中的位置vec4 center500 = u_viewMatrix * vec4(u_lightWorldPosition[1], 1.0);center500 /= center500.w;// 计算两个光源之间的法线,用于后续的投影计算vec3 circleNormal = normalize(center500.xyz - center0.xyz);// 转换坐标 end ========================================================================// 获取当前像素在视图空间中的位置vec4 viewPos = getPositionByDepth(uv);// 将视图空间中的点投影到一个平面上// 这个平面由扫描平面法线和扫描中心定义vec3 prjOnPlane = pointProjectOnPlane(circleNormal,center0.xyz,viewPos.xyz);// 计算投影点到扫描中心的距离float dis = length(prjOnPlane.xyz - center0.xyz);// 如果距离小于指定的半径,应用混合效果if (dis < u_radius) {// 计算混合因子float f = 1.0 - abs(u_radius - dis) / u_radius;f = pow(f, 4.0);// 根据混合因子混合当前颜色和测试颜色fragColor.rgb = mix(color.rgb, test_color.rgb/255.0, f);}}`;this.shaderProgram = initWebgl2Shaders(gl, vshader, fshader);this.nearFarUniformLocation = gl.getUniformLocation(this.shaderProgram, "nearFar");this.textureUniformLocation = gl.getUniformLocation(this.shaderProgram, "colorTex");this.depthTextureUniformLocation = gl.getUniformLocation(this.shaderProgram, "depthTex");this.textureUniformTestColor = gl.getUniformLocation(this.shaderProgram, "test_color");this.textureUniformLightWorldPosition =gl.getUniformLocation(this.shaderProgram, "u_lightWorldPosition");this.scanRadiusUniformLocation = gl.getUniformLocation(this.shaderProgram, 'u_radius');this.positionLocation = gl.getAttribLocation(this.shaderProgram, "position");}});// Initializes the new custom render node and connects to SceneViewconst luminanceRenderNode = new LuminanceRenderNode({view, point:localOriginRender});var gui = new dat.GUI();gui.add(luminanceRenderNode, 'test_float', 0, 1000).name('扫描半径');gui.add(luminanceRenderNode, 'test_brightness', 0, 4000).name('持续时间');var colorController = gui.addColor(luminanceRenderNode, 'test_color').name('颜色');// Toggle button to enable/disable the custom render nodeconst renderNodeToggle = document.getElementById("renderNodeToggle");renderNodeToggle.addEventListener("calciteSwitchChange", () => {luminanceRenderNode.enabled = !luminanceRenderNode.enabled;});});});
</script>
<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 在线示例:原生代码实现动态扩散效果

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 会计凭证IDOC有关增强
  • Bat常用指令
  • 【MySQL】MySQL库的操作
  • 数据结构基础讲解(七)——数组和广义表专项练习
  • 哈希表,算法
  • ECMAScript与JavaScript的区别:深入解析与代码示例
  • 【基础算法总结】二分查找
  • 算法练习题19——leetcode141环形链表
  • 基于51单片机的智能农业滴灌控制系统proteus仿真
  • 鸿蒙OS开发秘籍:打造优雅的登录状态管理系统
  • ai绘图软件哪个好用?解锁5款小白必备工具
  • Git的基本概念和使用方式
  • shader 案例学习笔记之绘制圆
  • springboot属性加载优先级和常见命令行属性
  • 一个Java中有用的JacksonUtil类
  • AWS实战 - 利用IAM对S3做访问控制
  • create-react-app做的留言板
  • ECMAScript入门(七)--Module语法
  • ES6核心特性
  • go语言学习初探(一)
  • JavaScript设计模式与开发实践系列之策略模式
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Spring声明式事务管理之一:五大属性分析
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 闭包--闭包作用之保存(一)
  • 构造函数(constructor)与原型链(prototype)关系
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 跳前端坑前,先看看这个!!
  • 一、python与pycharm的安装
  • 优化 Vue 项目编译文件大小
  • No resource identifier found for attribute,RxJava之zip操作符
  • Java性能优化之JVM GC(垃圾回收机制)
  • mysql面试题分组并合并列
  • 扩展资源服务器解决oauth2 性能瓶颈
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • (12)目标检测_SSD基于pytorch搭建代码
  • (2)空速传感器
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (Java数据结构)ArrayList
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (十)Flink Table API 和 SQL 基本概念
  • (一)WLAN定义和基本架构转
  • (转)EOS中账户、钱包和密钥的关系
  • (转)ObjectiveC 深浅拷贝学习
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • .NET 8.0 中有哪些新的变化?
  • .Net MVC + EF搭建学生管理系统
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .net 生成二级域名
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • .Net 应用中使用dot trace进行性能诊断
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)