站内搜索

输入关键词开始搜索

Cesium 北斗三维网格:在城市 3D Tiles 上做 GeoSOT-3D 剖分

文章目录

北斗三维网格案例把二维经纬网扩展到高度方向,并叠加到城市 3D Tiles 上。它不仅画格子,还会采样场景表面,标出与建筑或地形发生碰撞的体素。

城市 3D Tiles 上的北斗三维网格控制与碰撞检测场景

实现思路导图

flowchart TD
  ROOT[北斗三维网格]
  ROOT --> RANGE[范围定义]
  RANGE --> POLYGON[绘制多边形]
  RANGE --> LEVEL[层级与高度范围]
  ROOT --> SAMPLE[空间采样]
  SAMPLE --> CENTER[生成水平格心]
  SAMPLE --> HEIGHT[地形与建筑高度]
  ROOT --> COMPUTE[Worker 体素计算]
  COMPUTE --> COLLISION[结构或表面碰撞]
  ROOT --> RENDER[立方体实例化与拾取]

三维网格的输入

用户需要给出区域范围、网格层级和高度范围。二维区域先被离散成规则行列,高度再按层切分,每个单元都能得到独立编码。

const region = {
  west,
  south,
  east,
  north,
  minHeight,
  maxHeight,
  level,
};

层级越高,单元越小,体素数量会按平面面积和高度层数同时增长。因此页面必须同时限制最大网格数和最大显示数。

表面碰撞不是包围盒碰撞

案例使用 Cesium 的场景高度采样读取 3D Tiles 与地形表面,再把采样结果映射到体素列。表面模式只保留每列与场景相交的外壳单元;结构模式则可以把建筑从地面到表面的占用空间填实。

这两种模式用途不同:外壳适合观察表面覆盖,结构实体更适合构造后续风场或可达性分析的固体掩膜。

Worker 负责批量生成几何数据

经纬度、高度和编码计算会产生大量顶点。案例把网格几何生成放进 Worker,主线程只接收扁平化数组并创建 Cesium Primitive:

gridWorker.postMessage({
  type: "region",
  id: requestId,
  voxels,
  params: renderOptions,
});

请求带有递增 id。旧请求晚于新请求返回时会被丢弃,避免快速调整参数后出现旧网格覆盖新网格。

盒体和线框分开渲染

碰撞体素适合复用单位立方体,通过每个 GeometryInstance 的 modelMatrix 完成定位和缩放。线框则合并成一个 LINES Geometry。两条渲染路径可以独立开关,避免为了看线框仍提交全部半透明面。

编码查询

鼠标指向体素时,页面可以反查层级、行列、高度层和完整编码。这里的编码是空间索引,不等同于 Cesium Entity 的 id。业务数据应以网格编码为键,Cesium 拾取结果只负责找到当前显示对象。

截图中的红色体素代表什么

截图已经完成区域绘制和碰撞采样。青色边框是用户区域;红色体素是被判定为建筑结构的列,而不是把整块 3D Tiles 涂红。左侧统计中的“区域体素”是整个三维点阵数量,“当前显示”受 25000 上限限制,“结构列”则是高出局部地面阈值的二维列数。

切换“表面碰撞”时,每个二维列只显示与场景表面相交的一层。切换“结构实体”时,会从估计地面向上填充到建筑表面。截图选择结构实体,目的是让读者看到建筑占用体素与普通区域网格之间的区别。

从 GeoSOT 层级得到米制单元

GeoSOT 以度空间递归剖分,经纬方向的实际米制长度随纬度变化。案例在区域中心纬度计算东西、南北方向的近似单元尺寸,并为高度方向设置独立步长。

level → 经度跨度/纬度跨度
       → 在中心纬度换算 dx、dy
高度范围 → 按 dz 切成 nz 层

因此统计里会出现 2.9 × 3.9 × 3.9 m,而不是三个方向完全相等。三维编码仍按层级、行列和高度索引生成,显示尺寸则使用当地米制换算。

场景高度采样的限制

sampleHeightMostDetailed 只能采到当前场景可见并已加载的数据。程序在采样前等待 3D Tiles 加载,但等待存在超时;网络慢或瓦片未细化到足够层级时,建筑表面会偏粗。

采样得到的是某经纬度方向上的最高可见表面,不包含建筑内部空洞。随后通过局部低值窗口估计地面:

surfaceHeight - localGround >= structureMin

条件成立才计为结构。structureMin 太小会把坡面起伏当建筑,太大会漏掉低矮建筑。这个阈值必须结合数据分辨率调整。

Worker 返回的不是 Cesium 对象

Worker 不能安全传递 Viewer、Cartesian3 或 Geometry。它只返回可转移的数值数组:模型矩阵、颜色、线框顶点和索引。主线程再创建 Cesium 对象。

每个请求携带 id,返回时与 pendingReqId 比较。这是防重复提交,不是取消 Worker 计算。旧请求仍可能消耗 CPU,但其结果不会覆盖新参数生成的网格。

体素渲染为什么复用单位立方体

所有体素拓扑相同,只是位置、旋转、尺寸和颜色不同。程序复用一份 BoxGeometry,为每个实例提供 modelMatrix。相比为每个体素创建 Entity,这种批量 Primitive 能显著减少更新和绘制对象数量。

显示上限只限制提交给渲染器的体素,不改变完整统计和碰撞掩膜。截图只显示 924 个结构体素,但求解数据仍保留全部碰撞结果。

这个案例的主线是:定义三维区域 → 按 GeoSOT-3D 层级离散 → 采样 3D Tiles 表面 → 生成碰撞体素 → 用 Worker 与批量 Primitive 渲染。

从零复现:北斗三维网格教程

案例源码位于 src/demos/cesiumdemos/beidou-3d-grid/。这一案例先解决“空间怎样编号”,再解决“哪些格子与建筑相交”,最后才是立方体渲染。

第 1 步:定义分析范围和层级

让用户绘制水平多边形,并设置最低高程、最高高程和北斗网格层级。层级函数必须返回该层对应的水平边长和垂直步长。

const grid = {
  horizontalSize: levelToMeters(level),
  verticalSize: heightStep,
  minHeight,
  maxHeight,
};

先在面板显示估算列数、行数、层数和总体素数。总体素数超限时立即阻止计算,不能等 Worker 分配数组后再提示。

第 2 步:在局部坐标中生成格心

以区域中心建立 ENU 参考系,在包围矩形内按米制步长遍历 x/y。把格心换回经纬度后执行点在多边形判断,只保留区域内部列。

完成标志:只渲染最底层线框时,格子应覆盖用户多边形,边长与面板值一致,旋转相机不会产生倾斜或漂移。

第 3 步:批量采样建筑表面

每个水平格心只采一次建筑或地形表面高度,生成 surfaceHeights。同一列的所有垂直体素共享这个高度,不要每层重复执行场景拾取。

3D Tiles 未加载时采样会得到空值。必须等待瓦片可见并记录有效采样率;有效率过低时暂停生成,而不是把空值当 0 米。

第 4 步:把规则计算交给 Worker

主线程将局部列坐标、高度场、层高和模式放入 TypedArray,通过 transferable 发送。Worker 遍历列与层,返回体素类型、位置索引和统计。

worker.postMessage(
  { requestId, columns, surfaceHeights, minHeight, maxHeight, heightStep },
  [columns.buffer, surfaceHeights.buffer],
);

回调先比较 requestId。快速调整参数时,旧 Worker 结果必须丢弃,否则画面会回退到上一次层级。

第 5 步:实现两种碰撞模式

结构实体模式判断体素中心是否低于建筑表面;表面壳层模式判断体素高度区间是否跨过表面高度。前者适合 CFD 固体掩膜,后者适合展示建筑外壳占用。

const isSolid = cellTop <= surfaceHeight;
const hitsSurface = cellBottom <= surfaceHeight && cellTop >= surfaceHeight;

两种结果必须分别统计。不能用“显示上限”裁掉的数量反推碰撞率,因为显示上限只属于渲染阶段。

第 6 步:实例化单位立方体

创建一份单位 BoxGeometry,为每个可见体素生成 ENU 位置、水平尺寸和层高对应的模型矩阵。按类型分批设置颜色,使用 Primitive 或实例集合一次提交。

不要为每个体素创建 Entity。几千个 Entity 会把时间消耗在属性系统和可视化器调度,而不是网格本身。

第 7 步:拾取并解释网格

为实例附加格网 id、层号、经纬范围和高度区间。点击体素后显示这些字段,读者才能确认编号与空间位置一致。

验收与排错

  • 格网随纬度拉伸:检查水平步长是否在局部米制坐标中生成。
  • 所有建筑都只有一层体素:检查垂直索引是否用 floor((height-min)/step)。
  • 体素落到建筑旁边:检查采样位置与渲染格心是否使用同一 ENU 参考系。
  • Worker 完成后页面卡顿:限制显示体素数,并把矩阵数组也改为批量生成。

相关文章

Cesium 城市风场 CFD:北斗体素网格、LBM 与 GPU 粒子

从 3D Tiles 表面采样到固体掩膜、D2Q9 LBM 求解和粒子流线,梳理城市风场案例的完整管线

Cesium 全球北斗网格:自适应 LOD 与光标编码查询

在全球尺度上按相机高度选择 GeoSOT 网格层级,并用 Worker 生成地表格网和三维体素