GIS
3D Tiles 与 BIM
前情回顾:08 渲染管线 讲清了 GPU 渲染管线、HDR/LDR 色彩管理和 Tone Mapping 算法。当场景里的底图、地形、矢量都被高效渲染后,如果要把整座城市(含建筑、道路、桥梁)塞进浏览器,传统”一次性加载所有模型”的方案显然不可行。本篇回答:如何用 3D Tiles 流式加载海量三维数据,并把 BIM 模型精准地”放”到地球表面。
直觉问题
打开一个支持 3D 建筑的地图(如 Google Earth、Cesium 的 NYC 建筑层),观察这些现象:
- 为什么拉近看建筑很精细,远离看却是简模甚至消失? 如果每个 LOD 都加载最高精度的模型,内存和带宽都无法承受。
- 一栋 50MB 的摩天大楼模型是怎么被切成”可流式加载”的小块的? 谁决定先加载哪块、后加载哪块?
- BIM 模型通常以”局部坐标系”建模(比如以大楼中心为原点),但地图是地球坐标系。怎么把两者对齐?
- 平面工程和球面地球如何共存? 一座桥梁的设计图是平面的,但放到地球上必须考虑曲率。曲率带来的误差在什么场景下不可忽略?
读完本篇,你能回答:3D Tiles 的层级结构如何组织?空间索引(BVH/R-tree)为什么对海量场景至关重要?BIM 到 GIS 的坐标转换有多少种策略?平面/球面投影在工程精度与视觉正确性之间如何取舍?
核心概念白话讲
3D Tiles:把一座城市切成”俄罗斯套娃”
传统游戏引擎加载一个 3D 场景时,通常是”一把梭”——所有模型文件一次性读入内存。但城市级 3D 数据(如 NYC 全部建筑的精细模型)可能有几 TB;即便只加载视野内建筑,单次 draw call 提交的几何量也可能让 GPU 不堪重负。
3D Tiles 的核心思想:像 web 地图的二维瓦片一样,把三维空间也切成层级瓦片。每块瓦片自带不同精度的模型版本(LOD),按需加载、按需卸载。
┌─────────────────────────────────────┐
│ Tileset Root (整个城市) │
│ ├─ Level 0: 整城简化模型 (1 个 Tile) │
│ ├─ Level 1: 分成 4×4 区域网格 │
│ ├─ Level 2: 每个区域再 4×4 细分 │
│ ├─ Level 3: 到达街区级别... │
│ └─ Level N: 单栋建筑的精细模型 │
└─────────────────────────────────────┘
NOTE
3D Tiles 与 2D 瓦片的核心区别:2D 瓦片是固定 z/x/y 层级(如第 3 篇所讲),3D Tiles 瓦片可以任意形状(不是规则的矩形网格),每块瓦片用包围盒(Bounding Volume)定义空间范围,LOD 切换依据屏幕空间误差而非单纯的距离。
Tileset 结构:JSON 描述的树
3D Tiles 的入口是一个 tileset.json,它描述了一棵瓦片树。每个节点(Tile)包含:
| 字段 | 含义 | 示例 |
|---|---|---|
boundingVolume | 该瓦片的空间范围 | region / box / sphere |
geometricError | 该瓦片简化模型的几何误差(米) | 1000(很远)→ 0(最精细) |
refine | LOD 切换策略 | ADD(叠加)/ REPLACE(替换) |
children | 子瓦片数组 | 递归定义下一级 |
content | 实际数据文件 | .b3dm / .i3dm / .pnts / .cmpt |
TIP
geometricError 的直觉理解:假设某瓦片的几何误差是 200 米,意味着该瓦片的简化模型与真实精细模型之间,最大偏差不超过 200 米。当相机距离远、该瓦片在屏幕上只占几个像素时,200 米的误差看不出来,就用这个简化版;拉近后误差变得肉眼可见,就换更高精度的子瓦片。
空间索引:在三维世界里”快速定位”
当城市里有 10 万栋建筑、每栋建筑有 10 个 LOD 级别时,CPU 不可能每帧遍历全部 100 万个瓦片做视锥剔除。空间索引结构让”查找视野内物体”从 O(N) 降到 O(log N)。
BVH (Bounding Volume Hierarchy)
BVH 是一棵二叉树,每个节点代表一个 AABB(轴对齐包围盒),父节点的包围盒刚好包含所有子节点的包围盒。
┌─────────────┐
│ 根包围盒 │
│ (整个城) │
└──────┬──────┘
┌──────┴──────┐
┌───┴───┐ ┌───┴───┐
左半城 右半城 ...
查询过程:从根节点开始,若当前节点的包围盒与视锥相交,递归检查子节点;若不相交,整个子树跳过。
R-tree
R-tree 是 BVH 的近亲,但在 GIS 中更常见(如 PostGIS 的空间索引)。与 BVH 的区别:
| 特性 | BVH | R-tree |
|---|---|---|
| 树结构 | 二叉树 | 多叉树(每个节点 M 个子节点) |
| 常用触发 | 动态场景(动画、变形) | 静态空间数据查询 |
| 分裂策略 | 沿最长轴中点分裂 | 最小面积增长分裂 (Guttman) |
| GIS 适用 | 3D Tiles 层级 | 数据库空间索引 |
| 最大优势 | 构建快、剔除高效 | 支持动态插入/删除 |
NOTE
为什么 3D Tiles 用 BVH 而非 R-tree? 3D Tiles 瓦片树是静态预构建的(运行时不会插入新建筑),BVH 构建简单、遍历时 cache 友好。R-tree 更适合支持动态增删的 GIS 数据库查询场景。
地理锚定:把 BIM 模型”钉”到地球上
BIM(Building Information Modeling)软件(如 Revit、ArchiCAD)建模时,通常以局部坐标系工作:
- 原点设在建筑中心或工地一角
- 坐标单位是米
- 不考虑地球曲率(平面坐标系)
要把这个模型放到 3D 地图上,需要解决三个层面的转换:
1. 局部坐标 → ECEF(地球中心地球固定坐标系)
BIM 局部坐标 (x, y, z)
↓
NED 东北天坐标系(ENU 的变体,以锚点切平面为参考)
↓
ECEF 坐标(以地心为原点,地球自转同步旋转)
↓
WGS84 经纬度 + 椭球高(可选,用于标注)
数学上,变换矩阵由锚点的经纬高决定:
- 令锚点
lon, lat, h,计算其在 ECEF 下的坐标P_anchor - 构造局部到 ECEF 的旋转矩阵
R,由锚点处的北向N、东向E、地心向D单位向量组成 - 局部坐标
v_local的 ECEF 坐标:v_ecef = P_anchor + R · v_local
电池:
v_ecef = P_anchor + [N E D] · v_local
其中 N = [-sin(lat)cos(lon), -sin(lat)sin(lon), cos(lat)] (北向)
E = [-sin(lon), cos(lon), 0 ] (东向)
D = [-cos(lat)cos(lon), -cos(lat)sin(lon), -sin(lat)] (地心向下)
2. 旋转与缩放
BIM 模型可能有自己的”北向”定义。如果设计图的 Y 轴指向磁北,而 GIS 使用真北,就需要一个方位角旋转。此外,BIM 模型的米单位可能因缩尺而需要比例因子。
3. 高度基准
BIM 中的高度通常是相对高(如相对于工地地坪 ±0.000),而 GIS 需要椭球高或正高(相对于大地水准面的海拔 nob),参考 05 地形与 Worker。
WARNING
常见误区:直接把 BIM 的 Z 值当海拔高 BIM 中的高度是工程相对高,GIS 中的高度是大地水准面或椭球面参考。如果不做高程基准转换,一栋海拔 50m 的建筑可能被”埋藏”在地下(如果地面 DEM 显示该处海拔是 80m)。
平面 vs 球面:工程精度与视觉正确性的平衡
| 维度 | 平面投影 | 球面投影 |
|---|---|---|
| 适用距离 | 城市级(< 50 km) | 全球级 |
| 几何假设 | 地面是平面 | 地面是椭球面 |
| 角度保持 | 是(保角) | 是(局部保角) |
| 距离误差 | 小(工程可接受) | 无(真实测地距离) |
| GPU 成本 | 低(线性矩阵) | 高(需每次计算椭球函数) |
| BIM 兼容性 | 极佳(直接导入) | 需地理锚定转换 |
| 视觉走样 | 远处地平线不正确 | 真实 |
IMPORTANT
什么时候必须用球面? 当场景跨度超过约 100 km 时,平面假设会导致明显的几何误差。例如一架飞机从上海飞到北京(~1000 km),若用平面坐标,航线会是一条直线;在球面上,最短路径是大圆航线(凸向北极的弧线)。
原理与数学/机制
3D Tiles LOD 切换的 SSE 机制
3D Tiles 的 LOD 选择不依赖简单的距离阈值,而是基于屏幕空间误差(Screen-Space Error, SSE):
SSE = (geometricError × screenHeight) / (2 × distance × tan(fov/2))
geometricError:当前瓦片简化模型的几何误差(米)screenHeight:视口高度(像素)distance:相机到瓦片中心的距离(米)fov:相机垂直视场角
判定逻辑:
- 若
SSE > 最大允许像素误差(如 16 像素),说明该瓦片在屏幕上太粗糙,需加载子瓦片 - 若
SSE ≤ 最大允许像素误差,当前瓦片足够精细,停止细化
这个公式的优雅之处在于:误差与相机距离成反比,与视口大小成正比。同样的模型,在大屏幕上需要更高精度(SSE 阈值不变时,大屏需要更多瓦片)。
BVH 射线/视锥相交测试
BVH 的核心操作是包围盒 vs 视锥相交测试。AABB 与视锥的相交可以通过分离轴定理(Separating Axis Theorem, SAT)判断:
- 将 AABB 的 8 个顶点转换到视锥的 6 个裁剪空间平面
- 若存在某个平面,使得 AABB 的所有顶点都在该平面外侧 → 不相交
- 否则相交(需进一步视锥 vs 子节点测试或细化)
function intersects(aabb, frustum):
for each plane in frustum.planes:
if aabb.isCompletelyOutside(plane):
return false
return true
NOTE
早期拒绝的魔力:在 BVH 根节点就能剔除掉 80% 以上的瓦片时,整棵树的遍历开销极低。比如 100 万个瓦片的场景,根节点的 4 个子节点可能直接代表 4 个城区;若相机只看向其中一个城区,另外 3 个整枝都被剔除,从 100 万瞬间降到 25 万。
地理锚定矩阵分解
局部到 ECEF 的完整 4×4 变换矩阵 M 可分解为:
M = T_anchor · R_local · S_scale
其中:
T_anchor = translate(P_anchor) —— 平移到锚点
R_local = [N E D] —— 局部坐标轴到 ECEF 的旋转
S_scale = scale(sx, sy, sz) —— 米单位(以及可选的模型缩尺)
这个矩阵在 GPU 顶点着色器中被用于将 BIM 模型的局部顶点坐标变换到世界空间。
可视化对比与动手实验
3D Tiles 层级结构可视化
平面 vs 球面投影误差对比
| 场景 | 平面投影下的距离 | 球面投影下的距离 | 误差 |
|---|---|---|---|
| 1 km x 1 km 区域 | 1414 m (对角线) | 1414 m | < 0.001 m |
| 10 km x 10 km 区域 | 14142 m | 14141 m | ~0.1 m |
| 100 km x 100 km 区域 | 141421 m | 140848 m | ~573 m |
| 1000 km x 1000 km 区域 | 1414213 m | 1276280 m | ~138 km |
WARNING
100 km 是工程阈值:当场景跨度超过 100 km 时,平面投影的距离误差将超过 0.5 米,这对机场跑道长度、桥梁跨度等工程参数已经不可忽略。超过 200 km 时,视觉上线条明显”弯曲”,必须切换到球面坐标。
常见误区
-
误区:3D Tiles 瓦片必须是规则网格
- 真相:3D Tiles 瓦片可以是任意形状的包围盒(box/region/sphere),不受 z/x/y 网格限制。
-
误区:geometricError 越小越好
- 真相:geometricError 是”简化模型误差”的度量,不是”质量评分”。根节点的 geometricError 可以是 2km,表示”这个简化版与真实模型最大偏差 2km”——在远处屏幕上完全可以接受。
-
误区:BIM 模型可以直接拖到球面场景里用
- 真相:必须经过地理锚定转换(局部→ENU→ECEF),否则模型会出现在错误的位置,或因坐标系单位不匹配而被缩放/旋转错乱。
-
误区:平面坐标系够了,不需要球面
- 真相:超过约 100 km 范围时必须考虑球面曲率,否则工程测量和视觉表现都会出现显著误差。
-
误区:BVH 只在 3D Tiles 里使用
- 真相:BVH 是通用空间索引结构,广泛用于光线追踪、物理引擎碰撞检测、GIS 大场景剔除等场景。
延伸阅读与自测
权威参考
- OGC 3D Tiles Specification 1.1 —— 3D Tiles 官方标准
- Cesium 3D Tiles Overview —— Cesium 官方的 3D Tiles 技术概览
- CityGML 标准 —— 城市三维模型的 OGC 标准,常与 3D Tiles 配合使用
- BIM to GIS: Integration Approaches —— BIM/GIS 数据融合方法论
自测题
- 3D Tiles 的
geometricError到底是什么?如果根节点的值是 5000,子节点是 1000,这可能吗? - 为什么 3D Tiles 常用 BVH 而非 R-tree 做空间索引?数据库空间查询为什么偏向 R-tree?
- 一栋 BIM 建筑的中心在原点 (0,0,0),需要被放置在 WGS84 经度 121.47°、纬度 31.23°、海拔 50m 处。写出从局部坐标到 ECEF 的变换矩阵需要什么参数?
- 平面投影在 1000 km 对角距离上的误差有多少?什么场景下这个误差是不可接受的?
- 如果 3D Tiles 采用 REPLACE 策略而非 ADD 策略,对内存和绘制顺序有什么影响?
下一篇导引:10 调优手册 —— 当 3D 场景越来越复杂(瓦片 + 影像 + 矢量 + BIM),如何定位性能瓶颈、合理分配 GPU/CPU 资源?我们将从工具链、内存管理和帧预算三个维度给出可操作的排查与优化清单。