前言:当 Three.js 遇上 3D Tiles
在 WebGL 的世界里,Three.js 是浪漫的代名词,负责处理精致的渲染、酷炫的特效;而 Cesium 则是不折不扣的硬核派,统治着地理空间数据的天下。
但是,如果你想在 Three.js 的极致视觉体验中加载高精度的城市 3D 瓦片(3D Tiles),同时还要保持大地图操作的“丝滑感”,该怎么办?
今天我就来分享一下我在开发 Meteor3D 时的核心实现方案:如何利用 Three.js 高效加载 Cesium Ion 的 3D Tiles 数据,并实现一套近乎完美的相机控制系统。
1. 核心选型:为什么是 3d-tiles-renderer?
在 Three.js 中加载 3D Tiles,首选当然是 3d-tiles-renderer。它支持:
- LOD(细节分级渲染)
- 视锥剪裁
- 完整的 Cesium Ion 插件支持
- Draco 压缩解码
安装依赖
npm install 3d-tiles-renderer three
"3d-tiles-renderer": "^0.4.19"
"three": "^0.181.2"
2. 核心代码:加载 Cesium Ion 瓦片
加载 Cesium 数据最麻烦的是权限校验和坐标系转换。在 Meteor3D 中,我们采用了以下的高效配置:
2.1 初始化加载器
import { TilesRenderer } from '3d-tiles-renderer/three';
import { CesiumIonAuthPlugin, GLTFExtensionsPlugin } from '3d-tiles-renderer/three/plugins';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
const tiles = new TilesRenderer();
// 1. 注册 Cesium Ion 认证插件
tiles.registerPlugin(
new CesiumIonAuthPlugin({
apiToken: 'YOUR_ION_TOKEN_HERE',
assetId: '2275207', // 你的 Asset ID
}),
);
// 2. 注册 Draco 解码(大场景必备性能优化)
tiles.registerPlugin(
new GLTFExtensionsPlugin({
dracoLoader: new DRACOLoader().setDecoderPath(
'https://unpkg.com/three@0.153.0/examples/jsm/libs/draco/gltf/',
),
}),
);
scene.add(tiles.group);
2.2 坐标系大挪移(重点!)
Cesium 使用的是 ECEF(以地球中心为原点)坐标系,且 Z 轴向上。而 Three.js 通常是 Y 轴向上。如果不进行处理,你会发现模型在遥远的天边或者是倒过来的。
在 Meteor3D 中,我们监听了 load-root-tileset 事件,自动将瓦片对齐到 Three.js 的中心:
tiles.addEventListener('load-root-tileset', () => {
const sphere = new Sphere();
tiles.getBoundingSphere(sphere);
const position = sphere.center.clone();
const distanceToEllipsoidCenter = position.length();
// 计算将表面法线对齐到 Three.js Y 轴的旋转
const surfaceDirection = position.normalize();
const up = new Vector3(0, 1, 0);
const rotationToNorthPole = rotationBetweenDirections(surfaceDirection, up);
// 应用旋转并将模型中心重置到 Y=0
tiles.group.quaternion.copy(rotationToNorthPole);
tiles.group.position.y = -distanceToEllipsoidCenter;
});
3. 参数大字典:如何调教出丝滑的交互?
光加载出数据是不够的,在大场景下,默认的 OrbitControls 往往力不从心。我们使用了专门设计的 EnvironmentControls,并开放了一系列核心参数供用户调节。
| 参数名称 | 意义与物理作用 | 调优建议 |
|---|---|---|
| dampingFactor | 阻尼系数。决定了你松开鼠标后,场景缓慢滑行多久。 | 建议设置在 0.05 - 0.2 之间,让惯性感觉更自然。 |
| adjustHeight | 自动贴地。相机会根据下方的瓦片高度自动调整位置。 | 在地形起伏较大的漫游模式下开启,防止视角钻入地下。 |
| minAltitude | 最小俯仰角。限制相机不能低于地平线。 | 设置为 0 度,防止看穿大地底部的背面。 |
| maxAltitude | 最大俯仰角。限制相机最高俯仰程度。 | 通常限制在 85 度左右,防止翻转导致的眩晕感。 |
| cameraRadius | 碰撞碰撞半径。相机与物体的最小物理间距。 | 设置为 2-5 左右,防止镜头直接撞进楼体内部。 |
| useFallbackPlane | 兜底平面。当瓦片还在加载中(空洞)时的物理平面。 | 开启它,防止相机在数据未加载时坠入“虚空”。 |
| autoAdjustUp | 自动回正。确保视角的 Up 方向始终垂直于地面。 | 开启后,即便在复杂的旋转操作下,天空也永远在上方。 |
4. 性能压榨:如何保持 60 帧?
- LogarithmicDepthBuffer:在大地图场景下,Z-Fighting(深度冲突导致的闪烁)非常严重。初始化
WebGLRenderer时务必开启logarithmicDepthBuffer: true。 - Resolution Scaling:
tiles.setResolutionFromRenderer(camera, renderer); // 自动根据屏幕分辨率调整渲染精度 - 分帧更新:不要在每一帧都强行刷新所有 Tile 状态,利用
tiles.update()内部的调度机制。
结语
Three.js + 3D Tiles 为地理空间可视化打开了新世界的大门。如果你正在寻找一个开箱即用的解决方案,欢迎尝试我的项目 Meteor3D。它已经把这些坑都踩平了,并封装成了易用的编辑器。
- GitHub传送门:nikonikoCW/Meteor3DEditor
- 官网在线预览:meteor3d.cn
如果你在实现过程中遇到任何问题,欢迎在评论区讨论!
关注作者,解锁更多 3D 硬核技术分享 🧊