deck.gl 3D Tiles 可视化实战:Tile3DLayer 加载、TerrainController 地形导航与 TerrainExtension 图层贴合
2026/9/15 9:49:08 网站建设 项目流程

deck.gl 3D Tiles 可视化实战:Tile3DLayer 加载、TerrainController 地形导航与 TerrainExtension 图层贴合

【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl

deck.gl 对 OGC 3D Tiles 为核心骨架,结合仓库内Tile3DLayerTerrainControllerTerrainExtension的源码实现与官方示例,完整讲解从最小可运行配置到加载调优、版权声明的全流程,帮助你直接上手搭建基于 3D Tiles 的三维可视化应用。

3D Tiles 与 deck.gl 的集成概览

3D Tiles 是一种用于流式传输海量异构三维地理数据的开放规范,典型的例子包括 Google Maps 的倾斜摄影城市模型(photogrammetric city models)和 Cesium ion 托管的数据集。在 deck.gl 中,集成一个 3D Tiles 应用通常需要三块拼图:

  1. Tile3DLayer(位于 docs/api-reference/geo-layers/tile-3d-layer.md):负责按视口请求、缓存并渲染 tileset;
  2. TerrainController(位于 docs/api-reference/core/terrain-controller.md):提供地形感知的相机交互,使相机高度自动跟随地形;
  3. TerrainExtension(位于 docs/api-reference/extensions/terrain-extension.md,实验性扩展):将普通二维图层在 GPU 上重新投影到三维表面上。

从实现上看,Tile3DLayer是一个 CompositeLayer,其核心逻辑位于 modules/geo-layers/src/tile-3d-layer/tile-3d-layer.ts。它根据瓦片类型动态生成三种子图层:b3dm/i3dm格式使用ScenegraphLayer(子图层 id 为scenegraph)、pnts点云格式使用PointCloudLayer(id 为pointcloud)、ESRIMeshPyramids数据使用SimpleMeshLayer(id 为mesh),对应代码见 tile-3d-layer.ts。因此你可以在这些子图层上继续叠加 props 或进行像素级定制。

基础搭建:最小可运行配置

最简配置只需要一个Tile3DLayer加载 tileset,再配一个TerrainController负责相机交互。以 Google Maps 3D Tiles 为例,其 tileset 入口为https://tile.googleapis.com/v1/3dtiles/root.json,需要在请求头中携带X-GOOG-API-KEY

TypeScript / 纯 JS 版本

import {Deck, TerrainController} from '@deck.gl/core'; import {Tile3DLayer} from '@deck.gl/geo-layers'; const deckgl = new Deck({ initialViewState: { latitude: 50.089, longitude: 14.42, zoom: 16, pitch: 60, bearing: 90 }, controller: {type: TerrainController}, layers: [ new Tile3DLayer({ id: 'google-3d-tiles', data: 'https://tile.googleapis.com/v1/3dtiles/root.json', loadOptions: { fetch: {headers: {'X-GOOG-API-KEY': YOUR_API_KEY}} }, pickable: '3d', operation: 'terrain+draw' }) ] });

React 版本

import {DeckGL} from '@deck.gl/react'; import {TerrainController} from '@deck.gl/core'; import {Tile3DLayer} from '@deck.gl/geo-layers'; function App() { const layers = [ new Tile3DLayer({ id: 'google-3d-tiles', data: 'https://tile.googleapis.com/v1/3dtiles/root.json', loadOptions: { fetch: {headers: {'X-GOOG-API-KEY': YOUR_API_KEY}} }, pickable: '3d', operation: 'terrain+draw' }) ]; return ( <DeckGL initialViewState={{ latitude: 50.089, longitude: 14.42, zoom: 16, pitch: 60, bearing: 90 }} controller={{type: TerrainController}} layers={layers} /> ); }

注意:YOUR_API_KEY需要替换为你自己的 Google Maps API Key;仓库内官方示例通过构建环境变量注入,见 examples/website/google-3d-tiles/app.jsx。

两个关键 Props

以上配置中有两个 props 缺一不可,直接决定 3D Tiles 能否与地形导航、图层贴合协同工作:

  • pickable: '3d'—— 在 tileset 上启用深度拾取(depth picking)。这是TerrainController获取相机与指针下方地形高程的前提。普通pickable: true只能返回被拾取对象,而'3d'会让 picking info 对象中的coordinate字段成为投影到实际几何体上的三维点,相关说明见 docs/api-reference/core/layer.md。文档同时提醒该模式有额外性能开销。
  • operation: 'terrain+draw'—— 告诉 deck.gl 该图层既要视觉渲染(draw),又要作为地形表面(terrain)供其他图层贴合。如果只设'terrain',则该图层仅作为地形数据源而不参与绘制;'terrain+draw'则是二者兼得。

TerrainController:地形感知导航

TerrainController 继承自MapController,在其基础上加入了地形感知逻辑:当用户平移、缩放时,相机高度会自动调整以贴合地形表面。它还有两个值得注意的默认行为:

  • 默认rotationPivot: '3d':相机围绕指针下的三维点旋转,而不是围绕地图中心,这在有起伏地形的场景中交互更自然;
  • 支持MapController的全部选项(见 map-controller.md)。

在代码中,TerrainController既可以直接作为Deck顶层controller使用,也可以挂在MapView上(例如与多视图配合时):

import {Deck, MapView, TerrainController} from '@deck.gl/core'; new Deck({ views: new MapView({ controller: {type: TerrainController} }), initialViewState: viewState });

工作原理(源码级)

从 modules/core/src/controllers/terrain-controller.ts 可以看出其核心机制:

  1. 视口中心高程拾取:控制器通过requestAnimationFrame循环周期性地调用pickPosition(x + width / 2, y + height / 2)拾取视口中心点的三维坐标,并把coordinate[2]作为地形高度目标值缓存(见 terrain-controller.ts)。拾取间隔约 500ms,且拖拽中暂停、页面后台时 rAF 自动挂起,避免无谓的 GPU 回读。
  2. 平滑跟随:在每次updateViewport时,当前高度以SMOOTHING = 0.05的系数向目标高度插值,实现平滑过渡(见 terrain-controller.ts)。
  3. 首次拾取后基准重设:第一次成功拾取后,通过_rebaseViewport将视口基准调整为[0, 0, altitude],并相应微调 zoom,保证视觉上画面不跳动(见 terrain-controller.ts)。

重要前提:以上机制依赖场景中存在pickable: '3d'的图层提供高程数据。如果没有这样的图层,TerrainController拿不到地形信息,行为与普通MapController完全一致。而如果只使用默认控制器,相机会围绕海平面(sea-level plane)旋转,容易穿入地形内部。

将自定义图层贴合到地形表面

最常见的进阶需求是把自己的二维数据(如建筑轮廓 GeoJSON)叠加到三维表面上。此时需要用到实验性扩展 TerrainExtension,它在 GPU 上完成几何体重定位,而非逐顶点 CPU 计算。

贴合分为两步:

  1. Tile3DLayer上设置operation: 'terrain+draw'(让它既是视觉图层又是地形源);
  2. 给需要贴合地形的图层加上TerrainExtensionextensionsprop。
import {GeoJsonLayer} from '@deck.gl/layers'; import {_TerrainExtension as TerrainExtension} from '@deck.gl/extensions'; const layers = [ // 地形源 new Tile3DLayer({ id: 'google-3d-tiles', data: 'https://tile.googleapis.com/v1/3dtiles/root.json', loadOptions: { fetch: {headers: {'X-GOOG-API-KEY': YOUR_API_KEY}} }, pickable: '3d', operation: 'terrain+draw' }), // 被贴合的图层 new GeoJsonLayer({ id: 'buildings', data: BUILDINGS_GEOJSON_URL, stroked: false, filled: true, getFillColor: [0, 160, 180, 200], extensions: [new TerrainExtension()] }) ];

terrainDrawMode:两种贴合模式

TerrainExtension通过terrainDrawModeprop 提供两种绘制模式:

  • 'drape'(纹理贴合):把整个图层当作纹理覆盖在地形表面上,图层的 altitude 与 extrusion(挤出高度)会被忽略。适合多边形、路径这类扁平数据。该模式下地形覆盖物变化时会触发重绘(见 terrain-extension.ts 中的terrainCoverNeedsRedraw)。
  • 'offset'(锚点抬升):将每个对象按其锚点(通常由getPosition访问器定义,如 icon、scatterplot)垂直平移地形高程值。适合图标、散点这类三维对象。

自动判定:不指定时,扩展会从图层类型自动推断。其判定逻辑(见 terrain-extension.ts)是:若图层extruded为真(2.5D/3D 图层)或属性管理器中存在instancePositions锚点属性,则选择'offset',否则选择'drape'

另外两个补充细节:该扩展同时支持MapViewGlobeView(地形覆盖与高度图 FBO 都在绝对 Mercator 公共空间中计算,切换投影无需重绘);若与TerrainLayer地形源配合且在GlobeView上使用,需要将地形源的tesselator设为'grid'以保证网格在两种投影下都有效。

非贴合图层:保持独立坐标空间

并非所有图层都必须贴合地形。没有添加TerrainExtension的图层仍会像往常一样在自己的坐标空间渲染。这适用于两类场景:

  • UI 类图层,例如固定在某个海拔高度的文字标注(text labels at fixed altitudes);
  • 自带高程值的数据,比如点云、带有真实 Z 坐标的模型。

这类图层直接叠加在 tileset 之上即可,无需任何额外配置,视觉上它们会与三维表面共存但互不干预。

加载调优:loadOptions 参数详解

对于大型 tileset,合理调整加载选项能显著影响性能与画质。Tile3DLayerloadOptions在默认加载选项之上额外支持以下键(详见 tile-3d-layer.md):

  • cesium-ionCesiumIonLoader的选项;
  • 3d-tilesTiles3DLoader的选项;
  • i3sI3SLoader的选项;
  • tileset:tileset 元数据加载完成后透传给Tileset3D实例的参数。

从源码看,_loadTileset会先把loadOptions中的tileset键提取出来,与剩余选项分别处理后再构造Tileset3D(见 tile-3d-layer.ts)。下面是一份针对 Google 3D Tiles 的调优配置(与官方示例一致,见 app.jsx):

new Tile3DLayer({ // ... loadOptions: { fetch: {headers: {'X-GOOG-API-KEY': YOUR_API_KEY}}, tileset: { maximumScreenSpaceError: 20, maximumMemoryUsage: 512, memoryAdjustedScreenSpaceError: true } } })

三个核心参数的作用:

  • maximumScreenSpaceError—— 控制细节层级(LOD)。数值越小加载越精细,同时消耗的瓦片数量与带宽也越多;默认 16,官方示例使用 20 以平衡画质与性能。
  • maximumMemoryUsage—— 限制 GPU 内存占用(单位 MB)。达到上限后开始驱逐缓存中的瓦片。
  • memoryAdjustedScreenSpaceError—— 接近内存上限时动态调整 LOD,优先保证内存不超限,代价是远处瓦片可能变粗糙。

显示版权信息(Credits)

许多 3D Tiles 数据提供商(包括 Google)都要求把瓦片内嵌的版权信息展示给用户。Tile3DLayer通过loadOptions.tileset中的onTraversalComplete回调,可以从当前可见瓦片中提取版权信息:

const [credits, setCredits] = useState(''); const onTraversalComplete = (selectedTiles) => { const uniqueCredits = new Set(); selectedTiles.forEach(tile => { const {copyright} = tile.content.gltf.asset; copyright.split(';').forEach(uniqueCredits.add, uniqueCredits); }); setCredits([...uniqueCredits].join('; ')); return selectedTiles; }; new Tile3DLayer({ // ... loadOptions: { tileset: {onTraversalComplete} } })

要点:

  • 必须原样返回selectedTiles,因为该回调的返回值会被 tileset 继续用于后续遍历;
  • 版权字符串会随用户漫游、新瓦片可见而动态更新,因此把credits渲染在视口一角即可持续满足提供商署名要求;
  • 该回调只是tileset加载选项之一,与maximumScreenSpaceError等参数并列。

完整参考实现

仓库中的官方示例 examples/website/google-3d-tiles 是本文所有知识点的一体化落地,非常值得通读:

  • 使用Tile3DLayer加载 Google 3D Tiles,配置了上述全部调优参数与onTraversalComplete版权回调;
  • GeoJsonLayer上同时挂载TerrainExtensionDataFilterExtension,用distance_to_nearest_tree字段做颜色分级(scaleLinear)与数据过滤;
  • 通过MapView挂载TerrainController(额外开启touchRotate: trueinertia: 500),并支持一键切换GlobeView
  • 在页面左下角用绝对定位的div渲染 credits 字符串(见 app.jsx)。

在本地运行该示例前,请先设置环境变量GoogleMapsAPIKey,再安装依赖并启动开发服务器(示例目录下的 package.json 定义了相应脚本)。

从其他数据源加载 3D Tiles

虽然本指南以 Google Maps 为例,但Tile3DLayer通过loader/loadOptions天然支持多种数据源,常见的有:

  • Cesium ion:使用CesiumIonLoader,并在loadOptions中传入'cesium-ion': {accessToken: '<token>'},数据地址为https://assets.cesium.com/<asset_id>/tileset.json
  • ArcGIS I3S:使用I3SLoaderdata为 SceneServer 的 layer 入口 URL(如 San Francisco 建筑数据);
  • Google Maps:使用默认Tiles3DLoader加请求头鉴权,即本文示例。

三种来源的完整代码片段均可参考 tile-3d-layer.md。

常见问题与注意事项

  • 相机穿模:未使用TerrainController时相机绕海平面旋转,可能穿入地形。请确保 controller 类型为TerrainController,且场景中存在pickable: '3d'的图层。
  • 贴合图层没有效果:检查地形源是否设置了operation: 'terrain+draw',以及被贴合图层是否将TerrainExtension加入extensions。另外TerrainExtension目前是实验性特性,API 可能变动。
  • GPU 内存压力:优先调大maximumScreenSpaceError或调小maximumMemoryUsage,必要时开启memoryAdjustedScreenSpaceError让 LOD 自适应。
  • 鉴权失败:Google 3D Tiles 必须在每次请求头携带X-GOOG-API-KEY,且确保 Key 具备 Maps Tile API 的 3D Tiles 权限。
  • 版权合规:务必通过onTraversalComplete提取并展示瓦片内嵌版权信息,这是多数提供商的使用前提。

通过本文的配置与源码级解读,你现在可以基于 Tile3DLayer + TerrainController + TerrainExtension 三件套,快速搭建支持地形导航、图层贴合、加载调优与版权合规的 3D Tiles 可视化应用。

【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询