站内搜索

输入关键词开始搜索

Cesium 地形压平:量化地形顶点改写与边界羽化

文章目录

地形压平案例直接作用于已加载的量化地形数据。用户绘制区域后,程序把区域内顶点改到目标高程,并在边界附近做羽化过渡。

山地区域中的地形压平参数、羽化距离与性能监控面板

实现思路导图

flowchart TD
  ROOT[地形压平]
  ROOT --> INPUT[区域与目标高度]
  ROOT --> TILE[地形瓦片处理]
  TILE --> DECODE[解码 Quantized Mesh]
  TILE --> INSIDE[点在多边形判断]
  ROOT --> HEIGHT[高度修改]
  HEIGHT --> TARGET[绝对或相对标高]
  HEIGHT --> FEATHER[边缘羽化]
  ROOT --> ENCODE[重新量化与包围信息]
  ROOT --> REFRESH[刷新父子 LOD 瓦片]

五种目标高程

绝对高程直接使用输入海拔;相对高程在原地形基准上增加偏移;平均、最低和最高模式先统计区域采样值,再得到目标面。

switch (area.mode) {
  case FlattenMode.ABSOLUTE:
    targetHeight = area.height;
    break;
  case FlattenMode.RELATIVE:
    targetHeight = sourceHeight + area.height;
    break;
  default:
    targetHeight = area.computedHeight ?? area.height;
}

统计模式必须等采样完成后才能确定目标值,因此创建区域是异步操作。

羽化避免硬边拉丝

只修改多边形内部顶点会在边界形成垂直断层。案例对边界外一定距离内的顶点计算权重,在原高度和目标高度之间插值:

const t = smoothstep(0, featherDistance, distanceToBoundary);
const nextHeight = Cesium.Math.lerp(targetHeight, sourceHeight, t);

羽化距离为 0 时得到硬边。距离越大,过渡越缓,但影响范围也越大。

每次都从原始快照重建

连续编辑同一区域时,不能在已经压平过的数据上继续累加。管理器为瓦片保存原始顶点快照,每次修改都从快照恢复,再重新应用所有有效区域。

这一步防止误差累积,也保证删除某个区域后,重叠区域仍能按当前状态重新计算。

空间索引缩小瓦片范围

场景中已加载瓦片很多。案例为压平区域建立空间索引,只处理包围范围相交的瓦片。处理完成后局部刷新受影响瓦片,不重置整个地形提供器。

面板记录处理耗时、命中瓦片数、顶点数和批次规模。它们比单独看 FPS 更能定位一次编辑为什么变慢。

这是版本敏感实现

量化顶点缓存和 Globe 瓦片结构包含 Cesium 内部字段。案例固定在 Cesium 1.141 上验证。升级版本时必须重新检查字段、顶点编码、瓦片卸载和重载流程。

公开 API 能完成裁剪与覆盖渲染,但不能直接持久改写 World Terrain 顶点。这个实现换取了真实压平效果,也承担了版本兼容成本。

主线是:采样确定目标高程 → 空间索引命中瓦片 → 从原始快照改写顶点 → 羽化边界 → 局部刷新与性能记录。

截图为什么选择接近原地形的目标高程

新的截图已经完成一次绝对高程压平,目标是 2200 米。它没有使用原来的 100 米默认值,因为当前山地约 2000 至 2500 米,压到 100 米会形成夸张的垂直拉丝,不利于说明正常工程效果。

左侧底部状态显示“压平完成”,说明量化地形处理已经提交。压平面与周围山体颜色相同,是因为修改的是地形顶点,影像仍由原来的 ImageryLayer 贴到新几何上。

Quantized Mesh 中的高度如何恢复

Cesium 量化地形把顶点 u、v、height 压到整数范围。案例读取瓦片矩形、最低和最高高程,把量化值恢复成经纬度和米制高度:

longitude = lerp(rectangle.west, rectangle.east, u / MAX_SHORT)
latitude  = lerp(rectangle.south, rectangle.north, v / MAX_SHORT)
height    = lerp(minimumHeight, maximumHeight, h / MAX_SHORT)

计算目标高度后,再根据本瓦片新的最小最大值重新量化。只改 height 数组而不更新瓦片高程范围,会让 Cesium 用旧解码参数得到错误高度。

一个瓦片为何可能被重复处理

瓦片加载、细化和回收都由 Globe 管理。相机移动后,同一地理区域可能出现新的子瓦片。管理器监听已加载瓦片,并用空间索引判断它是否与任意压平区域相交。

每个瓦片首次处理时保存原始快照。之后无论添加、编辑还是删除区域,都从原始快照应用当前全部有效区域:

original tile → area A → area B → area C → final tile

这保证区域重叠时结果可重放。直接在当前高度上追加修改,会让相对高程重复累加,删除区域也无法恢复。

羽化权重的方向

多边形内部远离边界的顶点权重为 1,靠近边界逐渐降到 0。最终高度是:

height = lerp(sourceHeight, targetHeight, featherFactor)

羽化只对已有瓦片顶点生效。若地形层级很低、边界附近没有足够顶点,即使设置 50 米羽化也可能只有一两个过渡带。提升 Cesium 地形 LOD 比单纯增大羽化距离更有效。

模式切换的计算成本

绝对和相对模式不需要区域统计。平均、最低、最高模式要先采样区域高程,得到 computedHeight,然后再处理瓦片。

修改目标高度或羽化距离可以复用原始快照和空间索引;修改边界会改变相交瓦片集合,需要重新查询索引。面板的平均耗时与命中率就是用来区分“单瓦片修改慢”还是“候选瓦片过多”。

版本风险具体在哪里

案例访问量化地形缓冲、Globe Surface Tile 和刷新标记等内部字段。这些字段没有公共兼容承诺。升级 Cesium 后至少要回归:

  • 首次加载与相机重新进入区域;
  • 父子瓦片 LOD 切换是否出现裂缝;
  • 多区域重叠、删除和显隐;
  • 地形法线、包围球和拾取高度是否同步。

从零复现:地形压平教程

案例源码位于 src/demos/cesiumdemos/terrain-flatten/。该方案修改 Cesium Terrain 的 Quantized Mesh 数据,涉及内部结构,必须锁定 Cesium 版本并准备回归测试。

第 1 步:定义压平区域与高度模式

绘制多边形并保存经纬坐标。绝对模式直接设置目标海拔;相对模式先采样参考地形高程,再加偏移量得到目标值。

const targetHeight =
  mode === "absolute"
    ? configuredHeight
    : sampledReferenceHeight + relativeOffset;

截图使用 2200 米绝对高程,是为了让完成后的区域在当前山地和视角下可辨认。教学调试时也应先用明显目标值验证管线,再换业务值。

第 2 步:按瓦片包围范围筛选区域

地形瓦片到达后,先比较瓦片经纬矩形与压平多边形包围盒。完全不相交的瓦片原样返回,只有相交瓦片才进入顶点处理。

这一层粗筛能避免遍历全球所有 Quantized Mesh 顶点。多区域场景可再建立空间索引,快速得到与瓦片相交的区域列表。

第 3 步:解码 Quantized Mesh 顶点

Quantized Mesh 的 u/v/height 通常是 0–32767 的整数。根据瓦片矩形和最小、最大高程还原每个顶点的经纬度与真实高度。

longitude = west + u / 32767 · (east - west)
latitude  = south + v / 32767 · (north - south)
height    = minHeight + qh / 32767 · (maxHeight - minHeight)

首次处理时保存原始高度快照。后续参数修改必须从原始值重新计算,不能在已经压平的高度上连续叠加。

第 4 步:判断顶点是否受影响

将顶点经纬度投到区域局部二维平面,执行点在多边形判断。多边形边缘附近还要计算点到边界的最短距离,用于羽化权重。

const weight = inside
  ? Cesium.Math.clamp(distanceToBoundary / featherDistance, 0, 1)
  : 0;
const nextHeight = Cesium.Math.lerp(originalHeight, targetHeight, weight);

没有羽化时权重为 1,区域边界会形成垂直断崖;羽化区越宽,目标平台可用面积越小。

第 5 步:重新确定量化范围

修改后的高度可能超出瓦片原始 minHeight/maxHeight。先遍历得到新的最小、最大值,再把所有高度重新量化到 0–32767。

只改量化高度数组而不更新范围,会导致所有顶点按旧比例解码,结果整体偏移。还应同步更新包围球、地平线遮挡点和边界高程数据,否则剔除与几何不一致。

第 6 步:处理瓦片 LOD 接缝

父瓦片与子瓦片必须对同一地理位置应用同一个高度函数。区域边界切过瓦片边缘时,两侧顶点使用相同点在多边形和羽化计算,避免裂缝。

相机移出再移入时,新请求的瓦片仍需经过修改器。不能只改当前已加载瓦片,否则 LOD 切换后平台会恢复原状。

第 7 步:实现更新、删除与恢复

区域参数变化时递增版本,并让受影响瓦片失效重载。删除区域后用保存的原始快照恢复,或重新请求未修改数据;不要用反向偏移猜原高程。

多个区域重叠时定义明确优先级,例如后创建区域覆盖先创建区域。对每个顶点从原始高度开始,按排序后的区域依次求最终值。

验收与排错

  • 平台高度不等于目标值:检查重新量化时是否更新 min/max。
  • LOD 切换出现裂缝:确保父子瓦片使用同一地理高度函数。
  • 删除后无法恢复:确认保存的是第一次修改前的原始数据。
  • 拾取仍返回旧高度:地形几何、包围信息和拾取路径需要同步更新。

相关文章

Cesium 三维风场:用 WebGL 2 GPU 粒子描绘全球气流

拆解案例中的风场数据、GPU 粒子更新、轨迹渲染、颜色映射和 Vue 参数控制

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

介绍区域体素生成、建筑表面采样、结构碰撞检测、Worker 几何构建和网格编码查询