09-ArcGIS For JavaScript -- 基于ThreeJS实现动态GLTF模型加载

发布于:2025-03-20 ⋅ 阅读:(19) ⋅ 点赞:(0)


前言

通常我们在Web端三维场景中看到的动态模型一般都为GLTF或者GLB模型(GLB是GLTF的二级制心态)。而GLTF模型有分为带骨骼动画和不带骨骼动画两种形态。而上面或到的动态效果就是只带有骨骼动画的GLTF。

对于ArcGIS For JavaScript而言,目前可以通过符号化三维对象的方式去加载GLTF,但是在符号化的过程中无法去设置开启GLTF的动画参数,所以不管是骨骼模型还是普通模型,加载出来都只能看到静态的效果。在文章《03-ArcGIS For JavaScript结合ThreeJS功能》的时候,讲过RenderNode可以将ThreeJS的效果集中到ArcGIS的场景中,所以这里就介绍一下ArcGIS中通过ThreeJS去加载带有骨骼动画的GLTF模型。

一、ArcGIS如何加载GLTF

1.JavaScript 代码

首先看一下ArcGIS For JavaScript中如何加载GLTF。

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>Import glTF 3D Models | Sample | ArcGIS Maps SDK for JavaScript 4.32</title>

    <link rel="stylesheet" href="https://js.arcgis.com/4.32/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.32/"></script>

    <style>
        html,
        body,
        #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
        }

        #paneDiv {
            padding: 10px;
            max-width: 200px;
            background-color: rgba(255, 255, 255, 0.8);
            font-size: 1.1em;
        }

        #credits {
            font-size: 0.7em;
            line-height: 1.1em;
        }
    </style>
    <script>
        require([
            "esri/views/SceneView",
            "esri/Map",
            "esri/layers/GraphicsLayer",
            "esri/Graphic",
            "esri/geometry/Point"
        ], (SceneView, Map, GraphicsLayer, Graphic, Point) => {

            let scene = new Map({
                basemap: 'satellite'
            })

            const view = new SceneView({
                container: "viewDiv",
                map: scene,
                camera: {
                    heading: 359.20366065537,
                    fov: 55,
                    tilt: 1.2342814547871352,
                    position: {
                        latitude: 39.90281086554516,
                        longitude: 116.37414026811818,
                        z: 1445.7247159378603
                    }
                }
            });

            const graphicsLayer = new GraphicsLayer({
                elevationInfo: { mode: "on-the-ground" }
            });
            scene.add(graphicsLayer);

            view.when(() => {

                let symbol = {
                    type: "point-3d",
                    symbolLayers: [
                        {
                            type: "object",
                            resource: {
                                href: "https://developers.arcgis.com/javascript/latest/sample-code/import-gltf/live/tent.glb"
                            },
                            width: 100,
                            hight:100
                        }
                    ]
                };

                let point = new Point({
                    latitude: 39.903718309815375,
                    longitude: 116.37175217665084,
                    z: 60
                });

                let graphic = new Graphic({
                    geometry: point,
                    symbol
                })

                graphicsLayer.add(graphic);
            })
        });
    </script>
</head>
<body>
    <div id="viewDiv"></div>
</body>
</html>

2. 结果

在这里插入图片描述

二、ThreeJS使用

ThreeJS的使用方式可以参考前面《03-ArcGIS For JavaScript结合ThreeJS功能》

三、ArcGIS场景基于ThreeJS加载GLTF

1. 编写RenderNode引入代码

renderNodeClass.js


let RenderNodeClass = {
    constructor(options) {
        this.webgl = options.webgl;
        this.view = options.view;
        this.mixer = null;
        this.horseMixer = null;
        this.clock = null;
        this.horseClock = null;
    },

    setParams() {
        this.isStop = !this.isStop;
    },

    initialize() {
        let THREE = window.THREE;

        // this.mixer = new THREE.AnimationMixer();
        this.horseMixer = THREE.AnimationMixer;
        this.clock = new THREE.Clock();
        this.horseClock = new THREE.Clock();
        this.mixer = THREE.AnimationMixer;

        this.renderer = new THREE.WebGLRenderer({
            context: this.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); // 视口大小设置

        this.renderer.autoClear = false;
        this.renderer.autoClearDepth = false;
        this.renderer.autoClearColor = false;
        // this.renderer.autoClearStencil = false;

        let originalSetRenderTarget = this.renderer.setRenderTarget.bind(this.renderer);
        let that = this;
        this.renderer.setRenderTarget = function (target) {
            originalSetRenderTarget(target);
            if (target == null) {
                that.bindRenderTarget();
            }
        };

        this.scene = new THREE.Scene();
        // setup the camera
        let cam = this.camera;
        this._camera = new THREE.PerspectiveCamera(cam.fovY, cam.aspect, cam.near, cam.far);

        // 添加坐标轴辅助工具
        const axesHelper = new THREE.AxesHelper(1);
        axesHelper.position.copy(1000000, 100000, 100000);
        this.scene.add(axesHelper);

        let grid = new THREE.GridHelper(30, 10, 0xf0f0f0, 0xffffff);
        this.scene.add(grid);

        // setup scene lighting
        this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
        this.scene.add(this.ambient);
        this.sun = new THREE.DirectionalLight(0xffffff, 0.5);
        this.sun.position.set(-600, 300, 60000);
        this.scene.add(this.sun);

        window.gltf = null;

        const loader = new THREE.GLTFLoader();
        loader.load('Horse.glb', function (gltf) {
            console.log('gltf', gltf);
            gltf.scene.scale.set(10.8, 10.8, 10.8)
            that.scene.add(gltf.scene);
            gltf.scene.position.set(12956671.47026511,4851275.366457126, 10);
            gltf.scene.rotateX(Math.PI / 2);

            that.mixer = new THREE.AnimationMixer(gltf.scene);
            // obj.animations[0]:获得剪辑对象clip
            var AnimationAction = that.mixer.clipAction(gltf.animations[0]);
            window.gltf = gltf;
            // AnimationAction.timeScale = 1; //默认1,可以调节播放速度
            // AnimationAction.loop = THREE.LoopOnce; //不循环播放
            // AnimationAction.clampWhenFinished = true;//暂停在最后一帧播放的状态
            AnimationAction.play();//播放动画
        });

        // this.getCoords(context);
        this.resetWebGLState();
    },
    /**
     * 渲染器更新渲染
     * @memberof BuildingEffect
     * @method render
     * @param {Object} context 已有渲染器信息,无需传值
     */
    render(context) {
        let THREE = window.THREE;
        let cam = this.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]));
        // Projection matrix can be copied directly
        this._camera.projectionMatrix.fromArray(cam.projectionMatrix);


        if (this.mixer && this.mixer.update) {
            // 更新混合器相关的时间, clock.getDelta()方法获得两帧的时间间隔
            this.mixer.update(this.clock.getDelta());
        }
        this.renderer.state.reset();

        this.bindRenderTarget();

        this.renderer.render(this.scene, this._camera);
        // as we want to smoothly animate the ISS movement, immediately request a re-render
        this.requestRender();

        // cleanup
        this.resetWebGLState();
    }
}

2.调用RenderNode

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./jquery/jquery-1.12.3.min.js"></script>
    <script src="./threejs/three.min.js"></script>
    <script src="./threejs/GLTFLoader.js"></script>
    <script src="./renderNodeClass.js"></script>

    <link rel="stylesheet" href="https://js.arcgis.com/4.32/esri/themes/light/main.css" />
    <script src="https://js.arcgis.com/4.32/"></script>
    <style>
        body,
        html,
        #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
        }
    </style>

</script>
    <script>
        require([
            'esri/Map',
            'esri/views/SceneView',
            "esri/geometry/Mesh",
            "esri/Graphic",
            "esri/geometry/Point",
            "esri/geometry/Polyline",
            "esri/layers/GraphicsLayer",
            "esri/geometry/operators/densifyOperator",
            "esri/Camera",
            "esri/views/3d/webgl/RenderNode",
            "esri/views/3d/webgl"
        ], (Map, SceneView, Mesh, Graphic, Point, Polyline, GraphicsLayer, densifyOperator, Camera, RenderNode,webgl) => {

            let graphicLayer = new GraphicsLayer();
            let map = new Map({
                layers: [graphicLayer],
                basemap: 'satellite'
            })

            let view = new SceneView({
                container: 'viewDiv',
                viewingMode:'local',
                map,
                camera: {
                    heading: 46.490512697555566,
                    tilt: 65.03077172871896,
                    position: {
                        // longitude: 116.37280650987242,
                        // latitude: 39.88099012247863,
                        x: 12956520.797315104,
                        y: 4851088.587867753,
                        z: 127.55169651366651,
                        spatialReference: {
                            wkid: 102100
                        }
                    }
                }
            })

            let subRenderClass = RenderNode.createSubclass(RenderNodeClass);
            view.when(function(){
                new subRenderClass({
                    view,
                    webgl
                })
            })
        })
    </script>
</head>

<body>
    <div id="viewDiv">
    </div>
</body>

</html>

3、结果

在这里插入图片描述
这里面主要在使用ThreeJs的时候引入了GLTFLoader.js用于加载gltf文件,并且调用that.mixer = new THREE.AnimationMixer(gltf.scene)去启动动画效果,在render中实时更新就能看到动态的模型。

四 、总结

在ArcGIS场景中使用ThreeJS得时候,只能通过设置viewingMode=‘local’的方式去加载,及只能在平面效果上看,如果设置为global或出现因为坐标轴不一致导致的模型位置和旋转问题。