用Three.js打造高性能3D地图的实战经验与踩坑总结

东成(打工版) 交互 阅读 1,984
赞 14 收藏
二维码
手机扫码查看
反馈

为什么我要折腾3D地图?

最近接了个需求,要在网页里嵌个3D地图,展示城市建筑和数据分布。老板说“要酷一点”,产品经理说“最好能旋转缩放”,设计师甩过来一张Figma图,上面全是立体楼宇和飞线动画。我一想,行吧,反正也不是第一次搞地图了。但这次不是普通高德百度那种,得是真3D的——于是开始踩坑。

用Three.js打造高性能3D地图的实战经验与踩坑总结

市面上主流方案其实就那么几个:CesiumJS、Mapbox GL JS(现在叫Mapbox SDK for Web)、Three.js + 自定义地图瓦片,还有国内的超图、百度地图3D版。我挨个试了,折腾了一周,头发掉了不少,今天就来唠点实在的。

谁更灵活?谁更省事?

先说结论:如果你要快速上线、功能不复杂,Mapbox GL JS 是最省心的。但如果你要深度定制、搞建筑模型、加粒子效果,那只能上 CesiumJS 或 Three.js。我比较喜欢用 CesiumJS,虽然文档有点乱,但生态成熟,坑也踩得差不多了。

Mapbox 的优势是集成快,几行代码就能跑起来:

mapboxgl.accessToken = 'your-token';
const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v11',
  center: [116.4, 39.9],
  zoom: 10,
  pitch: 60, // 倾斜角度,开启3D视角
  bearing: 0
});

加个3D建筑也很简单,官方有 extrusion 图层:

map.addLayer({
  id: '3d-buildings',
  source: 'composite',
  'source-layer': 'building',
  filter: ['==', 'extrude', 'true'],
  type: 'fill-extrusion',
  minzoom: 15,
  paint: {
    'fill-extrusion-height': ['get', 'height'],
    'fill-extrusion-base': ['get', 'min_height'],
    'fill-extrusion-color': '#aaa'
  }
});

但问题来了:这些建筑高度是固定的,没法动态改,也不能加载自定义模型(比如BIM或者glTF)。而且国内网络下,Mapbox的瓦片经常加载慢,甚至被墙。我上次上线前没测国内环境,结果客户打来电话说“地图白屏”,当场社死。

CesiumJS:强大但有点重

我最终选了 CesiumJS,主要是因为它原生支持3D Tiles、glTF、地形高程,还能和GIS数据无缝对接。比如加载一个城市的3D建筑模型,就这么干:

const viewer = new Cesium.Viewer('cesiumContainer', {
  terrainProvider: Cesium.createWorldTerrain(),
  baseLayerPicker: false,
  animation: false,
  timeline: false
});

// 加载3D Tiles
const tileset = viewer.scene.primitives.add(
  new Cesium.Cesium3DTileset({
    url: 'https://jztheme.com/tiles/building/tileset.json'
  })
);
viewer.zoomTo(tileset);

这里注意我踩过好几次坑:3D Tiles 的坐标系必须和 Cesium 的 WGS84 一致,否则模型会飘到太平洋去。还有,Cesium 默认启用了很多特效(比如大气层、太阳光照),如果不关掉,低端机直接卡成PPT。我现在初始化都这么写:

const viewer = new Cesium.Viewer('cesiumContainer', {
  skyBox: false,
  skyAtmosphere: false,
  fog: false,
  shadows: false,
  terrainProvider: Cesium.createWorldTerrain({ requestWaterMask: false, requestVertexNormals: false })
});

性能确实比 Mapbox 重,首屏加载慢,但换来的是无限扩展性。比如我想在楼顶加个粒子喷泉,或者让某栋楼高亮闪烁,Cesium 都能直接操作 primitive。而 Mapbox 到这一步基本就卡死了。

Three.js:自由但太折腾

有同事说:“不如自己用 Three.js 搞,完全可控。” 理论上没错,但实际开发中,你得自己处理瓦片加载、坐标转换、LOD(细节层次)、拾取(raycasting)…… 光是把高德/天地图的2D瓦片投影到3D球面上,就得写几百行代码。我试过一次,改完后仍有一两个小问题:比如缩放时瓦片错位,或者点击位置不准。最后还是放弃了。

除非你是做游戏或艺术装置,否则别轻易上 Three.js + 自定义地图。维护成本太高,而且团队里没人能快速接手。

性能对比:差距比我想象的大

我在一台中端笔记本(i5 + 16G + MX350)上做了简单测试:

  • Mapbox GL JS:加载北京区域,带3D建筑,内存占用约 400MB,帧率稳定 50fps+
  • CesiumJS(默认配置):同样区域,内存 800MB+,帧率 30-40fps(开阴影时掉到20fps)
  • CesiumJS(精简配置,如上):内存 550MB,帧率 45fps 左右

所以,如果目标用户多是移动端或低配电脑,Mapbox 仍是首选。但如果你的应用是给大屏展示、展厅用,或者内网环境,那 Cesium 的性能完全可以接受。

我的选型逻辑

现在我拿到3D地图需求,会先问三个问题:

  1. 需要加载自定义3D模型吗?(比如BIM、倾斜摄影)→ 要的话,直接 Cesium
  2. 是否需要在国内稳定访问?→ 如果是,Mapbox 得配代理或换底图,否则放弃
  3. 交互复杂度高吗?(比如点击楼查询属性、动态热力图)→ Cesium 的 DataSource 和 Entity 系统更灵活

基于这些,我现在的默认选择是 CesiumJS,但会做性能优化:关掉不必要的特效、用 Draco 压缩3D Tiles、懒加载非可视区域。虽然启动慢点,但后续体验更稳。

至于国内那些商业GIS平台(比如超图),API封闭、文档老旧、调试困难,除非甲方强制要求,否则我一般不碰。

核心代码就这几行,但坑都在细节里

最后贴个我常用的 Cesium 初始化模板,包含防卡顿和基础交互:

const viewer = new Cesium.Viewer('map', {
  imageryProvider: new Cesium.UrlTemplateImageryProvider({
    url: 'https://jztheme.com/tiles/{z}/{x}/{y}.png'
  }),
  terrainProvider: Cesium.createWorldTerrain(),
  skyBox: false,
  skyAtmosphere: false,
  fog: false,
  shadows: false,
  infoBox: false,
  selectionIndicator: false,
  sceneModePicker: false,
  navigationHelpButton: false
});

// 禁用默认双击重置
viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

// 设置初始视角
viewer.camera.setView({
  destination: Cesium.Cartesian3.fromDegrees(116.4, 39.9, 1000),
  orientation: {
    heading: Cesium.Math.toRadians(0),
    pitch: Cesium.Math.toRadians(-30),
    roll: 0
  }
});

这里注意:自定义底图 URL 必须支持 CORS,否则本地开发会报错。我之前在本地起个 Express 服务代理一下才解决。

总结一下

以上是我踩坑后的总结:Mapbox 省事但受限,CesiumJS 灵活但重,Three.js 自由但太累。我现在的项目基本都用 CesiumJS,配合性能优化,效果和稳定性都能兼顾。当然,没有银弹——如果你只是做个简单的3D地球仪,说不定用 Mapbox 更快。

以上是我个人对3D地图技术方案的完整对比,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多(比如结合WebSocket做实时数据可视化),后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论