说明

本文为UE5-地形系统系列的第四篇文章,主要是对该系列的总结

主要类型

Landscape基本类型

ALandscapeProxy

Detail 面板上显示的属性变量都存放在 ALandscapeProxy 类中,这个类主要用来保存地形的详细信息和属性设定值。

ALandscape

ALandscape 继承自 ALandscapeProxy,在此基础上实现了更多功能。
ALanscape的主要作用是管理 LandscapeComponent,地形相关的真正的渲染数据在 ULandscapeComponent,地形渲染流程也是以ULandscapeComponent为基础单元。

ULandscapeComponent

ULandscapeComponent是 UE 在渲染地形、计算地形可视性和处理地形碰撞时采用的基本单位。
地形中的所有地形组件都具有相同的大小,并且始终为正方形。地形组件尺寸是在创建地形时决定的,取决于地形的大小和细节设置。

FLandscapeComponentSceneProxy

FLandscapeComponentSceneProxy 继承自 FPrimitiveSceneProxy,是 ULandscapeComponent 在渲染线程的代表,镜像了 ULandscapeComponent 在渲染线程的状态,其中包含了 Landscape 的 VertexBuffer, IndexBuffer, VertexFactory 等数据。

FLandscapeSharedBuffers

FLandscapeComponentSceneProxy 中包含一个 FLandscapeSharedBuffers, VertexBuffer, IndexBuffer, VertexFactory 等渲染所必需的数据,就是存在 FLandscapeSharedBuffers 中。

FLandscapeComponentSceneProxy 类的静态成员 static TMap<uint32, FLandscapeSharedBuffers*> SharedBuffersMap 管理所有的FLandscapeSharedBuffers。

SharedBuffersMap 的键是 SharedBuffersKey,对应着各自的 SharedBuffers。在 FLandscapeComponentSceneProxy 创建时会计算其 SharedBuffers 所对应的 SharedBuffersKey。

FLandscapeVertexBuffer

SharedBuffers 中的成员,存储该 FLandscapeComponentSceneProxy 的所有顶点,其存储形式为 FLandscapeVertex 类型,其中由四个 uint8 分量,xy 表示该顶点在其 Subsection 中的局部索引,zw 表示该顶点对应的 Subsection 在其 Component 中的索引。

VertexBuffer 真正的创建逻辑在 FLandscapeVertexBuffer::InitRHI 中完成,在其中遍历 Subsection 索引和顶点索引,完成各顶点的创建。

FLandscapeIndexBuffers

SharedBuffers 中的成员,存储顶点索引,与 VertexBuffer 不同的是,IndexBuffer 在 SharedBuffer 中以 FLandscapeIndexBuffers** 类型成员存储,实际上就相当于个二维数组,因为需要存储各个 LOD 层级下各自的 Index,因此多加一个维度。

其创建在 FLandscapeSharedBuffers::CreateIndexBuffers 中完成,在其中遍历 LOD 层级,计算各层对应的 Sebsection 中的 Quads 数量,在内层循环遍历 Subsection 和 Quads,每个 Quads 两个三角形,需要六个 Index,对应 VertexBuffer 中的四个顶点。

注:这里 Index 的生成并不是根据 LOD 步长跳着间隔计算,而是从 0 遍历到 LodSebsectionSizeQuads。可以想象,这样做高层级 LOD 的 Index 实际上对应的是低层级 LOD 下左下角的那些顶点。为使绘制地形时在不同 LOD 下 Component 大小一致,在其 VertexShader 中有对顶点坐标变换回 Base 下对应顶点坐标的操作(实际上就是归一化)。而 HeightMap 采样则是根据 LOD 采样其对应的 Mipmap

FLandscapeVertexFactory

地形所用的顶点工厂类型,使用 VertexBuffer 进行初始化,VertexShader 中对应 FVertexFactoryInput 的成员 Position 的分量定义与前述 VertexBuffer 中一致。

在 SharedBuffers 创建完成后,其对应的 FLandscapeComponentSceneProxy 也会取得该 VertexFactory 的指针,后续生成 StaticMeshBatch 时候,传给 MeshBatch 的就是这个 VertexFactory

ShaderParameter

主要涉及三个 ShaderParameter

  • FViewUniformShaderParameters
  • FLandscapeUniformShaderParameters
  • FLandscapeSectionLODUniformParameters

FViewUniformShaderParameters

这个并非专门给地形系统的 ShaderParameter,而是和 View 相关的参数,其中包含了 Landscape 相关的参数。

其中,最主要的是 LandscapePerComponentData 和 LandscapeIndirection 这两个参数
LandscapePerComponentData 保存的是当前 View 中,各个 Component 的 LOD,以一维 Buffer 的形式连续存储;而 LandscapeIndirection 保存的是当前 View 中,各个 Landscape 对应数据在 LandscapePerComponentData 中的开头索引。

与它们对应的应用端参数分别是 FSceneView::LandscapePerComponentDataBuffer 和 FSceneView::LandscapeIndirectionBuffer ,它们都是 SRV

其数据来源为当前 View 对应的 FLandscapeViewData::LandscapeLODData 和 FLandscapeViewData::LandscapeIndirection。渲染时,每帧都会对 FLandscapeViewData 进行 Reset,然后计算其 LODData 并填充这两个 Array,为其构建 RHIBuffer 并创建 SRV 传递给 FSceneView 中对应成员,以更新 Shader 中的数据。

FLandscapeUniformShaderParameters

这是地形相关的最基本的 ShaderParameter,其中包含了如 ComponentSize, HeightmapTexture,WeightmapTexture, Sampler 等

在渲染线程的 AddPrimitiveCommand 中调用 FPrimitiveSceneProxy::SetTransform,在其中继续调用 FLandscapeComponentSceneProxy::OnTransformChanged 完成 FLandscapeUniformShaderParameters 的设置。它在启动过程中设置,在运行时除非改变地形 Component 的设置,否则不会更新该 ShaderParameter

FLandscapeSectionLODUniformParameters

与 LOD 相关的 ShaderParameter,主要记录对应 Component 的 LODBias 及其所属的 Landscape Actor 的索引

每帧由相应的 FLandscapeRenderSystem 计算 LODBias,然后调用 UpdateBuffers 函数创建 SRV 进行参数更新

管理与辅助类型

FLandscapeRenderSystem

是地形渲染功能的辅助类型,在完成 FLandscapeComponentSceneProxy 创建后,在渲染线程的 AddPrimitiveCommand 任务中,调用 FLandscapeComponentSceneProxy::CreateRenderThreadResources,在其中完成创建。

所有 FLandscapeRenderSystem 被统一存储在全局变量 TMap<uint32, FLandscapeRenderSystem*> LandscapeRenderSystems 中,这个 Map 的键就是传入 FLandscapeRenderSystem 构造函数的 FLandscapeComponentSceneProxy 所对应的 LandscapeKey。也就是说,FLandscapeRenderSystem 可能与 FLandscapeComponentSceneProxy 一一对应,也可能同时存着多个 LandscapeKey 一致的 Proxy。
(BTW,上述多个 LandscapeKey 一致的 Proxy 也共享着同一个 SharedBuffers)

除此之外,FLandscapeRenderSystem 中还提供了一些用于计算 LOD 的函数。

FLandscapeSceneViewExtension

FLandscapeSceneViewExtension 继承自 FSceneViewExtensionBase,SceneViewExtension 类型是对 SceneView 的扩展,实现额外的操作、记录额外的数据。

FLandscapeSceneViewExtension 则是专门用于地形系统的 SceneViewExtension。
其中最重要的成员为 TArray<FLandscapeViewData> LandscapeViews,其中每一个 FLandscapeViewData 都对应一个 View,而 FLandscapeViewData::LandscapeLODData 和 FLandscapeViewData::LandscapeIndirection 在前面 FViewUniformShaderParameters 部分有涉及

整体流程

地形系统渲染流程

要点总结

  • Landscape 的渲染走的是 Static 绘制路径
  • 在游戏线程中进行地形子系统的 Tick,在该过程中完成 EditLayers 对于高度图与权重图的更新
  • 在启动运行过程中,创建每个 Component 在渲染线程中的代理 FLandscapeComponentSceneProxy,完成渲染线程所需的资源的创建,并向渲染线程入队 AddPrimitiveCommand
    • AddPrimitiveCommand 执行时完成 FLandscapeUniformShaderParameters 的设置以及 BoundingBox, FLandscapeRenderSystem 等资源的创建
  • 在渲染线程中,FSceneRenderer::Render 之初进行 FScene::Update
    • 其中完成各 LOD 下的 StaticMeshBatch 的生成
    • 对生成的 StaticMeshBatch 进行 MeshDrawCommand 的收集
    • 利用 FLandscapeSceneViewExtension 和 FLandscapeRenderSystem 计算 LOD 相关参数,更新相关的 Buffers,并完成 FViewUniformShaderParameters 和 FLandscapeSectionLODUniformParameters 的更新
    • 计算 StaticMeshes 的 Relevance,根据计算得到的 LODToRender 生成 MarkMask,以决定转译哪些 MeshDrawCommand
  • 添加 RDGPass,并在最后 ExecutePass,完成命令的分发与转译

注:静态绘制路径与动态绘制路径计算 Relevance 的时机不同。静态绘制路径先生成 MeshBatch、收集 MeshDrawCommand,然后才计算 Relevance,以决定转译哪些 Command;而动态绘制路径则是生成 MeshBatch 后,先进行 Relevance 的计算,然后才根据需要收集 MeshDrawCommand。

这其中的逻辑是,静态 MeshDrawCommand 不需要每帧生成,只有 StaticMeshes 改变时才需要,因此可以一开始提取收集完所有可能用到的静态 MeshDrawCommand,后续每帧都根据计算得到的 Relevance 取用所需的 MeshDrawCommand 进行转译即可;而动态 MeshDrawCommand 需要每帧生成,因此需要先进行 Relevance 计算,根据其结果只生成这一帧里需要的动态 MeshDrawCommand

Reference

Unreal Engine Documentation: Landscape Technical Guide

UE4地形系统(Landscape)

UE5引擎 PC端的Landscape渲染浅分析

UE4 地形 landscape

UE4移动端地形理解 - 高度LOD