Three.js教程
Cesium
3DTilesRendererJS
3DTilesRendererJS

NASA 开源的这个库,让 Three.js 也能渲染卫星级地理数据

我做 Web3D 很多年,一直有个很难绕开的问题:

Three.js 很好用,做模型展示、特效、可视化都没问题。但一旦涉及大规模地理数据,比如城市建筑群、火星地形、全球三维底图,就会卡死、爆内存,或者根本就不知道从哪里开始。

直到我发现了这个仓库:NASA-AMMOS/3DTilesRendererJS (opens in a new tab)

它的核心功能很简单:把 3D Tiles 这个大规模地理数据格式,接进 Three.js、Babylon.js 和 React Three Fiber 里。

3D Tiles 是什么

3D Tiles 是 Cesium 公司制定的一个开放标准,现在已经是 OGC 正式标准。

你可以把它理解为三维版的地图瓦片

普通地图(Google Maps、OSM)是把地球切成一块块 2D 图片按层级加载。3D Tiles 做的是同样的事,但数据是三维的:城市建筑、地形模型、点云扫描、卫星影像……每一块都有对应的细节层级(LOD),相机近的时候加载高精度,远了切换低精度,永远只加载视野里当前需要的那部分。

这就是为什么 Google Earth、Cesium Ion、国内很多三维 GIS 平台都在用它。数据量可以是 TB 级别,但用户看到的永远是流畅的。

问题是:这套标准的官方实现是 CesiumJS,一个完整的地理渲染引擎,很重,自成体系,和 Three.js 生态不兼容。

3DTilesRendererJS 做了什么

它把 3D Tiles 的核心逻辑——tileset 解析、LOD 判断、流式加载、视锥裁剪——从 CesiumJS 中剥离出来,封装成一个独立的 JavaScript 库,让你在 Three.js 项目里直接用。

装包很简单:

npm install 3d-tiles-renderer

最基础的用法是这样:

import { TilesRenderer } from '3d-tiles-renderer/three';
 
const tilesRenderer = new TilesRenderer('./path/to/tileset.json');
tilesRenderer.setCamera(camera);
tilesRenderer.setResolutionFromRenderer(camera, renderer);
 
// 加载完成后,把 tileset 移到场景中心
tilesRenderer.addEventListener('load-root-tileset', () => {
  const sphere = new Sphere();
  tilesRenderer.getBoundingSphere(sphere);
  tilesRenderer.group.position.copy(sphere.center).multiplyScalar(-1);
});
 
scene.add(tilesRenderer.group);
 
function renderLoop() {
  requestAnimationFrame(renderLoop);
  camera.updateMatrixWorld();
  tilesRenderer.update(); // 每帧更新,触发 LOD 判断和瓦片加载
  renderer.render(scene, camera);
}

注意 tilesRenderer.update() 这一行,必须在每一帧调用,它是整个系统的心跳:读取当前相机位置,计算哪些 tile 需要加载、哪些需要卸载,派发下载和解析任务。

3DTilesRendererJS 最小运行示例

接入 Google 真实地球地图

这个库最让我惊喜的是,它直接支持 Google Photorealistic 3D Tiles

Google 在 2023 年开放了一个 API,可以把真实世界的航拍三维模型以 3D Tiles 格式流式返回。精度到街道级别,有建筑、树木、地形,覆盖全球主要城市。

接入方式:

import { TilesRenderer } from '3d-tiles-renderer/three';
import { GoogleCloudAuthPlugin } from '3d-tiles-renderer/three/plugins';
 
const tiles = new TilesRenderer(
  'https://tile.googleapis.com/v1/3dtiles/root.json'
);
tiles.registerPlugin(new GoogleCloudAuthPlugin({ apiToken: 'YOUR_API_KEY' }));
tiles.setCamera(camera);
tiles.setResolutionFromRenderer(camera, renderer);
 
scene.add(tiles.group);

仓库里有现成案例的 https://nasa-ammos.github.io/3DTilesRendererJS/three/googleMapsAerial.html (opens in a new tab) ,直接在浏览器里跑真实地球。

接入 Cesium Ion:月球和火星

Cesium Ion 是 Cesium 公司的云平台,提供多种地理数据集,其中包括月球和火星表面的三维模型。

接入 Cesium Ion 需要先用 API Key 换一个临时 access token:

const url = new URL(`https://api.cesium.com/v1/assets/${assetId}/endpoint`);
url.searchParams.append('access_token', accessToken);
 
fetch(url, { mode: 'cors' })
  .then(res => res.json())
  .then(json => {
    const tileUrl = new URL(json.url);
    const version = tileUrl.searchParams.get('v');
 
    tiles = new TilesRenderer(tileUrl);
    tiles.fetchOptions.headers = {
      Authorization: `Bearer ${json.accessToken}`
    };
 
    // 每次请求子 tile 时,自动附上版本号
    tiles.preprocessURL = uri => {
      uri = new URL(uri);
      uri.searchParams.append('v', version);
      return uri.toString();
    };
  });

换一个 assetId,就能从地球切换到火星、月球。这就是为什么这个项目叫 AMMOS——它本来就是 NASA 喷气推进实验室(JPL)的任务数据分析与运行系统的一部分,真实用于星球探测数据可视化。

插件系统:功能扩展不改核心

这个库设计了一套插件机制,你可以把各种能力按需挂载上去,不用改核心逻辑。

处理 DRACO 压缩模型

3D Tiles 里的建筑模型(B3DM 格式)往往会用 DRACO 压缩,需要专门配置解码器:

import { GLTFExtensionsPlugin } from '3d-tiles-renderer/three/plugins';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
 
const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);
 
tiles.registerPlugin(new GLTFExtensionsPlugin({ gltfLoader }));

LOD 渐变过渡(FadeTilesPlugin)

切换 LOD 层级时,默认是瞬间替换,会有闪烁。加一个插件解决:

import { FadeTilesPlugin } from '3d-tiles-renderer/three/plugins';
tiles.registerPlugin(new FadeTilesPlugin({ duration: 0.3 }));

加载 WMS / WMTS 底图叠加

可以把国内的 WMS 地图服务叠加到三维地形上:

import { WMSPlugin } from '3d-tiles-renderer/three/plugins';
tiles.registerPlugin(new WMSPlugin({
  url: 'https://your-wms-server/wms',
  layers: 'your_layer_name',
}));

GeoJSON 矢量叠加

在三维地形上叠加 GeoJSON 边界、线段:

import { GeoJSONPlugin } from '3d-tiles-renderer/three/plugins';
tiles.registerPlugin(new GeoJSONPlugin({ geojson: yourGeoJSONData }));

多 renderer 共享缓存

如果你在同一个场景里要加载多个 tileset(比如建筑 + 地形 + 道路),默认每个 TilesRenderer 各自管理自己的下载队列,会互相抢带宽。

正确做法是让它们共享缓存和队列:

const tiles1 = new TilesRenderer('./buildings.json');
tiles1.setCamera(camera);
tiles1.setResolutionFromRenderer(camera, renderer);
 
const tiles2 = new TilesRenderer('./terrain.json');
tiles2.setCamera(camera);
tiles2.setResolutionFromRenderer(camera, renderer);
 
// 共享同一套缓存和队列,统一调度
tiles2.lruCache = tiles1.lruCache;
tiles2.downloadQueue = tiles1.downloadQueue;
tiles2.parseQueue = tiles1.parseQueue;
tiles2.processNodeQueue = tiles1.processNodeQueue;
 
scene.add(tiles1.group);
scene.add(tiles2.group);

这样下载优先级由单一调度器统一决定,不会出现两个 tileset 互相抢资源的情况。

支持的渲染器

这个库同时支持三个主流渲染环境:

Three.js(最完整):

import { TilesRenderer } from '3d-tiles-renderer/three';

Babylon.js(2025 年新增):

import { BabylonTilesRenderer } from '3d-tiles-renderer/babylonjs';

Babylon.js 官方文档也已经收录了集成教程。

React Three Fiber

import { ThreeTilesRenderer } from '3d-tiles-renderer/r3f';
 
function Scene() {
  return (
    <ThreeTilesRenderer url="./tileset.json" />
  );
}

R3F 版本封装成了声明式组件,直接放在 JSX 里就能用。

生态集成

仓库里列了几个有意思的社区集成,说明这个库已经在更大的生态里生长了:

  • three-geospatial:在 3DTilesRendererJS 基础上加了大气、云层渲染,做真实地球视觉效果
  • MapLibre 集成:官方示例展示了如何把 3D Tiles 渲染层叠加进 MapLibre 地图
  • iTowns、Giro3D:两个法国团队做的 Web GIS 框架,底层都用了这个库
  • 3DBAG Viewer:荷兰全国建筑三维模型的 Web 查看器,基于这个库构建

要不要用它

如果你的 Three.js 项目只是做产品展示、艺术装置、特效,不涉及真实地理数据,这个库对你没什么用。

但如果你要做:

  • 城市三维可视化(建筑、道路、地形)
  • 接入 Google 真实地球数据
  • 利用 Cesium Ion 的数据集
  • 地理信息系统(GIS)的三维展示层
  • 卫星、无人机点云数据展示

那这个库几乎是绕不开的选择。它把 3D Tiles 生态和 Three.js 生态之间的鸿沟填上了。

来源