UE5.8 Landscape 地形与地貌:高度图、权重层与自动植被

📑 目录

UE5.8 Landscape 地形与地貌:高度图、权重层与自动植被

被策划否定的第一张地形

美术阿琳接到的需求是一片 4 km × 4 km 的丘陵原野:要有缓坡草地、河谷沙滩、碎石山脊,还要在风口处长着低矮灌木,背阴处冒出蕨类。她打开 UE5.8,新建 Landscape,选了 8129 × 8129 的高度图分辨率,刷刷几下拉出一条山脉,再随便抹了几层绿色材质,兴高采烈地发给策划看。

策划在关卡里跑了两分钟,回来只说了三句话:山像塑料蛋糕,草长在石头上,河床边是绿的。阿琳有点委屈,但也知道问题在哪。她只动了高度图,没给材质层分配权重,也没让植被按地形规则生长。地形不是一张会动的壁纸,而是一套需要雕刻、分层、上材质、再喂给自然规则的系统。

这篇文章把 UE5.8 Landscape 的核心链路拆开讲一遍。从工具到雕刻层,从权重层到 Grass Type,从材质混合到大世界策略,全部围绕一个目标:让地形看起来不是人工拉的,而是自然长出来的。

1. Landscape 工具:在高度图上作画

UE5.8 的 Landscape Actor 本质上是一张二维高度图。每个格点存一个标量高度 h(x,z)h(x,z),渲染时引擎把这张网格在 Y 方向拉起,形成连续曲面。高度图的分辨率决定了地形细节的最小尺度,而不是视觉精度。视觉精度还要靠材质、法线、贴花和植被共同补齐。

创建 Landscape 时,几个参数会直接影响后续工作流:

  • Section Size:Landscape 被划分为多个 Section,每个 Section 内部再切成 Quad。Section Size 常见有 63 × 63、127 × 127、255 × 255。大 Section 减少绘制调用,小 Section 让 LOD 切换更细腻。
  • Sections Per Component:一个 Component 包含几个 Section,决定裁剪和流送粒度。
  • Overall Resolution:总顶点数,比如 8129 × 8129 对应 8k 高度图。
  • Scale:每个顶点的世界空间间距,决定 4 km 地图是用 4k 还是 8k 高度图。

阿琳第一次选 8129 × 8129 是因为数字大,后来她发现 4 km 地图用 4033 × 4033 配合 100 cm 的 Scale 更合理。8k 高度图不仅编辑卡顿,world machine 导入导出也慢。分辨率不是越高越好,关键是世界尺寸与顶点密度的匹配。

雕刻工具集中在 Landscape 编辑模式。常用笔刷包括:

  • Sculpt:直接抬升或压低高度,配合 Brush Falloff 控制边缘硬度。
  • Smooth:磨平高频噪声,适合处理机械感过强的拉峰。
  • Flatten:把笔刷区域压到同一高度,做平台、道路基底很快。
  • Ramp:两点之间生成平滑过渡坡道,做盘山公路省事。
  • Erosion:模拟水力侵蚀,在 ridge 和 valley 之间制造自然沟壑。
  • Noise:叠加程序化起伏,给大面积平原增加微地形。

阿琳后来养成了习惯:先用大半径 Smooth 定山脉走向,再用中等 Sculpt 雕山谷,最后用 Noise 加地表细节。Erosion 只用在少数视觉焦点区域,因为它会显著增加顶点数据量,低端机可能吃不住。

flowchart TD
    subgraph editor ["Landscape 编辑模式"]
        sculpt["Sculpt 笔刷"]
        smooth["Smooth 笔刷"]
        erode["Erosion 笔刷"]
        noise["Noise 笔刷"]
        layer["Layer Info 面板"]
    end
    subgraph engine ["UE5.8 引擎"]
        heightmap["Heightmap 纹理"]
        weightmap["Weightmap 纹理"]
        landscape_proxy["Landscape Proxy"]
        material["地形材质"]
    end
    sculpt --> heightmap
    smooth --> heightmap
    erode --> heightmap
    noise --> heightmap
    layer --> weightmap
    heightmap --> landscape_proxy
    weightmap --> landscape_proxy
    landscape_proxy --> material

2. Sculpting 与雕刻层:别把山雕成蛋糕

阿琳的山像塑料蛋糕,根子在于 Sculpt 笔刷用力过猛,过渡区太少。真实地形没有尖锐的孤立山峰,山脉是风化、侵蚀和沉积共同作用的结果。UE5.8 提供了 Sculpt Layer(雕刻层)功能,让美术把不同阶段的修改放在独立层上,随时回退、混合或导出。

雕刻层的工作逻辑类似 Photoshop 图层。每个 Layer 保存一套高度图偏移量,基础高度图不变。你可以在一个层上拉山脉,在另一个层上雕河谷,再开一个层试一种不同的 ridge 走向。层与层之间通过透明度或叠加模式混合。如果策划说山脊太锋利,你只需调低那个层的强度,不用整体重雕。

使用 Sculpt Layer 时需要注意几点:

  • 层数过多会明显增加 Landscape 数据量。每个层都是一张与地形等大的 float 纹理。8k 高度图加一个 Sculpt Layer 就可能多几十 MB。
  • 层之间混合顺序影响最终形状。Additive 层会叠加高度偏移,Alpha 层会按遮罩插值。
  • 导出雕刻层可以用于外部工具。比如把 Sculpt Layer 导到 Houdini 做侵蚀模拟,再导回 UE。

阿琳后来给项目定了三条规矩:基础层只保留大地形走势,细节层存放 Noise 和 Erosion,临时层用完即删。她还把每个 Layer 的命名规范写成 Layer_用途_版本,例如 Layer_Ridge_v02,避免三个月后自己都不知道哪层是干嘛的。

雕刻层还有一个好处:配合 Data Layers 做赛季内容切换。夏天把雪地 Sculpt Layer 隐藏,冬天把它打开并叠加白色材质层,同一张地图就能呈现不同季节的地貌。这种切换不需要重新烘焙光照,只需要切换 Layer 可见性。

flowchart TD
    A["创建基础 Landscape"] --> B["新建 Sculpt Layer: 山脉走势"]
    B --> C["新建 Sculpt Layer: 河谷侵蚀"]
    C --> D["新建 Sculpt Layer: 地表 Noise"]
    D --> E{视觉效果是否通过}
    E -->|"通过"| F["合并或保留层"]
    E -->|"不通过"| G["调整对应 Layer 强度"]
    G --> D
    F --> H["导出到外部工具可选"]
    H --> I["最终烘焙高度图"]

3. Layer Info 与权重层:让材质知道脚下是什么

地形材质混合的前提是知道每个像素属于哪种地表。Layer Info 就是干这个的。它定义了一套权重通道,每个通道对应一种地表类型,比如 Grass、Rock、Sand、Snow。美术用 Layer 笔刷在地形上涂抹,引擎把涂抹结果存成 Weightmap,材质再读取 Weightmap 决定怎么混贴图。

Weightmap 本质上是一张多通道纹理。每个通道的取值范围是 [0,1][0,1],表示该位置对应材质层的权重。一个像素上可以同时有多个非零通道,比如 0.60.6 的 Grass 加 0.40.4 的 Rock,材质会按这个比例插值两套贴图。

UE5.8 里创建 Layer Info 有两种方式:

  • Weight-Blended Layer:各层权重归一化,所有层加起来总和为 1。适合互斥地表,比如草地、岩石、沙地。
  • Non-Weight-Blended Layer:各层独立存储,不归一化。适合做细节叠加,比如雪地覆盖、湿润痕迹,不影响底层权重。

阿琳一开始全用 Weight-Blended,结果想在草地上叠加一层潮湿痕迹时发现主层被压没了。后来她改成基础层用 Weight-Blended,细节覆盖层用 Non-Weight-Blended,分工明确。

权重层的数量受材质采样器限制。每个 Layer 至少占用 Base Color、Normal、Roughness 等贴图采样。UE5.8 的地形材质通常采用 Texture Arrays 或 Virtual Texture 来缓解采样器压力。如果项目用传统 Texture Sample 节点,超过 16 层就可能触发 SM5 采样器上限。

Layer Info 还有一个隐藏成本:内存。每个 Layer 对应一张与地形分辨率匹配的 Weightmap。4k 地形有 16 个 Layer,Weightmap 数据量可能达到数百 MB。策划后来要求把远景 Layer 数量降到 4 层以内,只在玩家经常到达的近景区使用 8 层。

坡度也可以参与权重混合。材质里常用 WorldAlignedNormalDerivative 计算局部坡度:

s=arctan((hx)2+(hz)2)s = \arctan\left(\sqrt{\left(\frac{\partial h}{\partial x}\right)^2 + \left(\frac{\partial h}{\partial z}\right)^2}\right)

其中 ss 是坡度角,hx\frac{\partial h}{\partial x}hz\frac{\partial h}{\partial z} 是高度图在两个轴向的梯度。材质可以根据 ss 自动在陡坡铺岩石、缓坡铺草地,减少美术手工刷权重的工作量。

4. Grass Type 与自动植被:让草按规则长

Layer Info 解决的是地表材质,Grass Type 解决的是地表覆盖物。UE5.8 的 Landscape Grass Type 允许材质根据 Layer 权重和坡度,自动向 Foliage 系统输出草、花、碎石、小灌木等实例。美术不再需要手工刷每一棵草,只需要定义规则,引擎在运行前或运行时按规则生成实例。

Grass Type 的入口在材质编辑器的 LandscapeGrassOutput 节点。你可以把多个 Grass Type 挂到输出上,每个 Grass Type 再指定一种 Foliage Type。材质内部根据 Layer 权重决定某个 Grass Type 的密度。比如 Grass_Lawn 只在 Grass 层权重大于 0.7 的地方生长,Rock_Small 只在坡度大于 25° 的岩石层上出现。

阿琳把规则拆成两条主线:

  • 生物群落层:由 Landscape Layer 决定。草地层长草,沙滩层长芦苇和贝壳,岩石层长苔藓和碎石。
  • 地形约束层:由坡度和高度决定。陡坡排除高草,只留低矮地被;山顶风大,灌木变矮变稀疏;河谷低洼处湿度高,允许蕨类。

规则定好后,她用一个 LandscapeGrassOutput 节点把它们组合起来。示例如下:

// 材质函数伪代码,用于 LandscapeGrassOutput 的密度计算
float GrassDensity = saturate(GrassWeight - 0.2) * (1.0 - Steepness);
float RockDebrisDensity = saturate(RockWeight) * Steepness;
float FernDensity = saturate(GrassWeight * Wetness) * (1.0 - Steepness);

return float3(GrassDensity, RockDebrisDensity, FernDensity);

这里 Steepness 是归一化坡度,Wetness 是一个由高度和湿度图驱动的遮罩。实际在材质编辑器里,这些计算用节点完成,不需要手写 HLSL。

Grass Type 生成实例的时机也很关键:

  • 编辑器预生成:实例作为关卡数据保存,加载时直接存在。优点是 runtime 开销稳定,缺点是文件体积大。
  • 运行时按视图生成:实例随玩家移动动态创建和销毁,适合大世界的无限草原。缺点是在快速移动时可能出现 pops。

阿琳的项目用了混合策略:远景和背景草用运行时生成,近景视觉焦点处用手工 Foliage 雕琢。这样既能控制文件大小,又能保证镜头前不会出现规则感太强的实例分布。

sequenceDiagram
    participant M as "Landscape 材质"
    participant L as "Layer Info / 坡度"
    participant G as LandscapeGrassOutput
    participant F as "Foliage System"
    participant I as "实例化网格"

    M->>L: 读取 Grass/Rock/Sand 权重
    M->>L: 计算坡度与海拔约束
    M->>G: 输出各 Grass Type 密度
    G->>F: 按密度请求生成实例
    F->>F: 执行排斥半径与随机分布
    F->>I: 提交 Instanced Static Mesh

5. 地形材质混合:从颜色拼接到真实地表

地形材质的核心挑战是:一张地形可能覆盖几平方公里,却只有一套材质。如何在不同区域呈现草地、岩石、沙滩、雪地,同时保持近景细节和远景过渡?UE5.8 的标准做法是用 Landscape Layer Blend 节点,把多个材质层按 Weightmap 混合。

最简单的 Layer Blend 是线性插值:

Cfinal=i=1nwiCi,i=1nwi=1C_{final} = \sum_{i=1}^{n} w_i \cdot C_i, \quad \sum_{i=1}^{n} w_i = 1

其中 wiw_i 是第 ii 层的权重,CiC_i 是该层的颜色。这种插值在两层交界处容易发灰发脏,因为两个 RGB 直接平均会压低声调。更好的做法是用 Height-Based Blend,根据每层的高度图或法线长度决定混合锐度。

Height-Based Blend 的公式可以近似写成:

wi=wi(hi+ϵ)w_i' = w_i \cdot (h_i + \epsilon)

wi=wij=1nwjw_i'' = \frac{w_i'}{\sum_{j=1}^{n} w_j'}

hih_i 是第 ii 层的高度纹理值,ϵ\epsilon 是一个很小的偏移量,避免权重完全消失。这样岩石层在接缝处会按自身凹凸突出,草地会被压到缝隙里,混合边缘更有立体感。

阿琳还用了 Layer Breakup 技巧:在 Layer Blend 之后叠加一张 perlin noise,让两种地表的边界不那么笔直。自然界里草地和岩石的交界是参差不齐的,精确的权重边界反而会暴露人工痕迹。她在材质里用一张低频噪声扰动权重:

wi=wi+noise(x,z)0.1w_i''' = w_i'' + \text{noise}(x,z) \cdot 0.1

然后再做一遍归一化。这个扰动不能太大,否则会出现不属于该地表的纹理。0.05 到 0.15 的范围通常够用。

地形材质还需要处理贴图重复感。大面积草地如果用同一张贴图平铺,远景会出现明显图案。UE5.8 支持 Runtime Virtual Texture 和 Layered Materials,可以把近景高分辨率贴图与远景低分辨率贴图分开采样,再用距离混合。阿琳在地形材质里加了一个 Distance Blend 节点,让远景使用低分辨率 but 无重复的 macro texture,近景使用带细节的 micro texture。

Macro 与 micro 的混合距离设为 3000 cm 到 8000 cm。玩家站在地面上时,脚下的草有细节;望向远处山脊时,看到的是柔和的色块,而不是密密麻麻的贴图格子。

6. 与 Mesh Terrain 的对比:什么时候该换方案

UE5.8 同时提供了 Landscape 和 Mesh Terrain 两套地形方案。它们不是替代关系,而是互补关系。Landscape 适合开阔、连续、以高度图能表达的地形;Mesh Terrain 适合悬岩、洞穴、浮岛、破碎山体这类真三维结构。

从数据表示上看,Landscape 是一个函数 y=h(x,z)y = h(x,z),同一个水平坐标只能有一个高度。Mesh Terrain 是任意三角形网格,顶点可以朝任何方向移动,面片可以悬空、重叠、镂空。这种自由度让 Mesh Terrain 能做 Landscape 做不出的结构,但也意味着它失去了高度图带来的规则性、LOD 简化和专用渲染路径。

维度LandscapeMesh Terrain
数据结构高度图三角网格
真三维结构不支持悬垂/镂空支持
编辑方式笔刷、层、程序化雕刻、拉伸、布尔、Modifier
渲染路径Landscape 专用Nanite 虚拟几何
植被系统Grass Type 直接支持需额外适配
碰撞自动生成依赖 Mesh 碰撞设置
大世界流送与 World Partition 深度集成需手动拆分或配合 OFPA

阿琳的项目里,远景丘陵和河谷平原用 Landscape,近景破碎悬崖和山体隧道用 Mesh Terrain。两种地形在视觉上通过材质和贴花统一,玩家在关卡里跑图时不会感觉到切换。她在交接处留了 5 到 10 米的重叠带,避免两种几何在接缝处产生硬边。

Mesh Terrain 还有一个优势:它可以被 PCG Graph 直接采样。PCG 可以读取 Mesh Terrain 的法线、高度和表面属性,然后在上面撒石头、长藤蔓、生成倒木。Landscape 也能被 PCG 采样,但高度图约束让 PCG 无法处理悬垂面。阿琳在悬崖区域用 PCG 生成了大量随机碎石,效率和效果都比手工摆放好。

7. 性能优化:地形不是越细越好

地形优化是一场与数据量的博弈。高度图、Weightmap、Grass Type 实例、碰撞数据、LOD 切换,每一个环节都可能把帧率拉下来。UE5.8 提供了不少工具,但工具只是手段,优化思路才是关键。

第一个要点是分辨率控制。世界尺寸固定后,顶点密度由 Overall Resolution 和 Scale 决定。假设地图边长 LL,高度图分辨率 NN,则顶点间距 d=L/Nd = L / Ndd 越小,地表细节越丰富,但顶点数和绘制调用也越多。阿琳的项目把 dd 控制在 50 cm 到 100 cm 之间。小于 50 cm 的细节交给法线贴图和位移贴图,而不是真实顶点。

内存预算可以用一个简单公式估算:

Mheight=NxNz2bytesM_{height} = N_x \cdot N_z \cdot 2 \, \text{bytes}

Mweight=NxNzLcount1byteM_{weight} = N_x \cdot N_z \cdot L_{count} \cdot 1 \, \text{byte}

NxN_xNzN_z 是高度图尺寸,LcountL_{count} 是 Weightmap 层数。以 4k × 4k、8 个 Weight Layer 为例,高度图约 32 MB,Weightmap 约 128 MB,加上法线、光照图等,单张地形可能占用几百 MB 显存。移动端或低端主机上必须减少层数和分辨率。

第二个要点是 LOD 与 Tessellation。UE5.8 Landscape 自带基于距离的 LOD。远景用低分辨率 Section,近景用高分辨率 Section。Tessellation 可以增加近景三角形密度,但对 GPU 负担很大。阿琳的团队在主机端关闭 Tessellation,用 Nanite 处理岩石等离散细节,Landscape 只负责宏观起伏。

第三个要点是 Weightmap 压缩与流送。UE5.8 支持把 Weightmap 打包成 BC 格式压缩存储,减少显存占用。大世界项目还可以把远景 Layer 烘焙到低分辨率 Texture,只在玩家接近时加载高分辨率 Weightmap。

第四个要点是 Grass Type 的密度与距离。阿琳把草的渲染距离设为 8000 cm,超出距离后完全消失。 she 还用 Grass Type 的 Scale 范围控制实例大小,远处的草更小、更稀疏,既省面数又符合透视。她在材质里加了一个 distance fade:

α=1saturate(ddneardfardnear)\alpha = 1 - \text{saturate}\left(\frac{d - d_{near}}{d_{far} - d_{near}}\right)

dd 是到摄像机的距离,dneard_{near}dfard_{far} 是 fade 的起止距离。这个 alpha 可以控制草的透明度,也可以直接控制实例密度。

graph LR
    A["高分辨率 Landscape"] --> B{是否玩家焦点区域}
    B -->|"是"| C["保留完整 Layer 与实例"]
    B -->|"否"| D["降低 Weightmap 精度"]
    D --> E["减少 Grass Type 密度"]
    E --> F["启用 Distance Fade"]
    F --> G["烘焙 Runtime Virtual Texture"]
    C --> G
    G --> H["稳定帧率"]

8. 大世界地形策略:把整张地图切活

当世界尺寸超过 8 km × 8 km,单张 Landscape 已经不够用了。UE5.8 推荐用 World Partition 把大世界切成 Grid Cell,配合 HLOD、Streaming Source 和 Data Layers 实现动态加载。地形作为大世界的重要组成部分,必须从一开始就按 Cell 规划。

阿琳的团队把 16 km × 16 km 的地图切成 512 m × 512 m 的 Cell。每个 Cell 内部有自己的 Landscape Section、Lightmap、NavMesh 和 Grass Type 实例。玩家移动时,附近的 Cell 进入内存,远处的 Cell 被卸载或替换为 HLOD。

大世界地形的关键是避免跨 Cell 的硬接缝。Landscape 在 World Partition 下会自动处理相邻 Cell 的高度连续性,但 Weightmap 和 Layer Info 的边界需要美术注意。如果两个相邻 Cell 由不同人编辑,交界处的草地密度、岩石颜色可能出现跳变。团队规定:交界 50 米范围内的 Layer 权重必须共享同一个参考贴图,编辑时不能单独改动交界区。

HLOD 对大世界地形尤其重要。远处山脉如果仍按原始 Landscape 渲染,顶点数和绘制调用都会爆炸。UE5.8 的 Runtime HLOD 可以把多个 Cell 合并成低精度代理网格,远处用代理,近处用真实地形。代理网格不需要材质细节,只需要大致的明暗和轮廓。

大世界还需要考虑高度图数据来源。阿琳的团队用 Houdini 生成基础高度图,输出成 tiled EXR,再导入 UE 的 World Partition。每个 Tile 对应一个 Cell,命名规则与 Cell 坐标对齐。这样策划调整玩法区域时,美术只需要重导对应 Tile,不用动整张地图。

World Partition 下还有一个实用功能:Streaming Source。它告诉引擎哪些位置需要优先加载。玩家角色、载具、摄像机都可以是 Streaming Source。多人游戏里,每个玩家的客户端只需要加载自己附近的 Cell,服务器可以加载全部 Cell 或用更粗的粒度。阿琳在载具上加了 Streaming Source,提前 500 米预加载前方地形,避免高速行驶时弹出空洞。

尾声:地形成活的那一刻

阿琳第二次提交地形时,策划在场景里跑了十分钟,回来只说了一句:这次像真的。她没有用更复杂的工具,只是把高度图、Layer Info、Grass Type 和材质混合按正确的顺序串了起来。地形从一张灰度图变成了有坡度、有层理、有植被覆盖的野外。

UE5.8 Landscape 的强大之处在于它不是单一工具,而是一套环环相扣的系统。雕刻层管理形状的版本,权重层管理地表的分布,Grass Type 管理覆盖物,材质混合管理视觉细节,World Partition 管理大世界的流送。理解每一环的职责,再按项目规模选择组合方式,才是做出好地形的关键。