Files
UnrealEngine/Engine/Source/Runtime/Experimental/GeometryCollectionEngine/Private/GeometryCollection/GeometryCollectionRenderData.cpp
2025-05-18 13:04:45 +08:00

816 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryCollection/GeometryCollectionRenderData.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/GeometryCollectionObject.h"
#include "GeometryCollection/ManagedArray.h"
#if WITH_EDITOR
#include "NaniteBuilder.h"
#endif
FBoneMapVertexBuffer::~FBoneMapVertexBuffer()
{
CleanUp();
}
void FBoneMapVertexBuffer::CleanUp()
{
delete BoneMapData;
BoneMapData = nullptr;
}
void FBoneMapVertexBuffer::Init(TArray<uint16> const& InBoneMap, bool bInNeedsCPUAccess)
{
AllocateData(bInNeedsCPUAccess);
ResizeBuffer(InBoneMap.Num());
FMemory::Memcpy(Data, InBoneMap.GetData(), PixelFormatStride * NumVertices);
}
void FBoneMapVertexBuffer::ResizeBuffer(uint32 InNumVertices)
{
NumVertices = InNumVertices;
BoneMapData->ResizeBuffer(NumVertices);
Data = NumVertices != 0 ? BoneMapData->GetDataPointer() : nullptr;
}
void FBoneMapVertexBuffer::AllocateData(bool bInNeedsCPUAccess)
{
CleanUp();
BoneMapData = new TStaticMeshVertexData<uint16>(bInNeedsCPUAccess);
}
void FBoneMapVertexBuffer::InitRHI(FRHICommandListBase& RHICmdList)
{
const bool bHadData = BoneMapData != nullptr;
VertexBufferRHI = FRenderResource::CreateRHIBuffer(RHICmdList, BoneMapData, NumVertices, BUF_Static | BUF_ShaderResource | BUF_SourceCopy, TEXT("FBoneMapVertexBuffer"));
if (VertexBufferRHI != nullptr)
{
VertexBufferSRV = RHICmdList.CreateShaderResourceView(FShaderResourceViewInitializer(bHadData ? VertexBufferRHI : nullptr, PixelFormat));
}
}
void FBoneMapVertexBuffer::ReleaseRHI()
{
VertexBufferSRV.SafeRelease();
FVertexBuffer::ReleaseRHI();
}
void FBoneMapVertexBuffer::Serialize(FArchive& Ar, bool bInNeedsCPUAccess)
{
bNeedsCPUAccess = bInNeedsCPUAccess;
Ar << NumVertices;
if (Ar.IsLoading())
{
// Allocate the vertex data storage type.
AllocateData(bInNeedsCPUAccess);
}
if (BoneMapData != nullptr)
{
// Serialize the vertex data.
BoneMapData->Serialize(Ar);
// Make a copy of the vertex data pointer.
Data = NumVertices != 0 ? BoneMapData->GetDataPointer() : nullptr;
}
}
void FGeometryCollectionMeshResources::InitResources(UGeometryCollection const& Owner)
{
FName OwnerName = Owner.GetFName();
IndexBuffer.SetOwnerName(OwnerName);
PositionVertexBuffer.SetOwnerName(OwnerName);
StaticMeshVertexBuffer.SetOwnerName(OwnerName);
ColorVertexBuffer.SetOwnerName(OwnerName);
BoneMapVertexBuffer.SetOwnerName(OwnerName);
BeginInitResource(&IndexBuffer);
BeginInitResource(&PositionVertexBuffer);
BeginInitResource(&StaticMeshVertexBuffer);
BeginInitResource(&ColorVertexBuffer);
BeginInitResource(&BoneMapVertexBuffer);
}
void FGeometryCollectionMeshResources::ReleaseResources()
{
BeginReleaseResource(&IndexBuffer);
BeginReleaseResource(&PositionVertexBuffer);
BeginReleaseResource(&StaticMeshVertexBuffer);
BeginReleaseResource(&ColorVertexBuffer);
BeginReleaseResource(&BoneMapVertexBuffer);
}
void FGeometryCollectionMeshResources::Serialize(FArchive& Ar)
{
// If platform doesn't have manual vertex fetch then we will need position and
// bone map CPU access so that we can skin positions on CPU.
const bool bCPUAccessForBoneTransform = Ar.IsLoading() && GMaxRHIFeatureLevel <= ERHIFeatureLevel::ES3_1;
// Otherwise we don't need CPU access.
const bool bNeedsCPUAccess = false;
IndexBuffer.Serialize(Ar, bNeedsCPUAccess);
PositionVertexBuffer.Serialize(Ar, bCPUAccessForBoneTransform);
StaticMeshVertexBuffer.Serialize(Ar, bNeedsCPUAccess);
ColorVertexBuffer.Serialize(Ar, bNeedsCPUAccess);
BoneMapVertexBuffer.Serialize(Ar, bCPUAccessForBoneTransform);
}
void FGeometryCollectionMeshDescription::Serialize(FArchive& Ar)
{
Ar << NumVertices << NumTriangles << PreSkinnedBounds << Sections << SectionsNoInternal;
// need subsection data on cooked builds when using ray tracing
// to be able to build one BLAS per subsection
#if !RHI_RAYTRACING
if (Ar.IsCooking())
{
// No need for SubSections in cooked build.
TArray<FGeometryCollectionMeshElement> Dummy;
Ar << Dummy;
}
else
#endif
{
Ar << SubSections;
}
}
#if WITH_EDITOR
/** Data to return from BuildMeshDataFromGeometryCollection(). */
struct FGeometryCollectionBuiltMeshData
{
uint32 NumTexCoords = 0;
TArray<uint32> Indices;
FMeshBuildVertexData Vertices;
TArray<uint16> BonesPerVertex;
TArray<int32> MaterialsPerTriangle;
TArray<bool> InternalPerTriangle;
TArray<uint32> MeshTriangleCounts;
FBounds3f VertexBounds;
};
/** Get the data needed to build render data from a Geometry Collection. */
bool BuildMeshDataFromGeometryCollection(FGeometryCollection& InCollection, FGeometryCollectionBuiltMeshData& OutMeshData, bool bConvertVertexColorsToSRGB)
{
// Vertices Group
const TManagedArray<FVector3f>& VertexArray = InCollection.Vertex;
GeometryCollection::UV::FUVLayers UVsLayers = GeometryCollection::UV::FindActiveUVLayers(InCollection);
const TManagedArray<FLinearColor>& ColorArray = InCollection.Color;
const TManagedArray<FVector3f>& TangentUArray = InCollection.TangentU;
const TManagedArray<FVector3f>& TangentVArray = InCollection.TangentV;
const TManagedArray<FVector3f>& NormalArray = InCollection.Normal;
const TManagedArray<int32>& BoneMapArray = InCollection.BoneMap;
// Faces Group
const TManagedArray<FIntVector>& IndicesArray = InCollection.Indices;
const TManagedArray<bool>& VisibleArray = InCollection.Visible;
const TManagedArray<int32>& MaterialIDArray = InCollection.MaterialID;
const TManagedArray<bool>& Internal = InCollection.Internal;
// Geometry Group
const int32 NumGeometry = InCollection.NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<int32>& VertexStartArray = InCollection.VertexStart;
const TManagedArray<int32>& VertexCountArray = InCollection.VertexCount;
const TManagedArray<int32>& FaceStartArray = InCollection.FaceStart;
const TManagedArray<int32>& FaceCountArray = InCollection.FaceCount;
const int32 NumTexCoords = InCollection.NumUVLayers();
const bool bHasColors = ColorArray.Num() > 0;
// Iterate geometry groups and collect vertices.
FMeshBuildVertexData BuildVertexData;
BuildVertexData.UVs.SetNum(NumTexCoords);
TArray<uint16> BonesPerVertex;
TArray<int32> DestVertexStarts;
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BuildVertices);
// Gather destination vertex locations so that we can fill them in parallel.
int32 NumBuildVertices = 0;
DestVertexStarts.SetNumUninitialized(NumGeometry);
for (int32 GeometryGroupIndex = 0; GeometryGroupIndex < NumGeometry; GeometryGroupIndex++)
{
DestVertexStarts[GeometryGroupIndex] = GeometryGroupIndex > 0 ? DestVertexStarts[GeometryGroupIndex - 1] + VertexCountArray[GeometryGroupIndex - 1] : 0;
NumBuildVertices += VertexCountArray[GeometryGroupIndex];
}
BuildVertexData.Position.AddUninitialized(NumBuildVertices);
BuildVertexData.TangentX.AddUninitialized(NumBuildVertices);
BuildVertexData.TangentY.AddUninitialized(NumBuildVertices);
BuildVertexData.TangentZ.AddUninitialized(NumBuildVertices);
for (int32 TexCoord = 0; TexCoord < NumTexCoords; ++TexCoord)
{
BuildVertexData.UVs[TexCoord].AddUninitialized(NumBuildVertices);
}
if (bHasColors)
{
BuildVertexData.Color.AddUninitialized(NumBuildVertices);
}
BonesPerVertex.AddUninitialized(NumBuildVertices);
TArray<FBounds3f> Bounds;
ParallelForWithTaskContext(Bounds, NumGeometry, [&](FBounds3f& BatchBounds, int32 GeometryGroupIndex)
{
const int32 VertexStart = VertexStartArray[GeometryGroupIndex];
const int32 VertexCount = VertexCountArray[GeometryGroupIndex];
const int32 DestVertexStart = DestVertexStarts[GeometryGroupIndex];
TArray<FBounds3f> GeometryBounds;
ParallelForWithTaskContext(TEXT("GC:BuildVertices"), GeometryBounds, VertexCount, 500, [&](FBounds3f& BatchGeometryBounds, int32 VertexIndex)
{
BatchGeometryBounds += VertexArray[VertexStart + VertexIndex];
BuildVertexData.Position[DestVertexStart + VertexIndex] = VertexArray[VertexStart + VertexIndex];
BuildVertexData.TangentX[DestVertexStart + VertexIndex] = TangentUArray[VertexStart + VertexIndex];
BuildVertexData.TangentY[DestVertexStart + VertexIndex] = TangentVArray[VertexStart + VertexIndex];
BuildVertexData.TangentZ[DestVertexStart + VertexIndex] = NormalArray[VertexStart + VertexIndex];
if (bHasColors)
{
BuildVertexData.Color[DestVertexStart + VertexIndex] = ColorArray[VertexStart + VertexIndex].ToFColor(bConvertVertexColorsToSRGB /* sRGB */);
}
for (int32 TexCoord = 0; TexCoord < NumTexCoords; ++TexCoord)
{
FVector2f UV = UVsLayers[TexCoord][VertexStart + VertexIndex];
if (UV.ContainsNaN())
{
UV = FVector2f::ZeroVector;
}
BuildVertexData.UVs[TexCoord][DestVertexStart + VertexIndex] = UV;
}
const int32 BoneIndex = BoneMapArray[VertexStart + VertexIndex];
BonesPerVertex[DestVertexStart + VertexIndex] = (uint16)BoneIndex;
});
for (const FBounds3f& B : GeometryBounds)
BatchBounds += B;
});
for (const FBounds3f& B : Bounds)
OutMeshData.VertexBounds += B;
}
bool bAllOpaqueWhite = true;
for (const FColor& Color : BuildVertexData.Color)
{
if (Color != FColor::White)
{
bAllOpaqueWhite = false;
break;
}
}
if (bAllOpaqueWhite)
{
BuildVertexData.Color.Empty();
}
// Iterate geometry groups and collect indices.
// Can't go parallel here because we remove hidden faces as we go.
TArray<uint32> BuildIndices;
TArray<int32> MaterialsPerFace;
TArray<bool> InternalPerFace;
TArray<uint32> MeshTriangleCounts;
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::GatherBuildIndices);
BuildIndices.Reserve(IndicesArray.Num() * 3);
MaterialsPerFace.Reserve(IndicesArray.Num());
InternalPerFace.Reserve(IndicesArray.Num());
MeshTriangleCounts.Reserve(NumGeometry);
for (int32 GeometryGroupIndex = 0; GeometryGroupIndex < NumGeometry; ++GeometryGroupIndex)
{
const int32 FaceStart = FaceStartArray[GeometryGroupIndex];
const int32 FaceCount = FaceCountArray[GeometryGroupIndex];
const int32 DestFaceStart = MaterialsPerFace.Num();
const int32 DestVertexOffset = DestVertexStarts[GeometryGroupIndex] - VertexStartArray[GeometryGroupIndex];
for (int32 FaceIndex = 0; FaceIndex < FaceCount; ++FaceIndex)
{
// Remove hidden.
if (!VisibleArray[FaceStart + FaceIndex])
{
continue;
}
FIntVector FaceIndices = IndicesArray[FaceStart + FaceIndex];
FaceIndices = FaceIndices + FIntVector(DestVertexOffset);
// Remove degenerates.
if (BuildVertexData.Position[FaceIndices[0]] == BuildVertexData.Position[FaceIndices[1]] ||
BuildVertexData.Position[FaceIndices[1]] == BuildVertexData.Position[FaceIndices[2]] ||
BuildVertexData.Position[FaceIndices[2]] == BuildVertexData.Position[FaceIndices[0]])
{
continue;
}
BuildIndices.Add(FaceIndices.X);
BuildIndices.Add(FaceIndices.Y);
BuildIndices.Add(FaceIndices.Z);
const int32 MaterialIndex = MaterialIDArray[FaceStart + FaceIndex];
MaterialsPerFace.Add(MaterialIndex);
const bool bInternal = Internal[FaceStart + FaceIndex];
InternalPerFace.Add(bInternal);
}
MeshTriangleCounts.Add(MaterialsPerFace.Num() - DestFaceStart);
}
}
OutMeshData.NumTexCoords = NumTexCoords;
OutMeshData.Indices = MoveTemp(BuildIndices);
OutMeshData.Vertices = MoveTemp(BuildVertexData);
OutMeshData.BonesPerVertex = MoveTemp(BonesPerVertex);
OutMeshData.MaterialsPerTriangle = MoveTemp(MaterialsPerFace);
OutMeshData.InternalPerTriangle = MoveTemp(InternalPerFace);
OutMeshData.MeshTriangleCounts = MoveTemp(MeshTriangleCounts);
// Return false if we found no mesh data.
return OutMeshData.Indices.Num() > 0 && OutMeshData.Vertices.Position.Num() > 0;
}
struct FGeometryCollectionBuiltMeshSectionData
{
TArray<uint32> SortedIndices;
TArray<FGeometryCollectionMeshElement> Sections;
TArray<FGeometryCollectionMeshElement> SectionsNoInternal;
TArray<FGeometryCollectionMeshElement> SubSections;
};
void BuildSectionDataFromMeshData(FGeometryCollectionBuiltMeshData const& InMeshData, FGeometryCollectionBuiltMeshSectionData& OutSectionData)
{
// Find spans within the mesh data.
TArray<FGeometryCollectionMeshElement> Spans;
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::FindSpans);
int32 LastMaterialIndex = -1;
int32 LastIsInternal = -1;
int32 LastBoneIndex = -1;
for (int32 TriangleIndex = 0; TriangleIndex < InMeshData.MaterialsPerTriangle.Num(); ++TriangleIndex)
{
const int32 MaterialIndex = InMeshData.MaterialsPerTriangle[TriangleIndex];
const int32 IsInternal = InMeshData.InternalPerTriangle[TriangleIndex] ? 1 : 0;
const uint32 VertexIndex0 = InMeshData.Indices[TriangleIndex * 3];
const uint32 VertexIndex1 = InMeshData.Indices[TriangleIndex * 3 + 1];
const uint32 VertexIndex2 = InMeshData.Indices[TriangleIndex * 3 + 2];
const uint16 BoneIndex = InMeshData.BonesPerVertex[VertexIndex0];
const uint32 MinVertexIndex = FMath::Min(VertexIndex0, FMath::Min(VertexIndex1, VertexIndex2));
const uint32 MaxVertexIndex = FMath::Max(VertexIndex0, FMath::Max(VertexIndex1, VertexIndex2));
// Add a new span if the properties change.
if (MaterialIndex != LastMaterialIndex || IsInternal != LastIsInternal || BoneIndex != LastBoneIndex)
{
FGeometryCollectionMeshElement& Span = Spans.AddZeroed_GetRef();
Span.TransformIndex = BoneIndex;
Span.MaterialIndex = MaterialIndex;
Span.bIsInternal = IsInternal;
Span.TriangleStart = TriangleIndex;
Span.TriangleCount = 0;
Span.VertexStart = MinVertexIndex;
Span.VertexEnd = MaxVertexIndex;
}
// Update the current span.
FGeometryCollectionMeshElement& Span = Spans.Last();
Span.TriangleCount++;
Span.VertexStart = FMath::Min(Span.VertexStart, MinVertexIndex);
Span.VertexEnd = FMath::Max(Span.VertexEnd, MaxVertexIndex);
LastMaterialIndex = MaterialIndex;
LastIsInternal = IsInternal;
LastBoneIndex = BoneIndex;
}
}
// Sort spans by material and bone index.
TArray<int32> SortedSpanIndices;
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::SortSpans);
TArray<uint64> SortKeys;
SortKeys.SetNum(Spans.Num());
SortedSpanIndices.SetNum(Spans.Num());
for (int32 SpanIndex = 0; SpanIndex < SortedSpanIndices.Num(); SpanIndex++)
{
FGeometryCollectionMeshElement const& Span = Spans[SpanIndex];
SortKeys[SpanIndex] = ((uint64)Span.MaterialIndex << 56) | ((uint64)Span.bIsInternal << 48) | ((uint64)Span.TransformIndex << 32) | (uint64)Span.TriangleStart;
SortedSpanIndices[SpanIndex] = SpanIndex;
}
SortedSpanIndices.StableSort([&SortKeys](int32 A, int32 B)
{
return SortKeys[A] < SortKeys[B];
});
}
// Now copy spans to final section arrays.
TArray<FGeometryCollectionMeshElement> Sections;
TArray<FGeometryCollectionMeshElement> SectionsNoInternal;
TArray<FGeometryCollectionMeshElement> SubSections;
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BuildSections);
Sections.Reserve(Spans.Num());
SectionsNoInternal.Reserve(Spans.Num());
SubSections.Reserve(Spans.Num());
int32 LastMaterialIndex = -1;
uint8 LastIsInternal = 0xff;
int32 LastBoneIndex = -1;
int32 TriangleIndex = 0;
for (int32 SortedSpanIndex = 0; SortedSpanIndex < SortedSpanIndices.Num(); SortedSpanIndex++)
{
const int32 SpanIndex = SortedSpanIndices[SortedSpanIndex];
FGeometryCollectionMeshElement const& Span = Spans[SpanIndex];
// Add new section if required.
if (Span.MaterialIndex != LastMaterialIndex)
{
FGeometryCollectionMeshElement& Section = Sections.AddZeroed_GetRef();
Section.TransformIndex = -1;
Section.MaterialIndex = Span.MaterialIndex;
Section.bIsInternal = 0;
Section.TriangleStart = TriangleIndex;
Section.TriangleCount = 0;
Section.VertexStart = Span.VertexStart;
Section.VertexEnd = Span.VertexEnd;
}
{
// Accumulate this triangle to the current section.
FGeometryCollectionMeshElement& Section = Sections.Last();
Section.TriangleCount += Span.TriangleCount;
Section.VertexStart = FMath::Min(Section.VertexStart, Span.VertexStart);
Section.VertexEnd = FMath::Max(Section.VertexEnd, Span.VertexEnd);
}
// Add new no-internal section if required.
// Note that internal face spans have been sorted to the end of each full section.
if (Span.MaterialIndex != LastMaterialIndex && !Span.bIsInternal)
{
FGeometryCollectionMeshElement& Section = SectionsNoInternal.AddZeroed_GetRef();
Section.TransformIndex = -1;
Section.MaterialIndex = Span.MaterialIndex;
Section.bIsInternal = 0;
Section.TriangleStart = TriangleIndex;
Section.TriangleCount = 0;
Section.VertexStart = Span.VertexStart;
Section.VertexEnd = Span.VertexEnd;
}
// Accumulate this triangle if this is non-internal section.
if (!Span.bIsInternal)
{
FGeometryCollectionMeshElement& Section = SectionsNoInternal.Last();
Section.TriangleCount += Span.TriangleCount;
Section.VertexStart = FMath::Min(Section.VertexStart, Span.VertexStart);
Section.VertexEnd = FMath::Max(Section.VertexEnd, Span.VertexEnd);
}
// Add new subsection if required.
// Note that subsections don't care about the internal flag, since they are only ever used
// in editor in modes where we want to render internal faces.
if (Span.MaterialIndex != LastMaterialIndex || Span.TransformIndex != LastBoneIndex)
{
FGeometryCollectionMeshElement& Section = SubSections.AddZeroed_GetRef();
Section.TransformIndex = Span.TransformIndex;
Section.MaterialIndex = Span.MaterialIndex;
Section.bIsInternal = 0;
Section.TriangleStart = TriangleIndex;
Section.TriangleCount = 0;
Section.VertexStart = Span.VertexStart;
Section.VertexEnd = Span.VertexEnd;
}
{
// Accumulate this triangle to the current subsection.
FGeometryCollectionMeshElement& Section = SubSections.Last();
Section.TriangleCount += Span.TriangleCount;
Section.VertexStart = FMath::Min(Section.VertexStart, Span.VertexStart);
Section.VertexEnd = FMath::Max(Section.VertexEnd, Span.VertexEnd);
}
TriangleIndex += Span.TriangleCount;
LastMaterialIndex = Span.MaterialIndex;
LastIsInternal = Span.bIsInternal;
LastBoneIndex = Span.TransformIndex;
}
//TODO: Clear SectionsNoInternal if it is just a duplicate of Sections.
Sections.Shrink();
SectionsNoInternal.Shrink();
SubSections.Shrink();
}
// Copy indices to sorted index buffer.
TArray<uint32> SortedIndices;
SortedIndices.SetNumUninitialized(InMeshData.Indices.Num());
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::CopySortedIndices);
// Gather destination index locations so that we can fill them in parallel.
TArray<int32> DestIndexStarts;
DestIndexStarts.SetNumUninitialized(SortedSpanIndices.Num());
for (int32 SortedSpanIndex = 0; SortedSpanIndex < SortedSpanIndices.Num(); SortedSpanIndex++)
{
DestIndexStarts[SortedSpanIndex] = SortedSpanIndex > 0 ? DestIndexStarts[SortedSpanIndex - 1] + Spans[SortedSpanIndices[SortedSpanIndex - 1]].TriangleCount * 3 : 0;
}
ParallelFor(SortedSpanIndices.Num(), [&](int32 SortedSpanIndex)
{
const int32 SpanIndex = SortedSpanIndices[SortedSpanIndex];
FGeometryCollectionMeshElement const& Span = Spans[SpanIndex];
const int32 SourceIndexStart = Span.TriangleStart * 3;
const int32 DestIndexStart = DestIndexStarts[SortedSpanIndex];
ParallelFor(TEXT("GC:CopySortedIndices"), Span.TriangleCount, 500, [&](int32 TriangleIndex)
{
const int32 SourceIndex = SourceIndexStart + TriangleIndex * 3;
const int32 DestIndex = DestIndexStart + TriangleIndex * 3;
SortedIndices[DestIndex] = InMeshData.Indices[SourceIndex];
SortedIndices[DestIndex + 1] = InMeshData.Indices[SourceIndex + 1];
SortedIndices[DestIndex + 2] = InMeshData.Indices[SourceIndex + 2];
});
});
}
// Move to output.
OutSectionData.SortedIndices = MoveTemp(SortedIndices);
OutSectionData.Sections = MoveTemp(Sections);
OutSectionData.SectionsNoInternal = MoveTemp(SectionsNoInternal);
OutSectionData.SubSections = MoveTemp(SubSections);
}
FBoxSphereBounds BuildPreSkinnedBounds(FGeometryCollection& InCollection)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BuildBounds);
const TManagedArray<FBox>& BoundingBoxes = InCollection.BoundingBox;
const TManagedArray<FTransform3f>& Transform = InCollection.Transform;
const TManagedArray<int32>& Parent = InCollection.Parent;
const TManagedArray<int32>& TransformIndices = InCollection.TransformIndex;
const int32 NumBoxes = BoundingBoxes.Num();
TArray<FMatrix> GlobalMatrices;
GeometryCollectionAlgo::GlobalMatrices(Transform, Parent, GlobalMatrices);
bool bBoundsInit = false;
FBox PreSkinnedBounds(ForceInit);
for (int32 BoxIndex = 0; BoxIndex < NumBoxes; ++BoxIndex)
{
const int32 TransformIndex = TransformIndices[BoxIndex];
if (InCollection.IsGeometry(TransformIndex))
{
if (!bBoundsInit)
{
PreSkinnedBounds = BoundingBoxes[BoxIndex].TransformBy(GlobalMatrices[TransformIndex]);
bBoundsInit = true;
}
else
{
PreSkinnedBounds += BoundingBoxes[BoxIndex].TransformBy(GlobalMatrices[TransformIndex]);
}
}
}
return bBoundsInit ? FBoxSphereBounds(PreSkinnedBounds) : FBoxSphereBounds(ForceInit);
}
void CreateMeshData(FGeometryCollection& InCollection, FGeometryCollectionBuiltMeshData const& InMeshData, bool bInUseFullPrecisionUVs, FGeometryCollectionRenderData& OutRenderData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::CreateMeshData);
OutRenderData.bHasMeshData = true;
OutRenderData.MeshDescription.NumVertices = InMeshData.Vertices.Position.Num();
OutRenderData.MeshDescription.NumTriangles = InMeshData.Indices.Num() / 3;
const FConstMeshBuildVertexView VertexView = MakeConstMeshBuildVertexView(InMeshData.Vertices);
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::BufferInit);
OutRenderData.MeshResource.PositionVertexBuffer.Init(VertexView);
OutRenderData.MeshResource.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(bInUseFullPrecisionUVs);
OutRenderData.MeshResource.StaticMeshVertexBuffer.Init(VertexView);
OutRenderData.MeshResource.ColorVertexBuffer.Init(VertexView);
OutRenderData.MeshResource.BoneMapVertexBuffer.Init(InMeshData.BonesPerVertex);
}
FGeometryCollectionBuiltMeshSectionData SectionData;
BuildSectionDataFromMeshData(InMeshData, SectionData);
// Store the sorted indices that match the section data.
OutRenderData.MeshResource.IndexBuffer.SetIndices(SectionData.SortedIndices, EIndexBufferStride::AutoDetect);
OutRenderData.MeshDescription.Sections = MoveTemp(SectionData.Sections);
OutRenderData.MeshDescription.SectionsNoInternal = MoveTemp(SectionData.SectionsNoInternal);
OutRenderData.MeshDescription.SubSections = MoveTemp(SectionData.SubSections);
// Calculate bounds.
OutRenderData.MeshDescription.PreSkinnedBounds = BuildPreSkinnedBounds(InCollection);
}
void CreateNaniteData(FGeometryCollectionBuiltMeshData&& InMeshData, FGeometryCollectionRenderData& OutRenderData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::CreateNaniteData);
OutRenderData.bHasNaniteData = true;
FMeshNaniteSettings NaniteSettings = {};
NaniteSettings.bEnabled = true;
NaniteSettings.TargetMinimumResidencyInKB = 0; // Default to smallest possible, which is a single page
NaniteSettings.KeepPercentTriangles = 1.0f;
NaniteSettings.TrimRelativeError = 0.0f;
NaniteSettings.FallbackPercentTriangles = 1.0f; // 100% - no reduction
NaniteSettings.FallbackRelativeError = 0.0f;
ClearNaniteResources(OutRenderData.NaniteResourcesPtr);
Nanite::IBuilderModule& NaniteBuilderModule = Nanite::IBuilderModule::Get();
// Sections are left empty because they are not used (not building a coarse representation)
Nanite::IBuilderModule::FInputMeshData InputMeshData;
InputMeshData.Vertices = MoveTemp(InMeshData.Vertices);
InputMeshData.TriangleIndices = MoveTemp(InMeshData.Indices);
InputMeshData.MaterialIndices = MoveTemp(InMeshData.MaterialsPerTriangle);
InputMeshData.TriangleCounts = MoveTemp(InMeshData.MeshTriangleCounts);
InputMeshData.NumTexCoords = InMeshData.NumTexCoords;
InputMeshData.VertexBounds = InMeshData.VertexBounds;
if (!NaniteBuilderModule.Build(
*OutRenderData.NaniteResourcesPtr.Get(),
InputMeshData,
nullptr, // OutFallbackMeshData
nullptr, // OutRayTracingFallbackMeshData
nullptr, // RayTracingFallbackBuildSettings
NaniteSettings))
{
UE_LOG(LogStaticMesh, Error, TEXT("Failed to build Nanite for geometry collection. See previous line(s) for details."));
}
}
TUniquePtr<FGeometryCollectionRenderData> FGeometryCollectionRenderData::Create(FGeometryCollection& InCollection, bool bInEnableNanite, bool bInEnableNaniteFallback, bool bInUseFullPrecisionUVs, bool bConvertVertexColorsToSRGB)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FGeometryCollectionRenderData::Create);
TUniquePtr<FGeometryCollectionRenderData> RenderData = MakeUnique<FGeometryCollectionRenderData>();
FGeometryCollectionBuiltMeshData MeshBuildData;
if (BuildMeshDataFromGeometryCollection(InCollection, MeshBuildData, bConvertVertexColorsToSRGB))
{
if (!bInEnableNanite || bInEnableNaniteFallback)
{
CreateMeshData(InCollection, MeshBuildData, bInUseFullPrecisionUVs, *RenderData.Get());
}
if (bInEnableNanite)
{
CreateNaniteData(MoveTemp(MeshBuildData), *RenderData.Get());
}
}
return RenderData;
}
#endif // WITH_EDITOR
FGeometryCollectionRenderData::~FGeometryCollectionRenderData()
{
// We should have released any resources before now.
check(!bIsInitialized);
}
void FGeometryCollectionRenderData::Serialize(FArchive& Ar, UGeometryCollection& Owner)
{
const bool bIsArchiveValidCandidateForStrip = (Ar.IsCooking() || (Ar.IsCountingMemory() && Ar.IsFilterEditorOnly()));
if (Owner.bStripRenderDataOnCook && bIsArchiveValidCandidateForStrip)
{
// Don't cook rendering data.
// This is used if we expect to use a custom GC render path such as the ISM pool.
bool bDummy = false;
Ar << bDummy; // bHasMeshData
Ar << bDummy; // bHasNaniteData
return;
}
Ar << bHasMeshData;
#if WITH_EDITOR
bool bStripNaniteDataFromCook = Ar.IsCooking() && !DoesTargetPlatformSupportNanite(Ar.CookingTarget());
#else
bool bStripNaniteDataFromCook = false;
#endif
if (bStripNaniteDataFromCook)
{
bool bDummy = false;
Ar << bDummy;
}
else
{
Ar << bHasNaniteData;
}
if (bHasMeshData)
{
MeshResource.Serialize(Ar);
MeshDescription.Serialize(Ar);
}
else if (Ar.IsLoading())
{
MeshResource = {};
MeshDescription = {};
}
if (bHasNaniteData && !bStripNaniteDataFromCook)
{
InitNaniteResources(NaniteResourcesPtr);
if (Ar.IsSaving())
{
// Nanite data is 1:1 with each geometry group in the collection.
const int32 NumGeometryGroups = Owner.NumElements(FGeometryCollection::GeometryGroup);
if (!Owner.EnableNanite || NumGeometryGroups != NaniteResourcesPtr->HierarchyRootOffsets.Num())
{
Ar.SetError();
}
}
NaniteResourcesPtr->Serialize(Ar, &Owner, true);
}
else if (Ar.IsLoading())
{
ClearNaniteResources(NaniteResourcesPtr);
}
}
void FGeometryCollectionRenderData::InitResources(UGeometryCollection const& Owner)
{
if (bIsInitialized)
{
ReleaseResources();
}
if (bHasMeshData)
{
MeshResource.InitResources(Owner);
}
if (bHasNaniteData)
{
NaniteResourcesPtr->InitResources(&Owner);
}
bIsInitialized = true;
}
void FGeometryCollectionRenderData::ReleaseResources()
{
if (!bIsInitialized)
{
return;
}
if (bHasMeshData)
{
MeshResource.ReleaseResources();
}
if (bHasNaniteData)
{
NaniteResourcesPtr->ReleaseResources();
}
bIsInitialized = false;
}