Unreal – StaticMesh Analysis

engine: UE4.26.2 build version

StaticMesh, referred to as SM, represents the static mesh in the engine. In the new version of the engine, SM is also allowed to be built at runtime. Rendering an SM requires the support of SMC. SMC uses static/dynamic rendering paths, where allowed. Static rendering paths are used first and all rendering commands are cached.

Class Diagram

UStaticMesh class diagram

classDiagram

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

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

UStaticMeshComponent class diagram

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
}

Process

Dynamic build process

SM can be constructed through the mesh described by FMeshDescription. Generally, MD is wrapped by UStaticMeshDescription, and an SMD object can be created through SM's function.

// 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;
}

The main function of this function is to create an SMD object and register attributes. The FStaticMeshAttributes attribute register registers the following attributes in FMeshDescription.

  • Vertex [Vertex] – A point in space.
    • Position – position
  • VertexInstance[VertexInstance] – A point of a polygon.
    • TextureCoordinate – UVs
    • Normal – Normal
    • Tangent – ​​Tangent
    • BinormalSign – binormal
    • Color – color
  • Edge – The edge of the polygon.
    • IsHard – hard edge
  • Triangle - A polygon is composed of multiple triangles.
  • Polygon - A flat polygon.
    • Normal – Normal
    • Tangent – ​​Tangent
    • BinormalSign – binormal
    • Center – center
  • PolygonGroup – A group of polygons.
    • ImportedMaterialSlotName – Material slot name.

Then we can edit the SMD. In fact, we can edit the MD directly without using the SMD. Here we take the SMD cube as an example.

// 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);
}

After the SMD is built, it can be bound to the SM through the interface.

// 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);
}

This function accepts an SMD array, each element represents a LOD, and then takes out all the contents and forwards them to BuildFromMeshDescriptions. It also means that we can directly call BuildFromMeshDescriptions to bind MD instead of wrapping it through 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;
}

At this point, the dynamic process of SM ends.

Grid application process

The SM needs to be placed in the SMC before it can be displayed, so we start analyzing the grid display process from here.

// 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;
}

Rendering integration process

After the rendering state is updated, the SMC renderer agent is first rebuilt.

// 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;
}

After the proxy is created, FPrimitiveSceneInfo collects primitive information for use by the renderer.

// 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;
}

In addition to setting general properties, we only look at the settings of dynamic/static drawing paths here. After a series of checks in GetViewRelevance, SMC that meets the strict requirements uses static drawing paths. This drawing path will cache a large amount of data instead of rebuilding it every frame. . Different from dynamic drawing paths, when collecting static grid elements, the DrawStaticElements interface is called, and the DrawStaticElements interface fills the grids into FStaticPrimitiveDrawInterface in batches.

// 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;
}

From the above, you can see that the final index buffer used is PreCulledIndexBuffer, which is built in the UpdatePreCulledData function of SMC and can reduce a part of the triangle.

Post Reply