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视图

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的渲染帧给禁用调用

viewer.useDefaultRenderLoop = false

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

//将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的三维物体,每一帧都需要更新

//_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、同步两个渲染器的渲染

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可以对接这些数据。

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

高德卫星:http://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}
百度卫星:http://shangetu1.map.bdimg.com/it/u=x={x};y={y};z={z};v=009;type=sate&fm=46
谷歌卫星:http://www.google.cn/maps/vt?lyrs=m&gl=CN&x={x}&y={y}&z={z}
OMS地图:https://c.tile.openstreetmap.org/{z}/{x}/{y}.png

大概试了一下谷歌地图不但没有被墙而且访问速度还非常的快,整体无论是精度还是数据量都远远超过国产地图,妈的,主场优势的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技术的项目应该都不会虚了,毕竟有了这些东西你告诉我什么效果实现不了?上文的内容基本上已经足够把基础的框架给搭建出来,我不会直接给你我搭建的源码毕竟不认真理解一波自己搭建起来是学不到任何东西的。

ThreeJS集成地图瓦片》有23条留言

  1. 请问在cesium加载地形的情况下,旋转调整视角,threejs模型始终在最前面,不被地形遮挡,该怎么做才能使threejs模型像真的在地形中呢,希望被相应的地形所遮挡

    1. 这就有点难了,想要达到你说的效果只有可能去获取cesium的webgl上下文然后加入threejs的控制逻辑,这一步最主要的问题在于两个框架大概率会冲突而且要写的化难度也不小

  2. “即便是对ThreeJS的操作事件其实可以通过下层Cesium透传回来”,您好,能不能提供一下对threejs操作事件的demo或者思路呢,特别感谢!

    1. ThreeJS的操作本质上就是对Dom事件的响应,我们关闭three所在的canvas的事件监听之后完全可以通过拦截cesium的鼠标事件然后去触发threejs,我当时说的就是这个意思

  3. 有个小小的疑问。cesium不是可以直接加载三维模型吗?直接用cesium搞与cesium与three结合,与什么区别吗?

    1. 没错,cesium可以搞3DTile,但是这类模型很难去自定义渲染管线比如说环境光遮蔽、PBR管线等等,使用ThreeJS的原因是我们不仅仅在导入模型而是将完整的三维渲染引擎给搞进来了,很多复杂的甚至是炫酷的效果便可以在GIS上实现了(想想粒子系统、物理引擎)

  4. 一套完整的技术体系从原始地图数据、矢量切片服务、地图引擎、三维渲染,期待中,大神什么时候能发布一下

      1. 老大,mapbox可以捎着我,本人小白,还在看3dtile怎么加载到mapbox中,后续还有效果怎么弄,通过deck来设置独立的展示效果这条路咋样呢?

  5. hello,现在的问题是background和天空盒之类的渲染顺序,我在想有没有办法把Cesium的绘制 渲染到一张fbo上 然后取出rt和depth这样 就是将cesium的绘制过程完整的整合到了一起,仅仅是个想法。

  6. 这个threejs版本太低了,在vue中编译不通过,一直卡住.
    如果用最新版的threejs,用three添加的物体,在地球上显示不出来,不知道是不是新版的threejs,坐标和相机规则有什么改动.

    1. 确实是这样,我也是不知道为啥,经过我自己的测试只能是这个版本,估计是相机转换矩阵有变动。
      但是vue不应该编译不通过呀,实在不行就在vue.config里面externals掉用h5直接加载

  7. 你好,请问能给一个详细点的例子吗?
    我参照您的文章和官方那个3年前的例子弄出来没有效果

  8. 大佬,请问我想在高德地图中加载3d模型用threejs加载,目前已经实现将threejs中load的模型手动转为高德的3d层,但是对应svg来说,却是倒立的而且颜色也不对,正面可以看到边界,背面透明看不到svg呈现的ui

    1. 就是不知道要怎么把threejs中加载的文件如何运用到地图中,目前是手动转为高德的3d层,太麻烦了,所以想了解一下还有其他方法吗?毕竟以后如果换了其他地图,如果官方没有提供3d层,那这套方法就不能用了

      1. 首先,如果你想要实现的效果仅仅是在地图上渲染3D模型我不推荐集成ThreeJS,高德自带的GLTF加载器足够你使用了,官方参考是:https://lbs.amap.com/api/javascript-api/example/mesh/3d-gltf。
        其次,如果你的项目注重点是三维,想要地图引擎上实现复杂的3D效果,我建议放弃高德使用我这篇文章的Cesium与ThreeJS结合。
        最后,如果你开发的重度地图相关的项目,三维只是锦上添花的话,我最推荐的地图引擎是MapBox,这是目前最接近Google的引擎,完开源能够简单进行拓展。
        题外,我现在有一套完整的技术体系从原始地图数据、矢量切片服务、地图引擎、三维渲染都有,但是文章还没有写出来,年底太忙了,你可以期待一下。

回复 ff 取消回复