前言:为什么要通过这样的方式构建StaticMesh呢,需求是这样的:多线程创建海量UStaticMesh。但是UStaticMesh只能在GameThread线程创建,但是GameThread线程是主线程,如果含有大量耗时操作会导致线程阻塞,有没有一种方式在后台线程构建呢?这就是本文要解决的问题。为什么不通过MeshDescription直接构建呢?因为UStaticMesh的BuildFromMeshDescriptions不是静态方法,如果在主线程构建,还有有同样的问题,阻塞主线程。
(以下方法构建的UStaticMesh仅具有简单碰撞)
1、通过MeshDescription构建RenderData(发生在后台线程,包括MeshDescirption的构建也是在后台线程,只是这里不在给出了)
1)通过MeshDescription构建FStaticMeshLODResources
这个方法实际就是UStaticMesh的实例方法BuildFromMeshDescription,但是进行了改动,改动部分主要是增加了MaterialIndex参数,原始方法是自动计算的,但是这里进行了指定,因为自动计算需要UStaticMesh参与,但是此时还没有UStaticMesh。
void BuildFromMeshDescription(const FMeshDescription& MeshDescription, const int32 MaterialIndex, FStaticMeshLODResources& LODResources)
{
FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription);
// Fill vertex buffers
int32 NumVertexInstances = MeshDescription.VertexInstances().GetArraySize();
int32 NumTriangles = MeshDescription.Triangles().Num();
if (NumVertexInstances == 0 || NumTriangles == 0)
{
return;
}
TArray<FStaticMeshBuildVertex> StaticMeshBuildVertices;
StaticMeshBuildVertices.SetNum(NumVertexInstances);
TVertexAttributesConstRef<FVector3f> VertexPositions = MeshDescriptionAttributes.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector4f> VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors();
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs();
for (FVertexInstanceID VertexInstanceID : MeshDescription.VertexInstances().GetElementIDs())
{
FStaticMeshBuildVertex& StaticMeshVertex = StaticMeshBuildVertices[VertexInstanceID.GetValue()];
StaticMeshVertex.Position = VertexPositions[MeshDescription.GetVertexInstanceVertex(VertexInstanceID)];
StaticMeshVertex.TangentX = VertexInstanceTangents[VertexInstanceID];
StaticMeshVertex.TangentY = FVector3f::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID];
StaticMeshVertex.TangentZ = VertexInstanceNormals[VertexInstanceID];
for (int32 UVIndex = 0; UVIndex < VertexInstanceUVs.GetNumChannels(); ++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);
}
else
{
StaticMeshVertex.Color = FColor::White;
}
}
}
LODResources.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
FStaticMeshVertexBufferFlags StaticMeshVertexBufferFlags;
StaticMeshVertexBufferFlags.bNeedsCPUAccess = true;
StaticMeshVertexBufferFlags.bUseBackwardsCompatibleF16TruncUVs = false;
LODResources.VertexBuffers.StaticMeshVertexBuffer.Init(StaticMeshBuildVertices, VertexInstanceUVs.GetNumChannels(), StaticMeshVertexBufferFlags);
LODResources.bHasColorVertexData = bHasVertexColors;
FColorVertexBuffer& ColorVertexBuffer = LODResources.VertexBuffers.ColorVertexBuffer;
if (bHasVertexColors)
{
ColorVertexBuffer.Init(StaticMeshBuildVertices);
}
else
{
ColorVertexBuffer.InitFromSingleColor(FColor::White, NumVertexInstances);
}
// Fill index buffer and sections array
int32 NumPolygonGroups = MeshDescription.PolygonGroups().Num();
TPolygonGroupAttributesConstRef<FName> MaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames();
TArray<uint32> IndexBuffer;
IndexBuffer.SetNumZeroed(NumTriangles * 3);
FStaticMeshSectionArray& Sections = LODResources.Sections;
int32 SectionIndex = 0;
int32 IndexBufferIndex = 0;
EIndexBufferStride::Type IndexBufferStride = EIndexBufferStride::Force16Bit;
for (FPolygonGroupID PolygonGroupID : MeshDescription.PolygonGroups().GetElementIDs())
{
// Skip empty polygon groups - we do not want to build empty sections
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 (FTriangleID TriangleID : MeshDescription.GetPolygonGroupTriangles(PolygonGroupID))
{
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;
Section.MaterialIndex = MaterialIndex;
Section.bEnableCollision = true;
Section.bCastShadow = true;
if (MaxVertexIndex > TNumericLimits<uint16>::Max())
{
IndexBufferStride = EIndexBufferStride::Force32Bit;
}
SectionIndex++;
}
check(IndexBufferIndex == NumTriangles * 3);
LODResources.IndexBuffer.SetIndices(IndexBuffer, IndexBufferStride);
// Fill depth only index buffer
TArray<uint32> DepthOnlyIndexBuffer(IndexBuffer);
for (uint32& Index : DepthOnlyIndexBuffer)
{
// Compress all vertex instances into the same instance for each vertex
Index = MeshDescription.GetVertexVertexInstanceIDs(MeshDescription.GetVertexInstanceVertex(FVertexInstanceID(Index)))[0].GetValue();
}
LODResources.bHasDepthOnlyIndices = true;
LODResources.DepthOnlyIndexBuffer.SetIndices(DepthOnlyIndexBuffer, IndexBufferStride);
LODResources.DepthOnlyNumTriangles = NumTriangles;
LODResources.bHasColorVertexData = true;
// Fill reversed index buffer
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);
// Fill reversed depth index buffer
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);
}
2)通过MeshDescription集合构建FStaticMeshRenderData
实际上这个方法也是来自UStaticMesh的实例方法BuildRenderDataFromMeshDescriptions,但是进行了删减
FStaticMeshRenderData* BuildRenderDataFromMeshDescriptions(const TArray<const FMeshDescription*>& MeshDescriptions, const UStaticMesh::FBuildMeshDescriptionsParams& Params)
{
FStaticMeshRenderData* RenderData = new FStaticMeshRenderData;
const int32 NewNumLODs = MeshDescriptions.Num();
RenderData->AllocateLODResources(NewNumLODs);
FStaticMeshLODResourcesArray& LODResourcesArray = RenderData->LODResources;
for (int32 LODIndex = 0; LODIndex < LODResourcesArray.Num(); ++LODIndex)
{
LODResourcesArray[LODIndex].IndexBuffer.TrySetAllowCPUAccess(Params.bAllowCpuAccess);
if (Params.PerLODOverrides.IsValidIndex(LODIndex))
{
const UStaticMesh::FBuildMeshDescriptionsLODParams& LODParams = Params.PerLODOverrides[LODIndex];
LODResourcesArray[LODIndex].VertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(LODParams.bUseHighPrecisionTangentBasis);
LODResourcesArray[LODIndex].VertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(LODParams.bUseFullPrecisionUVs);
}
}
for (int32 LODIndex = 0; LODIndex < NewNumLODs; LODIndex++)
{
check(MeshDescriptions[LODIndex] != nullptr);
FStaticMeshLODResources& LODResources = RenderData->LODResources[LODIndex];
LODResources.bBuffersInlined = true;
BuildFromMeshDescription(*MeshDescriptions[LODIndex], 0, LODResources);
}
RenderData->Bounds = MeshDescriptions[0]->GetBounds();
for (int32 LOD = 0; LOD < NewNumLODs; ++LOD)
{
// @todo: some way of customizing LOD screen size and/or calculate it based on mesh bounds
if (true)
{
const float LODPowerBase = 0.75f;
RenderData->ScreenSize[LOD].Default = FMath::Pow(LODPowerBase, LOD);
}
else
{
// Possible model for flexible LODs
const float MaxDeviation = 100.0f; // specify
const float PixelError = UE_SMALL_NUMBER;
const float ViewDistance = (MaxDeviation * 960.0f) / PixelError;
// Generate a projection matrix.
const float HalfFOV = UE_PI * 0.25f;
const float ScreenWidth = 1920.0f;
const float ScreenHeight = 1080.0f;
const FPerspectiveMatrix ProjMatrix(HalfFOV, ScreenWidth, ScreenHeight, 1.0f);
RenderData->ScreenSize[LOD].Default = ComputeBoundsScreenSize(FVector::ZeroVector, RenderData->Bounds.SphereRadius, FVector(0.0f, 0.0f, ViewDistance + RenderData->Bounds.SphereRadius), ProjMatrix);
}
}
return RenderData;
}
3)通过MeshDescription构建FStaticMeshRenderData
实际上通过2)中的方法进行构建的
FStaticMeshRenderData* BuildRenderDataFromMeshDescription(const FMeshDescription& MeshDescription)
{
TArray<const FMeshDescription*> meshDescPtrs;
meshDescPtrs.Emplace(&MeshDescription);
UStaticMesh::FBuildMeshDescriptionsParams mdParams;
/*以下参数都是为了快速构建RenderData*/
// Do not commit since we only need the render data and commit is slow
mdParams.bCommitMeshDescription = false;
// Do not mark the package dirty since MarkPackageDirty is not thread safe
mdParams.bMarkPackageDirty = false;
mdParams.bUseHashAsGuid = true;
mdParams.bBuildSimpleCollision = true;
mdParams.bFastBuild = true;//快速构建
mdParams.bAllowCpuAccess = true;
return BuildRenderDataFromMeshDescriptions(meshDescPtrs, mdParams);
}
2、通过FStaticMeshRenderData构建UStaticMesh
TObjectPtr<UStaticMesh> UMeshUtils::BuildStaticMeshFromRenderData(TObjectPtr<UObject> Outer, FStaticMeshRenderData* RenderData)
{
// 创建 UStaticMesh 对象并设置渲染数据
UStaticMesh* Mesh = NewObject<UStaticMesh>(Outer);
Mesh->SetRenderData(std::move(TUniquePtr<FStaticMeshRenderData>(RenderData)));
Mesh->InitResources();
Mesh->CalculateExtendedBounds();
Mesh->CreateBodySetup();
if (UBodySetup* bodySetup = Mesh->GetBodySetup())
{
bodySetup->InvalidatePhysicsData();
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();
bodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
}
return Mesh;
}