You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Locality links are now only added between elements with the same material #rb brian.karis #preflight 6290cee5dd2be751aeda4a71 [CL 20392408 by Rune Stubbe in ue5-main branch]
759 lines
24 KiB
C++
759 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "NaniteBuilder.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Components.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "Hash/CityHash.h"
|
|
#include "Math/UnrealMath.h"
|
|
#include "GraphPartitioner.h"
|
|
#include "Cluster.h"
|
|
#include "ClusterDAG.h"
|
|
#include "MeshSimplify.h"
|
|
#include "DisjointSet.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "NaniteEncode.h"
|
|
#include "ImposterAtlas.h"
|
|
#include "UObject/DevObjectVersion.h"
|
|
|
|
#if WITH_MIKKTSPACE
|
|
#include "mikktspace.h"
|
|
#endif
|
|
|
|
namespace Nanite
|
|
{
|
|
|
|
class FBuilderModule : public IBuilderModule
|
|
{
|
|
public:
|
|
FBuilderModule() {}
|
|
|
|
virtual void StartupModule() override
|
|
{
|
|
// Register any modular features here
|
|
}
|
|
|
|
virtual void ShutdownModule() override
|
|
{
|
|
// Unregister any modular features here
|
|
}
|
|
|
|
virtual const FString& GetVersionString() const;
|
|
|
|
virtual bool Build(
|
|
FResources& Resources,
|
|
TArray<FStaticMeshBuildVertex>& Vertices, // TODO: Do not require this vertex type for all users of Nanite
|
|
TArray<uint32>& TriangleIndices,
|
|
TArray<int32>& MaterialIndices,
|
|
TArray<uint32>& MeshTriangleCounts,
|
|
uint32 NumTexCoords,
|
|
const FMeshNaniteSettings& Settings) override;
|
|
|
|
virtual bool Build(
|
|
FResources& Resources,
|
|
FVertexMeshData& InputMeshData,
|
|
TArrayView< FVertexMeshData > OutputLODMeshData,
|
|
uint32 NumTexCoords,
|
|
const FMeshNaniteSettings& Settings) override;
|
|
};
|
|
|
|
const FString& FBuilderModule::GetVersionString() const
|
|
{
|
|
static FString VersionString;
|
|
|
|
if (VersionString.IsEmpty())
|
|
{
|
|
VersionString = FString::Printf(TEXT("%s%s%s"), *FDevSystemGuids::GetSystemGuid(FDevSystemGuids::Get().NANITE_DERIVEDDATA_VER).ToString(EGuidFormats::DigitsWithHyphens),
|
|
NANITE_USE_CONSTRAINED_CLUSTERS ? TEXT("_CONSTRAINED") : TEXT(""),
|
|
NANITE_USE_UNCOMPRESSED_VERTEX_DATA ? TEXT("_UNCOMPRESSED") : TEXT(""));
|
|
}
|
|
|
|
return VersionString;
|
|
}
|
|
|
|
} // namespace Nanite
|
|
|
|
IMPLEMENT_MODULE( Nanite::FBuilderModule, NaniteBuilder );
|
|
|
|
|
|
|
|
namespace Nanite
|
|
{
|
|
|
|
struct FMeshData
|
|
{
|
|
TArray< FStaticMeshBuildVertex >& Verts;
|
|
TArray< uint32 >& Indexes;
|
|
};
|
|
|
|
static int MikkGetNumFaces( const SMikkTSpaceContext* Context )
|
|
{
|
|
FMeshData *UserData = (FMeshData*)Context->m_pUserData;
|
|
return UserData->Indexes.Num() / 3;
|
|
}
|
|
|
|
static int MikkGetNumVertsOfFace( const SMikkTSpaceContext* Context, const int FaceIdx )
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
static void MikkGetPosition( const SMikkTSpaceContext* Context, float Position[3], const int FaceIdx, const int VertIdx )
|
|
{
|
|
FMeshData *UserData = (FMeshData*)Context->m_pUserData;
|
|
for( int32 i = 0; i < 3; i++ )
|
|
Position[i] = UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].Position[i];
|
|
}
|
|
|
|
static void MikkGetNormal( const SMikkTSpaceContext* Context, float Normal[3], const int FaceIdx, const int VertIdx )
|
|
{
|
|
FMeshData *UserData = (FMeshData*)Context->m_pUserData;
|
|
for( int32 i = 0; i < 3; i++ )
|
|
Normal[i] = UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].TangentZ[i];
|
|
}
|
|
|
|
static void MikkSetTSpaceBasic( const SMikkTSpaceContext* Context, const float Tangent[3], const float BitangentSign, const int FaceIdx, const int VertIdx )
|
|
{
|
|
FMeshData *UserData = (FMeshData*)Context->m_pUserData;
|
|
for( int32 i = 0; i < 3; i++ )
|
|
UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].TangentX[i] = Tangent[i];
|
|
|
|
FVector3f Bitangent = BitangentSign * FVector3f::CrossProduct(
|
|
UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].TangentZ,
|
|
UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].TangentX );
|
|
|
|
for( int32 i = 0; i < 3; i++ )
|
|
UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].TangentY[i] = -Bitangent[i];
|
|
}
|
|
|
|
static void MikkGetTexCoord( const SMikkTSpaceContext* Context, float UV[2], const int FaceIdx, const int VertIdx )
|
|
{
|
|
FMeshData *UserData = (FMeshData*)Context->m_pUserData;
|
|
for( int32 i = 0; i < 2; i++ )
|
|
UV[i] = UserData->Verts[ UserData->Indexes[ FaceIdx * 3 + VertIdx ] ].UVs[0][i];
|
|
}
|
|
|
|
void CalcTangents(
|
|
TArray< FStaticMeshBuildVertex >& Verts,
|
|
TArray< uint32 >& Indexes )
|
|
{
|
|
#if WITH_MIKKTSPACE
|
|
FMeshData MeshData = { Verts, Indexes };
|
|
|
|
SMikkTSpaceInterface MikkTInterface;
|
|
MikkTInterface.m_getNormal = MikkGetNormal;
|
|
MikkTInterface.m_getNumFaces = MikkGetNumFaces;
|
|
MikkTInterface.m_getNumVerticesOfFace = MikkGetNumVertsOfFace;
|
|
MikkTInterface.m_getPosition = MikkGetPosition;
|
|
MikkTInterface.m_getTexCoord = MikkGetTexCoord;
|
|
MikkTInterface.m_setTSpaceBasic = MikkSetTSpaceBasic;
|
|
MikkTInterface.m_setTSpace = nullptr;
|
|
|
|
SMikkTSpaceContext MikkTContext;
|
|
MikkTContext.m_pInterface = &MikkTInterface;
|
|
MikkTContext.m_pUserData = (void*)(&MeshData);
|
|
MikkTContext.m_bIgnoreDegenerates = true;
|
|
genTangSpaceDefault( &MikkTContext );
|
|
#else
|
|
ensureMsgf(false, TEXT("MikkTSpace tangent generation is not supported on this platform."));
|
|
#endif //WITH_MIKKTSPACE
|
|
}
|
|
|
|
static void BuildCoarseRepresentation(
|
|
const TArray<FClusterGroup>& Groups,
|
|
const TArray<FCluster>& Clusters,
|
|
TArray<FStaticMeshBuildVertex>& Verts,
|
|
TArray<uint32>& Indexes,
|
|
TArray<FStaticMeshSection, TInlineAllocator<1>>& Sections,
|
|
uint32& NumTexCoords,
|
|
uint32 TargetNumTris,
|
|
float TargetError )
|
|
{
|
|
TargetNumTris = FMath::Max( TargetNumTris, 64u );
|
|
|
|
FBinaryHeap< float > Heap = FindDAGCut( Groups, Clusters, TargetNumTris, TargetError, 4096 );
|
|
|
|
// Merge
|
|
TArray< const FCluster*, TInlineAllocator<32> > MergeList;
|
|
MergeList.AddUninitialized( Heap.Num() );
|
|
for( uint32 i = 0; i < Heap.Num(); i++ )
|
|
{
|
|
MergeList[i] = &Clusters[ Heap.Peek(i) ];
|
|
}
|
|
|
|
// Force a deterministic order
|
|
MergeList.Sort(
|
|
[]( const FCluster& A, const FCluster& B )
|
|
{
|
|
return A.GUID < B.GUID;
|
|
} );
|
|
|
|
FCluster CoarseRepresentation( MergeList );
|
|
CoarseRepresentation.Simplify( TargetNumTris, TargetError, FMath::Min( TargetNumTris, 256u ) );
|
|
|
|
TArray< FStaticMeshSection, TInlineAllocator<1> > OldSections = Sections;
|
|
|
|
// Need to update coarse representation UV count to match new data.
|
|
NumTexCoords = CoarseRepresentation.NumTexCoords;
|
|
|
|
// Rebuild vertex data
|
|
Verts.Empty(CoarseRepresentation.NumVerts);
|
|
for (uint32 Iter = 0, Num = CoarseRepresentation.NumVerts; Iter < Num; ++Iter)
|
|
{
|
|
FStaticMeshBuildVertex Vertex = {};
|
|
Vertex.Position = CoarseRepresentation.GetPosition(Iter);
|
|
Vertex.TangentX = FVector3f::ZeroVector;
|
|
Vertex.TangentY = FVector3f::ZeroVector;
|
|
Vertex.TangentZ = CoarseRepresentation.GetNormal(Iter);
|
|
|
|
const FVector2f* UVs = CoarseRepresentation.GetUVs(Iter);
|
|
for (uint32 UVIndex = 0; UVIndex < NumTexCoords; ++UVIndex)
|
|
{
|
|
Vertex.UVs[UVIndex] = UVs[UVIndex].ContainsNaN() ? FVector2f::ZeroVector : UVs[UVIndex];
|
|
}
|
|
|
|
Vertex.Color = CoarseRepresentation.bHasColors ? CoarseRepresentation.GetColor(Iter).ToFColor(false /* sRGB */) : FColor::White;
|
|
|
|
Verts.Add(Vertex);
|
|
}
|
|
|
|
TArray<FMaterialTriangle, TInlineAllocator<128>> CoarseMaterialTris;
|
|
TArray<FMaterialRange, TInlineAllocator<4>> CoarseMaterialRanges;
|
|
|
|
// Compute material ranges for coarse representation.
|
|
BuildMaterialRanges(
|
|
CoarseRepresentation.Indexes,
|
|
CoarseRepresentation.MaterialIndexes,
|
|
CoarseMaterialTris,
|
|
CoarseMaterialRanges);
|
|
check(CoarseMaterialRanges.Num() <= OldSections.Num());
|
|
|
|
// Rebuild section data.
|
|
Sections.Reset(CoarseMaterialRanges.Num());
|
|
for (const FStaticMeshSection& OldSection : OldSections)
|
|
{
|
|
// Add new sections based on the computed material ranges
|
|
// Enforce the same material order as OldSections
|
|
const FMaterialRange* FoundRange = CoarseMaterialRanges.FindByPredicate([&OldSection](const FMaterialRange& Range) { return Range.MaterialIndex == OldSection.MaterialIndex; });
|
|
|
|
// Sections can actually be removed from the coarse mesh if their source data doesn't contain enough triangles
|
|
if (FoundRange)
|
|
{
|
|
// Copy properties from original mesh sections.
|
|
FStaticMeshSection Section(OldSection);
|
|
|
|
// Range of vertices and indices used when rendering this section.
|
|
Section.FirstIndex = FoundRange->RangeStart * 3;
|
|
Section.NumTriangles = FoundRange->RangeLength;
|
|
Section.MinVertexIndex = TNumericLimits<uint32>::Max();
|
|
Section.MaxVertexIndex = TNumericLimits<uint32>::Min();
|
|
|
|
for (uint32 TriangleIndex = 0; TriangleIndex < (FoundRange->RangeStart + FoundRange->RangeLength); ++TriangleIndex)
|
|
{
|
|
const FMaterialTriangle& Triangle = CoarseMaterialTris[TriangleIndex];
|
|
|
|
// Update min vertex index
|
|
Section.MinVertexIndex = FMath::Min(Section.MinVertexIndex, Triangle.Index0);
|
|
Section.MinVertexIndex = FMath::Min(Section.MinVertexIndex, Triangle.Index1);
|
|
Section.MinVertexIndex = FMath::Min(Section.MinVertexIndex, Triangle.Index2);
|
|
|
|
// Update max vertex index
|
|
Section.MaxVertexIndex = FMath::Max(Section.MaxVertexIndex, Triangle.Index0);
|
|
Section.MaxVertexIndex = FMath::Max(Section.MaxVertexIndex, Triangle.Index1);
|
|
Section.MaxVertexIndex = FMath::Max(Section.MaxVertexIndex, Triangle.Index2);
|
|
}
|
|
|
|
Sections.Add(Section);
|
|
}
|
|
}
|
|
|
|
// Rebuild index data.
|
|
Indexes.Reset();
|
|
for (const FMaterialTriangle& Triangle : CoarseMaterialTris)
|
|
{
|
|
Indexes.Add(Triangle.Index0);
|
|
Indexes.Add(Triangle.Index1);
|
|
Indexes.Add(Triangle.Index2);
|
|
}
|
|
|
|
CalcTangents(Verts, Indexes);
|
|
}
|
|
|
|
static void ClusterTriangles(
|
|
const TArray< FStaticMeshBuildVertex >& Verts,
|
|
const TArrayView< const uint32 >& Indexes,
|
|
const TArrayView< const int32 >& MaterialIndexes,
|
|
TArray< FCluster >& Clusters, // Append
|
|
const FBounds3f& MeshBounds,
|
|
uint32 NumTexCoords,
|
|
bool bHasColors )
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::ClusterTriangles);
|
|
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
LOG_CRC( Verts );
|
|
LOG_CRC( Indexes );
|
|
|
|
uint32 NumTriangles = Indexes.Num() / 3;
|
|
|
|
FAdjacency Adjacency( Indexes.Num() );
|
|
FEdgeHash EdgeHash( Indexes.Num() );
|
|
|
|
auto GetPosition = [ &Verts, &Indexes ]( uint32 EdgeIndex )
|
|
{
|
|
return Verts[ Indexes[ EdgeIndex ] ].Position;
|
|
};
|
|
|
|
ParallelFor( TEXT("Nanite.ClusterTriangles.PF"), Indexes.Num(), 4096,
|
|
[&]( int32 EdgeIndex )
|
|
{
|
|
EdgeHash.Add_Concurrent( EdgeIndex, GetPosition );
|
|
});
|
|
|
|
ParallelFor( TEXT("Nanite.ClusterTriangles.PF"), Indexes.Num(), 1024,
|
|
[&]( int32 EdgeIndex )
|
|
{
|
|
int32 AdjIndex = -1;
|
|
int32 AdjCount = 0;
|
|
EdgeHash.ForAllMatching( EdgeIndex, false, GetPosition,
|
|
[&]( int32 EdgeIndex, int32 OtherEdgeIndex )
|
|
{
|
|
AdjIndex = OtherEdgeIndex;
|
|
AdjCount++;
|
|
} );
|
|
|
|
if( AdjCount > 1 )
|
|
AdjIndex = -2;
|
|
|
|
Adjacency.Direct[ EdgeIndex ] = AdjIndex;
|
|
});
|
|
|
|
FDisjointSet DisjointSet( NumTriangles );
|
|
|
|
for( uint32 EdgeIndex = 0, Num = Indexes.Num(); EdgeIndex < Num; EdgeIndex++ )
|
|
{
|
|
if( Adjacency.Direct[ EdgeIndex ] == -2 )
|
|
{
|
|
// EdgeHash is built in parallel, so we need to sort before use to ensure determinism.
|
|
// This path is only executed in the rare event that an edge is shared by more than two triangles,
|
|
// so performance impact should be negligible in practice.
|
|
TArray< TPair< int32, int32 >, TInlineAllocator< 16 > > Edges;
|
|
EdgeHash.ForAllMatching( EdgeIndex, false, GetPosition,
|
|
[&]( int32 EdgeIndex0, int32 EdgeIndex1 )
|
|
{
|
|
Edges.Emplace( EdgeIndex0, EdgeIndex1 );
|
|
} );
|
|
Edges.Sort();
|
|
|
|
for( const TPair< int32, int32 >& Edge : Edges )
|
|
{
|
|
Adjacency.Link( Edge.Key, Edge.Value );
|
|
}
|
|
}
|
|
|
|
Adjacency.ForAll( EdgeIndex,
|
|
[&]( int32 EdgeIndex0, int32 EdgeIndex1 )
|
|
{
|
|
if( EdgeIndex0 > EdgeIndex1 )
|
|
DisjointSet.UnionSequential( EdgeIndex0 / 3, EdgeIndex1 / 3 );
|
|
} );
|
|
}
|
|
|
|
uint32 BoundaryTime = FPlatformTime::Cycles();
|
|
UE_LOG( LogStaticMesh, Log, TEXT("Adjacency [%.2fs], tris: %i, UVs %i%s"), FPlatformTime::ToMilliseconds( BoundaryTime - Time0 ) / 1000.0f, Indexes.Num() / 3, NumTexCoords, bHasColors ? TEXT(", Color") : TEXT("") );
|
|
|
|
FGraphPartitioner Partitioner( NumTriangles );
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::PartitionGraph);
|
|
|
|
auto GetCenter = [ &Verts, &Indexes ]( uint32 TriIndex )
|
|
{
|
|
FVector3f Center;
|
|
Center = Verts[ Indexes[ TriIndex * 3 + 0 ] ].Position;
|
|
Center += Verts[ Indexes[ TriIndex * 3 + 1 ] ].Position;
|
|
Center += Verts[ Indexes[ TriIndex * 3 + 2 ] ].Position;
|
|
return Center * (1.0f / 3.0f);
|
|
};
|
|
Partitioner.BuildLocalityLinks( DisjointSet, MeshBounds, MaterialIndexes, GetCenter );
|
|
|
|
auto* RESTRICT Graph = Partitioner.NewGraph( NumTriangles * 3 );
|
|
|
|
for( uint32 i = 0; i < NumTriangles; i++ )
|
|
{
|
|
Graph->AdjacencyOffset[i] = Graph->Adjacency.Num();
|
|
|
|
uint32 TriIndex = Partitioner.Indexes[i];
|
|
|
|
for( int k = 0; k < 3; k++ )
|
|
{
|
|
Adjacency.ForAll( 3 * TriIndex + k,
|
|
[ &Partitioner, Graph ]( int32 EdgeIndex, int32 AdjIndex )
|
|
{
|
|
Partitioner.AddAdjacency( Graph, AdjIndex / 3, 4 * 65 );
|
|
} );
|
|
}
|
|
|
|
Partitioner.AddLocalityLinks( Graph, TriIndex, 1 );
|
|
}
|
|
Graph->AdjacencyOffset[ NumTriangles ] = Graph->Adjacency.Num();
|
|
|
|
bool bSingleThreaded = NumTriangles < 5000;
|
|
|
|
Partitioner.PartitionStrict( Graph, FCluster::ClusterSize - 4, FCluster::ClusterSize, !bSingleThreaded );
|
|
check( Partitioner.Ranges.Num() );
|
|
|
|
LOG_CRC( Partitioner.Ranges );
|
|
}
|
|
|
|
const uint32 OptimalNumClusters = FMath::DivideAndRoundUp< int32 >( Indexes.Num(), FCluster::ClusterSize * 3 );
|
|
|
|
uint32 ClusterTime = FPlatformTime::Cycles();
|
|
UE_LOG( LogStaticMesh, Log, TEXT("Clustering [%.2fs]. Ratio: %f"), FPlatformTime::ToMilliseconds( ClusterTime - BoundaryTime ) / 1000.0f, (float)Partitioner.Ranges.Num() / OptimalNumClusters );
|
|
|
|
const uint32 BaseCluster = Clusters.Num();
|
|
Clusters.AddDefaulted( Partitioner.Ranges.Num() );
|
|
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::BuildClusters);
|
|
ParallelFor( TEXT("Nanite.BuildClusters.PF"), Partitioner.Ranges.Num(), 1024,
|
|
[&]( int32 Index )
|
|
{
|
|
auto& Range = Partitioner.Ranges[ Index ];
|
|
|
|
Clusters[ BaseCluster + Index ] = FCluster(
|
|
Verts,
|
|
Indexes,
|
|
MaterialIndexes,
|
|
NumTexCoords, bHasColors,
|
|
Range.Begin, Range.End, Partitioner, Adjacency );
|
|
|
|
// Negative notes it's a leaf
|
|
Clusters[ BaseCluster + Index ].EdgeLength *= -1.0f;
|
|
});
|
|
}
|
|
|
|
uint32 LeavesTime = FPlatformTime::Cycles();
|
|
UE_LOG( LogStaticMesh, Log, TEXT("Leaves [%.2fs]"), FPlatformTime::ToMilliseconds( LeavesTime - ClusterTime ) / 1000.0f );
|
|
}
|
|
|
|
static bool BuildNaniteData(
|
|
FResources& Resources,
|
|
IBuilderModule::FVertexMeshData& InputMeshData,
|
|
TArray< int32 >& MaterialIndexes,
|
|
TArray< uint32 >& MeshTriangleCounts,
|
|
TArrayView< IBuilderModule::FVertexMeshData >& OutputLODMeshData,
|
|
uint32 NumTexCoords,
|
|
const FMeshNaniteSettings& Settings
|
|
)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::BuildData);
|
|
|
|
if (NumTexCoords > NANITE_MAX_UVS)
|
|
{
|
|
NumTexCoords = NANITE_MAX_UVS;
|
|
}
|
|
|
|
FBounds3f VertexBounds;
|
|
uint32 Channel = 255;
|
|
for( auto& Vert : InputMeshData.Vertices )
|
|
{
|
|
VertexBounds += Vert.Position;
|
|
|
|
Channel &= Vert.Color.R;
|
|
Channel &= Vert.Color.G;
|
|
Channel &= Vert.Color.B;
|
|
Channel &= Vert.Color.A;
|
|
}
|
|
|
|
Resources.NumInputTriangles = InputMeshData.TriangleIndices.Num() / 3;
|
|
Resources.NumInputVertices = InputMeshData.Vertices.Num();
|
|
Resources.NumInputMeshes = MeshTriangleCounts.Num();
|
|
Resources.NumInputTexCoords = NumTexCoords;
|
|
|
|
// Don't trust any input. We only have color if it isn't all white.
|
|
const bool bHasVertexColor = Channel != 255;
|
|
const bool bHasImposter = Resources.NumInputMeshes == 1;
|
|
|
|
Resources.ResourceFlags = 0x0;
|
|
|
|
if (bHasVertexColor)
|
|
{
|
|
Resources.ResourceFlags |= NANITE_RESOURCE_FLAG_HAS_VERTEX_COLOR;
|
|
}
|
|
|
|
if (bHasImposter)
|
|
{
|
|
Resources.ResourceFlags |= NANITE_RESOURCE_FLAG_HAS_IMPOSTER;
|
|
}
|
|
|
|
TArray< uint32 > ClusterCountPerMesh;
|
|
TArray< FCluster > Clusters;
|
|
{
|
|
uint32 BaseTriangle = 0;
|
|
for (uint32 NumTriangles : MeshTriangleCounts)
|
|
{
|
|
uint32 NumClustersBefore = Clusters.Num();
|
|
if (NumTriangles)
|
|
{
|
|
ClusterTriangles(
|
|
InputMeshData.Vertices,
|
|
TArrayView< const uint32 >( &InputMeshData.TriangleIndices[BaseTriangle * 3], NumTriangles * 3 ),
|
|
TArrayView< const int32 >( &MaterialIndexes[BaseTriangle], NumTriangles ),
|
|
Clusters, VertexBounds, NumTexCoords, bHasVertexColor);
|
|
}
|
|
ClusterCountPerMesh.Add(Clusters.Num() - NumClustersBefore);
|
|
BaseTriangle += NumTriangles;
|
|
}
|
|
}
|
|
|
|
float SurfaceArea = 0.0f;
|
|
for( FCluster& Cluster : Clusters )
|
|
SurfaceArea += Cluster.SurfaceArea;
|
|
|
|
int32 FallbackTargetNumTris = Resources.NumInputTriangles * Settings.FallbackPercentTriangles;
|
|
float FallbackTargetError = Settings.FallbackRelativeError * 0.01f * FMath::Sqrt( FMath::Min( 2.0f * SurfaceArea, VertexBounds.GetSurfaceArea() ) );
|
|
|
|
bool bFallbackIsReduced = Settings.FallbackPercentTriangles < 1.0f || FallbackTargetError > 0.0f;
|
|
|
|
// If we're going to replace the original vertex buffer with a coarse representation, get rid of the old copies
|
|
// now that we copied it into the cluster representation. We do it before the longer DAG reduce phase to shorten peak memory duration.
|
|
// This is especially important when building multiple huge Nanite meshes in parallel.
|
|
if( bFallbackIsReduced )
|
|
{
|
|
InputMeshData.Vertices.Empty();
|
|
InputMeshData.TriangleIndices.Empty();
|
|
}
|
|
MaterialIndexes.Empty();
|
|
|
|
uint32 Time0 = FPlatformTime::Cycles();
|
|
|
|
FBounds3f MeshBounds;
|
|
TArray<FClusterGroup> Groups;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build::DAG.Reduce);
|
|
|
|
uint32 ClusterStart = 0;
|
|
for (uint32 MeshIndex = 0; MeshIndex < Resources.NumInputMeshes; MeshIndex++)
|
|
{
|
|
uint32 NumClusters = ClusterCountPerMesh[MeshIndex];
|
|
BuildDAG( Groups, Clusters, ClusterStart, NumClusters, MeshIndex, MeshBounds );
|
|
ClusterStart += NumClusters;
|
|
}
|
|
}
|
|
|
|
if( Settings.KeepPercentTriangles < 1.0f || Settings.TrimRelativeError > 0.0f )
|
|
{
|
|
int32 TargetNumTris = Resources.NumInputTriangles * Settings.KeepPercentTriangles;
|
|
float TargetError = Settings.TrimRelativeError * 0.01f * FMath::Sqrt( FMath::Min( 2.0f * SurfaceArea, VertexBounds.GetSurfaceArea() ) );
|
|
|
|
FBinaryHeap< float > Heap = FindDAGCut( Groups, Clusters, TargetNumTris, TargetError, 0 );
|
|
|
|
float CutError = Clusters[ Heap.Top() ].LODError;
|
|
|
|
for( FClusterGroup& Group : Groups )
|
|
Group.bTrimmed = Group.MaxParentLODError <= CutError;
|
|
|
|
uint32 NumVerts = 0;
|
|
uint32 NumTris = 0;
|
|
for( uint32 i = 0; i < Heap.Num(); i++ )
|
|
{
|
|
FCluster& Cluster = Clusters[ Heap.Peek(i) ];
|
|
|
|
Cluster.GeneratingGroupIndex = MAX_uint32;
|
|
Cluster.EdgeLength = -FMath::Abs( Cluster.EdgeLength );
|
|
NumVerts += Cluster.NumVerts;
|
|
NumTris += Cluster.NumTris;
|
|
}
|
|
|
|
Resources.NumInputVertices = FMath::Min( NumVerts, Resources.NumInputVertices );
|
|
Resources.NumInputTriangles = NumTris;
|
|
}
|
|
|
|
uint32 ReduceTime = FPlatformTime::Cycles();
|
|
UE_LOG(LogStaticMesh, Log, TEXT("Reduce [%.2fs]"), FPlatformTime::ToMilliseconds(ReduceTime - Time0) / 1000.0f);
|
|
|
|
for (int32 FallbackLODIndex = 0; FallbackLODIndex < OutputLODMeshData.Num(); ++FallbackLODIndex)
|
|
{
|
|
const uint32 FallbackStartTime = FPlatformTime::Cycles();
|
|
|
|
auto& FallbackLODMeshData = OutputLODMeshData[FallbackLODIndex];
|
|
|
|
// Copy the section data which will then be patched up after the simplification
|
|
FallbackLODMeshData.Sections = InputMeshData.Sections;
|
|
|
|
// % of first proxy not % of original
|
|
if( FallbackLODIndex > 0 )
|
|
{
|
|
FallbackTargetNumTris = OutputLODMeshData[0].TriangleIndices.Num() / 3;
|
|
FallbackTargetNumTris *= FallbackLODMeshData.PercentTriangles;
|
|
FallbackTargetError = 0.0f;
|
|
}
|
|
|
|
if( !bFallbackIsReduced && FallbackLODIndex == 0 )
|
|
{
|
|
Swap( FallbackLODMeshData.Vertices, InputMeshData.Vertices );
|
|
Swap( FallbackLODMeshData.TriangleIndices, InputMeshData.TriangleIndices );
|
|
CalcTangents( FallbackLODMeshData.Vertices, FallbackLODMeshData.TriangleIndices );
|
|
}
|
|
else
|
|
{
|
|
TArray<FStaticMeshSection, TInlineAllocator<1>> FallbackSections = InputMeshData.Sections;
|
|
BuildCoarseRepresentation(Groups, Clusters, FallbackLODMeshData.Vertices, FallbackLODMeshData.TriangleIndices, FallbackSections, NumTexCoords, FallbackTargetNumTris, FallbackTargetError);
|
|
|
|
// Fixup mesh section info with new coarse mesh ranges, while respecting original ordering and keeping materials
|
|
// that do not end up with any assigned triangles (due to decimation process).
|
|
|
|
for (FStaticMeshSection& Section : FallbackLODMeshData.Sections)
|
|
{
|
|
// For each section info, try to find a matching entry in the coarse version.
|
|
const FStaticMeshSection* FallbackSection = FallbackSections.FindByPredicate(
|
|
[&Section](const FStaticMeshSection& CoarseSectionIter)
|
|
{
|
|
return CoarseSectionIter.MaterialIndex == Section.MaterialIndex;
|
|
});
|
|
|
|
if (FallbackSection != nullptr)
|
|
{
|
|
// Matching entry found
|
|
Section.FirstIndex = FallbackSection->FirstIndex;
|
|
Section.NumTriangles = FallbackSection->NumTriangles;
|
|
Section.MinVertexIndex = FallbackSection->MinVertexIndex;
|
|
Section.MaxVertexIndex = FallbackSection->MaxVertexIndex;
|
|
}
|
|
else
|
|
{
|
|
// Section removed due to decimation, set placeholder entry
|
|
Section.FirstIndex = 0;
|
|
Section.NumTriangles = 0;
|
|
Section.MinVertexIndex = 0;
|
|
Section.MaxVertexIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint32 FallbackEndTime = FPlatformTime::Cycles();
|
|
UE_LOG(LogStaticMesh, Log, TEXT("Fallback %d/%d [%.2fs], num tris: %d"), FallbackLODIndex, OutputLODMeshData.Num(), FPlatformTime::ToMilliseconds(FallbackEndTime - FallbackStartTime) / 1000.0f, FallbackLODMeshData.TriangleIndices.Num() / 3);
|
|
}
|
|
|
|
uint32 EncodeTime0 = FPlatformTime::Cycles();
|
|
|
|
Encode( Resources, Settings, Clusters, Groups, MeshBounds, Resources.NumInputMeshes, NumTexCoords, bHasVertexColor);
|
|
|
|
uint32 EncodeTime1 = FPlatformTime::Cycles();
|
|
UE_LOG( LogStaticMesh, Log, TEXT("Encode [%.2fs]"), FPlatformTime::ToMilliseconds( EncodeTime1 - EncodeTime0 ) / 1000.0f );
|
|
|
|
if (bHasImposter)
|
|
{
|
|
uint32 ImposterStartTime = FPlatformTime::Cycles();
|
|
auto& RootChildren = Groups.Last().Children;
|
|
|
|
FImposterAtlas ImposterAtlas( Resources.ImposterAtlas, MeshBounds );
|
|
|
|
ParallelFor(
|
|
TEXT("Nanite.BuildData.PF"),
|
|
FMath::Square(FImposterAtlas::AtlasSize),
|
|
1,
|
|
[&](int32 TileIndex)
|
|
{
|
|
FIntPoint TilePos(
|
|
TileIndex % FImposterAtlas::AtlasSize,
|
|
TileIndex / FImposterAtlas::AtlasSize);
|
|
|
|
for (int32 ClusterIndex = 0; ClusterIndex < RootChildren.Num(); ClusterIndex++)
|
|
{
|
|
ImposterAtlas.Rasterize(TilePos, Clusters[RootChildren[ClusterIndex]], ClusterIndex);
|
|
}
|
|
});
|
|
|
|
UE_LOG(LogStaticMesh, Log, TEXT("Imposter [%.2fs]"), FPlatformTime::ToMilliseconds(FPlatformTime::Cycles() - ImposterStartTime ) / 1000.0f);
|
|
}
|
|
|
|
uint32 Time1 = FPlatformTime::Cycles();
|
|
|
|
UE_LOG( LogStaticMesh, Log, TEXT("Nanite build [%.2fs]\n"), FPlatformTime::ToMilliseconds( Time1 - Time0 ) / 1000.0f );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FBuilderModule::Build(
|
|
FResources& Resources,
|
|
TArray<FStaticMeshBuildVertex>& Vertices, // TODO: Do not require this vertex type for all users of Nanite
|
|
TArray<uint32>& TriangleIndices,
|
|
TArray<int32>& MaterialIndices,
|
|
TArray<uint32>& MeshTriangleCounts,
|
|
uint32 NumTexCoords,
|
|
const FMeshNaniteSettings& Settings)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build);
|
|
|
|
check(Settings.FallbackPercentTriangles == 1.0f); // No coarse representation used by this path
|
|
|
|
FVertexMeshData InputMeshData;
|
|
InputMeshData.Vertices = Vertices;
|
|
InputMeshData.TriangleIndices = TriangleIndices;
|
|
// Section are left empty because they are not touched anyway (not building a coarse representation)
|
|
|
|
TArrayView< FVertexMeshData > EmptyOutputLODMeshData;
|
|
|
|
return BuildNaniteData(
|
|
Resources,
|
|
InputMeshData,
|
|
MaterialIndices,
|
|
MeshTriangleCounts,
|
|
EmptyOutputLODMeshData,
|
|
NumTexCoords,
|
|
Settings
|
|
);
|
|
}
|
|
|
|
bool FBuilderModule::Build(
|
|
FResources& Resources,
|
|
FVertexMeshData& InputMeshData,
|
|
TArrayView< FVertexMeshData > OutputLODMeshData,
|
|
uint32 NumTexCoords,
|
|
const FMeshNaniteSettings& Settings)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::Build);
|
|
|
|
// TODO: Properly error out if # of unique materials is > 64 (error message to editor log)
|
|
check(InputMeshData.Sections.Num() > 0 && InputMeshData.Sections.Num() <= 64);
|
|
|
|
// Build associated array of triangle index and material index.
|
|
TArray<int32> MaterialIndices;
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(Nanite::BuildSections);
|
|
MaterialIndices.Reserve(InputMeshData.TriangleIndices.Num() / 3);
|
|
for (int32 SectionIndex = 0; SectionIndex < InputMeshData.Sections.Num(); SectionIndex++)
|
|
{
|
|
FStaticMeshSection& Section = InputMeshData.Sections[SectionIndex];
|
|
|
|
// TODO: Safe to enforce valid materials always?
|
|
check(Section.MaterialIndex != INDEX_NONE);
|
|
for (uint32 i = 0; i < Section.NumTriangles; ++i)
|
|
{
|
|
MaterialIndices.Add(Section.MaterialIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<uint32> MeshTriangleCounts;
|
|
MeshTriangleCounts.Add(InputMeshData.TriangleIndices.Num() / 3);
|
|
|
|
// Make sure there is 1 material index per triangle.
|
|
check(MaterialIndices.Num() * 3 == InputMeshData.TriangleIndices.Num());
|
|
|
|
return BuildNaniteData(
|
|
Resources,
|
|
InputMeshData,
|
|
MaterialIndices,
|
|
MeshTriangleCounts,
|
|
OutputLODMeshData,
|
|
NumTexCoords,
|
|
Settings
|
|
);
|
|
}
|
|
|
|
} // namespace Nanite
|