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

实现思路导图
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 切换出现裂缝:确保父子瓦片使用同一地理高度函数。
- 删除后无法恢复:确认保存的是第一次修改前的原始数据。
- 拾取仍返回旧高度:地形几何、包围信息和拾取路径需要同步更新。