返回知识库

GIS

3D Tiles 与 BIM

3D Tiles 与 BIM 封面
GISengine-webgpu3D TilesBIM空间索引

前情回顾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(最精细)
refineLOD 切换策略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 的区别:

特性BVHR-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)判断:

  1. 将 AABB 的 8 个顶点转换到视锥的 6 个裁剪空间平面
  2. 若存在某个平面,使得 AABB 的所有顶点都在该平面外侧 → 不相交
  3. 否则相交(需进一步视锥 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 层级结构可视化

3D Tiles 层级结构示例

平面 vs 球面投影误差对比

场景平面投影下的距离球面投影下的距离误差
1 km x 1 km 区域1414 m (对角线)1414 m< 0.001 m
10 km x 10 km 区域14142 m14141 m~0.1 m
100 km x 100 km 区域141421 m140848 m~573 m
1000 km x 1000 km 区域1414213 m1276280 m~138 km

WARNING

100 km 是工程阈值:当场景跨度超过 100 km 时,平面投影的距离误差将超过 0.5 米,这对机场跑道长度、桥梁跨度等工程参数已经不可忽略。超过 200 km 时,视觉上线条明显”弯曲”,必须切换到球面坐标。

常见误区

  1. 误区:3D Tiles 瓦片必须是规则网格

    • 真相:3D Tiles 瓦片可以是任意形状的包围盒(box/region/sphere),不受 z/x/y 网格限制。
  2. 误区:geometricError 越小越好

    • 真相:geometricError 是”简化模型误差”的度量,不是”质量评分”。根节点的 geometricError 可以是 2km,表示”这个简化版与真实模型最大偏差 2km”——在远处屏幕上完全可以接受。
  3. 误区:BIM 模型可以直接拖到球面场景里用

    • 真相:必须经过地理锚定转换(局部→ENU→ECEF),否则模型会出现在错误的位置,或因坐标系单位不匹配而被缩放/旋转错乱。
  4. 误区:平面坐标系够了,不需要球面

    • 真相:超过约 100 km 范围时必须考虑球面曲率,否则工程测量和视觉表现都会出现显著误差。
  5. 误区:BVH 只在 3D Tiles 里使用

    • 真相:BVH 是通用空间索引结构,广泛用于光线追踪、物理引擎碰撞检测、GIS 大场景剔除等场景。

延伸阅读与自测

权威参考

自测题

  1. 3D Tiles 的 geometricError 到底是什么?如果根节点的值是 5000,子节点是 1000,这可能吗?
  2. 为什么 3D Tiles 常用 BVH 而非 R-tree 做空间索引?数据库空间查询为什么偏向 R-tree?
  3. 一栋 BIM 建筑的中心在原点 (0,0,0),需要被放置在 WGS84 经度 121.47°、纬度 31.23°、海拔 50m 处。写出从局部坐标到 ECEF 的变换矩阵需要什么参数?
  4. 平面投影在 1000 km 对角距离上的误差有多少?什么场景下这个误差是不可接受的?
  5. 如果 3D Tiles 采用 REPLACE 策略而非 ADD 策略,对内存和绘制顺序有什么影响?

下一篇导引10 调优手册 —— 当 3D 场景越来越复杂(瓦片 + 影像 + 矢量 + BIM),如何定位性能瓶颈、合理分配 GPU/CPU 资源?我们将从工具链、内存管理和帧预算三个维度给出可操作的排查与优化清单。