说明
本文为UE5-地形系统系列的第一篇文章,主要是对UE5地形系统进行初步的整体分析
主要类型
ALandscapeProxy
继承体系如下:
- UObjectBase
- UObjectBaseUtility
- UObject
- AActor
- APartitionActor
- ALandscapeProxy
 
 
 - APartitionActor
 
 - AActor
 
 - UObject
 
 - UObjectBaseUtility
 
Detail 面板上显示的属性变量都存放在 ALandscapeProxy 类中,这个类主要用来保存地形的详细信息和属性设定值。
例如在 bEnableNanite 开启时,Detail 面板 Nanite 类目下 Advanced 选项就会变为可编辑
// Engine\Source\Runtime\Landscape\Classes\LandscapeProxy.h
class ALandscapeProxy : public APartitionActor, public ILandscapeSplineInterface
{
    GENERATED_BODY()
public:
    (...)
    UPROPERTY(EditAnywhere, Category = Nanite, AdvancedDisplay, meta = (EditCondition = "bEnableNanite", LandscapeInherited))
    int32 NaniteLODIndex = 0;
    UPROPERTY(EditAnywhere, Category = Nanite, AdvancedDisplay, meta = (EditCondition = "bEnableNanite", LandscapeInherited))
    bool bNaniteSkirtEnabled = false;
    UPROPERTY(EditAnywhere, Category = Nanite, AdvancedDisplay, meta = (EditCondition = "bEnableNanite", LandscapeInherited))
    float NaniteSkirtDepth = 0.1f;
    UPROPERTY(EditAnywhere, Category = Nanite, AdvancedDisplay, meta = (EditCondition = "bEnableNanite", LandscapeInherited))
    int32 NanitePositionPrecision = 0;
    UPROPERTY(EditAnywhere, Category = Nanite, AdvancedDisplay, meta = (EditCondition = "bEnableNanite", LandscapeInherited))
    (...)
}

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

每个地形都会被划分成多个地形组件。
地形组件是虚幻引擎在渲染地形、计算地形可视性和处理地形碰撞时采用的基本单位。

地形中的所有地形组件都具有相同的大小,并且始终为正方形。地形组件尺寸是在创建地形时决定的,取决于地形的大小和细节设置。

每个 Component 可以被分为 2*2个 Sections,也可以只有一个 Section ,每个 Section 可以由不同数量的 Quads 组成,Section 每个方向上的 Quads 数量皆为 Log N - 1,比如7, 15, 31…,最大为255。
因此,每个 Section 单个方向上的顶点数一定为2的指数。
每个 LandscapeComponent 的高度数据存储在一张HeightMap纹理中。因此,纹理的大小必定是2的指数。
组件数、分段数、四边形数、定点数 计算示例:
| 总体大小(顶点数) | 四边形数/分段 | 分段数/组件 | 地形组件尺寸 | 地形组件总数 | 
|---|---|---|---|---|
| 8129 x 8129 | 127 | 4 (2x2) | 254x254 | 1024 (32x32) | 
| 4033 x 4033 | 63 | 4 (2x2) | 126x126 | 1024 (32x32) | 
FLandscapeComponentSceneProxy
FLandscapeComponentSceneProxy 继承自 FPrimitiveSceneProxy,是 ULandscapeComponent 在渲染线程的代表,镜像了 ULandscapeComponent 在渲染线程的状态,其中包含了 Landscape 的 VertexBuffer, IndexBuffer, VertexFactory 等数据。
类似UE Mesh Drawing Pipeline中所述,对于 Landscape 的渲染也是从构建其 FLandscapeComponentSceneProxy 开始的。
FLandscapeSharedBuffers
FLandscapeComponentSceneProxy 中包含一个 FLandscapeSharedBuffers, 上面提到的FLandscapeComponentSceneProxy 中的 VertexBuffer, IndexBuffer 等渲染所必需的数据,实际上就是在 FLandscapeSharedBuffers 之中。
FLandscapeComponentSceneProxy类的静态成员,TMap<uint32, FLandscapeSharedBuffers*> SharedBuffersMap 管理所有的FLandscapeSharedBuffers。
SharedBuffersKey 是 SharedBuffersMap 中的键,对应着各自的 SharedBuffers。在 FLandscapeComponentSceneProxy 创建时也会计算其 SharedBuffers所对应的 SharedBuffersKey
FLandscapeComponentSceneProxy::FLandscapeComponentSceneProxy(ULandscapeComponent* InComponent)
    : (...)
{
    (...)
    const int8 SubsectionSizeLog2 = static_cast<int8>(FMath::CeilLogTwo(InComponent->SubsectionSizeQuads + 1));
    SharedBuffersKey = (SubsectionSizeLog2 & 0xf) | ((NumSubsections & 0xf) << 4) | (XYOffsetmapTexture == nullptr ? 0 : 1 << 31);
    (...)
}
可见 SharedBuffersKey 是由 SubsectionSizeQuads、NumSubsections、XYOffsetmapTexture 唯一确定的。也就是说,同属于一个 ALandscape 的不同 LandscapeComponent 有可能共用一个 SharedBuffers
FLandscapeSharedBuffers 在构造过程中会创建并设置其中的 VertexBuffer(FLandscapeVertexBuffer 类型),IndexBuffer(FIndexBuffer[] 类型)
// Engine\Source\Runtime\Landscape\Private\LandscapeRender.cpp
FLandscapeSharedBuffers::FLandscapeSharedBuffers(FRHICommandListBase& RHICmdList, const int32 InSharedBuffersKey, const int32 InSubsectionSizeQuads, const int32 InNumSubsections, const ERHIFeatureLevel::Type InFeatureLevel, const FName& InOwnerName)
    : SharedBuffersKey(InSharedBuffersKey)
    , NumIndexBuffers(FMath::CeilLogTwo(InSubsectionSizeQuads + 1))
    , SubsectionSizeVerts(InSubsectionSizeQuads + 1)
    , NumSubsections(InNumSubsections)
    , VertexFactory(nullptr)
    , FixedGridVertexFactory(nullptr)
    , VertexBuffer(nullptr)
    , TileMesh(nullptr)
    , TileVertexFactory(nullptr)
    , TileDataBuffer(nullptr)
    , bUse32BitIndices(false)
    , GrassIndexBuffer(nullptr)
{
    NumVertices = FMath::Square(SubsectionSizeVerts) * FMath::Square(NumSubsections);
    VertexBuffer = new FLandscapeVertexBuffer(RHICmdList, InFeatureLevel, NumVertices, SubsectionSizeVerts, NumSubsections, InOwnerName);
    IndexBuffers = new FIndexBuffer * [NumIndexBuffers];
    FMemory::Memzero(IndexBuffers, sizeof(FIndexBuffer*) * NumIndexBuffers);
    IndexRanges = new FLandscapeIndexRanges[NumIndexBuffers]();
    (...)
    // See if we need to use 16 or 32-bit index buffers
    if (NumVertices > 65535)
    {
        bUse32BitIndices = true;
        CreateIndexBuffers<uint32>(RHICmdList, InOwnerName);
        if (UE::Landscape::ShouldBuildGrassMapRenderingResources())
        {
            CreateGrassIndexBuffer<uint32>(RHICmdList, InOwnerName);
        }
    }
    else
    {
        CreateIndexBuffers<uint16>(RHICmdList, InOwnerName);
        if (UE::Landscape::ShouldBuildGrassMapRenderingResources())
        {
            CreateGrassIndexBuffer<uint16>(RHICmdList, InOwnerName);
        }
    }
}
template <typename INDEX_TYPE>
void FLandscapeSharedBuffers::CreateIndexBuffers(FRHICommandListBase& RHICmdList, const FName& InOwnerName)
{
    TArray<INDEX_TYPE> VertexToIndexMap;
    VertexToIndexMap.AddUninitialized(FMath::Square(SubsectionSizeVerts * NumSubsections));
    FMemory::Memset(VertexToIndexMap.GetData(), 0xff, NumVertices * sizeof(INDEX_TYPE));
    INDEX_TYPE VertexCount = 0;
    int32 SubsectionSizeQuads = SubsectionSizeVerts - 1;
    // 对各Lod创建不同大小的IndexBuffer
    int32 MaxLOD = NumIndexBuffers - 1;
    for (int32 Mip = MaxLOD; Mip >= 0; Mip--)
    {
        int32 LodSubsectionSizeQuads = (SubsectionSizeVerts >> Mip) - 1;
        TArray<INDEX_TYPE> NewIndices;
        int32 ExpectedNumIndices = FMath::Square(NumSubsections) * FMath::Square(LodSubsectionSizeQuads) * 6;
        NewIndices.Empty(ExpectedNumIndices);
        int32& MaxIndexFull = IndexRanges[Mip].MaxIndexFull;
        int32& MinIndexFull = IndexRanges[Mip].MinIndexFull;
        MaxIndexFull = 0;
        MinIndexFull = MAX_int32;
        {
            int32 SubOffset = 0;
            for (int32 SubY = 0; SubY < NumSubsections; SubY++)
            {
                for (int32 SubX = 0; SubX < NumSubsections; SubX++)
                {
                    int32& MaxIndex = IndexRanges[Mip].MaxIndex[SubX][SubY];
                    int32& MinIndex = IndexRanges[Mip].MinIndex[SubX][SubY];
                    MaxIndex = 0;
                    MinIndex = MAX_int32;
                    for (int32 y = 0; y < LodSubsectionSizeQuads; y++)
                    {
                        for (int32 x = 0; x < LodSubsectionSizeQuads; x++)
                        {
                            INDEX_TYPE i00 = static_cast<INDEX_TYPE>((x + 0) + (y + 0) * SubsectionSizeVerts + SubOffset);
                            INDEX_TYPE i10 = static_cast<INDEX_TYPE>((x + 1) + (y + 0) * SubsectionSizeVerts + SubOffset);
                            INDEX_TYPE i11 = static_cast<INDEX_TYPE>((x + 1) + (y + 1) * SubsectionSizeVerts + SubOffset);
                            INDEX_TYPE i01 = static_cast<INDEX_TYPE>((x + 0) + (y + 1) * SubsectionSizeVerts + SubOffset);
                            NewIndices.Add(i00);
                            NewIndices.Add(i11);
                            NewIndices.Add(i10);
                            NewIndices.Add(i00);
                            NewIndices.Add(i01);
                            NewIndices.Add(i11);
                            // Update the min/max index ranges
                            MaxIndex = FMath::Max<int32>(MaxIndex, i00);
                            MinIndex = FMath::Min<int32>(MinIndex, i00);
                            MaxIndex = FMath::Max<int32>(MaxIndex, i10);
                            MinIndex = FMath::Min<int32>(MinIndex, i10);
                            MaxIndex = FMath::Max<int32>(MaxIndex, i11);
                            MinIndex = FMath::Min<int32>(MinIndex, i11);
                            MaxIndex = FMath::Max<int32>(MaxIndex, i01);
                            MinIndex = FMath::Min<int32>(MinIndex, i01);
                        }
                    }
                    // update min/max for full subsection
                    MaxIndexFull = FMath::Max<int32>(MaxIndexFull, MaxIndex);
                    MinIndexFull = FMath::Min<int32>(MinIndexFull, MinIndex);
                    SubOffset += FMath::Square(SubsectionSizeVerts);
                }
            }
            check(MinIndexFull <= (uint32)((INDEX_TYPE)(~(INDEX_TYPE)0)));
            check(NewIndices.Num() == ExpectedNumIndices);
        }
        // Create and init new index buffer with index data
        (...)
    }
}
地形渲染
HeightMap格式
HeightMap的格式为RGBA8,RG存储顶点的高度值,R为高8位,G为低8位;BA存储了顶点法线。
高度值、法线向HeightMap数据转换计算方法
// Engine\Source\Runtime\Landscape\Private\LandscapeEditInterface.cpp
TexData.R = Height >> 8;
TexData.G = Height & 255;
TexData.B = static_cast<uint8>(FMath::RoundToInt32(127.5f * (Normal.X + 1.0f)));
TexData.A = static_cast<uint8>(FMath::RoundToInt32(127.5f * (Normal.Y + 1.0f)));
组件中Vertex和Index的组织形式
Index
// Engine\Source\Runtime\Landscape\Public\LandscapeRender.h
class FLandscapeSharedBuffers : public FRefCountedObject
{
public:
    (...)
    // array per mip level, storing FIndexBuffer pointers
    FIndexBuffer** IndexBuffers;
    (...)
};
SharedBuffers 中保存的是 IndexBuffer 的数组,其 IndexBuffer 的索引在组件中的排布如下:

Vertex
Vertex 的创建是在 FLandscapeVertexBuffer::InitRHI 中
// Engine\Source\Runtime\Landscape\Private\LandscapeRender.cpp
void FLandscapeVertexBuffer::InitRHI(FRHICommandListBase& RHICmdList)
{
    SCOPED_LOADTIMER(FLandscapeVertexBuffer_InitRHI);
    (...)
    VertexBufferRHI = RHICmdList.CreateBuffer(NumVertices * sizeof(FLandscapeVertex), BUF_Static | BUF_VertexBuffer, 0, ERHIAccess::VertexOrIndexBuffer, CreateInfo);
    FLandscapeVertex* Vertex = (FLandscapeVertex*)RHICmdList.LockBuffer(VertexBufferRHI, 0, NumVertices * sizeof(FLandscapeVertex), RLM_WriteOnly);
    int32 VertexIndex = 0;
    for (int32 SubY = 0; SubY < NumSubsections; SubY++)
    {
        for (int32 SubX = 0; SubX < NumSubsections; SubX++)
        {
            for (int32 y = 0; y < SubsectionSizeVerts; y++)
            {
                for (int32 x = 0; x < SubsectionSizeVerts; x++)
                {
                    Vertex->VertexX = static_cast<uint8>(x);
                    Vertex->VertexY = static_cast<uint8>(y);
                    Vertex->SubX = static_cast<uint8>(SubX);
                    Vertex->SubY = static_cast<uint8>(SubY);
                    Vertex++;
                    VertexIndex++;
                }
            }
        }
    }
    check(NumVertices == VertexIndex);
    RHICmdList.UnlockBuffer(VertexBufferRHI);
}
// Engine\Source\Runtime\Landscape\Public\LandscapeRender.h
struct FLandscapeVertex
{
    uint8 VertexX; // 顶点在所属Section中的X坐标 
    uint8 VertexY; // 顶点在所属Section中的Y坐标
    uint8 SubX; // 顶点所属Section在Component中的X坐标
    uint8 SubY; // 顶点所属Section在Component中的Y坐标
};
Vertex 以及 Section 坐标在 Component 中的排布如下:

Landscape渲染过程
创建FLandscapeComponentSceneProxy
首先调用FScene::AddPrimitive,在其中嵌套调用FScene::BatchAddPrimitivesInternal
FScene::BatchAddPrimitivesInternal 中遍历所有 UPrimitiveComponent 并创建其 FPrimitiveSceneProxy
// Engine\Source\Runtime\Renderer\Private\RendererScene.cpp
void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
    // If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
    if (Primitive->bBulkReregister)
    {
        return;
    }
    BatchAddPrimitivesInternal(MakeArrayView(&Primitive, 1));
}
// Engine\Source\Runtime\Renderer\Private\RendererScene.cpp
template<class T>
void FScene::BatchAddPrimitivesInternal(TArrayView<T*> InPrimitives)
{
    (...)
    for (T* Primitive : InPrimitives)
    {
        (...)
        FPrimitiveSceneProxy* PrimitiveSceneProxy  = nullptr;
        if (Primitive->GetPrimitiveComponentInterface())
        {
            checkf(!Primitive->GetSceneProxy(), TEXT("Primitive has already been added to the scene!"));
            PrimitiveSceneProxy = Primitive->GetPrimitiveComponentInterface()->CreateSceneProxy();
            check(SceneData.SceneProxy == PrimitiveSceneProxy); // CreateSceneProxy has access to the shared SceneData and should set it properly
        }
        else
        {
            check(!Primitive->ShouldRecreateProxyOnUpdateTransform()); // recreating proxies when updating the transform requires a IPrimitiveComponentInterface
            PrimitiveSceneProxy = Primitive->GetSceneProxy();
        }
        if(!PrimitiveSceneProxy)
        {
            // Primitives which don't have a proxy are irrelevant to the scene manager.
            continue;
        }
        // Create the primitive scene info.
        FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
        PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
        (...)
    }
    (...)
}
当遍历到的 UPrimitiveComponent 为 ULandscapeComponent,则创建 FPrimitiveSceneProxy 时会调用 ULandscapeComponent::CreateSceneProxy,于是创建出的便是地形的 FLandscapeComponentSceneProxy
// Engine\Source\Runtime\Landscape\Private\Landscape.cpp
FPrimitiveSceneProxy* ULandscapeComponent::CreateSceneProxy()
{
    return new FLandscapeComponentSceneProxy(this);
}
FLandscapeComponentSceneProxy 的构造函数,在其中主要完成了调用父类 FPrimitiveSceneProxy 的构造函数;完成 Sections、四边形、顶点数量的设置;设置高度图、权重图;设置LOD;地形材质收集;计算SharedBuffersKey等工作。
需要注意的是,FLandscapeComponentSceneProxy 的构造函数虽然初始化了 SharedBuffers, VertexFactory 等,但是并未真正完成其设置,也就是说此时还未创建 VertexBuffer 和 IndexBuffer 的数据。
渲染资源创建
FLandscapeComponentSceneProxy 创建完成后,使用 ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand) 宏向渲染线程入队AddPrimitiveCommand
// Engine\Source\Runtime\Renderer\Private\RendererScene.cpp
template<class T>
void FScene::BatchAddPrimitivesInternal(TArrayView<T*> InPrimitives)
{
    (...)
    for (T* Primitive : InPrimitives)
    {
        (...)
        CreateCommands.Emplace(
            PrimitiveSceneInfo,
            PrimitiveSceneProxy,
            // If this primitive has a simulated previous transform, ensure that the velocity data for the scene representation is correct.
            FMotionVectorSimulation::Get().GetPreviousTransform(ToUObject(Primitive)),
            RenderMatrix,
            Primitive->Bounds,
            AttachmentRootPosition,
            Primitive->GetLocalBounds()
        );
        (...)
    }
    if (!CreateCommands.IsEmpty())
    {
        ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
            [this, CreateCommands = MoveTemp(CreateCommands)](FRHICommandListBase& RHICmdList)
        {
            for (const FCreateCommand& Command : CreateCommands)
            {
                FScopeCycleCounter Context(Command.PrimitiveSceneProxy->GetStatId());
                Command.PrimitiveSceneProxy->SetTransform(RHICmdList, Command.RenderMatrix, Command.WorldBounds, Command.LocalBounds, Command.AttachmentRootPosition);
                Command.PrimitiveSceneProxy->CreateRenderThreadResources(RHICmdList);
                AddPrimitiveSceneInfo_RenderThread(Command.PrimitiveSceneInfo, Command.PreviousTransform);
            }
        });
    }
}
AddPrimitiveCommand执行时,渲染线程会先调用 FPrimitiveSceneProxy::SetTransform 设置包围盒、变换矩阵,并且创建并设置FLandscapeUniformShaderParams,然后将其加入 FLandscapeComponentSceneProxy::LandscapeUniformShaderParameters(TUniformBuffer类型)
// Engine\Source\Runtime\Engine\Private\PrimitiveSceneProxy.cpp
void FPrimitiveSceneProxy::SetTransform(FRHICommandListBase& RHICmdList, const FMatrix& InLocalToWorld, const FBoxSphereBounds& InBounds, const FBoxSphereBounds& InLocalBounds, FVector InActorPosition)
{
    // Update the cached transforms.
    LocalToWorld = InLocalToWorld;
    bIsLocalToWorldDeterminantNegative = LocalToWorld.Determinant() < 0.0f;
    // Update the cached bounds. Pad them to account for max WPO and material displacement
    const float PadAmount = GetAbsMaxDisplacement();
    Bounds = PadBounds(InBounds, PadAmount);
    LocalBounds = PadLocalBounds(InLocalBounds, LocalToWorld, PadAmount);
    ActorPosition = InActorPosition;
    // Update cached reflection capture.
    if (PrimitiveSceneInfo)
    {
        PrimitiveSceneInfo->bNeedsCachedReflectionCaptureUpdate = true;
        Scene->RequestUniformBufferUpdate(*PrimitiveSceneInfo);
    }
    // Notify the proxy's implementation of the change.
    OnTransformChanged(RHICmdList);
}
SharedBuffers
然后调用 FLandscapeComponentSceneProxy::CreateRenderThreadResources。在其中才是真正设置渲染所需的资源。在 FLandscapeSharedBuffers 构造函数中设置 VertexBuffer, IndexBuffer。然后进一步设置 SharedBuffers 中的 VertexFactory,以及Lod相关参数(以UniformBuffer的形式)
// Engine\Source\Runtime\Landscape\Private\LandscapeRender.cpp
void FLandscapeComponentSceneProxy::CreateRenderThreadResources(FRHICommandListBase& RHICmdList)
{
    (...)
    // 若SharedBuffers已在SharedBuffersMap中,则直接获取;若不在,则根据SharedBufferKey来创建 
    SharedBuffers = FLandscapeComponentSceneProxy::SharedBuffersMap.FindRef(SharedBuffersKey);
    if (SharedBuffers == nullptr)
    {
        FName BufferOwnerName;
        SharedBuffers = new FLandscapeSharedBuffers(RHICmdList, SharedBuffersKey, SubsectionSizeQuads,
                                                                NumSubsections, FeatureLevel, BufferOwnerName);
        FLandscapeComponentSceneProxy::SharedBuffersMap.Add(SharedBuffersKey, SharedBuffers);
        // 设置VertexFactory
        if (!XYOffsetmapTexture)
        {
            FLandscapeVertexFactory* LandscapeVertexFactory = new FLandscapeVertexFactory(FeatureLevel);
            LandscapeVertexFactory->Data.PositionComponent = FVertexStreamComponent(SharedBuffers->VertexBuffer, 0, sizeof(FLandscapeVertex), VET_UByte4);
            LandscapeVertexFactory->InitResource(RHICmdList);
            SharedBuffers->VertexFactory = LandscapeVertexFactory;
        }
        else
        {
            FLandscapeXYOffsetVertexFactory* LandscapeXYOffsetVertexFactory = new FLandscapeXYOffsetVertexFactory(FeatureLevel);
            LandscapeXYOffsetVertexFactory->Data.PositionComponent = FVertexStreamComponent(SharedBuffers->VertexBuffer, 0, sizeof(FLandscapeVertex), VET_UByte4);
            LandscapeXYOffsetVertexFactory->InitResource(RHICmdList);
            SharedBuffers->VertexFactory = LandscapeXYOffsetVertexFactory;
        }
        (...)
    }
    SharedBuffers->AddRef();
    // Assign vertex factory
    VertexFactory = SharedBuffers->VertexFactory;
    FixedGridVertexFactory = SharedBuffers->FixedGridVertexFactory;
    (...)
    LandscapeUniformShaderParameters.InitResource(RHICmdList);
    // 为每个Lod创建并设置uniform buffer
    const int32 NumMips = FMath::CeilLogTwo(SubsectionSizeVerts);
    // create as many as there are potential mips (even if MaxLOD can be inferior than that), because the grass could need that much :
    LandscapeFixedGridUniformShaderParameters.AddDefaulted(NumMips);
    for (int32 LodIndex = 0; LodIndex < NumMips; ++LodIndex)
    {
        FLandscapeFixedGridUniformShaderParameters Parameters;
        Parameters.LodValues = FVector4f(
            static_cast<float>(LodIndex),
            0.f,
            (float)((SubsectionSizeVerts >> LodIndex) - 1),
            1.f / (float)((SubsectionSizeVerts >> LodIndex) - 1));
        LandscapeFixedGridUniformShaderParameters[LodIndex].SetContents(RHICmdList, Parameters);
        LandscapeFixedGridUniformShaderParameters[LodIndex].InitResource(RHICmdList);
    }
    // Create MeshBatch for grass rendering
    (...)
}
AddPrimitiveCommand 执行的最后,渲染线程调用 AddPrimitiveSceneInfo_RenderThread,进行 Primitive 信息的收集,加入到AddedPrimitiveSceneInfos 中
地形材质
地形材质的收集实际上在 FLandscapeComponentSceneProxy 的构造函数中完成,收集并创建的 FMaterialRenderProxy 最终保存在FLandscapeComponentSceneProxy::AvailableMaterials 中
// Engine\Source\Runtime\Landscape\Private\LandscapeRender.cpp
FLandscapeComponentSceneProxy::FLandscapeComponentSceneProxy(ULandscapeComponent* InComponent)
    : (...)
{
    (...)
    TArray<UMaterialInterface*> AvailableMaterialInterfaces;
    if (FeatureLevel == ERHIFeatureLevel::ES3_1)
    {
        WeightmapTextures = InComponent->MobileWeightmapTextures;
        Algo::Transform(InComponent->MobileMaterialInterfaces, AvailableMaterials, GetRenderProxy);
        AvailableMaterialInterfaces.Append(InComponent->MobileMaterialInterfaces);
        //TODO: Add support for bUseDynamicMaterialInstance ?
    }
    else
    {
        WeightmapTextures = InComponent->GetWeightmapTextures();
        if (InComponent->GetLandscapeProxy()->bUseDynamicMaterialInstance)
        {
            Algo::Transform(InComponent->MaterialInstancesDynamic, AvailableMaterials, GetRenderProxy);
            AvailableMaterialInterfaces.Append(InComponent->MaterialInstancesDynamic);
        }
        else
        {
            Algo::Transform(InComponent->MaterialInstances, AvailableMaterials, GetRenderProxy);
            AvailableMaterialInterfaces.Append(InComponent->MaterialInstances);
        }
    }
    (...)
    if (ensure(AvailableMaterialInterfaces.Num() > 0))
    {
        for(int Index = 0; Index < AvailableMaterialInterfaces.Num(); ++Index)
        {
            bool bIsValidMaterial = false;
            UMaterialInterface* MaterialInterface = AvailableMaterialInterfaces[Index];
            if (MaterialInterface != nullptr)
            {
                bIsValidMaterial = true;
                const UMaterial* LandscapeMaterial = MaterialInterface->GetMaterial_Concurrent();
                // In some case it's possible that the Material Instance we have and the Material are not related, for example, in case where content was force deleted, we can have a MIC with no parent, so GetMaterial will fallback to the default material.
                // and since the MIC is not really valid, fallback to 
                UMaterialInstance* MaterialInstance = Cast<UMaterialInstance>(MaterialInterface);
                bIsValidMaterial &= (MaterialInstance == nullptr) || MaterialInstance->IsChildOf(LandscapeMaterial);
                // Check usage flags : 
                bIsValidMaterial &= !bHasStaticLighting || MaterialInterface->CheckMaterialUsage_Concurrent(MATUSAGE_StaticLighting);
            }
            if (!bIsValidMaterial)
            {
                // Replace the landscape material by the default material : 
                MaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface);
                AvailableMaterialInterfaces[Index] = MaterialInterface;
                AvailableMaterials[Index] = MaterialInterface->GetRenderProxy();
            }
        }
    }
    else
    {
        AvailableMaterialInterfaces.Add(UMaterial::GetDefaultMaterial(MD_Surface));
        AvailableMaterials.Add(AvailableMaterialInterfaces.Last()->GetRenderProxy());
    }
    (...)
}
Mesh Drawing
后续的绘制流程与UE Mesh Drawing Pipeline中所述基本一致,此处不再赘述
但需要注意的是,前述的渲染资源创建过程中只是设置了 LandscapeVertex 在当前 LandscapeComponent 中的索引位置。
在BasePass中,需要获取各 VertexFactory 的世界空间位置
// Engine\Shaders\Private\BasePassVertexShader.usf
void Main(
    FVertexFactoryInput Input,
    out FBasePassVSOutput Output
    (...))
{
    (...)
    FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
    float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
    float4 WorldPosition = WorldPositionExcludingWPO;
    (...)
}
各种 VertexFactory 都实现了各自的 GetVertexFactoryIntermediates 和 VertexFactoryGetWorldPosition 方法
LandscapeVertexFactory 中相关的主要函数的实现如下。在其中定义了采样高度图,并将顶点转换到世界空间等操作
// Engine\Shaders\Private\LandscapeVertexFactory.ush
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
    FVertexFactoryIntermediates Intermediates;
    Intermediates.SceneData = VF_GPUSCENE_GET_INTERMEDIATES(Input);
    Intermediates.ComponentIndex = GetComponentLinearIndex();
    Intermediates.LodValues = GetLodValues(Intermediates.ComponentIndex);
    Intermediates.LodBias = GetLodBias(Intermediates.ComponentIndex);
    Intermediates.InputPosition = Input.Position;
#if LANDSCAPE_TILE   
    Intermediates.InputPosition = Intermediates.InputPosition + Input.TileData;
    Intermediates.InputPosition.xy = min(Intermediates.InputPosition.xy, float2(Intermediates.LodValues.z, Intermediates.LodValues.z));
#endif
    float2 xy = Intermediates.InputPosition.xy * Intermediates.LodValues.w;
    float LODCalculated = CalcLOD(Intermediates.ComponentIndex, xy, Intermediates.InputPosition.zw);
    float LodValue = floor(LODCalculated);
    float MorphAlpha = LODCalculated - LodValue;
    // InputPositionLODAdjusted : Position for actual LOD in base LOD units
    float2 ActualLODCoordsInt = floor(Intermediates.InputPosition.xy * pow(2, -(LodValue - Intermediates.LodValues.x)));
    float InvLODScaleFactor = pow(2, -LodValue);
    // Base to Actual LOD, Base to Next LOD
    float2 CoordTranslate = float2( LandscapeParameters.SubsectionSizeVertsLayerUVPan.x * InvLODScaleFactor - 1, max(LandscapeParameters.SubsectionSizeVertsLayerUVPan.x * 0.5f * InvLODScaleFactor, 2) - 1 ) * LandscapeParameters.SubsectionSizeVertsLayerUVPan.y;
    float2 InputPositionLODAdjusted = ActualLODCoordsInt / CoordTranslate.x;
    // InputPositionNextLOD : Position for next LOD in base LOD units
    float2 NextLODCoordsInt = floor(ActualLODCoordsInt * 0.5);
    float2 InputPositionNextLOD = NextLODCoordsInt / CoordTranslate.y;
    // 计算采样纹理坐标
    float2 SampleCoords = InputPositionLODAdjusted * LandscapeParameters.HeightmapUVScaleBias.xy + LandscapeParameters.HeightmapUVScaleBias.zw + 0.5*LandscapeParameters.HeightmapUVScaleBias.xy + Intermediates.InputPosition.zw * LandscapeParameters.SubsectionOffsetParams.xy;
    // 采样高度图
    float4 SampleValue = Texture2DSampleLevel(LandscapeParameters.HeightmapTexture, LandscapeParameters.HeightmapTextureSampler, SampleCoords, LodValue-Intermediates.LodBias.x);
    float Height = DecodePackedHeight(SampleValue.xy);
    float2 SampleCoordsNextLOD = InputPositionNextLOD * LandscapeParameters.HeightmapUVScaleBias.xy + LandscapeParameters.HeightmapUVScaleBias.zw + 0.5*LandscapeParameters.HeightmapUVScaleBias.xy + Intermediates.InputPosition.zw * LandscapeParameters.SubsectionOffsetParams.xy;
    float4 SampleValueNextLOD = Texture2DSampleLevel(LandscapeParameters.HeightmapTexture, LandscapeParameters.HeightmapTextureSampler, SampleCoordsNextLOD, LodValue+1-Intermediates.LodBias.x);
    float HeightNextLOD = DecodePackedHeight(SampleValueNextLOD.xy);
#if LANDSCAPE_XYOFFSET // FEATURE_LEVEL >= FEATURE_LEVEL_SM4 only
    float2 SampleCoords2 = float2(InputPositionLODAdjusted * LandscapeParameters.WeightmapUVScaleBias.xy + LandscapeParameters.WeightmapUVScaleBias.zw + Intermediates.InputPosition.zw * LandscapeParameters.SubsectionOffsetParams.zz);
    float4 OffsetValue = Texture2DSampleLevel( LandscapeParameters.XYOffsetmapTexture, LandscapeParameters.XYOffsetmapTextureSampler, SampleCoords2, LodValue- Intermediates.LodBias.y );
    float2 SampleCoordsNextLOD2 = float2(InputPositionNextLOD * LandscapeParameters.WeightmapUVScaleBias.xy + LandscapeParameters.WeightmapUVScaleBias.zw + Intermediates.InputPosition.zw * LandscapeParameters.SubsectionOffsetParams.zz);
    float4 OffsetValueNextLOD = Texture2DSampleLevel( LandscapeParameters.XYOffsetmapTexture, LandscapeParameters.XYOffsetmapTextureSampler, SampleCoordsNextLOD2, LodValue+1-Intermediates.LodBias.y );
    float2 XYOffset = float2(((OffsetValue.r * 255.0 * 256.0 + OffsetValue.g * 255.0) - 32768.0) * XYOFFSET_SCALE, ((OffsetValue.b * 255.0 * 256.0 + OffsetValue.a * 255.0) - 32768.0) * XYOFFSET_SCALE );
    float2 XYOffsetNextLOD = float2(((OffsetValueNextLOD.r * 255.0 * 256.0 + OffsetValueNextLOD.g * 255.0) - 32768.0) * XYOFFSET_SCALE, ((OffsetValueNextLOD.b * 255.0 * 256.0 + OffsetValueNextLOD.a * 255.0) - 32768.0) * XYOFFSET_SCALE );
    InputPositionLODAdjusted = InputPositionLODAdjusted + XYOffset;
    InputPositionNextLOD = InputPositionNextLOD + XYOffsetNextLOD;
#endif
    Intermediates.LocalPosition = lerp( float3(InputPositionLODAdjusted, Height), float3(InputPositionNextLOD, HeightNextLOD), MorphAlpha );
    float2 Normal = float2(SampleValue.b, SampleValue.a);
    float2 NormalNextLOD = float2(SampleValueNextLOD.b, SampleValueNextLOD.a);
    float2 InterpNormal = lerp( Normal, NormalNextLOD, MorphAlpha ) * float2(2.0,2.0) - float2(1.0,1.0);
    Intermediates.WorldNormal = float3( InterpNormal, sqrt(max(1.0-dot(InterpNormal,InterpNormal),0.0)) );
    return Intermediates;
}
float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
    FDFMatrix LocalToWorld = GetInstanceData(Intermediates).LocalToWorld;
    return TransformLocalToTranslatedWorld(GetLocalPosition(Intermediates), LocalToWorld);
}
float3 GetLocalPosition(FVertexFactoryIntermediates Intermediates)
{
    return Intermediates.LocalPosition+float3(Intermediates.InputPosition.zw * LandscapeParameters.SubsectionOffsetParams.ww,0);
}
渲染流程总结
在一帧中地形渲染大致会经历如下流程:
- 在 GameThread 中进行地形子系统的 Tick,在其中会对 Heightmap, Weightmap 等数据进行更新;
 

- AddPrimitive,创建 LandscapeComponentSceneProxy 并收集地形材质,在渲染线程 AddPrimitiveCommand 中完成 LandscapeUniformShaderParameters 的设置;
 

- 在 FSceneRenderer::Render 的 InitViews 过程中,调用 FSceneRenderer::OnRenderBegin --> FLandscapeSceneViewExtension::PreRenderView_RenderThread --> FLandscapeRenderSystem::UpdateBuffers 完成 LandscapeSectionLODUniformParameters 的设置。在 InitViews 后续流程中完成 MeshDrawCommand 收集;
 

- 在 BassPass 中添加 RDG Pass,在最后 ExecutePass;
 

- 在 Pass 执行时创建 ParallelCommandListSet,创建并入队 FDrawVisibleMeshCommandsAnyThreadTask;
 

- FDrawVisibleMeshCommandsAnyThreadTask 任务是提交 MeshDrawCommand 至新命令队列,并将新命令队列加入 ParallelCommandListSet 中;
 - Pass 执行完毕退出前,ParallelCommandListSet 析构函数中进行 Dispatch 中,其中对其命令队列中的 MeshDrawCommand 进行异步转译,得到 RHICommand;
 

- 添加 RHI 线程任务提交得到的 RHIPlatformCommandList
 

