Contents
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.