Abstract
Keywords Cesium  Threejs  Threejs  Cesium 
Citation Yao Qing-sheng.ThreeJS 集成地图瓦片.FUTURE & CIVILIZATION Natural/Social Philosophy & Infomation Sciences,20240808. https://yaoqs.github.io/20240808/threejs-ji-cheng-di-tu-wa-pian/

转载自 ThreeJS 集成地图瓦片

前言

由于公司最近马上需要落成一套三维 GIS 系统,之前基于什么百度、高德、Echarts 之类都太 LOW 了而且没有办法达到项目的要求。无奈只能硬着头皮设计,最后发现可以使用 ThreeJS 来渲染三维模型同时将地图的瓦片落在三维场景底部实现整体功能,基于这个思路足足检索了大量内容足足搞了两天才落成一个基础的 Demo,伤的一 B。

首先必须说一下,这个标题可能有一点标题党的意思了,我不是直接将瓦片数据丢给 ThreeJS 渲染的(难度太大没有那么多时间),而是借助其他框架整合最终实现这个功能的,如果你是真正的硬核玩家那就没办法了。不过选择成熟框架进行整合出来的最终效果我觉得远远高于直接使用 ThreeJS。

DEM 瓦片数据 + 卫星图瓦片数据在 WebGL 之中的渲染

那么我使用的框架是什么呢?那就是大名鼎鼎的 CesiumJS,这个东西是全世界做 GIS 系统的开源项目之中最专业,最高效的框架,开源且免费!我这里一两句是没有办法说明它有多 NB 的,如果只论地图他可以把 BAT、谷歌、微软等一下大厂的地图全部安在脚下摩擦。

版本依赖

  1. ThreeJS 0.87.0
  2. Cesium 1.61.0

集成原理

无论是 Cesium 还是 ThreeJS 都是基于 WebGL 渲染意味这两者的空间坐标是相对一致的,加之 GIS 系统核心在于 GPS 坐标数据与笛卡尔坐标系的对应关系,在三维空间上这个关系是无论对 Cesium 还是 ThreeJS 都是一致的只需要对其进行一层解析就可以了。同时两者的渲染管线一致所以很多 WebGL 的原生封装两者是可以直接共用的。

集成上面最核心的部分就是将两个渲染引擎的渲染帧给同步,方式是将 ThreeJS 的帧生成函数与 Cesium 在同一帧调用完成,方式如下所示:

1、分别创建 Cesium 视图与 ThreeJS 视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var cesiumContainer = document.getElementById("cesiumContainer");
var ThreeContainer = document.getElementById("ThreeContainer");
//创建Cesium视图
var cesium = {}
cesium.viewer = new Cesium.Viewer(cesiumContainer, {});
//创建Three视图
var three = {};
three.scene = new THREE.Scene();
three.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
three.renderer = new THREE.WebGLRenderer({
alpha: true,
logarithmicDepthBuffer:true
});
ThreeContainer.appendChild(three.renderer.domElement);

2、将 Cesium 的渲染帧给禁用调用

1
viewer.useDefaultRenderLoop = false

3、同步两者的相机,由于用户直接的操作的是 Cesium 所以最终要求将 ThreeJS 的相机同步到 Cesium 上,每一帧更新。

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
//将Cesium的摄像头视场同步至THREE
three.camera.fov = Cesium.Math.toDegrees(cesium.viewer.camera.frustum.fovy);
//更新摄像头投影矩阵
three.camera.updateProjectionMatrix();
//关闭摄像头自动更新
three.camera.matrixAutoUpdate = false;
//获取Cesium相机矩阵
var cvm = cesium.viewer.camera.viewMatrix;
//获取Cesium相机逆矩阵
var civm = cesium.viewer.camera.inverseViewMatrix;
//设置three的世界坐标矩阵
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]
);
//重置视角
three.camera.lookAt(new THREE.Vector3(0, 0, 0));

4、同步 ThreeJS 的三维物体,每一帧都需要更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//_3DOBS是一个数组,数组之中的每一个元素都包含一个three的Object3D、经度、维度三项数据。
for (var id in _3DOBS) {
//获取经纬度
var LnL = [_3DOBS[id].longitude, _3DOBS[id].dimension];
//获取经纬度原点坐标
var center = Cesium.Cartesian3.fromDegrees(LnL[0], LnL[1]);
//获取经纬度原点坐标向上一个单位的坐标
var centerHigh = Cesium.Cartesian3.fromDegrees(LnL[0], LnL[1], 1);
//重置模型位置
_3DOBS[id].Group.position.copy(center);
//重置模型方向
_3DOBS[id].Group.lookAt(centerHigh);
}
//还有一步操作,因为Cesium是y轴向上所以需要将three的Object3D对象在X轴方向翻转90度。

5、同步两个渲染器的渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
three.Render = () => {
//这里的this是three对象
requestAnimationFrame(this.Render);
//渲染cesium
渲染cesium.viewer.render();
//渲染three
var width = ThreeContainer.clientWidth;
var height = ThreeContainer.clientHeight;
var aspect = width / height;
this.camera.aspect = aspect;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
this.renderer.render(this.scene, this.camera);
}

最后将两者对应的 dom 对象重叠在一起,Three 在上层,Cesium 在下层,将 Three 的事件捕捉禁用,启用背景透明。这些步骤之后两个渲染器基本上就可以协同工作了,后续可以自行对两个渲染器进行封装,然后分别实现各自的功能。

特性

1、完整的 ThreeJS 功能

ThreeJS 场景下导入的基于 PBR 渲染的 GLTF 模型

在渲染器的基础之上没有对 THREE 做任何修改,所以 ThreeJS 上拥有的特性全部可以无缝应用在这个项目之中,无论是粒子、动画、物理学模拟等等。由于这个特性存在,无论什么大场景的三维可视化、GIS 系统或者是一些大型工程的 BIM 都可以轻松实现,至少在地图上已经赢了。即便是对 ThreeJS 的操作事件其实可以通过下层 Cesium 透传回来。

2、各种地图瓦片数据对接

高德瓦片集成效果

目前我所知道的,无论是谷歌、高德、百度、腾讯还是其他乱七八糟的地图都是采用瓦片数据对整个地球进行分割虽然各家的瓦片完全不一样,但是格式是完全一样的,这就意味着他们是通用的,而且 Cesium 可以对接这些数据。

瓦片数据上我大概知道的有:

大概试了一下谷歌地图不但没有被墙而且访问速度还非常的快,整体无论是精度还是数据量都远远超过国产地图,妈的,主场优势的 BAT 简直丢脸。

3. 地形 DEM 数据可视化

这个效果无敌了

这个虽然看起来比较科幻,但是人家谷歌在多年前就已经有了,那就是将 DEM 数据在浏览器地图之中展示。不得不说这个还是效果还是 NB 的,但是人家谷歌使用的高度贴图,Cesium 其实有点悲剧没有办法直接使用谷歌的高度贴图(至少我没有找到,谁知道可以给我说一下),所以就退而求其次使用第三方的 DEM 数据,比较痛苦的是 Cesium 官方其实是有一套全球 14 级的 DEM 数据的基本上开箱即用,但是国内访问速度太慢了,所以要不然就自己按照其规则去下载 DEM 数据然后自己搭建,我看了一下 CSDN 上有一个老哥搞了一个教程可以学习一波地址是:点击跳转,但是比较伤的是数据转换之后太大了服务器带宽不够所以我还是去找了一下国内速度比较快的最终还真就找到了一个全中国 14 级 DEM,速度飞快:

https://lab.earthsdk.com/terrain/577fd5b0ac1f11e99dbd8fd044883638

最终的效果就是上图的样子了

4、CZML 数据文件封装

官网的卫星数据可视化 Demo 效果

这个东西我还没有完全搞懂,但是意思是非常清楚的,基本意味着 GIS 上能够用得上的所有数据呈现都有一个特定格式进行封装,包含了动画、路径、图标等等,我大概在官方 Demo 上看了一会,目前只能看看没有办法直接开始写但是东西就是这个东西,后续必须要学习一下,非常的震撼。

已知问题

1、目前的三维坐标转换函数只能在球面地图上良好的转换,平面上不行,主要是经纬度解析会 GG。

不是二维,是平面!

2、ThreeJS 在 0.87 之后所有版本不知道为什么无法正常转换坐标,目前只能用 0.87 也将就一下了。

总结

以后任何可以使用 GIS 技术的项目应该都不会虚了,毕竟有了这些东西你告诉我什么效果实现不了?上文的内容基本上已经足够把基础的框架给搭建出来,我不会直接给你我搭建的源码毕竟不认真理解一波自己搭建起来是学不到任何东西的。

References