基于three.js 和ArcGIS JS API 建筑物立面波纹墙动态渲染
简介
ArcGIS API for JavaScript 是ESRI 推出面向 WEB 端 GIS API,可构建引人注目的web 地图应用程序,通过交互式用户体验和令人惊叹的2D和3D可视化来释放地理时空大数据的潜力。同时提供了一个轻量级接口来访问SceneView的WebGL上下文,因此可以创建与内置层相同的方式与场景交互的自定义可视化。开发人员可以直接编写WebGL代码,也可以与第三方WebGL库集成
基于ArcGIS JS API 和 three.js 波纹墙扩展类ripplewallRenderer
define([
'dojo/_base/declare',
"esri/views/3d/externalRenderers"
], function (
declare,
externalRenderers
) {
var THREE = window.THREE;
var ripplewallRenderer = declare([], {
constructor: function (view, points, options) {
this.view = view;
const OPTIONS = {
altitude: 0,
speed: 0.015,
height: 10
}
this.options = this.extend({}, OPTIONS, options, {
points: points
});
// 顶点着色器
this.vertexs = {
normal_vertex: "\n precision lowp float;\n precision lowp int;\n ".concat(THREE.ShaderChunk.fog_pars_vertex, "\n varying vec2 vUv;\n void main() {\n vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n ").concat(THREE.ShaderChunk.fog_vertex, "\n }\n"),
}
// 片段着色器
this.fragments = {
rippleWall_fragment: "\n precision lowp float;\n precision lowp int;\n uniform float time;\n uniform float opacity;\n uniform vec3 color;\n uniform float num;\n uniform float hiz;\n\n varying vec2 vUv;\n\n void main() {\n vec4 fragColor = vec4(0.);\n float sin = sin((vUv.y - time * hiz) * 10. * num);\n float high = 0.92;\n float medium = 0.4;\n if (sin > high) {\n fragColor = vec4(mix(vec3(.8, 1., 1.), color, (1. - sin) / (1. - high)), 1.);\n } else if(sin > medium) {\n fragColor = vec4(color, mix(1., 0., 1.-(sin - medium) / (high - medium)));\n } else {\n fragColor = vec4(color, 0.);\n }\n\n vec3 fade = mix(color, vec3(0., 0., 0.), vUv.y);\n fragColor = mix(fragColor, vec4(fade, 1.), 0.85);\n gl_FragColor = vec4(fragColor.rgb, fragColor.a * opacity * (1. - vUv.y));\n }\n",
}
},
setup: function (context) {
this.renderer = new THREE.WebGLRenderer({
context: context.gl, // 可用于将渲染器附加到已有的渲染环境(RenderingContext)中
premultipliedAlpha: false, // renderer是否假设颜色有 premultiplied alpha. 默认为true
});
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比。通常用于避免HiDPI设备上绘图模糊
this.renderer.setViewport(0, 0, this.view.width, this.view.height); // 视口大小设置
// Make sure it does not clear anything before rendering
this.renderer.autoClear = false;
this.renderer.autoClearDepth = false;
this.renderer.autoClearColor = false;
this.renderer.autoClearStencil = false;
// The ArcGIS JS API renders to custom offscreen buffers, and not to the default framebuffers.
// We have to inject this bit of code into the three.js runtime in order for it to bind those
// buffers instead of the default ones.
var originalSetRenderTarget = this.renderer.setRenderTarget.bind(this.renderer);
this.renderer.setRenderTarget = function (target) {
originalSetRenderTarget(target);
if (target == null) {
context.bindRenderTarget();
}
};
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera();
var axesHelper = new THREE.AxesHelper(1);
axesHelper.position.copy(1000000, 100000, 100000);
this.scene.add(axesHelper);
this._setupScene(context);
},
_setupScene: function (context) {
var scope = this;
scope.material = new THREE.ShaderMaterial({
uniforms: {
time: {
type: "pv2",
value: 0
},
color: {
type: "uvs",
value: new THREE.Color(scope.options.color)
},
opacity: {
type: "pv2",
value: 0.3
},
num: {
type: "pv2",
value: 5
},
hiz: {
type: "pv2",
value: 0.15
}
},
vertexShader: scope.vertexs.normal_vertex,
fragmentShader: scope.fragments.rippleWall_fragment,
blending: THREE.AdditiveBlending, // .blending属性主要控制纹理融合的叠加方式.AdditiveBlending:加法融合模式
transparent: !0,
depthWrite: !1,
depthTest: !0,
wireframe: scope.options.wireframe === undefined ? false : scope.options.wireframe,
side: THREE.DoubleSide
});
const height = scope.options.height;
const wall = scope.options.points;
const positionsV = [];
let joinLonLat = [];
wall.forEach(xy => {
const polyPice = scope.coordinateToVector3([xy[0], xy[1]]);
positionsV.push(polyPice);
joinLonLat.push(polyPice.x);
joinLonLat.push(polyPice.y);
});
for (var a = joinLonLat, polySub = [], o = 0, s = 0; o < a.length - 2; o += 2, s++)
0 === o ?
polySub[0] = Math.sqrt((a[2] - a[0]) * (a[2] - a[0]) + (a[3] - a[1]) * (a[3] - a[1])) :
polySub[s] = polySub[s - 1] + Math.sqrt((a[o + 2] - a[o]) * (a[o + 2] - a[o]) + (a[o + 3] - a[o + 1]) * (a[o + 3] - a[o + 1]));
let pos = [],
uvs = [];
let polylenth = polySub[polySub.length - 1];
for (let d = 0, u = pos.length, p = uvs.length; d < positionsV.length - 1; d++) {
let pv1 = positionsV[d],
pv2 = positionsV[d + 1],
polyPice = polySub[d];
pos[u++] = pv1.x,
pos[u++] = pv1.y,
pos[u++] = 0,
uvs[p++] = 0 === d ? 0 : polySub[d - 1] / polylenth,
uvs[p++] = 0,
pos[u++] = pv2.x,
pos[u++] = pv2.y,
pos[u++] = 0,
uvs[p++] = polyPice / polylenth,
uvs[p++] = 0,
pos[u++] = pv1.x,
pos[u++] = pv1.y,
pos[u++] = height,
uvs[p++] = 0 === d ? 0 : polySub[d - 1] / polylenth,
uvs[p++] = 1,
pos[u++] = pv1.x,
pos[u++] = pv1.y,
pos[u++] = height,
uvs[p++] = 0 === d ? 0 : polySub[d - 1] / polylenth,
uvs[p++] = 1,
pos[u++] = pv2.x,
pos[u++] = pv2.y,
pos[u++] = 0,
uvs[p++] = polyPice / polylenth,
uvs[p++] = 0,
pos[u++] = pv2.x,
pos[u++] = pv2.y,
pos[u++] = height,
uvs[p++] = polyPice / polylenth,
uvs[p++] = 1
}
var geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(pos), 3));
geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array(uvs), 2));
scope.object3d = new THREE.Mesh(geometry, scope.material);
scope.scene.add(scope.object3d);
context.resetWebGLState();
},
setMaterialColor: function (rgb) {
if (!this.object3d) { return }
this.object3d.material.uniforms.color.value=rgb;
},
setwireframe: function () {
if (!this.object3d) { return }
this.object3d.material.wireframe = !this.object3d.material.wireframe;
},
setopacity: function (opacity) {
if (!this.object3d) { return }
this.object3d.material.opacity = opacity;
},
setaltitude: function (altitude) {
if (!this.object3d) { return }
this.object3d.position.z = altitude;
},
setscaleZ: function (scaleZ) {
if (!this.object3d) { return }
this.object3d.scale.z = scaleZ;
},
addAttribute: function (bufferGeomertry, key, value) {
bufferGeomertry.setAttribute(key, value);
return bufferGeomertry;
},
coordinateToVector3: function (coord, z = 0) {
const p = coord;
let transform = new THREE.Matrix4();
let transformation = new Array(16);
// const z = point.z === undefined ? 0 : point.z
transform.fromArray(
externalRenderers.renderCoordinateTransformAt(
this.view,
[p[0], p[1], z],
this.view.spatialReference,
transformation
)
);
let vector3 = new THREE.Vector3(
transform.elements[12],
transform.elements[13],
transform.elements[14]
);
return vector3;
},
extend: function (dest) { // (Object[, Object, ...]) ->
for (let i = 1; i < arguments.length; i++) {
const src = arguments[i];
for (const k in src) {
dest[k] = src[k];
}
}
return dest;
},
render: function (context) {
const cam = context.camera;
this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
this.camera.lookAt(
new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2])
);
this.object3d.material.uniforms.time.value += this.options.speed;
this.camera.projectionMatrix.fromArray(cam.projectionMatrix);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
externalRenderers.requestRender(this.view);
context.resetWebGLState();
}
});
return ripplewallRenderer;
});
波纹墙扩展类ripplewallRenderer 调用示例
const polygon = [
[
0,
0
],
[
100000,
0
],
[
100000,
100000
],
[
0,
100000
],
[
0,
0
]
]
let paramter = {
points: polygon,
options: {
color: '#FFD700',
height: 20000,
speed: 0.1
}
}
const rwRender = new ripplewallRenderer(view, paramter.points, paramter.options);
externalRenderers.add(view, rwRender);
效果图

建筑物底座渲染效果图
基于three.js和arcgis波纹墙
完整代码下载地址
基于three.js 和ArcGIS JS API 建筑物立面动态特效渲染
联系方式
qq: 314984468

本文介绍了如何结合three.js和ArcGIS JS API创建建筑物立面的波纹墙动态特效,包括波纹墙扩展类`ripplewallRenderer`的使用示例,展示了渲染效果,并提供了完整代码的下载链接和作者联系方式。
2198

被折叠的 条评论
为什么被折叠?



