虚幻 – StaticMesh 分析

引擎: UE4.26.2 构建版本

StaticMesh 简称 SM ,表示引擎中的 静态网格体 ,在新版本的引擎中, SM 也被允许在运行时构建,渲染一个 SM 需要 SMC 的支持, SMC 使用 静态/动态渲染路径 ,在允许的情况下优先使用 静态渲染路径 ,会缓存所有渲染命令。

类图

UStaticMesh 类图

classDiagram

IInterface_AssetUserData <|.. UStaticMesh
Interface_CollisionDataProvider <|.. UStaticMesh
UStaticMesh --|> UStreamableRenderAsset
UStaticMesh <-- UStaticMeshComponent

class UStaticMesh {
    +CreateStaticMeshDescription(...) UStaticMeshDescription*
    +BuildFromStaticMeshDescriptions(...)
    +BuildFromMeshDescriptions(...) bool
    +BuildFromMeshDescription(...)
}

UStaticMeshComponent 类图

classDiagram

FPrimitiveSceneProxy <|-- FStaticMeshSceneProxy
UPrimitiveComponent .. FPrimitiveSceneProxy : 镜像
UStaticMeshComponent .. FStaticMeshSceneProxy : 镜像
UActorComponent <|-- USceneComponent
USceneComponent <|-- UPrimitiveComponent
UPrimitiveComponent <|-- UMeshComponent
UMeshComponent <|-- UStaticMeshComponent
UStaticMeshComponent --> UStaticMesh

class UStaticMeshComponent {
    +SetStaticMesh(...) bool
}

class UActorComponent {
    +MarkRenderStateDirty()
    +MarkRenderTransformDirty()
    +MarkRenderDynamicDataDirty()
    +RecreatePhysicsState()
}

class USceneComponent {
    CalcBounds(const FTransform& LocalToWorld) FBoxSphereBounds
}

class UPrimitiveComponent {
    CreateSceneProxy() FPrimitiveSceneProxy*
    GetBodySetup() UBodySetup*
    GetMaterialFromCollisionFaceIndex(...) UMaterialInterface*
}

class UMeshComponent {
    GetNumMaterials() int32
}

class FPrimitiveSceneProxy {
    GetTypeHash() SIZE_T
    DrawStaticElements(...)
    GetViewRelevance(...) FPrimitiveViewRelevance
    CanBeOccluded() bool
    GetMemoryFootprint() uint32
}

class FStaticMeshSceneProxy {
    +GetMeshElement() bool
    +SetMeshElementGeometrySource() uint32
}

流程

动态构建流程

SM 可以通过 FMeshDescription 描述的网格体构建,一般 MD 被 UStaticMeshDescription 包裹,可通过 SM 的函数来创建一个 SMD 对象。

// Engine\Source\Runtime\Engine\Private\StaticMesh.cpp

UStaticMeshDescription* UStaticMesh::CreateStaticMeshDescription(UObject* Outer)
{
    if (Outer == nullptr)
    {
        Outer = GetTransientPackage();
    }

    UStaticMeshDescription* StaticMeshDescription = NewObject<UStaticMeshDescription>(Outer, NAME_None, RF_Transient);
    StaticMeshDescription->RegisterAttributes(); // 注册 MD 属性
    return StaticMeshDescription;
}

该函数的主要功能是创建一个 SMD 对象并且注册属性,其中 FStaticMeshAttributes 属性器在 FMeshDescription 中会注册以下属性。

  • 顶点[Vertex] – 空间中的一个点。
    • Position – 位置
  • 顶点实例[VertexInstance] – 多边形的一个点。
    • TextureCoordinate – UVs
    • Normal – 法线
    • Tangent – 切线
    • BinormalSign – 副法线
    • Color – 颜色
  • 边[Edge] – 多边形的边。
    • IsHard – 硬边
  • 三角形[Triangle] – 多边形由多个三角形组成。
  • 多边形[Polygon] – 一个平面多边形。
    • Normal – 法线
    • Tangent – 切线
    • BinormalSign – 副法线
    • Center – 中心
  • 多边形组[PolygonGroup] – 一组多边形。
    • ImportedMaterialSlotName – 材质插槽名称。

然后我们可以对 SMD 进行编辑,事实上可以不通过 SMD 直接编辑 MD ,这里拿 SMD 立方体做例子。

// Engine\Source\Runtime\StaticMeshDescription\Private\StaticMeshDescription.cpp

void UStaticMeshDescription::CreateCube(FVector Center, FVector HalfExtents, FPolygonGroupID PolygonGroup,
                                        FPolygonID& PolygonID_PlusX,
                                        FPolygonID& PolygonID_MinusX,
                                        FPolygonID& PolygonID_PlusY, 
                                        FPolygonID& PolygonID_MinusY,
                                        FPolygonID& PolygonID_PlusZ,
                                        FPolygonID& PolygonID_MinusZ)
{
    // 创建顶点

    // 通过 RequiredAttributes 拿到顶点集引用
    TVertexAttributesRef<FVector> Positions = GetVertexPositions();

    // 用于储存立方体 8 个顶点的 ID
    FVertexID VertexIDs[8];

    // 申请顶点内存
    MeshDescription.ReserveNewVertices(8);
    for (int32 Index = 0; Index < 8; ++Index)
    {
        // 创建并拿到其 ID
        VertexIDs[Index] = MeshDescription.CreateVertex();
    }

    // 赋值为立方体的四个顶点
    Positions[VertexIDs[0]] = Center + HalfExtents * FVector( 1.0f, -1.0f,  1.0f);
    Positions[VertexIDs[1]] = Center + HalfExtents * FVector( 1.0f,  1.0f,  1.0f);
    Positions[VertexIDs[2]] = Center + HalfExtents * FVector(-1.0f,  1.0f,  1.0f);
    Positions[VertexIDs[3]] = Center + HalfExtents * FVector(-1.0f, -1.0f,  1.0f);
    Positions[VertexIDs[4]] = Center + HalfExtents * FVector(-1.0f,  1.0f, -1.0f);
    Positions[VertexIDs[5]] = Center + HalfExtents * FVector(-1.0f, -1.0f, -1.0f);
    Positions[VertexIDs[6]] = Center + HalfExtents * FVector( 1.0f, -1.0f, -1.0f);
    Positions[VertexIDs[7]] = Center + HalfExtents * FVector( 1.0f,  1.0f, -1.0f);

    // lambda 用于创建一个多边形
    auto MakePolygon = [this, &VertexIDs, PolygonGroup](int32 P0, int32 P1, int32 P2, int32 P3) -> FPolygonID
    {
        // 通过 顶点ID 创建顶点实例
        FVertexInstanceID VertexInstanceIDs[4];
        VertexInstanceIDs[0] = MeshDescription.CreateVertexInstance(VertexIDs[P0]);
        VertexInstanceIDs[1] = MeshDescription.CreateVertexInstance(VertexIDs[P1]);
        VertexInstanceIDs[2] = MeshDescription.CreateVertexInstance(VertexIDs[P2]);
        VertexInstanceIDs[3] = MeshDescription.CreateVertexInstance(VertexIDs[P3]);

        // 获取并设置每个顶点实例的 UV
        TVertexInstanceAttributesRef<FVector2D> UVs = GetVertexInstanceUVs();
        UVs[VertexInstanceIDs[0]] = FVector2D(0.0f, 0.0f);
        UVs[VertexInstanceIDs[1]] = FVector2D(1.0f, 0.0f);
        UVs[VertexInstanceIDs[2]] = FVector2D(1.0f, 1.0f);
        UVs[VertexInstanceIDs[3]] = FVector2D(0.0f, 1.0f);

        // 用于储存多边形的 边ID
        TArray<FEdgeID> EdgeIDs;
        EdgeIDs.Reserve(4);

        // 创建多边形并得到 多边形ID 和 边ID
        FPolygonID PolygonID = MeshDescription.CreatePolygon(PolygonGroup, VertexInstanceIDs, &EdgeIDs);

        // 将每个边设置为硬边
        for (FEdgeID EdgeID : EdgeIDs)
        {
            GetEdgeHardnesses()[EdgeID] = true;
        }

        return PolygonID;
    };

    // 依次创建每个多边形
    PolygonID_PlusX = MakePolygon(0, 1, 7, 6);
    PolygonID_MinusX = MakePolygon(2, 3, 5, 4);
    PolygonID_PlusY = MakePolygon(1, 2, 4, 7);
    PolygonID_MinusY = MakePolygon(3, 0, 6, 5);
    PolygonID_PlusZ = MakePolygon(1, 0, 3, 2);
    PolygonID_MinusZ = MakePolygon(6, 7, 4, 5);

    // 申请 法线、切线、副法线、多边形中心 属性
    MeshDescription.PolygonAttributes().RegisterAttribute<FVector>(MeshAttribute::Polygon::Normal, 1, FVector::ZeroVector, EMeshAttributeFlags::Transient);
    MeshDescription.PolygonAttributes().RegisterAttribute<FVector>(MeshAttribute::Polygon::Tangent, 1, FVector::ZeroVector, EMeshAttributeFlags::Transient);
    MeshDescription.PolygonAttributes().RegisterAttribute<FVector>(MeshAttribute::Polygon::Binormal, 1, FVector::ZeroVector, EMeshAttributeFlags::Transient);
    MeshDescription.PolygonAttributes().RegisterAttribute<FVector>(MeshAttribute::Polygon::Center, 1, FVector::ZeroVector, EMeshAttributeFlags::Transient);

    // 为 MD 中的所有多边形设置 切线、法线、副法线和 中心 。
    FStaticMeshOperations::ComputePolygonTangentsAndNormals(MeshDescription);

    // 使用给定选项重新计算 MD 中每个顶点的任何 法线、切线 或 副法线 。
    FStaticMeshOperations::ComputeTangentsAndNormals(MeshDescription, EComputeNTBsFlags::Normals | EComputeNTBsFlags::Tangents);
}

构建完 SMD 后,可以通过接口绑定到 SM 。

// Engine\Source\Runtime\Engine\Private\StaticMesh.cpp

void UStaticMesh::BuildFromStaticMeshDescriptions(const TArray<UStaticMeshDescription*>& StaticMeshDescriptions, bool bBuildSimpleCollision)
{
    TArray<const FMeshDescription*> MeshDescriptions;
    MeshDescriptions.Reserve(StaticMeshDescriptions.Num());

    for (UStaticMeshDescription* StaticMeshDescription : StaticMeshDescriptions)
    {
        MeshDescriptions.Emplace(&StaticMeshDescription->GetMeshDescription());
    }

    FBuildMeshDescriptionsParams Params;
    Params.bBuildSimpleCollision = bBuildSimpleCollision;
    BuildFromMeshDescriptions(MeshDescriptions, Params);
}

此函数接受一个 SMD 数组,每个元素表示一个 LOD ,然后将内容全部取出转发给 BuildFromMeshDescriptions ,同样这里也表示我们可以直接调用 BuildFromMeshDescriptions 绑定 MD 而不是经过 SMD 包裹。

// Engine\Source\Runtime\Engine\Private\StaticMesh.cpp

bool UStaticMesh::BuildFromMeshDescriptions(const TArray<const FMeshDescription*>& MeshDescriptions, const FBuildMeshDescriptionsParams& Params)
{
    bIsBuiltAtRuntime = true;
    NeverStream = true;

    TOptional<FStaticMeshComponentRecreateRenderStateContext> RecreateRenderStateContext;

    // 如果该 SM 原来包含渲染数据 使用 ReleaseResources 施放
    bool bNewMesh = true;
    if (RenderData.IsValid())
    {
        bNewMesh = false;
        const bool bInvalidateLighting = true;
        const bool bRefreshBounds = true;
        RecreateRenderStateContext = FStaticMeshComponentRecreateRenderStateContext(this, bInvalidateLighting, bRefreshBounds);
        ReleaseResources();
        ReleaseResourcesFence.Wait();
    }

#if WITH_EDITOR

    (......)

#endif

    // 创建新的渲染数据
    RenderData = MakeUnique<FStaticMeshRenderData>();
    RenderData->AllocateLODResources(MeshDescriptions.Num());

    // 遍历每个 LOD 填充渲染数据
    int32 LODIndex = 0;
    for (const FMeshDescription* MeshDescriptionPtr : MeshDescriptions)
    {
#if WITH_EDITOR

        (......)

#endif
        check(MeshDescriptionPtr != nullptr);
        FStaticMeshLODResources& LODResources = RenderData->LODResources[LODIndex];

        // 通过 BuildFromMeshDescription 构建每一层 LOD
        BuildFromMeshDescription(*MeshDescriptionPtr, LODResources);

#if WITH_EDITOR

        (......)

#endif
        LODIndex++;
    }

    // 初始化资源
    InitResources();

    // 计算边界框
    RenderData->Bounds = MeshDescriptions[0]->GetBounds();
    CalculateExtendedBounds();

    // 计算 LOD 对应的屏幕大小
    for (int32 LOD = 0; LOD < MeshDescriptions.Num(); ++LOD)
    {
        if (true)
        {
            const float LODPowerBase = 0.75f;
            RenderData->ScreenSize[LOD].Default = FMath::Pow(LODPowerBase, LOD);
        }
        else
        {

            (......)

        }
    }

    CreateBodySetup();
    check(BodySetup);
    BodySetup->InvalidatePhysicsData();

    // 如果开启了简单碰撞 将包围盒作为简单碰撞
    if (Params.bBuildSimpleCollision)
    {
        FKBoxElem BoxElem;
        BoxElem.Center = RenderData->Bounds.Origin;
        BoxElem.X = RenderData->Bounds.BoxExtent.X * 2.0f;
        BoxElem.Y = RenderData->Bounds.BoxExtent.Y * 2.0f;
        BoxElem.Z = RenderData->Bounds.BoxExtent.Z * 2.0f;
        BodySetup->AggGeom.BoxElems.Add(BoxElem);
        BodySetup->CreatePhysicsMeshes();
    }

    if (!bNewMesh)
    {
        for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter)
        {
            UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(*Iter);
            if (StaticMeshComponent->GetStaticMesh() == this)
            {
                // 重置所有使用该 SM 的 SMC 的物理状态
                if (StaticMeshComponent->IsPhysicsStateCreated())
                {
                    StaticMeshComponent->RecreatePhysicsState();
                }
            }
        }
    }

    return true;
}

void UStaticMesh::BuildFromMeshDescription(const FMeshDescription& MeshDescription, FStaticMeshLODResources& LODResources)
{
    // MD 静态属性器
    FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription);

    // 填充 顶点缓冲区

    int32 NumVertexInstances = MeshDescription.VertexInstances().GetArraySize();
    int32 NumTriangles = MeshDescription.Triangles().Num();

    if (NumVertexInstances == 0 || NumTriangles == 0)
    {
        return;
    }

    TArray<FStaticMeshBuildVertex> StaticMeshBuildVertices;
    StaticMeshBuildVertices.SetNum(NumVertexInstances);

    // 拿到各项属性的访问器
    TVertexAttributesConstRef<FVector> VertexPositions = MeshDescriptionAttributes.GetVertexPositions();
    TVertexInstanceAttributesConstRef<FVector> VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals();
    TVertexInstanceAttributesConstRef<FVector> VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents();
    TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns();
    TVertexInstanceAttributesConstRef<FVector4> VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors();
    TVertexInstanceAttributesConstRef<FVector2D> VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs();

    // 遍历顶点实例 复制顶点 位置 切线 UV
    for (FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
    {
        FStaticMeshBuildVertex& StaticMeshVertex = StaticMeshBuildVertices[VertexInstanceID.GetValue()];

        StaticMeshVertex.Position = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)];
        StaticMeshVertex.TangentX = VertexInstanceTangents[VertexInstanceID];
        StaticMeshVertex.TangentY = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
        StaticMeshVertex.TangentZ = VertexInstanceNormals[VertexInstanceID];

        for (int32 UVIndex = 0; UVIndex < VertexInstanceUVs.GetNumIndices(); ++UVIndex)
        {
            StaticMeshVertex.UVs[UVIndex] = VertexInstanceUVs.Get(VertexInstanceID, UVIndex);
        }
    }

    // 复制 顶点颜色
    bool bHasVertexColors = false;
    if (VertexInstanceColors.IsValid())
    {
        for (FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
        {
            FStaticMeshBuildVertex& StaticMeshVertex = StaticMeshBuildVertices[VertexInstanceID.GetValue()];

            FLinearColor Color(VertexInstanceColors[VertexInstanceID]);
            if (Color != FLinearColor::White)
            {
                bHasVertexColors = true;
            }

            StaticMeshVertex.Color = Color.ToFColor(true);
        }
    }

    // 初始化 位置缓冲区 和 切线&UV缓冲区
    LODResources.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
    LODResources.VertexBuffers.StaticMeshVertexBuffer.Init(StaticMeshBuildVertices, VertexInstanceUVs.GetNumIndices());

    // 初始化 颜色缓冲区
    FColorVertexBuffer& ColorVertexBuffer = LODResources.VertexBuffers.ColorVertexBuffer;
    if (bHasVertexColors)
    {
        ColorVertexBuffer.Init(StaticMeshBuildVertices);
    }
    else
    {
        ColorVertexBuffer.InitFromSingleColor(FColor::White, NumVertexInstances);
    }

    // 填充 索引缓冲区

    int32 NumPolygonGroups = MeshDescription.PolygonGroups().Num();

    TPolygonGroupAttributesConstRef<FName> MaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames();

    TArray<uint32> IndexBuffer;
    IndexBuffer.SetNumZeroed(NumTriangles * 3);

    FStaticMeshLODResources::FStaticMeshSectionArray& Sections = LODResources.Sections;

    int32 SectionIndex = 0;
    int32 IndexBufferIndex = 0;

    // 默认使用 16 位缓冲区
    EIndexBufferStride::Type IndexBufferStride = EIndexBufferStride::Force16Bit; 

    // 遍历每个多边形组 每个多边形组将被当做一个 SM 分段
    for (FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs())
    {
        // 跳过不包含多边形的多边形组
        if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0)
        {
            continue;
        }

        // 添加一个新分段
        FStaticMeshSection& Section = Sections.AddDefaulted_GetRef();
        Section.FirstIndex = IndexBufferIndex; // 当前分段在索引缓冲区的偏移

        int32 TriangleCount = 0; // 三角形数
        uint32 MinVertexIndex = TNumericLimits<uint32>::Max(); // 最大顶点
        uint32 MaxVertexIndex = TNumericLimits<uint32>::Min(); // 最小顶点

        // 计算上面的三项 并 填充索引缓冲区
        for (FPolygonID PolygonID : MeshDescription.GetPolygonGroupPolygons(PolygonGroupID))
        {
            for (FTriangleID TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID))
            {
                for (FVertexInstanceID TriangleVertexInstanceIDs : MeshDescription.GetTriangleVertexInstances(TriangleID))
                {
                    uint32 VertexIndex = static_cast<uint32>(TriangleVertexInstanceIDs.GetValue());
                    MinVertexIndex = FMath::Min(MinVertexIndex, VertexIndex);
                    MaxVertexIndex = FMath::Max(MaxVertexIndex, VertexIndex);
                    IndexBuffer[IndexBufferIndex] = VertexIndex; // 填充索引缓冲区
                    IndexBufferIndex++;
                }

                TriangleCount++;
            }
        }

        Section.NumTriangles = TriangleCount;
        Section.MinVertexIndex = MinVertexIndex;
        Section.MaxVertexIndex = MaxVertexIndex;

        // 根据材质插槽名得到材质索引
        const int32 MaterialIndex = StaticMaterials.IndexOfByPredicate(
            [&MaterialSlotName = MaterialSlotNames[PolygonGroupID]](const FStaticMaterial& StaticMaterial) { return StaticMaterial.MaterialSlotName == MaterialSlotName; }
        );

        Section.MaterialIndex = MaterialIndex;
        Section.bEnableCollision = true;
        Section.bCastShadow = true;

        // 如果索引超出了 16 位则使用 32 位索引缓冲区
        if (MaxVertexIndex > TNumericLimits<uint16>::Max())
        {
            IndexBufferStride = EIndexBufferStride::Force32Bit;
        }

        SectionIndex++;
    }
    check(IndexBufferIndex == NumTriangles * 3);

    LODResources.IndexBuffer.SetIndices(IndexBuffer, IndexBufferStride);

    // 填充 仅深度索引缓冲区 在绘制深度缓冲区时使用
    TArray<uint32> DepthOnlyIndexBuffer(IndexBuffer);
    for (uint32& Index : DepthOnlyIndexBuffer)
    {
        // 将相同顶点的实例用唯一索引表示
        Index = MeshDescription.GetVertexVertexInstances(MeshDescription.GetVertexInstanceVertex(FVertexInstanceID(Index)))[0].GetValue();
    }

    LODResources.bHasDepthOnlyIndices = true;
    LODResources.DepthOnlyIndexBuffer.SetIndices(DepthOnlyIndexBuffer, IndexBufferStride);
    LODResources.DepthOnlyNumTriangles = NumTriangles;

    // 填充 反向顶点缓冲区
    TArray<uint32> ReversedIndexBuffer(IndexBuffer);
    for (int32 ReversedIndexBufferIndex = 0; ReversedIndexBufferIndex < IndexBuffer.Num(); ReversedIndexBufferIndex += 3)
    {
        Swap(ReversedIndexBuffer[ReversedIndexBufferIndex + 0], ReversedIndexBuffer[ReversedIndexBufferIndex + 2]);
    }

    LODResources.AdditionalIndexBuffers = new FAdditionalStaticMeshIndexBuffers();
    LODResources.bHasReversedIndices = true;
    LODResources.AdditionalIndexBuffers->ReversedIndexBuffer.SetIndices(ReversedIndexBuffer, IndexBufferStride);

    // 填充 反向仅深度索引缓冲区
    TArray<uint32> ReversedDepthOnlyIndexBuffer(DepthOnlyIndexBuffer);
    for (int32 ReversedIndexBufferIndex = 0; ReversedIndexBufferIndex < IndexBuffer.Num(); ReversedIndexBufferIndex += 3)
    {
        Swap(ReversedDepthOnlyIndexBuffer[ReversedIndexBufferIndex + 0], ReversedDepthOnlyIndexBuffer[ReversedIndexBufferIndex + 2]);
    }

    LODResources.bHasReversedDepthOnlyIndices = true;
    LODResources.AdditionalIndexBuffers->ReversedDepthOnlyIndexBuffer.SetIndices(ReversedIndexBuffer, IndexBufferStride);

    LODResources.bHasAdjacencyInfo = false;
}

至此 SM 的动态流程结束。

网格应用流程

SM 需要被放入 SMC 中才能显示,所以我们从此开始分析 网格显示流程 。

// Engine\Source\Runtime\Engine\Private\Components\StaticMeshComponent.cpp

bool UStaticMeshComponent::SetStaticMesh(UStaticMesh* NewMesh)
{
    // 如果新的和旧的 SM 相同 返回 false
    if(NewMesh == GetStaticMesh())
    {
        return false;
    }

    // 如果 SMC 被设置为 静态 则不允许修改 SM
    AActor* Owner = GetOwner();
    if (UWorld * World = GetWorld())
    {
        if (World->HasBegunPlay() && !AreDynamicDataChangesAllowed() && Owner != nullptr)
        {
            FMessageLog("PIE").Warning(FText::Format(LOCTEXT("SetMeshOnStatic", "Calling SetStaticMesh on '{0}' but Mobility is Static."),
                FText::FromString(GetPathName())));
            return false;
        }
    }

    PRAGMA_DISABLE_DEPRECATION_WARNINGS
    StaticMesh = NewMesh;
    PRAGMA_ENABLE_DEPRECATION_WARNINGS

    if (StaticMesh != nullptr && StaticMesh->RenderData != nullptr && FApp::CanEverRender() && !StaticMesh->HasAnyFlags(RF_ClassDefaultObject))
    {
        checkf(StaticMesh->RenderData->IsInitialized(), TEXT("Uninitialized Renderdata for Mesh: %s, Mesh NeedsLoad: %i, Mesh NeedsPostLoad: %i, Mesh Loaded: %i, Mesh NeedInit: %i, Mesh IsDefault: %i")
            , *StaticMesh->GetFName().ToString()
            , StaticMesh->HasAnyFlags(RF_NeedLoad)
            , StaticMesh->HasAnyFlags(RF_NeedPostLoad)
            , StaticMesh->HasAnyFlags(RF_LoadCompleted)
            , StaticMesh->HasAnyFlags(RF_NeedInitialization)
            , StaticMesh->HasAnyFlags(RF_ClassDefaultObject)
        );
    }

    // 标记渲染状态改变
    MarkRenderStateDirty();

    // 重置物理状态
    RecreatePhysicsState();

    // 更新导航信息
    bNavigationRelevant = IsNavigationRelevant();

    // 更新此组件流数据
    IStreamingManager::Get().NotifyPrimitiveUpdated(this);

    // 更新边界框
    UpdateBounds();

    // 标记缓存的材料参数名称改变
    MarkCachedMaterialParameterNameIndicesDirty();

#if WITH_EDITOR

    (......)

#endif

#if WITH_EDITORONLY_DATA

    (......)

#endif

    return true;
}

渲染整合流程

在渲染状态更新后,首先重建 SMC 渲染器代理。

// Engine\Source\Runtime\Engine\Private\StaticMeshRender.cpp

FPrimitiveSceneProxy* UStaticMeshComponent::CreateSceneProxy()
{
    // 如果 SM 无效 不创建代理
    if (GetStaticMesh() == nullptr || GetStaticMesh()->RenderData == nullptr)
    {
        return nullptr;
    }

    // 如果 LOD 设置无效  不创建代理
    const FStaticMeshLODResourcesArray& LODResources = GetStaticMesh()->RenderData->LODResources;
    if (LODResources.Num() == 0 || LODResources[FMath::Clamp<int32>(GetStaticMesh()->MinLOD.Default, 0, LODResources.Num()-1)].VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0)
    {
        return nullptr;
    }
    LLM_SCOPE(ELLMTag::StaticMesh);

    // 创建代理
    FPrimitiveSceneProxy* Proxy = ::new FStaticMeshSceneProxy(this, false);
#if STATICMESH_ENABLE_DEBUG_RENDERING
    SendRenderDebugPhysics(Proxy);
#endif

    return Proxy;
}

代理建立后, FPrimitiveSceneInfo 会收集图元信息以供渲染器使用。

// Engine\Source\Runtime\Engine\Private\StaticMeshRender.cpp

FPrimitiveViewRelevance FStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const
{   
    checkSlow(IsInParallelRenderingThread());

    FPrimitiveViewRelevance Result;
    Result.bDrawRelevance = IsShown(View) && View->Family->EngineShowFlags.StaticMeshes;
    Result.bRenderCustomDepth = ShouldRenderCustomDepth();
    Result.bRenderInMainPass = ShouldRenderInMainPass();
    Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
    Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow;

#if STATICMESH_ENABLE_DEBUG_RENDERING
    bool bDrawSimpleCollision = false, bDrawComplexCollision = false;
    const bool bInCollisionView = IsCollisionView(View->Family->EngineShowFlags, bDrawSimpleCollision, bDrawComplexCollision);
#else
    bool bInCollisionView = false;
#endif
    const bool bAllowStaticLighting = FReadOnlyCVARCache::Get().bAllowStaticLighting;

    if(
#if !(UE_BUILD_SHIPPING) || WITH_EDITOR
        IsRichView(*View->Family) || 
        View->Family->EngineShowFlags.Collision ||
        bInCollisionView ||
        View->Family->EngineShowFlags.Bounds ||
#endif
#if WITH_EDITOR
        (IsSelected() && View->Family->EngineShowFlags.VertexColors) ||
        (IsSelected() && View->Family->EngineShowFlags.PhysicalMaterialMasks) ||
#endif
#if STATICMESH_ENABLE_DEBUG_RENDERING
        bDrawMeshCollisionIfComplex ||
        bDrawMeshCollisionIfSimple ||
#endif
        (bAllowStaticLighting && HasStaticLighting() && !HasValidSettingsForStaticLighting()) ||
        HasViewDependentDPG()
        )
    {
        Result.bDynamicRelevance = true;

#if STATICMESH_ENABLE_DEBUG_RENDERING
        if(View->Family->EngineShowFlags.Collision || bInCollisionView)
        {
            Result.bDrawRelevance = true;
        }
#endif
    }
    else
    {
        Result.bStaticRelevance = true;

#if WITH_EDITOR
        Result.bEditorStaticSelectionRelevance = (IsSelected() || IsHovered());
#endif
    }

    Result.bShadowRelevance = IsShadowCast(View);

    MaterialRelevance.SetPrimitiveViewRelevance(Result);

    if (!View->Family->EngineShowFlags.Materials 
#if STATICMESH_ENABLE_DEBUG_RENDERING
        || bInCollisionView
#endif
        )
    {
        Result.bOpaque = true;
    }

    Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass;

    return Result;
}

除开设置一般属性,我们这里只看 动态/静态绘制路径 的设置,在 GetViewRelevance 中进行一系列检查后,符合苛刻要求的 SMC 使用 静态绘制路径 ,此绘制路径会将大量的数据缓存而不是每帧重建。与动态绘制路径不一样的是,在收集静态网格元素时,调用的是 DrawStaticElements 接口, DrawStaticElements 接口将网格批次填充到 FStaticPrimitiveDrawInterface 中。

// Engine\Source\Runtime\Engine\Private\StaticMeshRender.cpp

void FStaticMeshSceneProxy::DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)
{
    checkSlow(IsInParallelRenderingThread());
    if (!HasViewDependentDPG())
    {
        // 确定图元使用的 DPG
        uint8 PrimitiveDPG = GetStaticDepthPriorityGroup();
        int32 NumLODs = RenderData->LODResources.Num();
        bool bIsMeshElementSelected = false;
        const auto FeatureLevel = GetScene().GetFeatureLevel();
        const bool IsMobile = IsMobilePlatform(GetScene().GetShaderPlatform());
        const int32 NumRuntimeVirtualTextureTypes = RuntimeVirtualTextureMaterialTypes.Num();

        // 如果 LOD 被强制固定了
        if (ForcedLodModel > 0) 
        {
            // 获取 LOD
            int32 LODIndex = FMath::Clamp(ForcedLodModel, ClampedMinLOD + 1, NumLODs) - 1;
            const FStaticMeshLODResources& LODModel = RenderData->LODResources[LODIndex];

            // 绘制所有分段
            for(int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
            {
#if WITH_EDITOR

                (......)

#endif // WITH_EDITOR

                const int32 NumBatches = GetNumMeshBatches();
                PDI->ReserveMemoryForMeshes(NumBatches * (1 + NumRuntimeVirtualTextureTypes));

                // 绘制所有网格批次
                for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
                {
                    FMeshBatch BaseMeshBatch;

                    if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, PrimitiveDPG, bIsMeshElementSelected, true, BaseMeshBatch))
                    {
                        // 该渲染批次用于处理 RVT
                        if (NumRuntimeVirtualTextureTypes > 0)
                        {
                            FMeshBatch MeshBatch(BaseMeshBatch);
                            SetupMeshBatchForRuntimeVirtualTexture(MeshBatch);
                            for (ERuntimeVirtualTextureMaterialType MaterialType : RuntimeVirtualTextureMaterialTypes)
                            {
                                MeshBatch.RuntimeVirtualTextureMaterialType = (uint32)MaterialType;
                                PDI->DrawMesh(MeshBatch, FLT_MAX);
                            }
                        }

                        // 这是网格本身的渲染批次
                        {
                            // 提交网格批次 设置 LOD 切换屏幕大小为最大
                            PDI->DrawMesh(BaseMeshBatch, FLT_MAX);
                        }
                    }
                }
            }
        } 
        else // 如果没有设置 LOD 则根据屏幕大小切换
        {
            // 遍历每个 LOD 层级
            for(int32 LODIndex = ClampedMinLOD; LODIndex < NumLODs; LODIndex++)
            {
                const FStaticMeshLODResources& LODModel = RenderData->LODResources[LODIndex];
                float ScreenSize = GetScreenSize(LODIndex);

                bool bUseUnifiedMeshForShadow = false;
                bool bUseUnifiedMeshForDepth = false;

                (......)

                // 绘制所有分段
                for(int32 SectionIndex = 0;SectionIndex < LODModel.Sections.Num();SectionIndex++)
                {
#if WITH_EDITOR

                    (......)

#endif // WITH_EDITOR

                    const int32 NumBatches = GetNumMeshBatches();
                    PDI->ReserveMemoryForMeshes(NumBatches * (1 + NumRuntimeVirtualTextureTypes));

                    // 绘制所有网格批次
                    for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
                    {
                        FMeshBatch BaseMeshBatch;
                        if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, PrimitiveDPG, bIsMeshElementSelected, true, BaseMeshBatch))
                        {
                            // 该渲染批次用于处理 RVT
                            if (NumRuntimeVirtualTextureTypes > 0)
                            {
                                FMeshBatch MeshBatch(BaseMeshBatch);
                                SetupMeshBatchForRuntimeVirtualTexture(MeshBatch);

                                for (ERuntimeVirtualTextureMaterialType MaterialType : RuntimeVirtualTextureMaterialTypes)
                                {
                                    MeshBatch.RuntimeVirtualTextureMaterialType = (uint32)MaterialType;
                                    PDI->DrawMesh(MeshBatch, ScreenSize);
                                }
                            }

                            // 这是网格本身的渲染批次
                            {
                                FMeshBatch MeshBatch(BaseMeshBatch);
                                MeshBatch.CastShadow &= !bUseUnifiedMeshForShadow;
                                MeshBatch.bUseAsOccluder &= !bUseUnifiedMeshForDepth;
                                MeshBatch.bUseForDepthPass &= !bUseUnifiedMeshForDepth;

                                // 提交网格批次 设置 LOD 切换屏幕大小
                                PDI->DrawMesh(MeshBatch, ScreenSize);
                            }
                        }
                    }
                }
            }
        }
    }
}

bool FStaticMeshSceneProxy::GetMeshElement(
    int32 LODIndex,
    int32 BatchIndex,
    int32 SectionIndex,
    uint8 InDepthPriorityGroup,
    bool bUseSelectionOutline,
    bool bAllowPreCulledIndices,
    FMeshBatch& OutMeshBatch) const
{
    const ERHIFeatureLevel::Type FeatureLevel = GetScene().GetFeatureLevel();
    const FStaticMeshLODResources& LOD = RenderData->LODResources[LODIndex];
    const FStaticMeshVertexFactories& VFs = RenderData->LODVertexFactories[LODIndex];
    const FStaticMeshSection& Section = LOD.Sections[SectionIndex];
    const FLODInfo& ProxyLODInfo = LODs[LODIndex];

    UMaterialInterface* MaterialInterface = ProxyLODInfo.Sections[SectionIndex].Material;
    FMaterialRenderProxy* MaterialRenderProxy = MaterialInterface->GetRenderProxy();
    const FMaterial* Material = MaterialRenderProxy->GetMaterial(FeatureLevel);

    const FVertexFactory* VertexFactory = nullptr;

    FMeshBatchElement& OutMeshBatchElement = OutMeshBatch.Elements[0];

#if WITH_EDITORONLY_DATA

    (......)

#endif

    // SMC 是否覆盖了此网格 LOD 的顶点颜色 使用不同的顶点工厂
    if (ProxyLODInfo.OverrideColorVertexBuffer)
    {
        check(Section.MaxVertexIndex < ProxyLODInfo.OverrideColorVertexBuffer->GetNumVertices())

        VertexFactory = &VFs.VertexFactoryOverrideColorVertexBuffer;

        OutMeshBatchElement.VertexFactoryUserData = ProxyLODInfo.OverrideColorVFUniformBuffer.GetReference();
        OutMeshBatchElement.UserData = ProxyLODInfo.OverrideColorVertexBuffer;
        OutMeshBatchElement.bUserDataIsColorVertexBuffer = true;
    }
    else
    {
        VertexFactory = &VFs.VertexFactory;

        OutMeshBatchElement.VertexFactoryUserData = VFs.VertexFactory.GetUniformBuffer();
    }

    const bool bWireframe = false;

    const bool bRequiresAdjacencyInformation = !bUseSelectionOutline && RequiresAdjacencyInformation(MaterialInterface, VertexFactory->GetType(), FeatureLevel);

    const bool bUseReversedIndices = GUseReversedIndexBuffer && IsLocalToWorldDeterminantNegative() && (LOD.bHasReversedIndices != 0) && !bRequiresAdjacencyInformation && !Material->IsTwoSided();

    const bool bDitheredLODTransition = !IsMovable() && Material->IsDitheredLODTransition();

    const uint32 NumPrimitives = SetMeshElementGeometrySource(LODIndex, SectionIndex, bWireframe, bRequiresAdjacencyInformation, bUseReversedIndices, bAllowPreCulledIndices, VertexFactory, OutMeshBatch);

    if(NumPrimitives > 0)
    {
        OutMeshBatch.SegmentIndex = SectionIndex;

        OutMeshBatch.LODIndex = LODIndex;
#if STATICMESH_ENABLE_DEBUG_RENDERING
        OutMeshBatch.VisualizeLODIndex = LODIndex;
        OutMeshBatch.VisualizeHLODIndex = HierarchicalLODIndex;
#endif
        OutMeshBatch.ReverseCulling = IsReversedCullingNeeded(bUseReversedIndices);
        OutMeshBatch.CastShadow = bCastShadow && Section.bCastShadow;
#if RHI_RAYTRACING

        (......)

#endif
        OutMeshBatch.DepthPriorityGroup = (ESceneDepthPriorityGroup)InDepthPriorityGroup;
        OutMeshBatch.LCI = &ProxyLODInfo;
        OutMeshBatch.MaterialRenderProxy = MaterialRenderProxy;

        OutMeshBatchElement.MinVertexIndex = Section.MinVertexIndex;
        OutMeshBatchElement.MaxVertexIndex = Section.MaxVertexIndex;
#if STATICMESH_ENABLE_DEBUG_RENDERING
        OutMeshBatchElement.VisualizeElementIndex = SectionIndex;
#endif

        SetMeshElementScreenSize(LODIndex, bDitheredLODTransition, OutMeshBatch);

        return true;
    }
    else
    {
        return false;
    }
}

uint32 FStaticMeshSceneProxy::SetMeshElementGeometrySource(
    int32 LODIndex,
    int32 SectionIndex,
    bool bWireframe,
    bool bRequiresAdjacencyInformation,
    bool bUseReversedIndices,
    bool bAllowPreCulledIndices,
    const FVertexFactory* VertexFactory,
    FMeshBatch& OutMeshBatch) const
{
    const FStaticMeshLODResources& LODModel = RenderData->LODResources[LODIndex];
    const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
    const FLODInfo& LODInfo = LODs[LODIndex];
    const FLODInfo::FSectionInfo& SectionInfo = LODInfo.Sections[SectionIndex];

    FMeshBatchElement& OutMeshBatchElement = OutMeshBatch.Elements[0];
    uint32 NumPrimitives = 0;

    const bool bHasPreculledTriangles = LODInfo.Sections[SectionIndex].NumPreCulledTriangles >= 0;
    const bool bUsePreculledIndices = bAllowPreCulledIndices && GUsePreCulledIndexBuffer && bHasPreculledTriangles;

    if (bWireframe)
    {

        (......)

    }
    else
    {
        OutMeshBatch.Type = PT_TriangleList;

        if (bUsePreculledIndices)
        {
            OutMeshBatchElement.IndexBuffer = LODInfo.PreCulledIndexBuffer;
            OutMeshBatchElement.FirstIndex = SectionInfo.FirstPreCulledIndex;
            NumPrimitives = SectionInfo.NumPreCulledTriangles;
        }
        else
        {
            OutMeshBatchElement.IndexBuffer = bUseReversedIndices ? &LODModel.AdditionalIndexBuffers->ReversedIndexBuffer : &LODModel.IndexBuffer;
            OutMeshBatchElement.FirstIndex = Section.FirstIndex;
            NumPrimitives = Section.NumTriangles;
        }
    }

    (......)

    OutMeshBatchElement.NumPrimitives = NumPrimitives;
    OutMeshBatch.VertexFactory = VertexFactory;

    return NumPrimitives;
}

从上面可以看到最终使用的索引缓冲区是 PreCulledIndexBuffer ,该缓冲区是在 SMC 的 UpdatePreCulledData 函数中构建的,可以缩减一部分三角形。

发表回复