修订说明(2026-09-30):本文记录 2026-08-30 的阶段工作。早期代理方案、样本测量和原版算法证据已重新区分,未完成事项按当时验证范围理解。后续进展见高度图还原与POI、Stamp 和植被岩石布局。
AI 协作做游戏 · 资产逆向系列 · 程序化地形实现篇
上一篇《从多人联机到关卡生产:POI 布局、道路接口与自然地貌重建》把 POI、道路和自然地形接进了同一条关卡生产链。这一篇继续往地形内部走,重点说明自然地形怎样从配置关系变成可检查、可重复的 Web 生成过程。
更早的地形规则分析讨论了程序化地形数据里有哪些层级,C++ 运行时验证则验证了 Web 原型能否迁移到原生代码。这一篇把两条线合在一起:自然地形的配置大致怎样组织,我们如何从解包关系和参考样本逆向出一套可运行算法,Web 工具和 C++ 版本分别做了什么,以及六类差异很大的生态是怎样落到同一套方法上的。
这里的“还原”有明确边界。我们能确认的是解包配置中的所有权、层级、范围、权重和资源引用,也能从成组的 HMAP / RT0 里测量真实结果;当原始运行时处理代码或 Stamp 高度纹理缺失时,我们实现的是受这些证据约束、可重复验证的代理算法。C++ 与 Web 对齐,说明两套实现计算一致,不等于代理已经变成原游戏源码。
这次选取 Desert_Dune、Icy_Glaciers / Arctic、Plain、Tundra、Scorched_Moor 和 Ionic_Crimson 六类案例。它们只是逆向过程中的代表样本,不表示参考系统只有六种地貌。六类生态合计准备了 60 组 HMAP、60 组 RT0、60 个固定 Web 预设和六个独立 C++17 生成器。[1] POI、道路、洞穴、爆炸坑与运行时地形变化不在本文的自然地形基线内。
现有配置与样本不支持把自然地形简化为“一张噪声加若干 Stamp”
只看最终高度图,很容易把这套地形理解成一张大噪声,再随机盖一些局部 Stamp。这种做法能很快得到“像地形”的画面,却解释不了几个反复出现的特征:
- 同一生态的十张地图为什么会保留相似的空间节奏;
- 大片安静、可行走的区域为什么不会被随机细节占满;
- 局部形态为什么常落在顶部、山肩、低地或边缘等特定表面;
- Region 之间为什么能变化明显,却仍保持连续边界;
- 调整局部结构时,为什么不应该破坏整张地图的宏观起伏。
为避免把证据和实现混在一起,下面用三个标记:[D] 表示解包数据直接支持的事实,[R] 表示当前重建采用的算法,[P] 表示原始内容缺失时使用的可替换代理。
从目前能取得的配置和样本看,更合理的解释是一条分层生成链:
[D] Planet setup / RegionInput
→ [D] Region → SubRegion references and fields
→ [R] Region / SubRegion spatial ownership evaluation
→ [D] HeightGroups / CornerHeights records
→ [R] HeightGroups / CornerHeights evaluation and boundary conditioning
→ [R] broad support
→ [R] medium morphology
→ [R] localized residual
→ [D] ZoneItem / StampGroup / StampInfo hierarchy
→ [R/P] deterministic selection and proxy height rasterization
→ POI、道路与运行时形变(独立系统)

_图 1:全文的证据与结论边界。解包配置直接支持 [D],HMAP / RT0 作为独立测量证据约束 [R] 与 [P];Web/C++ 一致只证明两端计算一致,不能把代理提升为原始实现。_
在当前重建中,Region 与 SubRegion 引用被解释为空间所有权,HeightGroups 与 CornerHeights 记录被解释为区域高低关系和连续边界;大尺度支撑、中尺度形态和局部残差再把离散关系变成连续地表。自然 Stamp 最后进入已经成立的地形,只在合适的支持域里增加局部结构。这里的空间求值与插值顺序属于 [R],不是从字段存在直接推导出的原版公式。
这条顺序很重要。基座缺了一条山脊,不能靠增加 Stamp 数量补出来;中尺度肩部不成立,也不能用全局 FBM 把坡度指标硬拉上去。那样做也许会让画面更“丰富”,但错误被藏进了另一层,后面既难解释,也很难迁移到 C++。
我们怎样判断哪些是事实,哪些仍是代理
全文使用三种结论标签,并单独保留参考测量证据:[D] 表示解包数据直接支持的事实,[R] 表示当前重建,[P] 表示缺失原始内容时使用的代理;HMAP / RT0 测量用于约束和检验后两者,不另作为一种算法阶段。下面四节对应四类研究材料与实现结果,不是一条从高到低的四级可信度排序。
1. 解包配置:确定系统怎样组织内容
Region、SubRegion、ZoneItem、StampGroup 和 StampInfo 能提供明确的数据关系,包括:
- 谁拥有哪一组内容;
- 父组可以展开哪些子组;
- 范围、权重、重叠组和放置参数;
- StampInfo 的资源引用与组件关系;
- 不同生态、场景配方与候选池之间的映射。
这些属于数据事实。实现时应保留层级,不能先把它们压成一张“随机散布参数表”。否则虽然还能生成物体,却失去了原始配置最重要的组织信息。
2. 成组参考图:约束最终结果
单张高度图很容易误导。我们为每类案例准备十张 HMAP 和十张 RT0,测量高度分位数、坡度分布、中心与边缘差异、多个尺度的频带能量、局部结构密度,以及同一生态在十张图中的稳定区间。自然 Stamp 的数量、占地范围和表面归属也不是只凭图像判读,而是把配置层级与参考图中的局部结构联合审计后得到的约束。
参考图回答的是“结果实际长什么样”。它能推翻一个公式看似合理、空间分布却不对的方案。比如早期把缓坡按高度阈值切成固定平台,单看一张图还说得通;加入十图对照、内外圈统计和模糊尺度分析后,这种解释就不再稳定。
HMAP 与 RT0 的数据格式与使用约定
这两类参考资料用途不同,不能互换:
| 数据 | 当前读取约定 | 在恢复中的职责 | 不承担的职责 |
|---|---|---|---|
| HMAP | 4096²、16 位单通道 PNG;当前审计脚本从解码器提供的两个 8 位样本字节,按 PNG 16 位大端顺序组合为 uint16;行优先读取,不做 Y 翻转;发布图保留原始灰度值,不逐图着色或归一化 |
按 heightMeters = u16 × 200 / 65535 转成米;统计时可块均值降采样到 512²,并在有效圆盘或内圈掩码内计算高度、坡度和尺度残差 |
原始 4096² HMAP 不直接作为六个生成器的运行时高度输入;不能单独证明某个局部结构就是 Stamp 或 POI |
| RT0 | 1920×1080 RGB 渲染截图,与同编号 HMAP 作为一组参考 | 辅助确认地表材质、可见建筑、POI 与场景语义 | 不是高度数据,不参与高度拟合,也不能替代 HMAP 验收 |
HMAP 采用 2048m 世界尺度。原始 4096² 样本对应 0.5m/texel;降到 512² 后步长为 4m。坡度使用中心差分除以这个物理步长,再转换为角度,因此这里的坡度值依赖“200m 对应完整 uint16 范围”这一冻结换算。若后续取得更权威的垂直标尺,这个换算可以替换,原始 uint16 与空间拓扑仍保留。各生态把十组同编号 HMAP / RT0 全部纳入审计,不只挑“最好看”的样本;明显属于 POI、城市或人工线框的结构会保留在原始证据中,但不进入自然地形基线的拟合目标。有效区和内圈掩码由各审计脚本冻结,例如 Desert 使用半径约 0.33N 的有效圆盘,并在其 88% 半径内做更严格统计。视觉截图只用于空间对照;精确统计始终读取原始 16 位样本,不从浏览器或发布页面显示的亮度反推高度。[12]

_图 2:Scorched_Moor RT0 01,1920×1080 渲染参考。画面用于辨认水域、地表覆盖、POI 与人工结构,只承担场景语义对照;高度统计仍读取同编号 HMAP。_
3. 受约束的确定性重建:补足缺失的运行时处理逻辑
配置并不会自动告诉我们完整运行时公式。HeightGroups 怎样插值、候选怎样评分、多个边界条件按什么顺序叠加,仍可能缺少运行时处理代码。这里的做法不是凭视觉随意调参,而是:
- 保留配置中的所有权和层级;
- 用参考图确定允许的统计区间;
- 把每一层写成可单独关闭的确定性阶段;
- 用固定 Seed 和阶段输出检查是哪一层造成偏差;
- 冻结后再迁移到 C++,不在两端分别调参。
4. 代理形态:明确标注尚未取得的原始内容
部分 Stamp 的支持范围、组件组合和配置层级已经取得,但原始高度纹理并不完整。当前实现会依据支持域、组件几何、层级、参考图统计和地表条件生成可重复的高度代理。这些形态不是无约束噪声,但也不能写成“原始 Stamp 已恢复”。
这一区分直接影响验证结论:
C++ ≈ Web 冻结版本
证明的是原生实现复现了我们的冻结算法;只有同时经过配置证据和真实参考图检验,才有资格讨论它与参考目标的接近程度。
还原流程:先恢复职责,再恢复形状
实际工作不是从一个巨大的最终公式开始,而是从职责分解开始。
建立配置 IR
第一步把解包数据转换成中间表示。IR 不追求把所有字段变成同一种结构,而是保留原有关系:RegionInput 选择场景配方,Region 拥有 SubRegion,高度记录进入连续地貌,ZoneItem 再引用 StampGroup 列表。列表顺序、权重字段和 StampInfo 引用是 [D];按稳定顺序遍历、进行确定性加权选择,则是当前生成器冻结的 [R]。在没有原始运行时处理代码时,不能把后者写成已经证实的原始运行逻辑。
这一层解决两个问题。第一,Web 和 C++ 可以读取同一份语义;第二,当结果不对时,我们能追溯到某个 Region、某组候选或某条资源引用,而不是只剩一堆难以解释的噪声参数。

_图 3:配置 IR 保留连续地貌与自然 Stamp 两条支路的层级、归属、范围、权重和资源引用。解包记录属于 [D],其后的空间求值、稳定选择和代理高度分别属于 [R] / [P]。_
给参考图建立测量基线
每类生态先跑十图统计,而不是先调画面。常用指标包括:
P01 / P50 / P99与有效高差;- 平均坡度、坡度分位数和低坡面比例;
- 16m、64m、256m 等尺度上的箱式低通差分 RMS;
- 内圈、外圈与边缘带的高度和坡度差异;
- 连通高地、低地与边界过渡的空间分布;
- 配置层级与参考图联合审计后的自然 Stamp 数量、占地范围、父子层级和表面归属。
指标用于定位问题,不作为唯一目标。两张图可以拥有相似的平均坡度,却有完全不同的山体位置、方向和可行走空间。因此最终仍要同时检查空间相关性、阶段图与三维表面。
逐层建立高度场
自然地形基线覆盖 2048m × 2048m。分辨率为 N 时,高度存储为一维 Float32Array:
index = y × N + x
step = 2048 / N
Web 和 C++ 都在像素中心采样,局部生成域为 [0, 2048) × [0, 2048):worldX = (x + 0.5) × step,worldY = (y + 0.5) × step。(0,0) 是与数组第 0 行、第 0 列关联的采样域角点,+X 沿列递增,+Y 沿行递增,地图中心为 (1024,1024);第一个实际样本位于 (step/2, step/2)。这套坐标是生成器的局部约定,不自动等同于 UE 世界轴或地理方向。验证链不做 Y 翻转;引擎导入必须显式提供局部域到目标世界的变换,包括是否翻转 Y。双线性回读使用 gx = x / 2048 × N - 0.5 与同样的 gy,越界索引夹到有效范围。不同分辨率表达同一个 2048m 世界,但候选网格、整数半径与间距检查仍可能产生量化差异,所以跨分辨率验收看的是同一职责和统计区间,而不是假设逐像素相等。[5]

_图 4:局部生成域、像素中心、行优先 Float32 与 RAW 旁路元数据的完整数据约定。下游系统必须显式处理目标世界变换和 Y 方向。_
每个阶段只处理自己的职责:
H0 = Region / SubRegion base
H1 = applyHeightGroups(H0)
H2 = conditionCornersAndBoundaries(H1)
H3 = addBroadSupport(H2)
H4 = addMediumMorphology(H3)
H5 = addLocalizedResidual(H4)
H6 = placeAndRasterizeNaturalStamps(H5, supportMasks)

_图 5:从 Region 基座到自然 Stamp 的分层高度场。每层只补自己的尺度和空间职责,并保留独立输出;后层不能用来掩盖前层缺失。_
Region 层决定主要空间分区;HeightGroups 建立大块高低关系;CornerHeights 和边界调节保证相邻区域不出现无依据的断裂;broad support 建立山体、盆地和缓坡的主体;medium morphology 增加肩部、次级脊线与宽谷;localized residual 只补较小尺度的表面变化。
最后才计算 Stamp。候选点不在全图盲撒,而是从配置拥有的父范围和保留解包列表顺序的 StampGroup records 中取样,再由当前重建稳定使用,并根据顶部、山肩、坡面、低地或边缘等支持掩码评分。通过有限次确定性尝试、间距检查、父子约束和局部包围盒栅格化,把代理形态落到世界坐标中。
用失败阶段定位错误层
每个阶段都能独立输出,也能在 Web 里关闭。调试时不问“最终图为什么不像”,而是按顺序问:
- Region 与 SubRegion 的大轮廓是否正确;
- HeightGroups 是否产生了错误的平台或空洞;
- CornerHeights 是否破坏了边缘连续性;
- broad support 是否承担了本该属于 medium 的细节;
- residual 是否变成了全局噪声;
- Stamp 是否落在错误表面,或用数量掩盖了基座问题。
这种阶段审计比继续堆参数有效得多。它也为 C++ 迁移提供了天然的测试边界。
Web 版本:它是实验台,不只是一个 3D 预览器
Web 工具承担三件事:快速重建、阶段消融和证据对照。六类生态并没有被合并到一个通用噪声函数,而是各自路由到独立模块,复用确定性哈希、采样、滤波、统计和渲染基础设施。
Web 单次生成的执行顺序
在页面里修改参数或切换阶段后,一次生成按下面的顺序执行:
| 顺序 | Web 端动作 | 这一层负责什么 |
|---|---|---|
| 1 | 读取数据源、Seed、分辨率、场景档、阶段和生态参数 | 固定本次运行的输入;参考高度图条目只进入对照路径,不运行生成算法 |
| 2 | computeField() 按生态分派到独立生成器 |
Desert、Arctic、Plain、Tundra、Scorched Moor 和 Ionic Crimson 保留各自的配置与阶段实现,不经过一个通用地形函数 |
| 3 | 生成器运行到当前选择的阶段 | 按该生态已完成的重建边界生成 H、TIER、阶段图层和诊断数据;阶段选择会截断后续计算,不是渲染后的显示滤镜 |
| 4 | 将结果转换为预览网格 | 根据高度场建立顶点和法线,并上传位置、法线与调试着色数据;少数连续基座只对预览网格做轻量平滑,原始高度数组不变 |
| 5 | 计算统计与参考对照 | 统计高差、坡度、频带、覆盖率和 Stamp 增量,并在适用时与同生态参考高度图使用相同一比较范围与指标比较 |
| 6 | 刷新视图与诊断面板 | 绘制地形、阶段色阶或 Stamp 增量,同时显示耗时、当前阶段、配置范围和未完成边界 |
因此,页面里的 Region、HeightGroups、CornerHeights、Broad Support、Medium、Residual 和 Stamps 不是一组材质查看模式,而是生成调用的检查点。比如停在 CornerHeights 时,后面的宽尺度支撑、局部残差和 Stamp 根本不会参与当前高度场。这样才能确认误差是从哪一层开始出现,而不是只比较两张最终截图。
这里也刻意把“生成结果”和“预览修饰”分开。指标、参考图对照以及 RAW/fixture 使用生成器返回的原始高度场;为减轻固定三角剖分在连续轮廓上形成的锯齿,部分生态会在建预览网格时使用一个局部条件滤波,但它不回写 H,也不参与 Web/C++ 一致性判断。

_图 6:Web 页面固定本次输入后,由 computeField() 分派到六套生态生成器,并运行到当前阶段门。生成器返回的原始高度场分别进入统计、参考对照和 fixture 支路,以及不回写原始数据的 WebGL 预览支路。_
下面这页把流程落到一次真实生成上。Desert 10、Seed 20260710、512² 分辨率和相机位置保持不变,只依次移动阶段门。六帧都来自 Web 工具中的同一条重建分支:HeightGroups 建立区域高低关系,CornerHeights 调整边界,随后依次加入大尺度连续支持、中尺度自然形态和低幅局部残差,最后才栅格化自然 StampGroup。每一步都继承上一步返回的高度场 H,不会切换配方,也不会重新套用另一张参考模板。

_图 7:同一次 Desert Web 生成的六个连续阶段。顺序为 HeightGroups → CornerHeights → broad support → medium morphology → localized residual → natural StampGroup。所有截图使用相同输入、相机和显示范围;后续阶段不会参与当前高度场。画面没有补画 POI、道路或运行时形变。_
确定性随机与世界尺度
所有随机选择都由 32 位 Seed 派生。Web 端通过 Math.imul、有符号 32 位中间值和 >>> 0 固定溢出语义,C++ 端使用 uint32_t 与相同的混合常量;随机浮点统一取混合值的高 24 位并除以 16777216。Region、HeightGroup、Stamp 父组和子实例使用不同命名空间及坐标哈希,避免某一阶段新增一次随机调用后,后面的结果全部漂移。配置数组和候选列表在进入选择前也保持冻结顺序;fixture 会捕捉顺序改变造成的结果漂移。[5]
距离参数以米表达,再由世界步长换算为像素:
radiusPx = radiusMeters / (worldMeters / N)
这样 256、512、1024 和 2048 分辨率表达的是同一世界,不是四套不同参数。高度阶段写入 Float32Array 时即截断为 float32,fixture 导出和 C++ 比较读取的也是这些存储后的阶段数组;JS 表达式内部仍可能暂时使用 Number。分辨率提高会增加采样精度,但局部候选和整数半径仍会量化,也不会修复错误的形态职责。
高度场与掩码
连续高度使用 Float32Array(N × N),离散或量化掩码使用 Uint8Array。多个阶段要同时保留高度、坡度、支持域、Stamp 覆盖和诊断场,因此场缓冲随分辨率平方增长:
| 分辨率 | 单个 Float32 场 | 8 个 Float32 场 | 12 个 Float32 场 |
|---|---|---|---|
| 256² | 0.25 MiB | 2 MiB | 3 MiB |
| 512² | 1 MiB | 8 MiB | 12 MiB |
| 1024² | 4 MiB | 32 MiB | 48 MiB |
| 2048² | 16 MiB | 128 MiB | 192 MiB |
这张表只估算 Float32 场缓冲,不是浏览器总内存;Three.js 几何、GPU 纹理、候选对象和诊断缓存都不在其中。交互预览通常使用 512²,阶段检查再升到 1024²,最终导出和原生冒烟测试使用 2048²。这样能避免每次改一个参数都支付最高分辨率成本。
滤波与频带
宽尺度支撑和尺度分析需要大量模糊运算。实现没有为每个像素遍历完整二维窗口,而是使用横向、纵向两次滑动窗口。对固定数量的阶段而言,完整场计算为 O(kN²),而不是朴素二维核的 O(N²r²)。公共 C++ helper 把米制半径四舍五入为至少 1 个像素,并采用边界夹取;本文的 16m / 64m / 256m 指箱式窗口半径,离散窗口宽度为 2r + 1。各生态冻结模块若使用不同边缘约定,原生端按该模块逐阶段复现,而不是强行改成同一滤波器。[5]
low16 = boxBlur(H, 16m)
low64 = boxBlur(H, 64m)
low256 = boxBlur(H, 256m)
band16_64 = low16 - low64
band64_256 = low64 - low256
这里的 low16 / low64 / low256 分别表示 16m、64m、256m 箱式低通;两两相减得到的是尺度残差代理,不是严格的 Fourier 频谱。它们不是为了给画面“加锐化”,而是检查某个阶段把变化放到了什么尺度。需要严格频谱分析时,离线脚本另走 FFT;Web 面板里的箱式差分只用于快速诊断。
Stamp 候选与局部栅格化
自然 Stamp 的主要流程如下:
配置拥有的 Parent Range
→ ZoneItem
→ [D] ordered StampGroup records
→ [R] deterministic weighted StampInfo selection
→ support-conditioned candidates
→ deterministic finite attempts
→ spacing / overlap / parent-child checks
→ local footprint rasterization
→ grounding and fusion

_图 8:自然 Stamp 从配置拥有的 Parent Range 和候选记录出发,经过稳定选择、表面支持评分、间距与父子约束,再在局部包围盒内栅格化和融合。_
候选尝试数有上限,落点只在局部包围盒内栅格化。设活动域数量为 A,每个域最多检查 Ccheck 个候选,放置阶段约为 O(A × Ccheck);若间距检查直接线性扫描既有实例,最坏会退化到 O(P²)。栅格化成本则接近所有实例局部包围盒像素数之和 O(Σ bboxPixels),而不是每个 Stamp 都重扫 N²。Web 面板里“Stamp Placement”“Stamp Lowland”等开关也不是显示滤镜,它们会真正切断对应计算阶段,用来判断问题来自候选、支持域还是融合。
Web 预设是可复现实验记录
每个案例都保存 Seed、生态、场景配方、阶段和关键控制值。当前共有 60 个固定预设,校验脚本会检查六类生态各十个预设的映射与控件完整性。文章中的最终图也来自这些预设,而不是挑一张临时结果后再手工修饰。[2][3]
C++ 版本:冻结算法,再做逐阶段同构迁移
Web 版本适合快速拆阶段和看结果,但最终生成器不能依赖浏览器或 Node 运行时。原生迁移采用六个独立 C++17 生成器和一套公共基础库。公共头文件负责确定性哈希、噪声、采样、统计和 RAW 输出;各生态模块保留自己的阶段、参数与配置池。
迁移遵循一个简单规则:C++ 不重新调一套“看起来差不多”的参数。Web 阶段一旦冻结,就导出同一 Seed、同一分辨率、同一选项下的逐阶段 float32 fixture;C++ 按相同运算顺序运行,再比较每一个阶段。
Frozen Web reference implementation
→ export stage fixtures
→ C++ same seed / stage / options
→ compare correlation, RMS and max abs
→ fix the first diverging stage
→ export row-major float32 RAW in meters

_图 9:Web 冻结参考实现到 C++17 生成器的逐阶段迁移。两端共享输入、阶段和数值语义;出现偏差时停在第一个分歧阶段修复,不在原生端重新调参。_
原生输出约定为 2048m × 2048m 世界、N × N 行优先 float32、单位米。RAW 本身没有文件头,字节数必须是 N × N × 4。交换协议定义为 little-endian;当前 writer 写出主机原生 float 字节,在现有小端 Windows 环境中与协议一致,迁移到大端平台时必须显式换序。写出前的有限值检查目前由生成与比较链承担,RAW writer 本身不额外拒绝 NaN/Inf。[4][5]
一份 RAW 至少需要以下旁路元数据才能被下游系统正确解释:Width、Height、WorldSizeMeters、SampleConvention=PixelCenter、Origin、AxisConvention、YFlip、Unit=Meters、Endianness=LittleEndian、Stage、Seed/Preset 或 InputDigest,以及 FormatVersion。其中阶段、输入摘要和版本不参与逐字节解码,但决定 fixture 是否与正确的生成输入配对。
需要严格一致的阶段会固定浮点运算顺序;较大的配置池则机械生成 .inc,避免人工抄写时改变顺序或权重。
| 语义 | Web | C++ |
|---|---|---|
| 32 位混合 | Math.imul / >>> 0 |
uint32_t 与相同常量 |
| 随机浮点 | 取高 24 位并除以 2²⁴ |
同一公式 |
| 高度存储 | Float32Array |
float 数组 |
| 滤波 | 滑动窗口与冻结边界顺序 | 同边界、同顺序 helper |
| 配置池 | 冻结数组 | 机械生成 .inc |
怎样读对齐数字
这里的 corr 是对两个 N × N float 场去均值后计算的 Pearson 空间相关系数;RMS 与 max abs 直接使用米制差值。三者用途不同:corr 适合发现形状漂移,RMS 反映整体数值误差,max abs 用于定位极端点。近常量字段的方差太小时,corr 不稳定,验收以 RMS 与 max abs 为主;不同生态和阶段的阈值保存在各自冻结脚本中。下表列的是各冻结文档中最清楚的一组证据快照,不应横向当作生态质量排行榜;— 表示该冻结摘要没有汇总这个指标,不表示误差为零。
| 案例 | 阶段 | 分辨率 / 样本 | 汇总方式 | corr | RMS / max abs | 当前状态 |
|---|---|---|---|---|---|---|
| Desert_Dune | final | 256² / 4 Seeds | 四个 Seed 的 final 最坏误差;完整验收另含 512² 与 13 个阶段 | — | max abs 1.907e-6m |
严格对齐已通过 |
| Icy_Glaciers / Arctic | final Stamp | 128² / 10 presets | 十预设 mean corr | 0.945551 |
— | 生成器已实现;严格 parity 未完成 |
| Plain | final Stamp | 128² / 10 presets | 十预设 mean corr | 0.907443 |
— | 生成器已实现;严格 parity 未完成(主要偏差为 distributed 03 / 05) |
| Tundra | final Stamp | 256² / 10 presets | 十预设 mean corr + worst RMS | 0.999880 |
max RMS 0.0684m |
冻结阈值已通过 |
| Scorched_Moor | final Stamp | 512² / 10 presets | 十预设 mean corr + worst RMS / max abs | 0.999911 |
max RMS 0.0477m;max abs 0.2944m |
冻结阈值已通过 |
| Ionic_Crimson | final Stamp | 512² / 10 presets | 十预设 mean corr + worst RMS / max abs | 1.000000 |
max RMS 2.794e-9m;max abs 9.537e-7m |
float32 容差内对齐 |
“当前状态”来自各生态的完整冻结夹具;表中的 corr、RMS 或 max abs 是代表摘要,不一定单独覆盖全部阶段、分辨率和 Seed。
这些数字回答的是“原生版本是否复现 Web 参考实现”,不直接衡量生成图与原始参考 HMAP 的相似度。后者仍通过十图统计、空间分布、阶段审计和三维表面检查来判断。2048² 生成已经完成冒烟验证,但由于本轮没有冻结硬件、编译选项、冷暖启动和计时区间,本文不发布不可复现的耗时数字。[1][4]
六个案例:同一方法怎样处理不同生态
下面每个案例都给出一张原始参考 HMAP 和一张固定 Web 预设结果。参考 HMAP 使用原始 16 位灰度数据,Web 图是可交互三维预览的发布截图。两者不是逐像素复制关系:恢复目标是重建配置约束下的生成机制和统计形态,而不是把某张参考图直接拟合进结果。

_图 10:六个案例共用配置 IR、十图测量、分层 foundation、支持域 Stamp 和逐阶段 fixture;差异在于各自要解决的问题、局部组织与当前完成边界。_
Desert_Dune:先纠正“平台”解释,再恢复稀疏 Stamp
Desert 最早的问题不是参数,而是解释错了。只按高度阈值看图,很容易把缓慢抬升的地形理解成固定台地或双平台结构。加入十张参考图、不同半径的低通结果和中心—边缘统计后,这种结构并不稳定,因此内部里程碑 R5-RS-S2(Region 5 Reference Support / Sparse Stamps v2)冻结为:
Region / SubRegion foundation
→ reference support
→ top / shoulder / slope / low masks
→ reference displacement
→ sparse natural Stamps
Desert 的 Stamp 不负责制造整座山。它们主要补父平台簇、子组件、附着短脊、山肩碎片和少量微细节;跨组宽侧台也保留在配置拥有的范围内。四个 Seed、13 个阶段分别在 256² 和 512² 对齐,并以 2048² 做最终输出。[6]
这里要额外说明 reference support / displacement 的证据等级:当前 Desert 实现会读取由十张 HMAP 离线提取并冻结的低频支撑与 16–64m 肩部资产,资产文件本身也标注为 image-derived,而不是解包出的运行时资源。生成时不会直接读取 4096² 原始 HMAP,但这些预计算资产仍属于 [R] 的参考条件,不应写成完全独立于参考图的原始算法。十个 Web 预设用于发布与形态覆盖;其中四个 Seed 进入了 13 阶段的严格 C++ fixture 链,两组数字职责不同。[13]

_图 11:Desert_Dune 参考 HMAP 07,4096² PNG16;按 200/65535 转米,行优先读取且不做 Y 翻转。真正稳定的是大尺度支撑、缓坡与局部稀疏结构的关系,而不是固定高度阈值。_

_图 12:Desert 07,512²,Seed 20260707,R5-RS-S2 最终阶段。自然 Stamp 开启,但不承担宏观地貌;该结果使用 HMAP 派生的冻结支撑资产。_
Icy_Glaciers / Arctic:从全局粗糙噪声退回分层冰原
Arctic 的初版曾把 Voronoi、全局 FBM 和圆形局部结构一起压进高度场,结果不是全图粗糙,就是只剩几块孤立凸包。十图测量显示,这组参考的中位有效高差并不夸张,低坡面比例很高,能量主要分布在宽尺度起伏和有限局部结构上。
重做后,Region、HeightGroups、CornerHeights、broad / medium / residual 分开计算,Stamp 候选再根据顶部、山肩和低地支持域进入。Arctic_RegionInput 中能解出 47 个 StampGroup 和 69 个唯一 StampInfo 支持资产;由于原始 Stamp 高度纹理仍缺失,局部高度使用低振幅代理,并明确保留代理标记。[7]
原生生成器已经覆盖这条链,但 final Stamp 的十预设相关性仍为 0.945551,所以当前状态是“可运行、可比较”,不是严格 parity 完成。现有摘要只把分歧定位到 final Stamp 链,实例选择、代理轮廓、融合顺序与 float32 截断各占多少尚未冻结结论;后续应继续找第一个分歧阶段,而不是通过增加粗糙噪声遮盖。

_图 13:Icy_Glaciers 参考 HMAP 01,4096² PNG16;按统一米制换算与行序读取。大面积缓坡与宽尺度冰原是主体,局部结构不能扩散成全图噪声。_

_图 14:Arctic 01,512²,Seed 20260831,coldrocky / broad 配方,最终阶段,代理自然 Stamp 开启。_

_图 15:Arctic 工程诊断图,用于对照参考与生成结果的大尺度分布。它记录的是恢复过程中的证据,不代替最终预设。_
Plain:把 Stamp 数量改成有空间组织的“人口”
Plain 的难点不是生成平缓地形,而是让自然 Stamp 既有变化又不失控。完整链条保留 Region / SubRegion、HeightGroups、CornerHeights、broad support、medium、residual,再进入受 Deciduous 配置约束的 Stamp 阶段。
内部里程碑 P-ST-L9(Plain Stamp Layout v9)之后,候选域使用世界坐标网格保持分辨率稳定:活动域按 48m 组织,父组按 32m 组织。不同预设不是简单修改密度,而是选择 compact、distributed 或 boundary 三类场景组织。Plain 10 的 dense 预设产生 58 个 Parent、315 个 StampInfo 放置和 3 个活动域。[8]
48m 活动域、32m 父组网格和三类场景组织均属于 [R]。解包数据提供候选层级、范围与支持组件;原始系统怎样划分活动域、选择 Parent 和生成高度轮廓,目前仍未确认。

_图 16:Plain 参考 HMAP 10,4096² PNG16。平缓不等于没有结构,重点在活动域、边界和局部簇的节奏。_

_图 17:Plain 10,512²,Seed 20260910,boundary / dense 配方,最终阶段,代理自然 Stamp 开启。_

_图 18:Plain 的 sparse、standard、active、dense 人口包络对照。它检查的是空间组织和支持域,不只是实例总数。_
Plain 的 C++ 生成器已经实现,但严格 parity 尚未完成:distributed 03 / 05 的相关性低于其他预设。这个偏差没有通过增加 Stamp 或全局噪声掩盖,而是留在后续继续检查分布与浮点路径。
Tundra:把连续地貌和 Moor 层级分开冻结
Tundra 使用 openMoor、brokenMoor 和 windwardMoor 三类配方。内部里程碑 T-R3(Tundra Region v3)负责 Region、HeightGroups、CornerHeights、broad、medium 和 residual,先把苔原主体冻结;T-ST4(Tundra Stamp v4)再把解出的 Moor ZoneItem 层级展开为保留列表顺序的 StampGroup records,由当前重建稳定使用,并使用 fitV1 高度代理。[9]
这样做避免了一个常见错误:看到 Moor 形态明显,就让 Stamp 同时承担大尺度地形。实际上,Moor 的父子层级和放置密度可以影响局部辨识度,却不应该改变 Region 基座。当前原始高度纹理和精确激活组仍未完全取得,因此这部分继续标记为代理。
早期 T-R1 候选曾用多个等权椭圆台块拼主体,结果出现明显的“盖章平台”感,中小尺度变化也落在参考区间下沿。后续先改为少量宽域载体形成连续基座,再让 Moor Stamp 只补局部层级;这正是连续地貌与 Stamp 分开冻结的原因。

_图 19:Tundra 参考 HMAP 04,4096² PNG16,作为十图统计与空间形态验收的一份样本。_

_图 20:Tundra 04,512²,Seed 20261014,brokenMoor 配方,最终阶段,代理 Moor Stamp 开启。_

_图 21:Tundra 十预设工程画廊。每个预设保存自己的固定 Seed,用于检查 open、broken 与 windward 三类组织在可重复输入下的稳定性;它们不是同一 Seed 的十种渲染。_
Scorched_Moor:同一候选池做出三种场景组织
Scorched_Moor 的三个代表配方来自十图空间分类:distributedActive 对应活动遍布有效域、具有较强中小尺度变化的样本;compactUplift 对应中心或局部抬升、外圈较安静的样本;edgeBiasedBroken 只吸收边缘偏置和断续组织,不拟合参考 03 / 07 中明显的人工直线与矩形结构。连续地貌阶段保持独立,Stamp 阶段再沿着 ZoneItem → StampGroup records → deterministic weighted StampInfo selection 展开,并根据低地、山肩和顶部支持域选择落点。[10]
这一案例最有价值的一次失败发生在 medium subdomain:早期实现把每个子域硬绑定到最强 Parent,所有权切换处会出现尖锐接缝,而且分辨率变化后接缝位置还会漂移。冻结版本改成重叠 Parent 支持的连续加权混合,先解决基座连续性,再进入 Stamp。解包侧共审计到 41 个自然 StampGroup、53 个 StampInfo 和 40 个唯一资产;原生端已经覆盖三类配方的十预设验证。
这里特别排除了不属于自然基线的 ZoneItem59 / se_city:解码出的资源标识明确指向城市内容,与自然地形基线的所有权不一致,排除依据来自配置语义,不是看图猜测。否则城市内容进入自然候选池后,数量和覆盖率也许仍能调得“合理”,职责却已经错了。原始高度纹理缺失的部分继续使用代理,不把支持几何误写成原始高度形状。

_图 22:Scorched_Moor 参考 HMAP 01,4096² PNG16;它属于 distributedActive 一类空间组织。_

_图 23:Scorched_Moor 01,512²,Seed 20261101,distributedActive 配方,最终阶段,代理自然 Stamp 开启。_

_图 24:Scorched_Moor 十组参考与生成高度对照。十图一起看,能避免为某一张图过拟合。_
Ionic_Crimson:配置层级已经稳定,细小可见结构仍需继续补
Ionic_Crimson 使用 frontierUplift、distributedBroken 和 compactIslands 三种组织。后半段生成链明确区分三类结论:[D] Parent Range / ZoneItem / Group records → [R] stable traversal and selection → [P] proxy height;前半段仍按 Region → HeightGroups → CornerHeights → broad → medium → localized residual / foundation 分层建立连续地貌。
在这条链里,Stamp 负责 Parent 范围内的小型簇、局部凸起和可见组件密度,不负责重做已经由 Region、broad 与 medium 建立的宏观轮廓。缺少原始高度纹理的组件继续使用 [P] 代理。
这一案例的配置 IR、十图人口拟合和表面拟合已经比较稳定,C++ 在 512² 可达到 1.0 空间相关,最大浮点差约 9.5367e-7m。但从参考图看,部分较小的可见组件仍少于原始样本。这个缺口属于局部组件与代理 Stamp 层,后续应继续补齐有配置或参考证据支持的小尺度形态,而不是增加全局噪声或无来源的额外 Stamp。[11]

_图 25:Ionic_Crimson 参考 HMAP 01,4096² PNG16,用于连续地貌和局部可见结构的联合验收。_

_图 26:Ionic_Crimson 01,512²,Seed 20260901,frontierUplift 配方,最终阶段,代理自然 Stamp 开启。_

_图 27:Ionic_Crimson 连续地貌生成画廊,用来观察宏观基础是否稳定,再决定是否进入 Stamp 调整。_
六个案例放在一起看,真正复用的是什么
六类生态最终复用的不是某一组噪声参数,而是一套工作方法:
| 案例 | 最初要解决的问题 | 当前恢复方法 | Stamp 职责 | 完成状态与边界 |
|---|---|---|---|---|
| Desert_Dune | 固定台地 / 双平台误读 | 十图低通、中心—边缘统计与参考条件化支撑 | 稀疏局部簇、短脊和山肩碎片 | 四 Seed 严格 fixture 通过;支撑资产由 HMAP 派生,Stamp 高度仍是代理 |
| Arctic | 全局粗糙或孤立凸包 | 连续地貌分层后再按支持域放置 | 低振幅顶部、山肩与低地结构 | 生成器已实现;final Stamp 严格 parity 未完成 |
| Plain | 平缓地表上的实例只增数量、不成组织 | 世界网格活动域与 compact / distributed / boundary 配方 | 组织 Parent 与局部人口 | 生成器已实现;严格 parity 未完成,distributed 03 / 05 仍有原生偏差 |
| Tundra | 等权台块与 Moor 层混写 | 宽域连续基座和 Moor 层级分开冻结 | 局部辨识度与密度 | parity 阈值通过;精确激活组和原始高度纹理未取得 |
| Scorched_Moor | Parent 硬归属造成接缝,三类空间组织混在一起 | 连续重叠支持与三类场景配方 | 按表面支持形成 distributed / compact / edge-biased 组织 | 十预设原生验证通过;自然高度仍为代理,se_city 已排除 |
| Ionic_Crimson | 中大尺度成立但小组件不足 | 三类组织加分层 foundation | 小型簇、局部凸起与组件密度 | Web/C++ 在 float32 容差内;细小可见组件仍需补充 |
这张表比较的是工程职责和成熟度,不是六个生态与原始游戏的相似度排名。
| 层级 | 所有案例共同遵守的规则 | 允许变化的部分 |
|---|---|---|
| 配置 | 保留 Region、SubRegion、ZoneItem、StampGroup、StampInfo 所有权 | 生态自己的候选池、权重与场景配方 |
| 连续地貌 | 先 Region / HG / CH,再 broad / medium / residual | 各尺度振幅、方向、边界与空间分区 |
| Stamp | 受支持域、层级、间距和有限尝试约束 | 父子结构、人口密度、表面偏好和代理形态 |
| 验证 | 十图统计、固定 Seed、阶段输出、Web/C++ 对齐 | 各案例重点指标与接受阈值 |
| 证据边界 | 缺失内容必须标注 proxy | 代理可随新证据替换,但不能冒充原始资产 |
这也是为什么“做一个通用噪声函数复刻全部地貌”不是目标。真正可复用的是层级、职责、验证协议和数据接口;生态差异仍需要各自的配置和受约束实现。
哪些内容已经完成,哪些还没有
当前可交付范围包括:
- 六类代表生态、每类十个固定 Web 预设;
- 60 组 HMAP 与 60 组 RT0 的参考入口;
- 可逐阶段观察、可切换 Seed 和分辨率的 Web 实验工具;
- 六个不依赖 Node 的 C++17 生成器,各案例的 parity 成熟度分别记录;
- Web → C++ 的阶段 fixture、相关性、RMS 与最大绝对误差检查;Arctic final Stamp 和 Plain distributed 03 / 05 仍保留已知偏差;
- 2048m × 2048m、float32 米制 RAW 输出;
- 本文使用的六组 HMAP、一张 RT0、六张最终预设图、五张工程诊断图和七张方法 SVG。
仍未解决或不在本文范围的内容包括:
- 缺失 Stamp 高度纹理对应的原始形状;
- 部分场景的精确激活组与运行时处理逻辑;
- Plain distributed 03 / 05 的剩余原生偏差;
- Ionic_Crimson 的细小可见组件不足;
- Basic / Haunted Swamp 缺少足够 HMAP,当前不作为完成案例;
- POI、道路、洞穴、任务空间、爆炸坑和运行时位移;
- 引擎内的分块、流送、碰撞、材质、植被与服务器性能。
最后一组内容尤其需要保持边界。自然地形恢复回答“地表基座怎样形成”;POI 和道路回答“玩法内容怎样进入世界”;运行时系统再处理分块、同步和变化。把它们提前揉进一个高度公式,短期看起来省事,长期会让归属和验证全部混乱。
内部实现证据与冻结入口
本文结论来自项目内的解包数据、参考 HMAP / RT0、Web 实现、C++ 实现和阶段验证记录。对应冻结版本为 Git commit fbe45c0。主要证据入口如下:
- [1] 六生态最终交接记录:预设、验证结果与完成边界;
- [2]
terrain_preview_3d.html:Web 预览与阶段控制入口; - [3]
terrain_reference_presets_v1.js:60 个固定参考预设; - [4] 原生 C++ 总览:入口、输出约定与验证结果;
- [5]
terrain_native_common_v1.h:确定性哈希、采样、统计与 RAW 公共实现; - [6]
HANDOFF_DESERT_R5RS_CPP_20260804.md:Desert R5-RS-S2 与逐阶段原生验证; - [7]
ARCTIC_REGION_V1_STATUS.md:Arctic 配置、参考统计与代理边界; - [8]
CODEX_HANDOFF_PLAIN_STAMP_PSTL3_20260824.md:Plain Stamp 人口与场景组织; - [9]
CODEX_HANDOFF_TUNDRA_T_R3_T_ST4_NATIVE_20260827.md:Tundra 连续地貌与 Moor 层级; - [10]
CODEX_HANDOFF_SCORCHED_MOOR_SM_ST1_NATIVE_20260827.md:Scorched_Moor 配方与原生验证; - [11]
CODEX_HANDOFF_IONIC_CRIMSON_ICS1_20260827.md:Ionic_Crimson 配置 IR、代理阶段与 parity; - [12]
analyze_desert_reference_v2.js与analyze_desert_detail.py:PNG16 解码、米制换算、有效区、坡度和多尺度统计约定; - [13]
extract_region5_reference_support_assets_v1.js:Desert HMAP 派生支撑资产及其 image-derived 状态。
文中原始参考图来自对应生态的解包 HMAP 数据;发布图的逐文件来源另存于内部图片清单。参考图只用于研究和验证,不应被理解为项目自行创作的游戏素材。
结尾
这次还原真正有价值的部分,不是终于找到了一个“万能地形公式”,而是建立了一条能不断被新证据修正的工程链:配置提供层级与归属证据,当前重建负责把这些关系解释为空间结果,参考图约束结果,Web 用来拆阶段和快速试验,C++ 用逐阶段 fixture 保证迁移不走样,六类生态再检验这套方法能否跨越明显不同的地貌。
这条链也让后续工作更清楚。拿到新的 Stamp 高度纹理,可以只替换代理层;发现某个 Region 使用逻辑,可以回到对应阶段;进入引擎后,则在不改变自然地形基线的前提下继续接 POI、道路、分块、碰撞和运行时同步。每次新增能力都有明确归属,也有可以回归的证据。
下一篇会单独展开 C++ 实现。重点不再是展示 Web 里的阶段结果,而是把冻结的配置 IR、确定性随机、Float32 高度场、分层生成顺序和 Stamp 栅格化逐项迁移到原生代码,再用同一组 Seed 与阶段 fixture 找出 Web 和 C++ 的第一个分歧点。最终目标是让原生生成器能够脱离浏览器运行,同时保留这一篇建立的阶段边界和验证口径。
《从配置到逆向重建:程序化地形的还原方法、Web 原型与 C++ 迁移》有1条评论