程序化地形还原(四):沿原版算法重建四种地貌的高度生成链

同一套高度公式用于不同地貌时,仍可能得到错误的结果。排查这四套请求时,几个主要差异出在数据交接上:查询读了哪一版高度,绘制按什么顺序执行,纹理上传后何时能用。本文沿生成过程说明这些问题的成因与修复。

第一篇沿一条苔原请求解释高度图计算,第二篇展开 POI、Stamp、植被与岩石布局。第三篇讨论这些数据如何进入 UE 的运行时地形、碰撞和联机流程。本篇回到生成端,记录 10 月 3 日至 9 日的一轮扩展:以四套地貌请求检查同一套算法,并把支撑排序、纹理行序和连续高度绘制进一步移入 C++。

截至本次整理,平原(Plain)、沙丘(Desert Dune)、离子赤原(Ionic Crimson)与焦灼荒原(Scorched Moor)四个案例的自然 Stamp 父记录,以及区域内部点和角点高度,已经有完整对照结果。以相同的显式启动配置重新生成,C++/HLSL 输出与冻结 Web 基线的四份 Vista 前、Location 高度纹理写入后的 4096² float32 高度场逐位一致。与参考目标的比较则仍保留毫米级残差,外围范围和引擎运行时另有验收要求。

这次扩展中,影响最大的错误集中在阶段之间:使用了错误版本的任务边界,把平台后的高度用于本应读取平台前高度的查询,混用了 CPU 记录顺序与 GPU 绘制顺序,以及没有随纹理驻留级别同步更新几何。下面先看四个案例的结果,再从请求查表开始,沿各阶段说明高度怎样生成,最后展开这些错误的定位和修复过程。

四种地貌,使用同一个比较范围

四组对比均使用中央半径 450 米的共同范围,采样间距为 0.5 米,每组统计 2,544,680 个像素。图中依次为参考目标高度、独立生成高度和绝对误差。圆形仅用于限定统计范围,不代表地图边界,也不代表外围融合半径。

沙丘:参考目标、独立生成与误差

图 1|数据绘图:沙丘(Desert Dune)案例。右栏的连续误差色标上限为 2 毫米,最大绝对误差约 0.5913 毫米。它显示数值残差,不是地表材质或光照差异。

平原:参考目标、独立生成与误差

图 2|数据绘图:平原(Plain)案例。相同中央范围内的最大绝对误差约 0.5569 毫米;本次统计没有超过 1 毫米的像素。

离子赤原:参考目标、独立生成与误差

图 3|数据绘图:离子赤原(Ionic Crimson)案例。最大绝对误差约 1.5011 毫米,49 个像素超过 1 毫米,没有超过 1 厘米的像素。

焦灼荒原:参考目标、独立生成与误差

图 4|数据绘图:焦灼荒原(Scorched Moor)案例。最大绝对误差约 1.3332 毫米,37 个像素超过 1 毫米。现有参考仅覆盖中央裁片,不能据此给出整图误差。

案例 Seed 平均绝对误差 最大绝对误差 超过 1 毫米 超过 1 厘米
平原(Plain) 2763691705 0.0334 毫米 0.5569 毫米 0 0
沙丘(Desert Dune) 2429929577 0.0084 毫米 0.5913 毫米 0 0
离子赤原(Ionic Crimson) 930813242 0.0129 毫米 1.5011 毫米 49 0
焦灼荒原(Scorched Moor) 2431905037 0.0258 毫米 1.3332 毫米 37 0

四个 Seed 用来标识本次请求。复现时仍需相同的任务条件、静态资源、配置版本和启动策略;只复制 Seed 不足以确定结果。中文地貌名称用于本文阅读,对应的英文配置名列在表中。

这些数字来自编码前的 float32 高度数组,未做均值、中位数或整体高度偏移补偿。误差图在缩小时,对每个 2×2 显示块保留最大误差,避免孤立差异被平均掉。图中的一个着色块因此可能代表多个原始像素,表里的数量始终按原分辨率统计。

这里测试的是四套固定请求,各自使用不同配置、执行不同分支。这四套请求已能独立生成对应结果;其他 Seed、任务条件和特殊分组仍需另外测试。

本轮结果与阅读路线

本轮结果

四套固定请求均能从请求重新生成至 Location 高度纹理写入阶段。
分别保持各案例基线的显式启动与查询配置时,C++/HLSL 与冻结 Web 的四份 Vista 前 4096² float32 数组逐位一致。
与参考目标比较,中央共同范围最大绝对误差约为 0.56~1.50 毫米。
外围 Vista、UE 运行时、物件装配与联机仍按各自范围独立验收。

这两类高度结果使用不同的比较对象:冻结 Web 用于检查迁移是否保留已有实现行为,参考目标用于衡量独立生成的数值残差。后文分别列出三种比较的对象与范围;与冻结基线逐位一致,不代表与参考目标一致。

正文从 demo 页面开始,依次说明配置、阶段结果和问题定位。需要查某个环节时,可以直接使用下面的导航。

阅读目标 对应章节
确认实际生成入口与高度版本 demo 生成链
追踪请求、查表、两次抽选与地点类别 请求与配置
查阅高度参数、来源混合、类型与曲线 Region 配方与连续高度
对照阶段图与具体实例 阶段产物与局部实例
定位间距、状态版本、行序、排序和资源问题 七类阶段一致性问题
核对迁移结果及比较范围 C++ 迁移、验证口径
查看引擎接入状态与本文结论 UE 验证、结论

图注按来源区分:运行截图来自实际 demo 页面;数据绘图读取已归档高度数组或布局记录;配置与机制示意解释读取关系。标为教学示例的数值只用于演算,不作为该 Seed 的实际区域记录。

demo 实际执行了哪条生成链

先看 demo 的完整页面。左侧保留请求条件、配置选择结果和阶段切换,右侧展示所选阶段的地面。阅读时先确认本局输入,再看选中了哪份配方,最后切换阶段观察高度变化。本例是焦灼荒原请求,选中 Region 36、profile 0;后面的配置说明、六阶段截图和两组局部实例图都围绕这一次作业展开。

demo 完整页面:请求配置、生成阶段与高度结果

图 5|运行截图:demo 完整页面截图。Seed、Planet、Scene、阵营、难度和任务输入保留在左侧,六个阶段可切换;右侧显示 Location 资源高度绘制后的结果。它展示内圈高度,尚未包含外围 Vista。

本篇对应的是四地貌独立生成 demo:Web 提交请求和展示结果,上游 C++ 生成布局、区域与实例,独立 D3D11 宿主执行自写 HLSL 生成高度。第三篇的 UE 运行时工程另有入口与验收状态,不能把这个 demo 的最新修复直接算作 UE 已同步完成。

当前生成后端先读取 200 字节请求模板,只替换指定 Seed,保留其余条件;再加载静态目录并执行上游 C++。因此,选择“平原(Plain)”或“焦灼荒原(Scorched Moor)”案例,会同时确定任务、场景与资源筛选的初始条件。页面显示的配方索引则是后续查表和抽选的结果,需要与请求输入分开阅读。

demo 的实例生成、三类查询输入与像素绘制顺序

图 6|机制示意:按实际调用区分上游生成与像素绘制。自然 Stamp 父实例在上游已经形成,绘制时才展开静态高度资产。三类高度输入分别保存,不能按“最新高度”统一替换。

后文使用下表中的阶段名。Hbase 是平滑后的基础像素高度,Hplatform 是强制平整后的输出。某个请求没有平台写入时,两份数组可能完全相同,但查询与绘制仍要读取各自约定的版本。图 6 分开画出实例生成和像素写入,因为父实例生成后,还要等到后续阶段才参与高度绘制。

阶段名 输入与处理 保留的产物或用途
上游场景生成 请求、静态配置及布局、区域和实例处理 带高度三角网、自然 Stamp 父记录与地点记录
栅格化 Raster 对带高度三角形逐像素插值 栅格高度数组
平滑 Smooth 对栅格场执行多尺度滤波 Hbase,供基础查询金字塔及后续处理使用
强制平整 Platform 从 Hbase 查询平台目标,再写入平台 Hplatform,供 Location 支撑绘制作为底图
Location 支撑 辅助查询保留 Hbase,像素写入读取 Hplatform 支撑调整后的地面及独立支撑参数
自然 Stamp 高度绘制 展开已接受父实例的静态高度资产 自然局部形状写入后的地面
Location 高度纹理绘制 在当前地面上写入地点静态高度资产 本次内圈连续绘制的最后一份高度
外围 Vista 独立的外围输入与融合处理 不包含在本文四份 Vista 前逐位回归中

上游先产生布局、区域与实例

初始化完成后,地点的候选与位置处理先提供本局空间输入。区域结构随后计算归属、高度组、类型和 Zone;地点与区域之间还会经过关联、附着和后续布局更新。当前入口中,同类操作会在不同阶段再次调用,不能把它们压缩成一次“地点生成完毕”并认为以后不再变化。

地点来源进入基础角点赋高;边界、类型和曲线继续进入细分高度,产生带高度的内部点和角点。自然 Stamp 循环随后结合区域成员、任务范围、障碍和查询数据接受父实例,当前入口再组装并输出用于栅格化的高度三角网。运行记录还分别保留地点绘制记录与自然 Stamp 父记录,后续资源适配再将它们展开为局部单元。

图 6 中,自然 Stamp 父实例属于上游输出,支撑、自然 Stamp 和 Location 高度纹理属于后续像素处理。父实例先被选出并保存,等支撑地面完成后,才将其静态高度资产写入地形。两条流程通过实例记录连接,不能把像素绘制顺序当成实例生成顺序。

三类高度输入不能互换

消费者 当前 demo 实际读取 用途 不能替换为
自然 Stamp 候选查询 基础角点高度、下坡邻接、外侧距离和空间索引 得到位置处的高度、坡度和范围条件 支撑后或最终 GPU 高度
Location 支撑辅助查询 平滑后的基础像素高度 Hbase 及其查询金字塔 边界采样、高度包络、辅助高度 平台写入后的 Hplatform
支撑像素绘制 平台后的 Hplatform 和已经计算的支撑参数 改写当前地表 只有查询高度、没有平台的旧底图

自然 Stamp 查询先通过空间索引找到角点编号,读取该角点高度并乘以调用方尺度。存在有效下坡邻点时,再由高度差除以水平距离,经过 atan 转成坡度角。它使用的是上游角点场及邻接关系,没有从最终高度纹理采样,也不等同于渲染网格的法线坡度。

Location 支撑辅助查询使用的是另一条路径。自产三角网经栅格化和平滑后形成 Hbase,发布为约定纹理行序,再构造八层查询金字塔。查询结果包含辅助高度、包络以及接受/剔除采样统计;后面还要计算准入、目标、父子协调、发射与邻接,不是把查询返回值直接当作最终支撑高度。

强制平整同样从 Hbase 计算平台目标并写入 Hplatform,但 Location 支撑查询仍保留 Hbase。支撑绘制再读取 Hplatform 作为底图。程序为这两份高度分别保存文件路径和哈希,查询与绘制绑定的输入也不同。后文离子赤原的修复,就是把误接的平台后查询改回 Hbase。

绘制准备可以提前,像素写入仍按顺序执行

静态 Level/Unit、纹理、patch 网格与驻留计划可以在像素绘制前准备好;“已经解析自然资源”不表示自然高度已写入地面。端到端验证器准备绘制包后,由连续 C++ 绘制入口依次执行支撑、自然 Stamp、Location,最后才产生用于比较的高度数组。

这个连续入口不是另一套从 Seed 开始的完整生成器。它接收调用方提供的支撑目录、平台高度和绘制记录,保证后继绘制顺序;端到端验证器另外保证这些输入来自本次上游生成。核对这两项结果时,需要分别检查绘制输出和整条输入来源。

原版分析已经核对了配置读取、父记录、阶段内核和共同范围高度。检查 demo 时,还要逐项确认这些规则是否接入了当前入口、测试请求是否执行了对应分支,以及输出是否完成参考对照。源码中存在一个函数,并不足以说明这次请求已经通过它生成了结果。

请求怎样确定 Region 与 profile

生成入口需要三部分数据:请求提供本局条件,静态表保存可选资源及规则,执行状态记录分支选择和随机序列。只保留 Seed,会丢失另外两部分对结果的影响。

下面沿焦灼荒原请求解释配置:Seed 2431905037 查到 Planet 索引 73、Scene 索引 23,最终选中区域配方 36 和 profile 0。先看各字段被哪一步读取,再回到同一次 demo 作业查看结果。三角网、栅格化和局部写入另用一组平原图说明,图注会标明对应请求。

请求配置汇总

图 7|配置示意:焦灼荒原固定请求的读取主线:请求标识先查表,资格条件限制候选,Seed 再参与配方与 profile 抽选。下排列出后续读取这些配置的步骤。

请求进入当前入口时保存为 200 字节记录。汇报图展示的是便于阅读的逻辑字段,不是这 200 字节的完整内存布局。例如,Scene 的逻辑标识已经在解码时转换成内部索引。字段显示在哪个框里,不能代替对实际序列化格式的说明。

这里的“独立生成”指生成分支不读取参考目标的中间高度、实例列表或父变换。参考数组只在生成完成后进入误差比较;本次计算自产的阶段文件则可继续传给下一阶段。Web 负责提交作业和显示,高度来自 C++ 核心、自写 HLSL 和独立图形执行程序。冻结 Web 基线也是这套实现产生的文件,因此它与 C++ 输出的逐位比较属于实现回归,不能作为两套独立算法对原版的双重证明。

图 7 的连接线表示数据读取关系。Planet 决定从哪些集合寻找配方,并提供地点资格条件;Scene 限定 profile 类别,解释任务编号并提供场景规则。二者共同约束配方选择。选中的 Region 配方再进入区域分组、类型选择和资源处理,经过角点、三角网与局部绘制,才形成像素高度。后文按这一顺序说明各字段的用途。

先区分配置、实例和计算产物

一份 Region 配方可以被多个请求重复使用。同一配方在不同 Seed、任务和空间布局下,会产生不同的区域归属和地点实例;这些实例再参与来源赋高和局部纹理写入。配方本身保存的不是一张固定高度图,也不是已经摆好的建筑清单。

数据 本例的具体内容 读取阶段 产生的结果
逻辑请求 Seed、Planet/Scene 标识、九项任务和条件列表 初始化及任务匹配 命中的静态记录、任务种类与执行条件
Planet 记录 两路集合引用 23/23、类型 9、B0/D4 列表 区域入口和地点资格判断 候选集合、模板与地点的过滤条件
Scene 记录 允许类别 [1,2]、32 槽任务表、规则引用 配方资格及任务处理 可选 profile 类别、逐项任务种类和派生模式
候选集合 七个配方引用及各自外层权重 Region 抽选 本局使用的配方 36
配方内 profile 类别、档位权重、标签和地点类别权重 档位选择及地点候选处理 profile 0,以及本局后续使用的约束
高度与类型参数 四组权重、距离参数、噪声参数、类型表 区域分类、角点及细分高度 高度组、类型、Zone 引用和带高度的点
本局实例 地点位置、资源引用、自然 Stamp 父实例与朝向 来源计算及资产展开 来源高度、绘制变换、局部纹理输入
阶段高度数组 栅格化、平滑、支撑和局部写入结果 后续查询、绘制与最终比较 可查询、可编码的地表高度

表中的输出分为三类:查表和抽选确定记录,资格条件限制候选,高度计算产生数值。Planet 类型 9、profile 类别 1 和地点类别 1 分别用于兼容判断、档位资格和资源分配;它们与后面的高度组、像素高度属于不同字段。后面的配方图会逐项说明这些字段在哪一步使用。

实现中需要明确区分静态配方索引、profile 索引、区域编号、实例编号和纹理像素坐标。传递索引时若未注明所属数据表,接收模块可能从错误的表中读取记录。

Planet 查表与地点资格条件

Planet 在这里限定 Region 候选集合与 Location 资源资格,尚不计算高度。查表确定记录后,集合引用用于选择配方,资格条件用于筛选地点资源。

Planet 静态表有 438 条记录。本例通过请求标识查到从零计数的索引 73,即表中第 74 条。这个索引是标识匹配的结果,不能用请求数值直接做算术换算。Seed 不参与这一步查找。

Planet 请求、查表与输出

图 8|配置示意:Planet 只按标识查找记录,再按分支读取集合引用。集合编号、类型和条件列表分别交给 Region 查找与地点资格检查。

命中记录保存两路区域候选集合引用,本例都是 23。当前独立调用显式使用分支 0;原版上游怎样设置这个状态,尚未完整确认。四套测试请求对应的两路引用分别为 23/23、3/3、21/21、24/24,因此这四个案例的候选集合选择不受这一个分支差异影响。它没有证明原版四局的状态值都是 0。

这条记录还提供地点资格判断。本例 Planet 的 B0 列表为空,D4 列表为 [9,9],类型值为 9。B0、D4 暂时沿用字段标识;已确认的是读取方式,还不能为它们附上未经核验的玩法名称。

候选地点的 B0 列表为空时通过这一项;非空时,需要与 Planet 的 B0 列表相交。因此,Planet 空列表不等于所有候选都通过。D4 判断则允许候选值为 0、候选值出现在 Planet 列表中,或者 Planet 列表包含 9。本例符合最后一个条件,但阵营、难度、模板等资格判断仍需继续。

请求中的 B0 控制字、Planet 的 B0 列表和候选地点的 B0 列表属于不同字段。即使它们在分析记录里使用相似名称,也不能交换输入。类型值 9 还参与模板兼容检查,对应模板掩码的 bit 7;通过地点资格不等于任意模板都可用。

Scene 的任务种类与允许类别

Scene 提供三类配置:允许的 profile 类别、任务种类和派生区域规则。下面先说明各自用途,再看九项请求如何匹配任务表。

Scene 静态表有 161 条记录,本例请求标识命中索引 23。图中上排依次展示输入、读出的配置字段及其使用位置,下排保留九次任务匹配的实际槽位与种类。Scene 的分类、允许 profile 类别和任务种类来自不同字段。

Scene 请求、任务表与输出

图 9|配置示意:Scene 的三类输出分别使用:允许类别用于配置档筛选,任务种类控制类别分配分支,规则引用派生的 Zone 模式用于后续子区域处理。

本例允许的 profile 类别是 [1,2]。它用于筛选区域配方中的配置档,不是“本局有两个任务”,也不是某个地点的最终类别。Scene 分类 1 参与默认任务半径配置;半径覆盖引用为 0,含义是没有覆盖,不能解释为半径为零。

Scene 还保存一个规则引用,经过构造逻辑得到 Zone 模式 3。表中保存的原始引用与执行时派生的模式需要分开;不能把模式 3 当成 Scene 表里直接存放的一个数值字段。对应分支在地点摆放后检查模式标志与子区域覆盖,再写入类型、Zone 和高度组信息。

九项请求怎样匹配三十二槽任务表

任务表有 32 个槽,每槽 24 字节。目前已确认的是任务编号和任务种类,分别位于槽内偏移 0 和 12;其余 16 字节不在本文中推断用途。

任务匹配与 Location 后续处理

图 10|配置示意:任务匹配与地点生成。重复任务按请求顺序处理;跳过类别分配后仍有资源选择和摆放。

本例请求保留九项任务编号及其原始顺序,其中前两项相同,接下来的两项也相同。它们依次命中槽位 [0,0,1,1,4,6,5,9,13],取得种类 [0,0,0,0,3,3,3,3,2]。重复编号并没有在此处去重,九次匹配也不能改写成七个独立任务。

这些种类值影响执行路径。种类大于 1 时,跳过地点类别分配;后续的资源选择和摆放仍然继续。种类 0 则进入相应的类别分配逻辑。把“跳过类别分配”写成“不生成地点”,会遗漏后面的实际工作,也会错误估计随机数消耗。

目前种类 0、3、2 的业务名称仍未确认。可以解释它们控制了哪个分支,却不应根据编号、位置或视觉结果补成具体任务名称。任务种类、Scene 允许类别与 Location 类别虽然都使用整数,仍是三个不同的输入域。

Region 配方与 profile 分别抽选

这里先选 Region 配方,再从已选配方内部选 profile。两次分别用同一个 Seed 初始化随机状态,各自按总票数取模;它们不连续消费同一条状态序列。

Planet 命中的候选集合 23 在外部映射中称为 region_moor_arid。它保存七个配方索引 [16,36,19,46,68,34,63],对应权重为 [0,1,1,0,0,1,1]。这些值需要先结合 Scene 的类别限制判断资格,才能进入实际抽选。

区域候选资格与两次抽选

图 11|配置与抽选示意:区域候选资格与抽选。展示焦灼荒原请求选中配方 36、profile 0 的输入和票数。

每份配方包含八个 profile。选择前还有兼容归一化:若八项权重和为零,或旧标志、旧类别、旧权重不满足当前格式条件,就把各档权重清零,将 profile 0 的权重设为 1,并使用旧类别。资格判断读取的是这个处理后的投影,不能只看原始八档表中的某一行。

配方 36 的旧标志为 0、旧类别为 1、旧权重为 1,八档权重和为 1.8,因此没有触发重写。当前选择器实现了资格判断所需的兼容投影,但初始化入口尚未认证选中旧格式后整份 profile 负载的复制重写;若最终选中的配方需要这种重写,入口会拒绝。它能排除不合格候选,不代表所有旧格式都已经能继续生成。本例选中配方 36,不进入这个未集成分支。配方 34、63 的兼容投影类别分别为 3、5,不符合 Scene 的 [1,2]。另外三项 16、46、68 即使符合类别,集合权重也是零,不获得普通抽选票数。最后参与本例普通抽选的是 36 和 19。

权重先按 float32 乘以 1000,再转换为无符号整数票数。这里两个配方各有 1000 票,总计 2000。随机状态按下式计算,乘加保留 uint64 溢出语义:

state = uint64(seed) × 0x5851f42d4c957f2d + 0x14057b7ef767814f
target = state % totalTickets

本例状态为 0xb1509b2349729f98,对 2000 取模得到 520,选中配方 36。内部遍历跳过零票项,累计正票数,首次满足累计值大于等于 target 时返回。复现时应保留这个边界条件,而不是换成另一种常见的区间写法。

接下来从配方 36 的合格 profile 中再选一次。profile 0 为类别 1、权重 1;profile 1 为类别 2、权重 0.8,分别得到 1000 和 800 票。这两次选择各自从相同 Seed 初始化状态,不是连续消费同一个随机状态。 因而第二次仍使用上面的 state,对 1800 取模得到 720,选中 profile 0。

七十五份配方不是七十五种独立地貌

区域配方及候选集合关系

图 12|静态配置关系:配方与候选集合的引用关系。静态配方、候选集合与显示家族名称是不同层次。

静态表共 75 份配方,每份 3296 字节。配方 36 是从零计数的第 37 份,外部映射名为 region_moor_mountain;名称映射不是记录内直接保存的字符串。

34 个候选集合可以共享配方。现有汇总按 13 个家族标签归类,共引用 46 个不同配方,其中包含零权重引用;另外 29 份没有出现在这批集合引用中。未被引用不等于已经废弃,75 份记录也不等于 75 个星球或 75 种互不相关的生态。

到这里,两次抽选只确定了配方 36 与 profile 0,区域高度和地点资源尚未生成。集合权重已经完成配方选择,profile 权重已经完成档位选择;接下来读取的标签、地点类别权重和高度组权重,各自控制新的步骤。图 13 继续展开 profile 在选中后怎样被使用。

profile 怎样约束地点资源

profile 标签和地点类别消费者

图 13|配置示意:选中的 profile 继续提供标签和地点类别权重。地点类别分配还需要任务种类、配额和任务槽可用性,不能简化成逐任务按权重抽选。

profile 除了类别与抽选权重,还保存标签约束和地点类别权重。本例 profile 0 的标签槽为 [0,0,0,0],解析在第一个 0 结束,得到零标签掩码;它不是四个名为 0 的标签。标签的全部匹配、任意匹配和排除条件仍分别执行,零掩码不应被概括成整个 profile 无条件通过。

profile 0 对地点类别 1 至 6 的权重为 [1,0,0,0,0,0];profile 1 则为 [1,1,0,0,0,0]。这张表参与地点资格与类别处理,与前面 Scene 允许的 profile 类别 [1,2] 不是同一张表。任务种类决定是否进行类别分配,选中的 profile 再提供分配所用权重。

兼容特殊组与覆盖规则的条件及实现边界

图 14|条件规则示意:兼容处理、特殊组和覆盖规则分别说明。配方 36 不触发旧格式重写;另外两列解释条件路径,不作为本局特殊规则命中记录。

图 14 另列出三类条件规则。兼容资格投影可以参与筛选,但选中旧格式后的完整负载重写尚未集成;特殊组只作用于具有相应标识的区域;覆盖匹配得到记录后,还需完整上下文才能应用。是否执行这些规则,还需检查本次请求的条件和调用记录。

地点类别权重怎样进入实际分配

profile 中的类别权重不是让每个任务独立按比例抽一个类别。当前目标类别分配器还读取各类别配额,以及每个任务槽已经筛出的可用类别。建立可用性时,候选资源先经过阵营、难度、模板、标签与列表约束;只有存在合格候选,任务槽才把相应类别标为可用。

分配器先产生类别遍历顺序,跳过权重不大于零的类别,再按配额尝试分配。一个任务槽必须同时满足:种类不大于 1、尚未分配 target、当前类别可用。若还有一个只支持该类别的未分配槽,则暂不把这个类别分给拥有多种选择的槽。这样能避免先用完唯一可行的分配位置。

例如,教学条件下槽 A 只支持类别 1,槽 B 支持类别 1、2,类别 1 和 2 的配额各为一。如果遍历先处理类别 1,A 的选择比 B 更受限制,分配器优先保留 A 的可行性;随后 B 仍可用于类别 2。这是配额与可用性的约束,不是把 B 的抽选概率简单改为 50%。本例 profile 0 的类别 2 权重为零,则这个教学条件并不成立,不能直接套成当前请求的实际分配结果。

同一组权重在不同步骤中用法不同。目标类别分配器只检查权重是否为正,据此决定要不要处理该类别;后面的候选资源选择还可能使用权重大小。核对实现时,需要分别检查这两个读取位置。

得到 target 后,任务槽只是有了类别目标,后面还要选择资源、提出位置、检查并摆放。类别分配成功时地点尚未生成;跳过类别分配的任务也可能继续执行资源选择和摆放。

Region 配方怎样成为连续高度

配方 36 进入高度计算后,先为区域选择初始高度组,再按低组距离调整分组;地点和区域边界提供来源,来源混合形成角点高度;类型对应的 Zone 曲线与噪声继续计算细分点高度。图 15 标出这些步骤各自读取哪些参数。下面按这个计算顺序解释数值,最终再与三角网和阶段截图对应。

配方 36 的高度消费者

图 15|机制示意:高度计算的读取关系。区域分组、类型和 Zone、基础来源及细分高度各自保留输出;带高度几何随后进入栅格化。

初始高度组使用四项权重 0.4、1.25、1、0,总和 2.65。通常先取得空间噪声样本;当随机值小于阈值 0.1 时,才改用随机取样。不能把阈值写成每个区域都会附加 10% 的高度扰动。

用一个教学样本说明分组:将样本归一到 [0,1] 后取 0.5,乘总权重得到 1.325。累计权重在组 1 后为 0.4,在组 2 后为 1.65,因此命中组 2。这不是本 Seed 某个实际区域的追踪记录,而是用真实配置值解释选择规则。

初始高度组的随机状态怎样变化

基础分组先检查特殊规则。如果该区域对应的特殊项启用强制标志,就直接写组 1,不进入普通取样。在普通分支中,程序先推进一次阶段随机状态,得到 draw;只有 draw 小于 0.1 时,再推进一次并把第二个样本作为 value。其余情况从区域中心位置读取空间噪声,并映射到 [0,1]。

因此空间噪声分支仍消耗第一次随机数,强制分组分支则不经过这段普通抽样。若重写时只在“需要随机样本”时调用随机函数,后面区域读到的序列就会发生偏移。基础分组和类型选择还各有按阶段初始化的随机状态,也不能接成任意共享的一串抽样。

基础分组用累计值严格大于 threshold 作为命中条件。前面 Region 票数抽选使用的是累计值大于等于 target;两个阶段的边界比较不同。教学样本 0.5 不处于边界,两种写法都会得到相同分组,但用它验收实现,检查不出这个差别。

按低组距离调整高度分组

初始噪声将区域分成组 1 至 4,此时只有组号,还没有地面高度。接下来根据区域到低组的距离调整分组,再用调整后的组号选择类型和曲线。

低组距离调整随后以组 1 为锚点;没有组 1 时改用组 2。这项回退只属于距离调整函数;后续地点角点赋高的当前实现仍要求存在组 1,没有低组锚点时会拒绝,不能把前一步的回退推广为整链都支持无组 1 的输入。计算到最近锚点的中心距离,减去间距后截为非负数,再以配置上限与本次最大距离的较小值作为 cap。这个阶段只读取组 2、3、4 的权重,总和为 2.25,组 1 的 0.4 不参与这里的累计。

d = max(nearestAnchorDistance − spacing, 0)
cap = min(configuredCap, maximumDistance)
target = min(d / cap, 1) × (weight2 + weight3 + weight4)

这段表达式省略了实现中的接近零判断。以 cap=425、d=300 为教学条件,target 约为 1.588;它超过组 2 的累计值 1.25,进入组 3。这里 cap=425 是配置上限实际生效的条件示例,并不是声明当前地图的最大距离必然大于 425。

配方 36 的分组取样与距离参数

图 16|配置与教学示意:七项分组与距离参数。配置值来自配方 36;取样值 0.5 与距离 300 是教学条件,不代表本局具体区域。

前面分组演算使用四组权重,距离调整只使用组 2~4 的权重;其余参数在来源与细分阶段继续使用。下面保留完整十四项字段,供实现时逐项回查。偏移以这份 3296 字节记录的起点为零;英文名按已确认的读取用途命名,不表示找到了原版符号。图中分组展示读取关系,表中保留逐项值。噪声频率还经过内部缩放,距离字段也需要结合调用方坐标,因此不直接为每项输入追加“米”或“赫兹”。

偏移 参数用途及描述名 配置值 主要消费者
0x18 高度组 1 权重 heightGroup1Weight 0.4 初始分组、细分噪声阈值
0x14 高度组 2 权重 heightGroup2Weight 1.25 初始分组、低组距离调整、细分噪声阈值
0x10 高度组 3 权重 heightGroup3Weight 1 同上
0x0C 高度组 4 权重 heightGroup4Weight 0 同上;本值不增加正权重
0x1C 基础噪声频率输入 baseNoiseFrequencyInput 5 初始分组、类型选择、细分主噪声
0x20 改用随机值的阈值 randomInsteadOfNoiseThreshold 0.1 初始分组、类型选择的取样分支
0x24 高度幅度输入 heightAmplitudeInput 75 地点来源、基础角点来源
0x28 另一层噪声频率输入 alternateNoiseFrequencyInput 0.5 细分第二层噪声
0x2C 混合噪声幅度 subdivisionMixedNoiseAmplitude 7.5 细分混合项
0x30 基础噪声幅度 subdivisionBaseNoiseAmplitude 0 细分主噪声独立项
0x34 径向高度项幅度 radialHeightAmplitude −500 径向候选及最大值比较
0x38 高度过渡宽度 heightWidth 550 来源距离归一化
0x3C 低组距离上限 lowGroupDistanceCap 425 低组距离调整
0x40 高度边缘参数 heightMargin 4 来源距离扣减
配方 36 的来源细分噪声与径向高度参数

图 17|配置与教学示意:另外七项高度参数:来源贡献、细分噪声和径向候选。N、v、alt、mask、h 与 w 为计算变量,示例贡献尚未完成后续来源混合。

来源与角点计算先从边界距离中扣除 margin=4,再由 width=550 控制归一化距离,并乘以幅度输入 75 及调用方归一化因子。单个来源贡献可写成:

d = max(rawBoundaryDistance − 4, 0)
sourceContribution = min(d / 550, 1) × (75 / N)

其中 N 是调用方传入的归一化因子。教学条件 d=275 时,贡献是 37.5/N;后续还会与地点来源按权重混合,不能直接把它写成最终地面高度 37.5 米。37.5/N 只完成了距离到来源贡献这一步,后面还要计算混合权重。

细分高度中的噪声也不只有一个幅度。主频率输入为 5,另一频率输入为 0.5;主噪声映射到 [0,1] 后,先以高度组权重构造阈值,再产生 v:

k = (0.4 / 2.65) × 1.15
denominator = clamp(1 − k, 0.001, 1)
v = clamp((n − k) / denominator, 0, 1)
noiseContribution = (v × 0 + v × alt × 7.5) × (1 − mask)

本例基础噪声幅度为 0,但混合幅度为 7.5,仍可产生局部细节。mask 来自底图采样,控制这部分贡献,再与曲线结果合成。将“基础噪声幅度为零”解释为“整个阶段没有噪声”,会遗漏第二项。

这份底图也有明确来源:当前入口先将自产底图三角形栅格化,保留 512² 掩码、256²/128² 的最大值阶段和最终 128² R8 场,再将 R8 样本除以 255 后交给细分高度。它是本次布局的中间产物,不是参考目标高度,也不是后面最终 4096² 的平滑高度。旧诊断接口可以显式替换这个输入,但本文四请求端到端验证没有使用该外部替换参数。

径向幅度 −500 也不是从全图统一减去 500。代码先生成 candidate = w×(−500)+(1−w)×h,再返回 max(h,candidate)。它能否改变一个点,取决于该点原高度和径向权重;不能只根据参数的正负判断输出一定抬高或降低。

地点来源怎样参与角点高度

前面的 sourceContribution 只解释单项距离如何映射为高度。实际地点来源还先做空间查询:使用地点 XY 查到所属区域;被排除的资源选择,或者归属高度组 1 的地点,来源高度为零。其他地点查找最近的低组中心,再计算到该低组多边形边界的最近距离,扣除 margin 后进入幅度计算。这不是简单地用“地点到最近低组中心的距离”直接赋高。

地点内部的范围也参与后续权重。对于角点 P,计算 P 到地点中心的距离,再减去地点范围 extent,得到 t。当前入口传入 radius=512、gain=10、nearDistance=96;这些是这条已检查调用的参数,不是所有生态的通用常量。

t = distance(P, locationCenter) − extent

baseWeight = 1000                         当 t < 0
baseWeight = (1 − t / radius)^6           当 0 ≤ t < radius
baseWeight = 0                            当 t ≥ radius

weight = baseWeight
当 t < nearDistance:
    weight *= (1 − t / nearDistance) × gain

六次幂让地点外部的影响迅速衰减。教学条件 t=256、radius=512 时,基础权重是 0.5^6=1/64;t=384 时只有 0.25^6=1/4096。处于地点范围内则使用另一条较强权重分支,不能把六次幂公式向内无限延长。

角点还保留自己的来源贡献和权重。若角点归属低组,直接写零;否则将所有有效地点来源与自身来源共同归一:

cornerHeight = (sum(locationWeight × locationSourceHeight)
                + ownWeight × ownSourceHeight)
               / (sum(locationWeight) + ownWeight)

例如,教学条件下两个地点的来源高度为 12、20,权重为 2、1;角点自身来源为 8、权重为 1,混合结果是 (2×12+1×20+1×8)/4=13。这些来源和权重是为解释公式选取的教学值,不是本局某个角点的运行记录。

零来源也可能参与分母。当前实现对部分无效选择写入零来源高度,但保留后续距离权重处理;如果迁移时将这些项整条删除,分母变小,其余来源的影响就会被放大。仅核对非零来源的坐标和高度,仍可能遗漏这种差异。

这个阶段输出的是角点高度数组、地点来源高度和自有距离等计算产物。它不决定建筑最终 Z,也不等于后面的 Location 支撑目标。地点先以 XY 和资源关系参与基础地形,再查询成形后的地面计算支撑,最后写入局部高度资产;同一个实例在三个阶段承担不同职责。

类型表把区域分类连接到后续曲线

类型表与 Zone、曲线和规则消费者

图 18|配置与教学示意:用“普通区域已属于高度组 2”的教学条件演示筛选与选择。候选值来自配方 36,样本 0.5 为教学取值;后续 Zone 和合并分别使用不同数据。

配方类型表有 16 槽,每槽 68 字节,目前解释了其中六个字段,其余 44 字节继续保留。一个槽不是一块实际区域;它描述类型候选及后续使用的 Zone、曲线倍率、权重和高度组约束。

普通抽选只使用满足高度组条件且权重大于零的记录。下表仍保留完整 16 槽,供实现时核对;零权重项和终止后的槽位不会因此进入本局普通抽选。

槽 偏移 类型键 Zone 曲线倍率 类型权重 高度组条件 合并概率
0 0x238 18 1 1 0 1 1
1 0x27C 19 2 1 0 0 1
2 0x2C0 20 3 1 0 1 1
3 0x304 24 29 1 0 1 1
4 0x348 25 27 1 0 1 1
5 0x38C 26 28 1 0 1 1
6 0x3D0 8 66 1 0.5 1 1
7 0x414 7 65 1 0.2 2 1
8 0x458 11 63 1 0.15 2 1
9 0x49C 9 66 1 1 2 1
10 0x4E0 12 65 1 0.5 3 1
11 0x524 10 66 1 1 3 1
12 0x568 4 67 1 1 4 1
13 0x5AC 0 0 1 1 0 1
14 0x5F0 0 0 1 1 0 1
15 0x634 0 0 1 1 0 1

例如槽 6 保存类型 8、Zone 66、曲线倍率 1、类型权重 0.5、要求高度组 1、合并概率 1。只有先满足条件再被选中,这条记录才影响实际区域。合并概率 1 取消的是概率拒绝,类型相同与边关系等条件仍然存在,不能说所有符合高度组的区域都会合并。

普通候选解析遇到类型键 0 时结束,本例在槽 13 停止;后面仍保存的槽 14、15 不因此成为两个实际区域。零类型权重项不参与普通加权抽选。特殊规则和覆盖表又有自己的条件与优先级,不能因为记录存在,就断言这次地图已执行了它们。

用配方 36 演算类型筛选

对普通区域,类型选择先读取已经计算的高度组,只保留要求该高度组或使用通配条件的正权重类型。配方 36 的普通正权重候选可整理如下,数值 0.2、0.15 为便于阅读的十进制显示,运行仍使用原始 float32:

要求高度组 类型 Zone 类型权重 曲线倍率 合并概率
1 8 66 0.5 1 1
2 7 65 0.2 1 1
2 11 63 0.15 1 1
2 9 66 1 1 1
3 12 65 0.5 1 1
3 10 66 1 1 1
4 4 67 1 1 1

以高度组 2 的普通区域为例,筛选后只留下类型 7、11、9,总权重约 1.35;高度组 1 的类型 8 此时不能加入分母。选一个归一化教学样本 value=0.5,缩放为约 0.675,依次减去 0.2 和 0.15 后仍未命中,最终落在类型 9,取得 Zone 66 及对应参数。

这个例子解释了“分类先限制候选,再使用权重”的顺序。若先从所有类型里抽一次,再发现高度组不符而改成默认类型,抽选分布和随机序列都可能变化。特殊区域则可以通过特殊表直接取得类型;已有类型的区域也不会按普通分支重新抽一次。

普通分支的样本来源沿用噪声或随机取样的条件结构,但有自己的阶段状态。相同坐标和相同主频率让空间噪声具有相关性,并不意味着两个阶段实际使用了同一个随机样本。

类型选定后,同类型的相邻区域才能进入合并检查。实现读取边标志,并在相应检查位置推进随机状态,即使合并概率为 1,也不能直接删掉随机调用。概率条件、阻隔标志和已经形成的分组范围共同影响结果。合并产物保存每个区域对应的成员范围,后续边界处理和自然 Stamp 遍历都要读取这个分组结构。

当前类型选择器对尚未集成的邻接排除条件会拒绝输入,不能因为普通表已经能算出类型,就宣称每种配置的邻接规则均已实现。

Zone 曲线怎样变成细分点高度

类型表中的 Zone 是后续曲线查找所用的引用,不是高度值。例如 Zone 66 不表示 66 米,它需要查询相应的静态曲线,再以当前细分点到关联边界采样的距离确定曲线位置。

距离计算先对关联采样点求平方距离,减去边宽平方,截为非负数,再取最小值和平方根。当前内核将距离乘以 1/64 并钳位到 [0,1],据此定位曲线段及段内比例。它并不是直接把“最近区域中心距”传进曲线。

curveHeight = curve[i]
              + (curve[i+1] − curve[i]) × fraction × multiplier

倍率只乘插值增量,不整体缩放曲线基值。教学条件下曲线端点为 10、18,fraction=0.25,倍率为 0.5,结果为 10+(18−10)×0.25×0.5=11;若将整段插值结果乘以 0.5,会得到 6,改变的已不只是增量。配方 36 表中倍率为 1,无法暴露这个错误,因此教学例特意采用 0.5,不能把它记成配方真实值。

细分高度再叠加前文的受 mask 控制噪声,执行径向候选比较,输出内部点和角点高度。随后三角网读取这些结果进行插值。Region 参数的作用在这里才逐步转换为可绘制高度;类型号、Zone 号和高度组号不会直接写成像素值。

自然资源权重还会被当前位置重新计算

区域类型提供 Zone 引用,Zone 再提供可遍历的 StampGroup;每个组保存自己的摆放参数、候选项和重叠策略。当前独立输入保留 96 字节组记录与 104 字节候选记录。记录长度已经确定,但部分字段的用途仍未解释。遍历还结合合并后的区域成员及调度配额,因此“Zone 内有三个组”不等于只生成三个实例。

组负责组织摆放过程,候选项负责描述可选资源及其资格,接受的父实例才保存本局的资源引用、位置和朝向。父实例随后经静态 Level/Unit 数据展开为高度绘制参数。同一个候选可以被接受多次,同一个父资源也可能展开多个局部单元;组数、候选数、父实例数和绘制项数需要分别统计。

配方与区域决定使用哪些资源集合,但集合内一项资源的静态权重不一定就是本次抽选权重。自然 Stamp 选择器还读取当前候选位置的坡度、高度、外侧距离及分类,通过启用的条件逐项调整权重。

每项范围使用四个值 [a,b,c,d]。低于 a 或高于 d 时权重为零;a 到 b 之间线性增加;b 到 c 保持;c 到 d 之间线性衰减。只有对应 gate 启用时,才读取这项范围参与计算,不能因为记录中保存了范围,就默认全部生效。

教学条件下静态权重为 2,某项启用范围为 [0,10,20,30]。输入为 5 时权重变为 1;输入为 15 时保留 2;输入为 25 时又变为 1。若另一项启用条件再使权重减半,最终权重继续乘以 0.5,而不是重新恢复静态值。

分类上下界还可先将候选直接排除。调整完成后,程序才累加有效权重并抽选;总权重还需超过“候选数乘以 2^-23”的阈值,不能简单改成大于零。这里的权重计算决定选哪份资源,后面的区域、碰撞与任务边界检查决定该实例能否接受,两者分属不同步骤。

这也解释了同一资源集合为什么在两个位置上可能选出不同内容:当前位置改变参与抽选的有效权重,实例检查又可能触发重试。因此,估算实例数量还需要追踪候选生成、权重调整和重试过程,仅看资源表中的原始权重不够。

上面说明了配方中的权重、距离、类型和 Zone 如何参与选择与高度计算。具体区域选中了哪项,仍以运行记录为准;特殊覆盖在完整调用上下文中的应用也还没有全部验收。下一节从布局和三角网开始,看这些计算在高度场中的结果。

从实例布局到连续像素高度

先用平原汇报页观察几何和高度数组的交接。这组请求的 Seed 为 2763691705,包含 39 个 Location、1174 个自然 Stamp 父实例,与前面的焦灼荒原案例分开。图 19 至图 22 采用已归档的自产 C++/HLSL 阶段数据;随后图 23、图 24 回到配方 36 的同一次 demo 作业。本文整理期间没有重新执行生成器。

平原地点布局与自然 Stamp 父实例

图 19|布局数据图:平原实际父级布局,与前面的荒原配置案例分开。

地点与自然 Stamp 先形成父级位置和朝向。父实例再经静态资源中的 Level、Unit 和局部变换展开成绘制输入。图中的父级布局标出了资源的位置和朝向。父资源内部的纹理、网格和植被还要继续展开,各自的世界变换需要另行核对。

平原三角网、栅格化与平滑

图 20|阶段数据绘图:实际自产阶段数据,三角网数量对应显示裁片。

这页从带高度的三角网继续展示栅格化和平滑。显示裁片包含 39,837 个三角形,但这个数不是完整地图的总三角形数。逐像素插值将几何变为高度数组,平滑改变连续地面的起伏。三角网、栅格场和平滑场各自保留,才能确认差异首先发生在赋高、覆盖还是滤波。

阶段高度图取相同的 900 米方形范围,使用共同的高度色带、方向和光照,色带范围约为 −42.018 至 68.225 米。这些顶视 relief 图由高度数据生成,不是 Web 三维截图。它们适合观察阶段形状,不能以画面中的颜色块数量代替原始 float32 像素统计。

平原基础、平整与 Location 支撑

图 21|阶段数据绘图:阶段输出与查询所读取的高度版本分别表达。

强制平整与 Location 支撑继续读取前面的高度,但“用于查询的基础高度”和“当前正在改写的 GPU 高度”需要分别保存。平台写入已经发生,不代表后续所有查询都应该改读平台结果。后文的离子赤原案例会具体说明这项阶段依赖如何产生误差。

平原自然 Stamp 与 Location 高度写入

图 22|阶段数据绘图:三幅真实阶段场使用共同裁切、色标与光照。

最后两次局部写入分别来自自然 Stamp 和 Location 静态高度资产。图中依次保留支撑后、自然 Stamp 后和 Location 高度纹理后的真实地面;统一范围便于对照局部形状的变化。两者使用不同实例来源和资源关系,也不能以最终高度近似代替实例接受顺序与绘制参数的核对。

平原图展示了从布局到局部资产写入的过程。下面回到配方 36,查看同一次生成的六阶段截图,再逐项分析细分、随机状态和数据绑定中的错误。

配方 36 在 demo 中的六阶段结果

回到焦灼荒原请求:Seed 为 2431905037,选中 Region 36、profile 0,生成 66 条 Location 记录和 1501 个自然 Stamp 父实例。Location 记录包含占位记录,因此 66 不能直接当作建筑数量;自然 Stamp 父实例数也不能当作山丘、树木或岩石数量。下列两页来自这一次作业,镜头、显示范围、色标、光照和高度倍率保持一致。

配方 36 的栅格化、平滑与平台阶段截图

图 23|运行截图:从左向右为栅格化、平滑和平台阶段。本请求没有平台写入,第二、第三阶段的完整高度数组相同。

左图以带高度的三角网为输入,通过像素插值生成第一份高度数组,仍能看到局部三角形轮廓。中图对这份数组执行平滑,得到供后续查询使用的 Hbase;大尺度起伏保留,局部过渡更连续。右图是平台阶段的输出 Hplatform。本请求没有平台写入,因此它与 Hbase 完全相同;阶段执行到这里,并不要求地面一定发生变化。

同一请求的 Location 支撑、自然 Stamp 和 Location 高度纹理阶段

图 24|运行截图:从左向右为 Location 支撑、自然 Stamp 和 Location 资源高度绘制,沿用图 23 的请求与显示条件。

图 24 的左图以 Hplatform 为底图,按 Location 支撑目标与过渡范围调整地面。目标计算仍查询图 23 的 Hbase,两份高度的职责没有因为本请求数值相同而合并。中图再将已接受的自然 Stamp 父实例展开成静态高度绘制,加入较密集的局部起伏;右图在此基础上写入 Location 资源的具体轮廓,形成这次内圈生成的最后一份阶段高度。

这两页展示了三类产物:配方与布局决定带高度的几何,栅格化和平滑形成连续底图,支撑及静态资产绘制继续调整局部地面。若最终结果出现差异,应先比较相邻阶段数组,找到差异首次出现的位置,再检查该阶段读取的参数、资源和执行顺序。后面的实例局部图沿用这六份数组,用更小的范围观察支撑与自然绘制的净变化。

这些截图展示内圈高度,不包含外围 Vista,也没有用地表材质、植被或岩石遮盖高度变化。Web 预览将 4096² 数组按 8×8 平均缩为 512²,再生成显示网格。预览清晰度与原始高度精度是两回事;后面的局部图直接读取完整阶段数组。

自然 Stamp 父实例与阶段高度的局部对照

沿用配方 36 的焦灼荒原作业,父实例 #496 使用资源 17720cadbae59a9e,位置为 (1257.093, 1109.809) 米,朝向约为 5.35195 rad。接受记录还保留 root 49、group 0、member 254 和 grid 10;父记录的区域类别字段为 66。这些字段用于追溯候选来自哪一轮区域遍历,不代表该资源只有一个绘制形状。图中箭头沿父矩阵的局部 +X 方向,按该矩阵的角度约定转换。

自然 Stamp 496 的位置朝向与真实阶段高度局部对照

图 25|局部数据绘图:同作业的 207×102 米局部范围,直接绘自 4096² 阶段高度。橙框是父实例的支撑包围框,白箭头是局部 +X 方向。差值图比较整个自然绘制阶段,不是单独绘制 #496 的结果。

图 25 放大图 24 中自然绘制前后的同一处位置:左上是支撑地面,右上是全部自然实例写入后的地面,左下保留后续 Location 高度纹理结果。右下按“自然绘制后减自然绘制前”计算净变化,橙色表示抬高,蓝色表示降低。父记录提供资源、位置和朝向,静态资产展开提供具体局部形状;差值图则说明这一阶段实际改动了哪些地面。

这里没有根据最终山丘的外观反推父实例。橙框直接来自同一条 304 字节父记录中的支撑 AABB。它用于摆放检查,不能当作高度纹理的精确外边缘;图中也包含其他父实例,因此不能把框内全部起伏或差值峰值都归因于 #496。若要核对单个实例的独立贡献,还需要另行保留逐绘制输出。

Location 的辅助查询、支撑目标与局部结果

再看同一作业的 Location #1,它位于 (901.870, 1070.721) 米,资源键为 384cd0efc44cee6b。静态资源提供三个支撑圆,同作业的支撑输出记录提供它们变换后的世界位置与半径。下面按这些记录标出实际范围,四幅图使用同一裁片与 XY 比例。

Location 1 的实际支撑圆辅助高度与阶段结果

图 26|局部数据绘图:同作业的 276×136 米局部范围。橙线为 Location #1 的三个支撑圆,白十字为父实例位置。支撑阶段净变化包含裁片内所有支撑的共同作用。

这次圆周查询保留 196 个采样点、剔除 116 个采样点,报告的高度包络为 24.8096~32.5587 米。计算得到的辅助高度约为 29.1874 米,经过家族协调与目标处理后,支撑目标约为 27.2404 米。两个值属于不同阶段,不能用同一个“地点高度”代称,也不能当作建筑最终 Z。

图 26 的左上与右上对应平台后、支撑后两份数组,显示三个支撑圆附近及过渡范围的地面调整。右下按同一位置相减,标出这次支撑阶段的抬高与降低。左下是后续自然 Stamp 和 Location 资源绘制完成的地面。辅助高度和支撑目标用于计算调整参数;前后差值显示地面实际改动的位置;最终阶段还包含后续写入的静态轮廓。

图 25、图 26 都直接读取本次作业的编码前浮点数组,使用统一的 0~83 米高度色标及相同方向的显示光照,差值图另用各自注明的对称量程。坐标以 2048 米计算域左下角为原点;读取原生纹理行序时只转换一次 Y 方向。它们是数据绘图,六阶段总览才是浏览器截图;两者都没有修改源高度。这些图沿用已经完成的作业,不代表重新执行了参考对照。

多地貌验证暴露出的阶段一致性问题

四套请求没有启用完全相同的分支。离子赤原的平台写入让查询版本差异产生实际高度变化;沙丘暴露行序和驻留绑定问题;焦灼荒原暴露支撑执行层序问题。下面按问题所在的接口组织案例,分别列出修复前后的数值、复现条件,以及实际完成的对照。

案例 地貌或验证对象 检查的阶段关系
细分间距 离子赤原(Ionic Crimson) 配置间距进入不同采样分支时的缩放
任务范围版本 离子赤原(Ionic Crimson) 初始化范围与地点布局后范围
查询高度版本 离子赤原(Ionic Crimson) Hbase 查询与 Hplatform 绘制底图
二维行序 沙丘(Desert Dune) CPU 数组发布与纹理查询约定
记录与执行顺序 焦灼荒原(Scorched Moor) CPU self 编号与 GPU 层排序
驻留绑定 沙丘(Desert Dune) mip 链、尺寸常量与叶片几何
异步查询发布 四地貌离线设备与状态机测试 GPU 完成、行复制、描述符发布和所有权

下面各节按异常、原因和修复结果展开。最后一节检查的是离线 GPU 完成状态和查询发布状态机,游戏现场的并发行为仍未全部验证。

细分间距必须保留分支含义

基础高度的计算需要区域内部点、角点和连接关系。若细分间距错误,变化会沿着点数、拓扑、区域归属一路传到高度与实例摆放。此时调整最终高度纹理无法恢复上游已经改变的空间结构。

离子赤原首先暴露了规则网格分支的问题。配置中的间距进入细分阶段时,规则网格分支除以 2,另一分支除以 4。独立入口此前统一使用除以 4,导致该案例的采样过密,随后点编号与拓扑也发生变化。

用描述性变量表示,这段配置处理为:

effectiveSpacing = configuredSpacing / (regularGrid ? 2 : 4)

这个规则适用于本次恢复的细分阶段,不能推广成所有地图采样的通用缩放。间距进入不同阶段时,应分别核对调用方是否已做转换,避免再次相除。

规则网格的数量由 float32 尺寸与间距决定:

nx = ceil(float32(width  / spacing))
ny = ceil(float32(height / spacing))

x = (i − floor(nx / 2)) × spacing + spacing / 2 + centerX
y = (j − floor(ny / 2)) × spacing + spacing / 2 + centerY

这组公式对应当前规则网格内核。实现按行输出,内层递增 X;半计数采用整数除法。奇数与偶数数量下的点列位置因此需要分别检查。规则网格分支不消耗随机数,随机采样分支则有独立的随机推进过程。

例如,一个以零为中心、宽度 80 米、有效间距 20 米的教学域得到 nx=4。四列 X 坐标依次为 −30、−10、10、30 米,首个点相对边界向内半个间距。若配置进入细分前少做了一次间距转换,列数和位置都会改变;后面再把坐标整体乘一个比例,也无法恢复原来的候选数量和编号。

修复间距后,离子赤原得到 11,413 个内部点和 22,541 个角点,共 33,954 个高度值。随后完整细分对照还检查了区域、边、候选、归属和空间桶:395 个区域、35,133 条边及相关连接信息通过对照。单独核对高度值还不够,因为错误的连接关系有时能在局部得到相近高度,却会改变后续搜索。

规则网格内核另外使用了 108 组合成输入、25,251 个点检查边界情况。这项测试说明内核在这些输入上符合参考规则,与离子赤原整套细分验收属于不同层级,数量不应合并成“验证了多少张地图”。

细分修复后,Stamp 数量仍未一致,说明剩余差异还出在其他环节。细分对照通过后,仍需继续核对 Stamp 循环,不能把两项验收合在一起。

任务范围应读取地点布局后的版本

自然 Stamp 的完整循环包含候选生成、资源选择、位置和朝向计算、碰撞检查、任务范围判断、重试与接受。某个候选失败后,程序怎样继续推进,也会影响后面的随机状态。因此,“实例总数接近”不足以判断循环是否还原。

一个候选即使通过碰撞检查,也可能在任务范围检查被拒绝。反过来,任务范围允许的位置,还可能因已有实例和边界约束失败。两种拒绝来自不同数据,不能在诊断中合并为一个“摆放失败”。

离子赤原曾生成 1,006 条父记录,参考为 1,012 条。首个遗漏候选的位置与参考相同,说明最初的坐标生成已经对上;进一步检查发现,该候选四次通过碰撞检查,却均被任务范围判断拒绝。问题转向了范围参数的来源。

独立入口使用了初始化阶段的任务范围比例,约为 0.38。地点布局完成后,上游会根据已布置的 Location 扩展这一范围;本次自产后继值约为 0.3930511。自然循环应读取后者,入口却仍传入初始化值。修复参数交接后,缺少的六条记录及随后发生变化的随机序列一起恢复。

这里没有按参考数量补六个实例。修复的是循环所读的阶段参数,之后从初始请求重新执行。位置、资源、接受顺序与随机状态随控制流重新生成。若只在末尾补足数量,后续记录仍会保留错误序列。

四案例最终对照使用完整的 304 字节自然 Stamp 父记录。为排除不同进程地址的影响,仅将两个地址指针槽规范化为零;坐标、角度、资源键、区域归属及其他字段保持原值。每套请求还独立生成两次,分别检查重复结果和参考结果。

案例 自然 Stamp 父记录 区域内部点 角点 本轮对照
平原(Plain) 1,174 11,928 23,606 规范化完整记录一致,细分高度位模式一致
沙丘(Desert Dune) 1,184 11,854 23,459 同上
离子赤原(Ionic Crimson) 1,012 11,413 22,541 同上
焦灼荒原(Scorched Moor) 1,501 11,798 23,391 同上

这些数量统计父记录,不是可见山丘数量,也不是树木或岩石数量。一份父资源可以包含多个局部高度形状和物件;多个父实例还可能在最终地形上重叠。

Location 支撑查询与绘制使用不同高度版本

Location 支撑先查询地面,再计算辅助高度、父子协调和外扩。如果查询读错高度版本,即使其他参数相同,目标也会变化。离子赤原的平台处理暴露了这个问题。

该案例有 23 处强制平整区域。平台目标按连通组计算:同一配置的相邻区域形成组,符合类别条件的外侧边界参与查询。对边界中点向外延伸 32 米,再以 32 米半径查询高度包络。组目标采用以下规则:

target = min(0.5 × low + 0.5 × high, low + limit)

low 和 high 来自有效查询包络,limit 来自区域配置。若没有有效包络,当前已恢复分支将两者置零。这个回退条件需要和目标公式一同实现;只复制公式会漏掉无有效查询的情况。

举一个教学数值:包络最低 6 米、最高 10 米、配置限幅 1 米时,中点高度为 8 米,但 low + limit 只有 7 米,最终目标取 7 米。这说明配置限幅会限制相对低侧的抬升;它不是对最终高度统一乘一个强度。这里的数字仅解释公式,与下文实测目标分开。

本案例 23 个区域形成一个连通组,13 次边界查询的位置及 23 个目标值通过对照,目标约为 7.0749998 米。随后平台写入在 GPU 高度图上改变了 802,760 个像素。这些结果分别证明目标计算与平台绘制,不代表后续 Location 支撑已经读取正确输入。

错误发生在平台之后:独立入口从平台输出重新构造 CPU 查询金字塔,再把它交给 Location 支撑。但当前恢复的参考路径保留了平台写入前的基础查询描述符。地点辅助高度继续从该描述符查询;GPU 上的当前地面则已经包含平台。

这里的错误来自高度版本绑定:Hbase 和 Hplatform 都是合法产物,却被当成同一个可覆盖的 height。查询公式本身即使正确,读取平台后的场也会改变辅助目标。用不同阶段变量保留这两份输入,接口关系就能直接检查:

baseHeight    = Smooth(Rasterize(baseTriangles))
queryLevels   = BuildQueryPyramid(baseHeight)
platformHeight = DrawPlatforms(baseHeight, platformTargets)
supportTarget = QuerySupport(queryLevels, location)
supportHeight = DrawSupport(platformHeight, supportTarget)

伪代码省略资源创建与异步等待,重点是两处输入没有被同名变量覆盖。queryLevels 属于保留的基础查询版本,platformHeight 属于当前绘制版本。若每一步都覆写一个 height 变量,就很难从接口上发现错误的阶段替换。

当前排序回归中,离子赤原中央 450 米内的最大误差约为 1.5011 毫米。此前修复查询高度版本的那一轮,最大误差从约 0.4278 米降至约 1.5030 毫米,超过 1 厘米的像素由 310,128 个降为零。两组毫米值属于不同后续版本;修前的 42.8 厘米峰值有明确的阶段来源,不能归因于一般浮点误差。

其余三个案例没有平台写入,平整前后的基础数组相同。这解释了同一处接线错误为何没有同时造成四套明显异常。增加地貌案例后,平台写入等不同分支才实际参与计算,原先两份数组恰好相同时隐藏的输入错误也随之暴露。

Location 支撑目标仍是高度处理的辅助量。它参与地面调整和家族影响,不能直接当作建筑最终 Z;建筑装配、地表采样和碰撞还需要自己的运行时步骤。

CPU 高度只在纹理发布时转换行序

沙丘曾出现内圈平均约 26.534 米的大差异。排查发现两处交接问题:CPU 高度发布为纹理时遗漏了行序转换,之后纹理行序进入查询金字塔时又被额外翻转。两处都处理二维数组,却不应各自凭经验添加一次翻转。

高度数组有二维含义,也有一维存储顺序。CPU 计算按照世界 +Y 方向枚举行,GPU 纹理按本管线约定的纹理 -Y 方向存行。两个数组都可能尺寸正确、数值范围合理,直接交换却会让查询位置与地形对不上。

这一约定属于当前生成链,不能解释为所有图形 API 天生要求同一种 Y 翻转。接口需要明确三件事:第零行对应地面的哪一侧,在哪一步转换,以及查询索引按哪种行序计算。

发布转换为:

texture[row, column] = cpu[N − 1 − row, column]

每个 float32 样本只移动位置,不做高度计算,不改变位模式。C++ 接口先拒绝零尺寸、样本数量不符及非有限数,再逐行复制。

CPU 到纹理的行序转换

图 27|教学示意:四行教学数组的发布转换。列内顺序和样本位模式保持不变;后续查询若再次翻转,就会恢复错误方向。

修复方式是把发布转换集中为一个 C++ 函数,输出明确标为纹理行序。后续步骤直接读取该定义,不再根据显示效果决定是否翻转。测试使用非对称数组,因为上下对称的地形可能掩盖整行反转;同时检查截断输入和非有限高度的拒绝行为。

坐标错误常常影响大面积,但不一定让画面完全失真。若地貌总体呈圆形,翻转后仍有山坡和低地,肉眼可能认为只是随机结果不同。数值诊断需要追踪一个世界位置在每个数组中的行列,而不能只比较两张缩略图。

记录编号与 GPU 执行顺序分别保存

焦灼荒原此前按 CPU 插入顺序直接绘制,遗漏了层排序。其支撑目标、邻接索引与许多缓冲数据已经正确,最终高度仍留下约 3.42 厘米的最大残差。仅检查参数表,可能将绘制顺序造成的差异误判为数值精度问题。

支撑数据包含圆、矩形、邻接范围和顶点中的自身编号。CPU 需要按 Location 组织记录,才能稳定引用这些表;GPU 还需要按高度绘制层执行。两种顺序服务不同用途。

当前 CPU 记录按 Location 编号升序排列,同一个 Location 内圆在矩形前,同类保持输入顺序。圆、矩形参数表仍分别保存。每条绘制记录的 self 编号对应 CPU 行,用于查找邻接范围及相关参数。

GPU 执行则按本次支撑 pass 的层号排序:矩形为 900,圆为 901,同层保持原顺序。绘制序列保存的是 CPU 行编号,不是圆参数表或矩形参数表的编号。调整执行顺序时,各参数表仍保持原来的索引对应关系。

记录编号与执行排列

图 28|教学示意:混合圆与矩形的教学记录。CPU 行编号稳定保留,GPU 只读取另一份执行排列。层号 900/901 属于当前支撑路径,不是通用材质优先级。

以两个 Location 为例,CPU 可以保存为“地点 A 的圆、地点 A 的矩形、地点 B 的圆、地点 B 的矩形”。GPU 则先执行两个矩形,再执行两个圆,但每次仍使用原来的 CPU 行编号读取参数。这样能同时保持记录组织和绘制层规则。

在当前接口中,CPU 行编号用来查记录,GPU 执行排列决定绘制次序。只改次序时,原编号和参数引用保持不变;如果连 CPU 行也重新排列,就要同步更新 self 与邻接跨度。这个编号只在当前缓冲及接口内有效,不能当成跨作业的永久标识。实例批处理和材质排序也要分别处理记录编号与执行次序。

恢复独立 GPU 执行排列后,重新执行支撑、自然 Stamp 和 Location 三个后续阶段,中央范围最大误差由 34.1530 毫米降为 1.3332 毫米;超过 1 厘米的像素由 4,826 个降为零,超过 1 毫米的像素由 14,493 个降为 37 个。参数缓冲保持不变,改变的是执行排列。

叠加操作依赖顺序,因此这里必须重跑完整后续链。只看支撑后某个点减少了多少,不能直接替代最终误差;后面的高度纹理可能覆盖或再次改变该点。

C++ 迁移进一步将两份排列同时输出:一份描述原始形状输入如何映射到 CPU 行,一份描述 GPU 应依次执行哪一行。迁移时还重建邻接跨度,并更新顶点里的 self 编号。缺少任意一项,都可能让排序正确而引用错误。

四案例共有 77、88、74、87 次支撑绘制。新 C++ 输出与已归档输入的缓冲和执行排列逐字节一致,重新绘制的支撑像素与已归档支撑输出逐位一致。这项回归证明排序迁移保留了已有行为,不应单独称为一次新的原版整图验收。

驻留 mip 必须与尺寸常量和几何同步

本轮沙丘中央共同范围的最大误差约为 0.5913 毫米。下面回溯这个结果对应的驻留修复,同时保留早先含 Vista 版本的整图记录;两者的比较范围仍分别标明。

沙丘修复行序后,仍有一个约 11.543 米的异常峰。逐阶段检查发现,基础高度、平台和支撑后该点都是约 49.189884 米;自然 Stamp 的第 3 次绘制将它改成约 60.733341 米,后面的 Location 和 Vista 没有改写它。由此可以把问题限定到一次确定的绘制,而非整张地图的高度基准。

高度纹理的 mip 通常被理解为精度选择。在这条链里,驻留级别还与资源尺寸、shader 常量和局部叶片几何相关。若只切换纹理字节起点,却保留旧尺寸和旧几何,生成的高度覆盖就会发生变化。

同一个静态资源的完整尺寸为 608×608,第一级 mip 为 304×304。受控测试分别选择 mip0、mip1、mip2,并同步更新纹理链、尺寸常量及叶片 root/center。mip1 下,该点保留底图高度,周围 3×3 邻域也重现了参考中的中心未覆盖和周边贡献;mip2 又产生不同结果。

驻留级别与绑定数据同步

图 29|机制示意:驻留策略同时决定像素链、尺寸常量和叶片几何。608²/304²来自本次沙丘诊断资源;图中分支解释绑定关系,不展示源纹理像素。

这项测试没有把目标像素手工改回去。它改变的是资源绑定策略,之后从请求重新生成整条链。在当时包含 Vista 的沙丘版本中,4096² 最大误差降至约 0.5913 毫米,原峰值点误差降至约 0.003815 毫米。该整图记录属于当时版本,最新排序版本的外围仍需重新合成核对。

本轮采用显式 max_mip=1 启动配置。它不是把全部资源统一裁为 mip1:固定加载类和流送类按各自描述符处理,生成器据此建立有效驻留链。不同资源的初始级别和目录状态不能用一个全局裁剪操作替代。

后期资源状态与该策略相符,是支持绑定诊断的一项证据;它没有证明历史启动配置原值,也没有证明每次绘制之前的异步升级已经完成。因此当前配置应作为可复现实验输入保留,而不能写成所有原版场景天然使用同一值。

静态纹理目录、升级请求、CPU 状态更新与 GPU 上传完成还属于不同阶段。某个 CPU 字段已经写为目标 mip,不足以说明设备上的新纹理可供绘制。资源升级中,新 mip 前缀需要上传,旧链还要复制到新的级别位置;测试这些操作参数,也不自动证明真实并发时序已经验收。

GPU 回读完成后才能发布查询数据

支撑查询读取多级高度,而 GPU 计算与 CPU 查询之间还有一次异步回读。提交计算后,即使纹理对象和请求记录已经存在,CPU 也不能立即读取像素。图 30 展示了后续步骤:等待 GPU 完成,Map 暂存纹理,逐行复制,再发布查询描述符。CPU 查询从发布完成后才可使用这份数据。

10 月 7 日的工作沿着请求创建、排队、GPU 计算、回读和描述符发布继续核对。在独立 D3D11 设备上,四地貌各八个查询层的实际 GPU 输出通过对照;CPU 行复制、完成轮询和发布路径也分别检查。

异步查询回读与发布

图 30|机制示意:查询层的生命周期。未就绪时保留待处理句柄;完成查询成功后才 Map、按 RowPitch 复制并发布稳定 CPU 数据。设备测试与游戏现场并发验收分开。

GPU 回读纹理具有自己的行距。每行实际占用的 RowPitch 可能大于有效像素字节数,复制时需要逐行读取,而不能把整片映射内存当作紧密数组直接搬运。本例 float32 单通道的有效行字节数为 width × 4。

完成查询成功后才映射暂存纹理,按行复制到 CPU 缓冲,随后解除映射并释放相应设备对象。发布步骤把有效描述符写入查询表,清除该层待处理句柄。当前八层状态机中,处理掉最后一批句柄的那次轮询仍返回“本轮遇到待处理项”;下一次空轮询才进入结束通知。

这个返回规则影响调用方怎样驱动状态机。如果把“本次处理后没有句柄”直接当成“本次应结束”,就会改变通知时机。用阻塞等待强行保证数据已经存在,可以帮助离线数值测试,却不能代替对原发布过程的核对。

描述符还涉及两种所有权:请求记录可以在发布后回收,像素存储仍需供 CPU 查询使用。若把两者作为同一个对象释放,数据可能在简单测试中暂时可读,后续分配复用时才出错。当前局部测试将已回收记录覆盖为不同字节,确认发布后的查询不再依赖请求记录;像素清理则另行检查。

现有证据包括实际离线 GPU 完成查询和 C++ 发布状态机,仍未覆盖游戏现场的资源注册、原 worker pool 并发和完整所有权生命周期。离线设备可以证明本次输出和调用顺序,不能替代真实运行中的全部调度条件。

C++ 迁移怎样保存阶段定义

此次进一步移入 C++ 的部分包括行序发布、支撑缓冲重排、GPU 执行排列和连续高度绘制入口。连续入口依次调用本实现的支撑、自然 Stamp 与 Location 绘制程序,实际高度像素仍由自写 HLSL 计算。

Python 继续承担部分静态资源解析、序列化和上游阶段编排。主要算法模块和后续绘制已有 C++ 实现及连续入口,阶段编排仍有 Python 参与,高度像素也仍由 GPU 计算。

部分 当前职责 本轮需要保留的信息
C++ 场景核心 请求、布局、区域、细分、角点高度、自然 Stamp 父实例 配置分支、记录顺序与随机状态
静态资源适配 Level/Unit、纹理目录、局部变换等输入组织 资源版本、尺寸及变换层级
C++ 支撑模块 查询、目标、邻接、记录排列与执行排列 查询高度版本、CPU self 编号、GPU 层序
C++/HLSL 高度绘制 栅格化、平滑、支撑、自然与地点高度写入 行序、mip 绑定、采样器与阶段输出
Web 工具 作业提交、报告与图形预览 作业输入和输出身份,不另造高度结果

连续绘制入口接收调用者提供的阶段数据,本身不检查这些数据是否来自本次请求。端到端验证器负责追踪它们与请求、静态库的对应关系。单独给绘制器传入支撑高度或实例文件并执行成功,只能证明它能够处理这组输入。

验证入口使用新输出目录,拒绝已有结果与成功标记;某一阶段失败立即停止,后续结果不应继续产生。验证器由此避免复用旧高度图,确保每份输出都来自本次成功执行的阶段。

四次重新生成完成后,验证器才读取冻结 Web 的原始高度数组。第一次统一选择 GPU 查询后端时,平原与沙丘仍有微小差异;检查冻结基线配置后确认,这两套原本使用 CPU float32 查询,另外两套使用 GPU 查询。保持各自原配置重新生成后,四份 4096² 数组全部位模式相同,文件哈希也一致。

两种查询后端不应无条件互换。它们在本次配置下可以很接近,但浮点计算的执行方式仍会影响末位。迁移验收首先要复现选定基线的输入条件,再决定是否统一后端,以及统一后用什么误差标准验收。

逐位一致与毫米级残差对应不同的比较

这组实验同时包含模块对照、实现回归和目标对照。即使数组尺寸相同,这三类实验检查的内容也不同,需要分别说明比较双方。

比较 输入和对象 本轮结果 不能由此推出
同输入内核对照 自产阶段输入,在同一宿主运行自写和参考内核 对应已测阶段像素可逐位一致 全部上游参数及历史设备状态一致
C++ 与冻结 Web 回归 同一请求、静态库、显式启动与查询配置;Vista 前 4096² float32 四案例均零失配、哈希一致 与参考目标整张地图零差异
独立生成与参考目标 生成结束后比较共同中央范围 最大误差约 0.56~1.50 毫米 所有 Seed、外围、碰撞及联机均通过

误差统计使用:

error(P) = abs(generated(P) − reference(P))
MAE      = sum(error(P)) / sampleCount
maxError = max(error(P))
over1mm  = count(error(P) > 0.001)

本文表中平均值是平均绝对误差 MAE,不是 RMS。第一、二篇的 R16 一致率、中央范围 RMS 和本轮 MAE 来自不同实验,不能直接拼接为一条“精度提高了多少”的曲线。

外围 Vista 融合机制

图 31|机制示意:外围 Vista 的输入与融合关系。四种地貌的外围整图验证范围另见正文。

中央共同范围以外,各案例的验证进度不同。平原与离子赤原已有半径 620 米内的比较;焦灼荒原只有中央参考裁片;沙丘先前的含 Vista 版本有整图记录,但最新排序版本没有重新完成整图 Vista 回归。四套 C++ 与 Web 的逐位一致范围则统一止于 Location 高度写入后、Vista 前。

剩余的毫米级差异还需要继续定位。排查中将两个案例的最大误差点逐次绘制回放,确认哪些自然 Stamp 和 Location 改写了该点;自写与参考内核在相同自产输入上的结果一致。这次回放缩小了排查范围,但还没找到造成残差的具体历史参数或计算环节。现有结果不足以把这些末位差异都归因于 GPU 精度。

报告仍保留 finalScene=false,因为完整场景入口、全部外围和运行期资源时序还没有按统一条件验收。父记录、支撑排序和已测像素的对照结果继续有效,但完整场景尚未通过。

接入 UE 前仍需补齐的验证

前三篇已经说明了 UE 内的高度会话、地形网格和网络摘要。此次 C++/HLSL 结果来自独立 Windows D3D11 验证程序,没有因此自动成为 UE RHI 中的同一条生成链。引擎侧仍需接入新的阶段定义和排序规则,再分别检查设备资源、打包与运行时行为。

接入时需要一并处理这次确认的数据依赖:基础查询高度与平台输出分别保存,CPU self 编号与 GPU 执行序列分别保存,纹理行序只转换一次,驻留 mip 与尺寸常量、叶片几何共同更新。如果只迁移计算函数,仍沿用旧入口的输入绑定方式,这些错误可能再次出现。

专用服务器也不能直接使用客户端的图形路径。若继续采用 CPU 高度实现,就需要逐阶段比较它与本轮 GPU 基线的数值;如果服务器读取离线产物,则应明确其生成与版本条件。选择哪条路线属于引擎实现问题,本次四案例像素对照没有替它完成验收。

树木、岩石和 POI 的运行时装配同样单列。本轮自然 Stamp 父记录通过,不等于该父资源内所有网格、植被、岩石的世界矩阵和碰撞都重新通过;Location 高度纹理通过,也不等于建筑最终位置与导航均正确。第二篇已有的物件验证保持原来的日期和范围,本篇没有新增整场景物件数量结论。

下一步可以把这套四请求基线作为引擎接入的回归输入,先核对阶段数组及资源绑定,再检查完整外围、物件变换、碰撞和联机。这样,出现差异时能够回到明确的输入和阶段,而不必从最终截图重新猜测。

结论

四套固定请求已能沿相同的配置读取、基础高度和局部绘制流程生成对应结果。扩展地貌也让此前没有执行的分支参与了测试:没有平台写入时,Hbase 与 Hplatform 相同,查询绑错版本也可能看不出差异;离子赤原写入平台后,这个错误才显现出来。

这些修复都涉及阶段之间的数据交接。间距改变了点列和拓扑,旧任务范围拒绝了本应接受的候选,错误的查询版本改变了支撑目标。行序、绘制次序、mip 绑定和 GPU 完成状态又分别影响后续写入。迁移时除了公式,还需要保留每一步的输入版本、索引关系和执行条件。

保持各案例的基线配置时,C++/HLSL 与冻结 Web 的四份 Vista 前 4096² float32 输出逐位一致,说明迁移保留了已有实现的结果。与参考目标相比,中央共同范围仍有毫米级残差。前者验证迁移是否保留结果,后者衡量独立生成与参考目标的差异。完整场景仍需另外验收。

接下来的工程工作是把已明确的阶段定义接入 UE,再分别检查外围 Vista、物件变换、碰撞与联机。单点残差仍可继续定位,但不能代替这些运行时验证。已归档的四套请求、阶段数组和资源绑定条件,将作为接入时追踪差异的依据。

AI 协作复盘

本次文章整理使用了近一周的阶段记录、完整记录对照报告、四份 C++/Web 原始高度回归报告,以及行序、支撑排序和查询发布的 C++ 源码。对比图沿用已有实际高度数组生成的结果,配置数据图依据近一周的汇报 SVG 重新编排,完整参数与类型表保留在正文;阶段图沿用汇报产物,行序、排序、mip 与回读图用于解释迁移细节;本文没有重新运行一次完整地图生成,也没有新增引擎性能数字。

整理过程中需要特别核对历史记录的时效。早期文档中的“只输出基础高度”、离子赤原的 1,006 条 Stamp,以及焦灼荒原的 3.42 厘米残差,都已被后续结果更新。如果只读取文档开头,或把每份报告的成功项相加,就会得到错误的当前状态。

“逐位一致”也需要明确比较对象。四份 Vista 前 C++/Web 4096² float32 数组已经逐位一致,与参考目标的高度比较却仍有残差。保留双方、配置、范围和数据格式,才能让后来的实现继续复现这些结果。因此正文保留了各阶段实际读取的数据,以及对应的实验记录,便于后续接入和复查。

发表评论

了解 AI Native Game Development 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读