
HD2 渲染地形使用了2次绘制。
远景地形网格
PASS1 用于绘制远离摄像机的地形,不会开启置换纹理与积雪下陷,绘制远处大面积的简单地形。

(最终的远景网格)
基于距离的连续 LOD
每块地形网格 instance 的大小为 32*32,通过计算顶点到相机位置的距离,为每块网格选择合适的缩放尺寸。远处的地形网格会更加稀疏。低级 LoD 会在与高级 LoD 衔接的边缘削减边数以确保在世界空间中拼凑出无缝的地形网格。
抬升地形高度
通过顶点相对于相机的距离,计算应映射到的高度纹理 Mipmap 级别(限制在 0 到 4 之间),对高度图 __tex_hmap 进行采样,获取地形高度,并乘以 terrain_size.z 进行缩放,完成高度起伏。
远景地形不会开启置换,此处是为地形附加宏观高度上的起伏

(采样高度纹理,为地形添加起伏)
近处网格与积雪下陷
PASS2 地形绘制以摄像机为中心,绘制极近距离的地形,这里开启置换与积雪下陷的细节地形,为确保有足够的三角面数塌陷地形,此处使用细分着色器大幅增加三角面数。

与远景地形不同,摄像机附近的地形不划分 LoD,通过细分直接产生的海量三角面将会应用置换纹理,添加地形细节
积雪凹陷的交互是通过一张动态生成的高度遮罩纹理 tex_trample,引擎根据玩家移动轨迹,实时更新这张图。代码中的 camera_center_pos 和纹理缩放系数 0.0078125(1/128)表明,这张纹理并不覆盖整个世界,而是以相机(或玩家)为中心,大小为 128x128 单位的局部跟随贴图。

(tex_trample)
HD2 将多层地表材质的 ID 打包在了一个单通道的32位纹理中( __tex_material_map),采样此纹理并对读取到的值进行了密集的位运算(移位掩码:>> 26, >> 21, >> 16, >> 8)。
然后根据解码出的材质索引,去 trample_min_max 缓冲区中读取该地形材质允许的最大下陷深度和最小下陷深度,这是存放着各种地形凹陷程度的一张表。此表中的绝大多数材质都被置零,不可以凹陷,而积雪的下陷度可以很大。

(trample_min_max)
脚印贴花

浅薄的雪地不会产生几何层面上的凹陷,但会留下脚印贴花(法线)。
顶点着色器使用一个逻辑上的立方体,通过确定某个像素点在投影立方体内的相对位置,就可以重建出对应的屏幕坐标。
先读取地形原有的法线,将其从 [0, 1] 映射回 [-1, 1] 范围,再计算投影方向与地形法线的点积。
通过传入的的两个参数 angle_fade_start 与 angle_fade (余弦 + 1),根据地形法线与上方向点乘结果做 smoothstep,如果地形法线与脚印的法线角度差异过大,则降低 Alpha 值( > 56°)或直接剔除( > 71° ),防止脚印在斜坡上发生严重的拉伸变形。

雪地的细节法线

地形的延迟渲染阶段结束之后,在已有的地形法线 gbuffer 之上又覆盖了一层法线细节信息。
场景中存在多种不同状态的雪打包在同一个纹理数组中,通过外部传入的 snow_index 来动态切换当前地形区域应该显示哪种雪质。着色器提取了世界坐标的水平面分量(HD2 使用的 Stingray 引擎世界坐标右手 Zup 坐标系,和 UE 相反),并乘以一个硬编码的基础缩放值 0.1875
最后为地形应用了由材质参数控制的缩放变量 snow_tile,并将 snow_index 作为纹理组索引放入向量,形成最终用于采样的坐标

( __tex_snow_pnrb_array )
积雪的冰晶闪烁

HD2 为积雪材质的地形增加了额外的细节,注意此处太阳光照下的积雪表面,具有随镜头移动而闪烁的冰晶。
为了模拟细碎的冰渣颗粒感。这一步通过采样一张三通道的噪声纹理完成。

(__tex_glint_sample)
首先,根据外部参数 snow_glint_size 缩放 UV 坐标,采样噪声纹理。
Z通道的值被还原为 [-1, 1] 的范围,并乘以强度参数 snow_glint_roughness。这一噪声将被叠加到雪地的全局粗糙度 snow_roughness 上。
除此之外,着色器利用 X 和 Y 通道构建了一个虚拟的冰晶微法线。并通过乘以 0.15 压平法线方向,使这部分冰晶发生严重倾斜。
有了微法线后,计算半程向量与微法线的点乘,并使用极端的指数 pow(96),筛出万分之一的闪光点。
冰晶闪烁的强度主要来源于噪声纹理的 Z 通道,再叠加微法线的细小贡献,最后叠加进粗糙度 RT,留给后续延迟渲染的着色阶段处理,在那里产生最后的闪烁效果。