基于three.js 和ArcGIS JS API 建筑物立面动态特效渲染

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

简介

   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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ISpaceART

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值