• 最新的Cesium和Three的整合方法(附完整代码)


    1 简介

    截止2022年7月30日,最新的Cesium-Three整合示例,基于如下版本:

    2 在线地址

    [ Live Demo ]:Cesium integrate Threejs (syzdev.cn)

    3 屏幕截图

    在这里插入图片描述

    4 参考资料

    • 实现原理:https://cesium.com/blog/2017/10/23/integrating-cesium-with-threejs/
    • 代码示例:https://github.com/CesiumGS/cesium-threejs-experiment

    5 原理简述

    Cesium和Three都是基于WebGL的三维框架,在渲染成DOM时,本质上都是canvas。所以两者的集成需要解决如下几个问题:

    1. 场景叠加:将两个canvas在DOM上叠加到一起,解决方式为将Three的canvas叠加在Cesium的canvas之上,通过CSS实现;
    2. 交互:由于屏幕上要显示两个canvas,所以需要关闭其中一个交互功能,在Three的canvas上添加CSS样式pointer-events:none来屏蔽其鼠标交互,主要用Cesium来实现交互。
    3. 刷新同步:关闭Cesium中的默认帧渲染useDefaultRenderLoop,用cesium.viewer.render();来手动触发帧渲染,在Three中是通过requestAnimationFrame来实现帧渲染的,所以在每个循环渲染帧中要同步渲染Cesium和Three的场景。
    4. 坐标:在renderThreeObj()方法中有详细的描述,Three场景下的3D对象,也是跟随改变其位置和方向的。

    6 改动说明

    Cesium官方提供的示例程序“cesium-threejs-experiment”中使用的版本如下:

    • Cesium ^1.45.0
    • Three 0.87.1

    显然,这在现在是过时的,若用最新的Cesium和Three版本直接套用原来的代码,会出现如下问题:

    • Cesium初始化无法找到容器
    • Three场景无法正确的显示,加载的模型在场景中找不到

    解决方法如下,注释initCesium()方法中Cesium初始化选项creditContainer : "hidecredit",可以解决Cesium初始化时无法找到容器的问题:

    function initCesium() {
      cesium.viewer = new Cesium.Viewer(cesiumContainer, {
        // creditContainer : "hidecredit", // Cannot read properties of null (reading 'appendChild')
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    去除renderThreeObj()方法中的three.camera.lookAt(new THREE.Vector3(0,0,0));语句,并在配置Three相机矩阵之前添加three.camera.lookAt(0, 0, 0)

    function renderThreeObj() {
      three.camera.lookAt(0, 0, 0) // here
      three.camera.matrixWorld.set(...)
      three.camera.matrixWorldInverse.set(...)
      // three.camera.lookAt(new THREE.Vector3(0,0,0));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    同时,需要给Three容器添加如下CSS样式:

    #ThreeContainer canvas {
      pointer-events: none;
      position: absolute;
      top: 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    7 完整代码

    7.1 GitHub(推荐)

    GitHub地址:https://github.com/syzdev/Cesium-Three

    若读者已经安装了Git,可以在指定文件夹内打开Git Bash窗口,输入如下指令:

    git clone https://github.com/syzdev/Cesium-Three.git
    
    • 1

    该指令会将代码克隆到指定文件夹中,再通过服务发布即可访问。

    7.2 原生代码

    下面代码中的第三方库Cesium和Three都是以CDN的形式引入的,上述GitHub中的代码都是以本地文件的形式发布的:

    DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
      <meta name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
      <title>Cesium integrate Threejstitle>
      
      <link rel="stylesheet" href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css">
      <script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js">script>
      
      <script src="https://cdn.jsdelivr.net/npm/three@0.143.0/build/three.min.js">script>
      <style>
        html,
        body,
        #cesiumContainer {
          width: 100%;
          height: 100%;
          margin: 0;
          padding: 0;
          overflow: hidden;
        }
    
        .cesium-viewer .cesium-widget-credits {
          display: none;
        }
    
        #ThreeContainer canvas {
          pointer-events: none;
          position: absolute;
          top: 0;
        }
      style>
    head>
    
    <body>
      <div id="cesiumContainer">div>
      <div id="ThreeContainer">div>
      <script>
        let three = {
          renderer: null,
          camera: null,
          scene: null,
        }
    
        let cesium = {
          viewer: null,
        }
    
        let minWGS84 = [115.23, 39.55];
        let maxWGS84 = [116.23, 41.55];
    
        let cesiumContainer = document.getElementById('cesiumContainer')
        let ThreeContainer = document.getElementById('ThreeContainer')
        let _3Dobjects = [] // Could be any Three.js object mesh
    
        function _3DObject() {
          this.threeMesh = null // Three.js 3DObject.mesh
          this.minWGS84 = null // location bounding box
          this.maxWGS84 = null
        }
    
        function initCesium() {
          cesium.viewer = new Cesium.Viewer(cesiumContainer, {
            useDefaultRenderLoop: false,
            selectionIndicator: false,
            homeButton: false,
            sceneModePicker: false,
            infoBox: false,
            navigationHelpButton: false,
            navigationInstructionsInitiallyVisible: false,
            animation: false,
            timeline: false,
            fullscreenButton: false,
            allowTextureFilterAnisotropic: false,
            baseLayerPicker: false,
    
            // contextOptions: {
            //   webgl: {
            //     alpha: false,
            //     antialias: true,
            //     preserveDrawingBuffer: true,
            //     failIfMajorPerformanceCaveat: false,
            //     depth: true,
            //     stencil: false,
            //     anialias: false,
            //   },
            // },
    
            targetFrameRate: 60,
            // resolutionScale: 0.1,
            orderIndependentTranslucency: true,
            geocoder: false,
            imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
              url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
            }),
            automaticallyTrackDataSourceClocks: false,
            // creditContainer : "hidecredit", // Cannot read properties of null (reading 'appendChild')
            dataSources: null,
            clock: null,
            terrainShadows: Cesium.ShadowMode.DISABLED,
          })
          cesium.viewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(
              (minWGS84[0] + maxWGS84[0]) / 2,
              (minWGS84[1] + maxWGS84[1]) / 2 - 1.25,
              200000
            ),
            orientation: {
              heading: Cesium.Math.toRadians(0),
              pitch: Cesium.Math.toRadians(-60),
              roll: Cesium.Math.toRadians(0),
            },
            duration: 3,
          })
        }
    
        function initThree() {
          let fov = 45
          let width = window.innerWidth
          let height = window.innerHeight
          let aspect = width / height
          let near = 1
          let far = 10 * 1000 * 1000
          three.scene = new THREE.Scene()
          three.camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
          three.renderer = new THREE.WebGLRenderer({ alpha: true })
          ThreeContainer.appendChild(three.renderer.domElement)
        }
    
        function addBoxEntity() {
          cesium.viewer.entities.add({
            name: 'Box',
            position: Cesium.Cartesian3.fromDegrees(
              (minWGS84[0] + maxWGS84[0]) / 2,
              (minWGS84[1] + maxWGS84[1]) / 2 - 0.5,
              7000
            ),
            box: {
              dimensions: new Cesium.Cartesian3(16000.0, 16000.0, 16000.0),
              material: Cesium.Color.RED.withAlpha(0.5),
              fill: true,
              outline: true,
              outlineColor: Cesium.Color.BLACK,
              outlineWidth: 1.0,
            },
          })
        }
    
        function addPolygonEntity() {
          cesium.viewer.entities.add({
            name: 'Polygon',
            polygon: {
              hierarchy: Cesium.Cartesian3.fromDegreesArray([
                minWGS84[0],
                minWGS84[1],
                maxWGS84[0],
                minWGS84[1],
                maxWGS84[0],
                maxWGS84[1],
                minWGS84[0],
                maxWGS84[1],
              ]),
              material: Cesium.Color.PINK.withAlpha(0.4),
            },
          })
        }
    
        function addBoxGeometry() {
          const geometry = new THREE.BoxGeometry()
          const material = new THREE.MeshNormalMaterial()
          const dodecahedronMesh = new THREE.Mesh(geometry, material)
          dodecahedronMesh.scale.set(15000, 15000, 15000) // scale object to be visible at planet scale
          dodecahedronMesh.position.z += 7000.0 // translate "up" in Three.js space so the "bottom" of the mesh is the handle
          dodecahedronMesh.rotation.x = Math.PI / 2 // rotate mesh for Cesium's Y-up system
          let dodecahedronMeshYup = new THREE.Group()
          dodecahedronMeshYup.add(dodecahedronMesh)
          three.scene.add(dodecahedronMeshYup) // don’t forget to add it to the Three.js scene manually
          // Assign Three.js object mesh to our object array
          let _3DOB = new _3DObject()
          _3DOB.threeMesh = dodecahedronMeshYup
          _3DOB.minWGS84 = minWGS84
          _3DOB.maxWGS84 = maxWGS84
          _3Dobjects.push(_3DOB)
        }
    
        function init3DObject() {
          // Cesium entity
          addPolygonEntity()
          addBoxEntity()
          // Three.js Objects
          addBoxGeometry()
        }
    
        function renderCesium() {
          cesium.viewer.render()
        }
    
        function renderThreeObj() {
          // register Three.js scene with Cesium
          three.camera.fov = Cesium.Math.toDegrees(cesium.viewer.camera.frustum.fovy) // ThreeJS FOV is vertical
          // three.camera.updateProjectionMatrix();
          let cartToVec = function (cart) {
            return new THREE.Vector3(cart.x, cart.y, cart.z)
          }
    
          // Configure Three.js meshes to stand against globe center position up direction
          for (let id in _3Dobjects) {
            minWGS84 = _3Dobjects[id].minWGS84
            maxWGS84 = _3Dobjects[id].maxWGS84
            // convert lat/long center position to Cartesian3
            let center = Cesium.Cartesian3.fromDegrees(
              (minWGS84[0] + maxWGS84[0]) / 2,
              (minWGS84[1] + maxWGS84[1]) / 2
            )
            // get forward direction for orienting model
            let centerHigh = Cesium.Cartesian3.fromDegrees(
              (minWGS84[0] + maxWGS84[0]) / 2,
              (minWGS84[1] + maxWGS84[1]) / 2,
              1
            )
            // use direction from bottom left to top left as up-vector
            let bottomLeft = cartToVec(
              Cesium.Cartesian3.fromDegrees(minWGS84[0], minWGS84[1])
            )
            let topLeft = cartToVec(
              Cesium.Cartesian3.fromDegrees(minWGS84[0], maxWGS84[1])
            )
            let latDir = new THREE.Vector3().subVectors(bottomLeft, topLeft).normalize()
            // configure entity position and orientation
            _3Dobjects[id].threeMesh.position.copy(center)
            _3Dobjects[id].threeMesh.lookAt(centerHigh.x, centerHigh.y, centerHigh.z)
            _3Dobjects[id].threeMesh.up.copy(latDir)
          }
          // Clone Cesium Camera projection position so the
          // Three.js Object will appear to be at the same place as above the Cesium Globe
          three.camera.matrixAutoUpdate = false
          let cvm = cesium.viewer.camera.viewMatrix
          let civm = cesium.viewer.camera.inverseViewMatrix
    
          three.camera.lookAt(0, 0, 0)
    
          three.camera.matrixWorld.set(
            civm[0],
            civm[4],
            civm[8],
            civm[12],
            civm[1],
            civm[5],
            civm[9],
            civm[13],
            civm[2],
            civm[6],
            civm[10],
            civm[14],
            civm[3],
            civm[7],
            civm[11],
            civm[15]
          )
    
          three.camera.matrixWorldInverse.set(
            cvm[0],
            cvm[4],
            cvm[8],
            cvm[12],
            cvm[1],
            cvm[5],
            cvm[9],
            cvm[13],
            cvm[2],
            cvm[6],
            cvm[10],
            cvm[14],
            cvm[3],
            cvm[7],
            cvm[11],
            cvm[15]
          )
    
          let width = cesiumContainer.clientWidth
          let height = cesiumContainer.clientHeight
    
          let aspect = width / height
          three.camera.aspect = aspect
          three.camera.updateProjectionMatrix()
          three.renderer.setSize(width, height)
          three.renderer.clear()
          three.renderer.render(three.scene, three.camera)
        }
    
        function loop() {
          requestAnimationFrame(loop)
          renderCesium()
          renderThreeObj()
        }
    
        initCesium() // Initialize Cesium renderer
        initThree() // Initialize Three.js renderer
        init3DObject() // Initialize Three.js object mesh with Cesium Cartesian coordinate system
        loop() // Looping renderer
    
      script>
    body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
  • 相关阅读:
    openssl之中文手册
    redux 中间件
    又一个SQL Developer中调试存储过程的例子
    使用自定义委托来调用Lua中的多返回值和长参数类型函数
    [足式机器人]Part3机构运动微分几何学分析与综合Ch01-3 平面运动微分几何学——【读书笔记】
    【数据挖掘】数据预处理和运用概念以及对鸢尾花数据集分类的分位数图和直方图的实际运用
    Rank-embedded Hashing for Large-scale Image Retrieval
    蓝桥杯刷题四
    Find My资讯|AirTag 正在帮更多人找到丢失的行李,Find My用处越来越大
    固定元素宽度根据文本的长度缩小字号,超出缩小字号
  • 原文地址:https://blog.csdn.net/syzdev/article/details/126072050