文章目录
地形抬升保留区域内原有起伏,把整块真实地表整体提高指定距离,同时补出侧壁、底面和与周边地形连接的坑体。它不是一个平面盒子。

实现思路导图
flowchart TD
ROOT[地形抬升]
ROOT --> INPUT[区域与抬升量]
ROOT --> SAMPLE[原始地形采样]
SAMPLE --> TOPGRID[顶部三角网]
SAMPLE --> TEXTURE[区域影像纹理]
ROOT --> GEOMETRY[封闭块体]
GEOMETRY --> TOP[保形抬升顶部]
GEOMETRY --> WALL[四周侧壁]
GEOMETRY --> BASE[底面封口]
ROOT --> REPLACE[裁剪原地形并提交 Primitive]
创建任务是一条异步管线
边界绘制完成后,TerrainLiftManager 依次完成地形采样、三角网构建、影像纹理获取和 Primitive 创建。页面通过进度回调展示当前阶段:
const manager = new TerrainLiftManager(viewer, {
onProgress: (value) => {
progress.value = value;
},
onLiftComplete: () => finishActiveOperation(id),
onLiftError: (reason) => failOperation(id, reason),
});
质量档位控制边界点数、内部网格和纹理尺寸。快速预览适合调边界,高质量适合参数稳定后的最终展示。
顶面保留真实起伏
区域内部先采样原始高程。每个顶点的新高度是:
const liftedHeight = sourceHeight + liftHeight;
因此山脊、沟谷仍保留,只是整体上移。顶面三角网的 UV 来自局部包围盒,用于映射当前影像纹理。
影像纹理需要单独获取
TerrainTextureRenderer 按区域范围渲染或请求当前底图,生成顶面使用的纹理。相机截图不能替代这一步,因为截图包含视角透视和其他场景对象,无法稳定映射到地块 UV。
侧壁和坑体闭合地块
每个边界采样点至少对应原地表、抬升顶面和坑底三个高度。相邻点连接后分别形成地块侧壁与坑壁。边界顺序必须保持一致,重复首尾点也要在建网前统一处理。
多区域裁剪继续由共享管理器维护,避免一个地块覆盖另一个地块的 Globe 裁剪状态。
可取消和过期结果
高质量任务可能持续数秒。取消操作时不仅隐藏加载层,还要给任务标记失效。之后返回的采样或纹理结果必须检查任务 id,不能继续创建 Primitive。
if (activeAreaId !== request.id) return;
草稿与归档
草稿保存当前浏览器中的边界和参数;JSON 归档用于跨会话恢复。两者都保存可重建数据,不序列化 GPU 资源。恢复时重新执行采样和几何构建。
这个案例的主线是:采样真实地表 → 整体增加高度 → 生成纹理化顶面和闭合侧壁 → 用任务状态管理异步创建与取消。
截图中的抬升块怎样识别
截图中央的棕色地块已经离开原山体,边缘能看到从原地表到抬升顶面的侧壁。左侧区域列表出现“抬升 500m、坑深 300m、快速、4 点”,底部状态显示任务完成。
快速档位只使用 200 个边界采样和 32 级顶面网格,所以截图中的轮廓较粗。它用于验证几何和参数,不代表最终 2048px 纹理质量。
顶面不是一个平面
内部网格按多边形包围盒建立。每个有效顶点先采样原地形高程,再加统一抬升量:
liftedHeight(i) = sampledTerrainHeight(i) + liftHeight
因此原来的山脊和沟槽保留下来。所谓“整体抬升”是所有顶点增加同一高度,不是把区域压平成一个水平面。
无效网格点不会进入三角形。边界附近还要裁掉跨出多边形的网格单元,否则顶面会呈现包围盒形状。
为什么要俯视渲染影像纹理
顶面升高后仍需要原区域影像。案例在原地表上方建立正交俯视相机,把当前影像渲染到离屏纹理,再按区域包围盒生成 UV。
正交投影保证纹理比例与地面平面一致。使用透视相机截图会产生近大远小,贴到顶面后道路和山脊位置无法对齐。
纹理阶段需要临时修改相机和场景显示状态,所以必须保存并恢复主相机、裁剪和可见对象。取消任务时也要恢复这些快照。
闭合体由五部分组成
- 抬升后的真实起伏顶面。
- 顶面边界到原地表边界之间的地块侧壁。
- 原地表以下的坑壁。
- 地块自身底面。
- 原位置留下的坑底。
抬升高度和坑深分别控制不同高度段。把两者合成一个值会让“抬升 500 米、地块厚 300 米”无法表达。
质量档位改变哪些资源
| 档位参数 | 影响 |
|---|---|
| 边界采样数 | 侧壁与坑壁贴合程度 |
| 顶面精度 | 内部三角网数量 |
| 纹理尺寸 | 顶面影像清晰度 |
| 分批大小 | 单次采样和构建阻塞时间 |
质量切换不是单个 Shader uniform,必须重新采样和构建。面板在任务开始前确定质量快照;创建过程中继续拖动参数,不会改变正在运行的任务。
异步任务的完成条件
一个区域只有在采样、纹理、几何、裁剪全部成功后才从 building 变成可编辑状态。任何阶段失败都销毁已创建资源并移除区域占位。
activeAreaId 用来淘汰取消后的回调。这只能防止结果提交,不能让已经发出的网络请求立即停止。进度条反映当前任务阶段,不是 Cesium 地形请求的精确字节进度。
从零复现:地形抬升教程
案例源码位于 src/demos/cesiumdemos/terrain-lift/。抬升不是把原地形顶点直接加高,而是隐藏目标区域原地形,再构建一个保留原表面起伏的封闭块体。
第 1 步:确定区域和抬升量
绘制多边形后转到局部 ENU,检查自交并计算包围范围。保存抬升高度 offsetHeight,同时记录质量档位对应的内部采样间距。
快速预览使用稀疏采样;最终质量再增加顶部网格密度。不要让拖动高度滑块每个像素都重新请求地形。
第 2 步:生成顶部采样点
在多边形包围矩形中建立规则格,保留多边形内部点,并把边界加密点一起加入。批量调用 sampleTerrainMostDetailed 获取原始高度。
完成标志:暂时以 PointPrimitive 显示样本,边界上无大间隙,山脊和沟谷处点高程变化连续。
第 3 步:三角化原始地表
在局部二维平面将内部点和边界点构造成三角网。每个顶点保留 originalHeight,抬升后的顶部高程为:
const liftedHeight = originalHeight + offsetHeight;
所有点加同一个偏移,因此顶部保留原山体起伏。若把全部点设置为同一高度,得到的是压平台,不是抬升块。
第 4 步:捕获并映射原区域影像
根据区域包围范围建立正交视图,捕获或请求原地形影像,保存为顶部纹理。顶部顶点的 UV 由局部包围矩形归一化得到。
uv.x = (localX - minX) / (maxX - minX);
uv.y = (localY - minY) / (maxY - minY);
先用棋盘纹理验证方向,再换真实影像。出现镜像时修正 V 坐标,不要旋转整个几何。
第 5 步:构建侧壁和底面
沿加密边界各生成原始高程点与抬升高程点,连接为侧壁。底面使用原边界或略低的封口面,使块体从任何视角都不露内部。
侧壁法线应朝外,顶面法线朝上,底面法线朝下。开启背面剔除后逐个视角检查,能快速发现三角形绕序错误。
第 6 步:隐藏原地形区域
使用与开挖类似的裁剪或遮罩移除原区域表面,避免抬升块下方仍显示原山体产生重叠。裁剪范围只覆盖当前区域,并保留其他区域已有状态。
第 7 步:提交完整资源包
只有地形采样、顶部纹理、顶面、侧壁、底面和裁剪都成功后,才把区域状态切为 ready。失败时按创建逆序销毁已完成资源。
高度调整可以复用原始样本和纹理,只重算顶点高度与包围体;边界或质量改变才重新采样和三角化。
验收与排错
- 顶部变成平板:确认每个顶点保留自己的原始高程。
- 顶部纹理倒置:检查正交捕获方向和 UV 的 V 轴。
- 侧壁有洞:边界加密数组必须首尾闭合。
- 抬升后相机靠近时对象消失:重算包含新高度的包围球。