引擎: 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 函数中构建的,可以缩减一部分三角形。