// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshUtilities.h" #include "MeshUtilitiesPrivate.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Materials/Material.h" #include "RawMesh.h" #include "StaticMeshResources.h" #include "MeshCardRepresentation.h" #include "DistanceFieldAtlas.h" #include "MeshRepresentationCommon.h" #include "Containers/BinaryHeap.h" // Debug option for investigating card generation issues #define DEBUG_MESH_CARD_VISUALIZATION 0 int32 constexpr NumAxisAlignedDirections = 6; int32 constexpr MaxCardsPerMesh = 32; class FGenerateCardMeshContext { public: const FString& MeshName; const FEmbreeScene& EmbreeScene; FCardRepresentationData& OutData; FGenerateCardMeshContext(const FString& InMeshName, const FEmbreeScene& InEmbreeScene, FCardRepresentationData& InOutData) : MeshName(InMeshName), EmbreeScene(InEmbreeScene), OutData(InOutData) {} }; #if USE_EMBREE struct FSurfel { FVector3f Position; FVector3f Normal; FVector3f LocalSurfelPosition[NumAxisAlignedDirections]; float RayCache[NumAxisAlignedDirections]; }; struct FClusteringParams { float SurfelRadius = 0.0f; float SurfelExtendRadius = 0.0f; float NormalWeightTreshold = 0.0f; float DistanceWeightTreshold = 0.0f; int32 MinSurfelsPerCluster = 0; float MinDensityPerCluster = 0.0f; }; FVector3f AxisAlignedDirectionIndexToNormal(int32 AxisAlignedDirectionIndex) { const int32 AxisIndex = AxisAlignedDirectionIndex / 2; FVector3f Normal(0.0f, 0.0f, 0.0f); Normal[AxisIndex] = AxisAlignedDirectionIndex & 1 ? 1.0f : -1.0f; return Normal; } uint8 NormalToAxisAlignedDirectionIndex(FVector3f Normal) { const float AbsMaxComponent = Normal.GetAbsMax(); int32 AxisIndex = 0; if (FMath::Abs(Normal.X) >= AbsMaxComponent) { AxisIndex = 0; } else if (FMath::Abs(Normal.Y) >= AbsMaxComponent) { AxisIndex = 1; } else { AxisIndex = 2; } return AxisIndex * 2 + (Normal[AxisIndex] >= 0.0f ? 1 : 0); } FMatrix44f GetCardBasis(FVector3f Normal) { FVector3f XAxis; FVector3f YAxis; Normal.FindBestAxisVectors(XAxis, YAxis); XAxis = FVector::CrossProduct(Normal, YAxis); XAxis.Normalize(); return FMatrix44f(XAxis, YAxis, Normal, FVector::ZeroVector).GetTransposed(); } struct FSurfelScene { TArray Surfels; TArray SurfelIndicesPerDirection[NumAxisAlignedDirections]; float TwoSidedTriangleRatio = 0.0f; }; class FSurfelCluster { public: FMatrix44f WorldToLocal; FVector3f Normal; FBox Bounds; TArray SurfelIndices; float MinRayZ = FLT_MAX; uint8 AxisAlignedDirectionIndex = UINT8_MAX; // Best surfels to add to this cluster int32 BestSurfelIndex = -1; float BestSurfelWeight = 0.0f; void Reset() { Bounds.Init(); SurfelIndices.Reset(); MinRayZ = FLT_MAX; AxisAlignedDirectionIndex = UINT8_MAX; } void SetDirection(uint8 InAxisAlignedDirectionIndex) { AxisAlignedDirectionIndex = InAxisAlignedDirectionIndex; Normal = AxisAlignedDirectionIndexToNormal(InAxisAlignedDirectionIndex); WorldToLocal = GetCardBasis(Normal); } bool IsValid(const FClusteringParams& ClusteringParams) const { const float SurfelArea = PI * ClusteringParams.SurfelRadius * ClusteringParams.SurfelRadius; const FVector3f CardSize = 2.0f * Bounds.GetExtent(); const float Density = (SurfelIndices.Num() * SurfelArea) / (CardSize.X * CardSize.Y); return SurfelIndices.Num() >= ClusteringParams.MinSurfelsPerCluster && Density > ClusteringParams.MinDensityPerCluster; } void AddSurfel(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, int32 SurfelToAddIndex); void UpdateBestSurfel(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, TBitArray<>& SurfelAssignedToAnyCluster); }; bool CanAddSurfelToCluster(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, const FSurfelCluster& Cluster, int32 SurfelToAddIndex) { const FSurfel& SurfelToAdd = SurfelScene.Surfels[SurfelToAddIndex]; float MinRayZ = Cluster.MinRayZ; const FVector3f LocalSpacePositon = SurfelToAdd.LocalSurfelPosition[Cluster.AxisAlignedDirectionIndex]; float BoundsMaxZ = FMath::Max(Cluster.Bounds.Max.Z, LocalSpacePositon.Z + ClusteringParams.SurfelExtendRadius); const float RayTFar = SurfelToAdd.RayCache[Cluster.AxisAlignedDirectionIndex]; if (RayTFar < FLT_MAX) { const float ClusterSpaceHitPointZ = LocalSpacePositon.Z + RayTFar; MinRayZ = FMath::Min(MinRayZ, ClusterSpaceHitPointZ); } return MinRayZ > BoundsMaxZ; } float SurfelNormalWeight(const FSurfel& Surfel, FVector3f ClusterNormal, const FClusteringParams& ClusteringParams) { return ((ClusterNormal | Surfel.Normal) - ClusteringParams.NormalWeightTreshold) / (1.0f - ClusteringParams.NormalWeightTreshold); } void FSurfelCluster::AddSurfel(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, int32 SurfelToAddIndex) { const FSurfel& SurfelToAdd = SurfelScene.Surfels[SurfelToAddIndex]; SurfelIndices.Add(SurfelToAddIndex); const FVector3f LocalSpacePositon = SurfelToAdd.LocalSurfelPosition[AxisAlignedDirectionIndex]; Bounds += (LocalSpacePositon - ClusteringParams.SurfelExtendRadius); Bounds += (LocalSpacePositon + ClusteringParams.SurfelExtendRadius); const float RayTFar = SurfelToAdd.RayCache[AxisAlignedDirectionIndex]; if (RayTFar < FLT_MAX) { const float ClusterSpaceHitPointZ = LocalSpacePositon.Z + RayTFar; MinRayZ = FMath::Min(MinRayZ, ClusterSpaceHitPointZ); } // Check if all surfels are visible after add check(MinRayZ > Bounds.Max.Z); } void FSurfelCluster::UpdateBestSurfel(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, TBitArray<>& SurfelAssignedToAnyCluster) { BestSurfelIndex = -1; BestSurfelWeight = 0.0f; for (int32 SurfelIndex : SurfelScene.SurfelIndicesPerDirection[AxisAlignedDirectionIndex]) { if (!SurfelAssignedToAnyCluster[SurfelIndex]) { const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex]; float SurfelWeight = SurfelNormalWeight(Surfel, Normal, ClusteringParams); if (SurfelWeight > BestSurfelWeight) { const FVector3f LocalSpaceSurfelPosition = Surfel.LocalSurfelPosition[AxisAlignedDirectionIndex]; if (SurfelIndices.Num() > 0) { const FVector3f BoundsCenter = Bounds.GetCenter(); const FVector3f BoundsExtent = Bounds.GetExtent(); const FVector3f Distance = FVector3f::Max((LocalSpaceSurfelPosition - BoundsCenter).GetAbs() - BoundsExtent, FVector3f(0.0f, 0.0f, 0.0f)); const float ManhattanDistance = Distance.X + Distance.Y + Distance.Z / 4.0f; const float DistanceWeight = FMath::Clamp(1.0f - ManhattanDistance / ClusteringParams.DistanceWeightTreshold, 0.0f, 1.0f); SurfelWeight *= DistanceWeight; } // Weight by visibility const float RayTFar = Surfel.RayCache[AxisAlignedDirectionIndex]; if (RayTFar < FLT_MAX) { SurfelWeight *= FMath::Clamp(RayTFar / 1100.0f + 0.1f, 0.1f, 1.0f); } if (SurfelWeight > BestSurfelWeight) { if (CanAddSurfelToCluster(ClusteringParams, SurfelScene, *this, SurfelIndex)) { BestSurfelIndex = SurfelIndex; BestSurfelWeight = SurfelWeight; } } } } } } float SurfelHalton(int32 Index, int32 Base) { float Result = 0.0f; float InvBase = 1.0f / Base; float Fraction = InvBase; while (Index > 0) { Result += (Index % Base) * Fraction; Index /= Base; Fraction *= InvBase; } return Result; } void GenerateSurfels( const FGenerateCardMeshContext& Context, const FBox& MeshCardsBounds, FSurfelScene& SurfelScene, FClusteringParams& ClusteringParams, FLumenCardBuildDebugData& DebugData) { const uint32 NumSourceVertices = Context.EmbreeScene.Geometry.VertexArray.Num(); const uint32 NumSourceIndices = Context.EmbreeScene.Geometry.IndexArray.Num(); const int32 NumSourceTriangles = NumSourceIndices / 3; if (NumSourceTriangles == 0) { return; } // Generate random ray directions over a hemisphere constexpr uint32 NumRayDirectionsOverHemisphere = 64; TArray RayDirectionsOverHemisphere; { FRandomStream RandomStream(0); MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumRayDirectionsOverHemisphere, RandomStream, RayDirectionsOverHemisphere); } FVector3f NormalPerDirection[NumAxisAlignedDirections]; FMatrix44f WorldToLocalPerDirection[NumAxisAlignedDirections]; for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < NumAxisAlignedDirections; ++AxisAlignedDirectionIndex) { NormalPerDirection[AxisAlignedDirectionIndex] = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex); WorldToLocalPerDirection[AxisAlignedDirectionIndex] = GetCardBasis(NormalPerDirection[AxisAlignedDirectionIndex]); } float TotalArea = 0.0f; int32 NumTwoSidedTriangles = 0; TArray TriangleArea; TArray TriangleNormal; TArray TriangleIndices; TriangleArea.Reserve(NumSourceTriangles); TriangleNormal.Reserve(NumSourceTriangles); TriangleIndices.Reserve(NumSourceIndices); for (int32 SourceTriangleIndex = 0; SourceTriangleIndex < NumSourceTriangles; ++SourceTriangleIndex) { const int32 Index0 = Context.EmbreeScene.Geometry.IndexArray[SourceTriangleIndex * 3 + 0]; const int32 Index1 = Context.EmbreeScene.Geometry.IndexArray[SourceTriangleIndex * 3 + 1]; const int32 Index2 = Context.EmbreeScene.Geometry.IndexArray[SourceTriangleIndex * 3 + 2]; const FVector3f Pos0 = Context.EmbreeScene.Geometry.VertexArray[Index0]; const FVector3f Pos1 = Context.EmbreeScene.Geometry.VertexArray[Index1]; const FVector3f Pos2 = Context.EmbreeScene.Geometry.VertexArray[Index2]; const FVector3f Cross = (Pos1 - Pos0) ^ (Pos2 - Pos0); const FVector3f Normal = -Cross.GetSafeNormal(); const float Area = 0.5f * Cross.Size(); TriangleIndices.Add(Index0); TriangleIndices.Add(Index1); TriangleIndices.Add(Index2); TriangleNormal.Add(Normal); TriangleArea.Add(Area); TotalArea += Area; // Add an extra triangle if it's a two sided one if (Context.EmbreeScene.Geometry.TriangleDescs[SourceTriangleIndex].IsTwoSided()) { TriangleIndices.Add(Index0); TriangleIndices.Add(Index1); TriangleIndices.Add(Index2); TriangleNormal.Add(-Normal); TriangleArea.Add(Area); TotalArea += Area; ++NumTwoSidedTriangles; } } SurfelScene.TwoSidedTriangleRatio = NumTwoSidedTriangles / float(NumSourceTriangles); TArray TriangleCDF; TriangleCDF.SetNumUninitialized(TriangleArea.Num()); for (int32 TriangleIndex = 0; TriangleIndex < TriangleArea.Num(); ++TriangleIndex) { TriangleCDF[TriangleIndex] = TriangleArea[TriangleIndex]; if (TriangleIndex > 0) { TriangleCDF[TriangleIndex] += TriangleCDF[TriangleIndex - 1]; } } const float TargetSurfelRadius = 10.0f; const float TargetSurfelArea = PI * TargetSurfelRadius * TargetSurfelRadius; const int32 TargetNumSurfels = int32(TotalArea / TargetSurfelArea + 0.5f); const int32 NumSurfels = FMath::Clamp(TargetNumSurfels, 128, 5000); SurfelScene.Surfels.Reserve(NumSurfels); // Derive clustering params from surface area and CVars { const float SurfelArea = TotalArea / NumSurfels; ClusteringParams.SurfelRadius = FMath::Sqrt(SurfelArea / PI); ClusteringParams.SurfelExtendRadius = 1.5f * ClusteringParams.SurfelRadius; static const auto CVarMeshCardRepresentationMinDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.MeshCardRepresentation.MinDensity")); static const auto CVarMeshCardRepresentationNormalTreshold = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.MeshCardRepresentation.NormalTreshold")); static const auto CVarMeshCardRepresentationDistanceTreshold = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.MeshCardRepresentation.DistanceTreshold")); ClusteringParams.NormalWeightTreshold = FMath::Clamp(CVarMeshCardRepresentationNormalTreshold->GetValueOnAnyThread(), 0.0f, 1.0f); ClusteringParams.DistanceWeightTreshold = FMath::Clamp(CVarMeshCardRepresentationDistanceTreshold->GetValueOnAnyThread(), 0.0f, FLT_MAX) * FMath::Max(TargetSurfelRadius, ClusteringParams.SurfelRadius); ClusteringParams.MinSurfelsPerCluster = 20; ClusteringParams.MinDensityPerCluster = FMath::Clamp(CVarMeshCardRepresentationMinDensity->GetValueOnAnyThread(), 0.0f, 1.0f); } int32 TriangleIndex = 0; for (int32 SampleIndex = 0; SampleIndex < NumSurfels; ++SampleIndex) { const float SampleArea = ((SampleIndex + 0.5f) / NumSurfels) * TotalArea; while (SampleArea > TriangleCDF[TriangleIndex] && TriangleIndex + 1 < TriangleArea.Num()) { ++TriangleIndex; } // Pick random sample in a triangle const float R0 = SurfelHalton(SampleIndex + 1, 2); const float R1 = SurfelHalton(SampleIndex + 1, 3); const float L0 = 1.0f - sqrtf(R0); const float L1 = sqrtf(R0) * (1.0f - R1); const float L2 = sqrtf(R0) * R1; const int32 Index0 = TriangleIndices[TriangleIndex * 3 + 0]; const int32 Index1 = TriangleIndices[TriangleIndex * 3 + 1]; const int32 Index2 = TriangleIndices[TriangleIndex * 3 + 2]; const FVector3f Position0 = Context.EmbreeScene.Geometry.VertexArray[Index0]; const FVector3f Position1 = Context.EmbreeScene.Geometry.VertexArray[Index1]; const FVector3f Position2 = Context.EmbreeScene.Geometry.VertexArray[Index2]; FSurfel Surfel; Surfel.Position = L0 * Position0 + L1 * Position1 + L2 * Position2; Surfel.Normal = TriangleNormal[TriangleIndex]; uint32 NumHits = 0; uint32 NumBackFaceHits = 0; constexpr float SurfaceRayBias = 0.1f; // Check if point can be clustered const FMatrix44f SurfaceBasis = MeshRepresentation::GetTangentBasisFrisvad(Surfel.Normal); for (int32 RayIndex = 0; RayIndex < RayDirectionsOverHemisphere.Num(); ++RayIndex) { const FVector3f RayOrigin = Surfel.Position; const FVector3f RayDirection = SurfaceBasis.TransformVector(RayDirectionsOverHemisphere[RayIndex]); FEmbreeRay EmbreeRay; EmbreeRay.ray.org_x = RayOrigin.X; EmbreeRay.ray.org_y = RayOrigin.Y; EmbreeRay.ray.org_z = RayOrigin.Z; EmbreeRay.ray.dir_x = RayDirection.X; EmbreeRay.ray.dir_y = RayDirection.Y; EmbreeRay.ray.dir_z = RayDirection.Z; EmbreeRay.ray.tnear = SurfaceRayBias; EmbreeRay.ray.tfar = FLT_MAX; FEmbreeIntersectionContext EmbreeContext; rtcInitIntersectContext(&EmbreeContext); rtcIntersect1(Context.EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay); if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID) { ++NumHits; if (FVector::DotProduct(RayDirection, EmbreeRay.GetHitNormal()) > 0.0f && !EmbreeContext.IsHitTwoSided()) { ++NumBackFaceHits; } } } const bool bInsideGeometry = NumHits > 0 && NumBackFaceHits > NumRayDirectionsOverHemisphere * 0.2f; // Fill ray cache for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < NumAxisAlignedDirections; ++AxisAlignedDirectionIndex) { const FVector3f WorldSpaceDirection = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex); Surfel.RayCache[AxisAlignedDirectionIndex] = 0.0f; const float SurfelNormalDotRayDir = FMath::Clamp(Surfel.Normal | WorldSpaceDirection, 0.0f, 1.0f); if (SurfelNormalDotRayDir > 0.0f) { const FVector3f RayOrigin = Surfel.Position; const FVector3f RayDirection = WorldSpaceDirection; FEmbreeRay EmbreeRay; EmbreeRay.ray.org_x = RayOrigin.X; EmbreeRay.ray.org_y = RayOrigin.Y; EmbreeRay.ray.org_z = RayOrigin.Z; EmbreeRay.ray.dir_x = RayDirection.X; EmbreeRay.ray.dir_y = RayDirection.Y; EmbreeRay.ray.dir_z = RayDirection.Z; EmbreeRay.ray.tnear = SurfaceRayBias; EmbreeRay.ray.tfar = FLT_MAX; FEmbreeIntersectionContext EmbreeContext; rtcInitIntersectContext(&EmbreeContext); rtcIntersect1(Context.EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay); Surfel.RayCache[AxisAlignedDirectionIndex] = EmbreeRay.ray.tfar; } } // Fill local surfel positions { for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < NumAxisAlignedDirections; ++AxisAlignedDirectionIndex) { Surfel.LocalSurfelPosition[AxisAlignedDirectionIndex] = WorldToLocalPerDirection[AxisAlignedDirectionIndex].TransformPosition(Surfel.Position); } } const int32 SurfelAxisAlignedDirectionIndex = NormalToAxisAlignedDirectionIndex(Surfel.Normal); const bool bValidSurfel = !bInsideGeometry && Surfel.RayCache[SurfelAxisAlignedDirectionIndex] > 2.0f * ClusteringParams.SurfelRadius; if (bValidSurfel) { SurfelScene.Surfels.Add(Surfel); } #if DEBUG_MESH_CARD_VISUALIZATION FLumenCardBuildDebugData::FSurfel& DebugSurfel = DebugData.Surfels.AddDefaulted_GetRef(); DebugSurfel.Position = Surfel.Position; DebugSurfel.Normal = Surfel.Normal; DebugSurfel.SourceSurfelIndex = SurfelScene.Surfels.Num() - 1; DebugSurfel.Type = bValidSurfel ? FLumenCardBuildDebugData::ESurfelType::Valid : FLumenCardBuildDebugData::ESurfelType::Invalid; #endif } TArray SurfelIndicesPerDirection[NumAxisAlignedDirections]; for (int32 SurfelIndex = 0; SurfelIndex < SurfelScene.Surfels.Num(); ++SurfelIndex) { const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex]; for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < NumAxisAlignedDirections; ++AxisAlignedDirectionIndex) { const float SurfelWeight = SurfelNormalWeight(Surfel, NormalPerDirection[AxisAlignedDirectionIndex], ClusteringParams); if (SurfelWeight > 0.0f) { SurfelScene.SurfelIndicesPerDirection[AxisAlignedDirectionIndex].Add(SurfelIndex); } } } } void GrowSingleCluster( const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, TBitArray<>& SurfelAssignedToAnyCluster, FSurfelCluster& Cluster) { do { Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster); if (Cluster.BestSurfelIndex >= 0) { Cluster.AddSurfel(ClusteringParams, SurfelScene, Cluster.BestSurfelIndex); SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex] = true; } } while (Cluster.BestSurfelIndex >= 0); } int32 FindBestSeed(const FSurfelScene& SurfelScene, const FSurfelCluster& Cluster) { int32 NormalHistogram[NumAxisAlignedDirections]; for (int32 DirectionIndex = 0; DirectionIndex < NumAxisAlignedDirections; ++DirectionIndex) { NormalHistogram[DirectionIndex] = 0; } FVector3f PositionSum = FVector3f(0.0f, 0.0f, 0.0f); for (int32 SurfelIndex : Cluster.SurfelIndices) { const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex]; PositionSum += Surfel.Position; const int32 NormalBucketIndex = NormalToAxisAlignedDirectionIndex(Surfel.Normal); NormalHistogram[NormalBucketIndex] += 1; } const FVector3f AveragePosition = PositionSum / Cluster.SurfelIndices.Num(); int32 BestSurfelIndex = 0; int32 BestSurfelNormalBucketSize = 0; float BestSurfelDistanceSq = FLT_MAX; for (int32 SurfelIndex : Cluster.SurfelIndices) { const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex]; const int32 NormalBucketIndex = NormalToAxisAlignedDirectionIndex(Surfel.Normal); const int32 NormalBucketSize = NormalHistogram[NormalBucketIndex]; float SurfelDistanceSq = (AveragePosition - Surfel.Position).SizeSquared(); if (NormalBucketSize > BestSurfelNormalBucketSize || (NormalBucketSize == BestSurfelNormalBucketSize && SurfelDistanceSq < BestSurfelDistanceSq)) { BestSurfelIndex = SurfelIndex; BestSurfelNormalBucketSize = NormalBucketSize; BestSurfelDistanceSq = SurfelDistanceSq; } } return BestSurfelIndex; } void GrowAllClusters( const FClusteringParams& ClusteringParams, TArray& Clusters, const FSurfelScene& SurfelScene, TBitArray<>& SurfelAssignedToAnyCluster ) { // Reset all clusters and find their new best seeds for (FSurfelCluster& Cluster : Clusters) { const int32 ClusterSeedIndex = FindBestSeed(SurfelScene, Cluster); for (int32 SurfelIndex : Cluster.SurfelIndices) { SurfelAssignedToAnyCluster[SurfelIndex] = false; } Cluster.Reset(); Cluster.SetDirection(NormalToAxisAlignedDirectionIndex(SurfelScene.Surfels[ClusterSeedIndex].Normal)); Cluster.AddSurfel(ClusteringParams, SurfelScene, ClusterSeedIndex); SurfelAssignedToAnyCluster[ClusterSeedIndex] = true; } // Fill surfel heap for (FSurfelCluster& Cluster : Clusters) { Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster); } // Cluster surfels int32 BestClusterToAddIndex = -1; float BestSurfelToAddWeight = 0.0f; do { BestClusterToAddIndex = -1; BestSurfelToAddWeight = 0.0f; for (int32 ClusterIndex = 0; ClusterIndex < Clusters.Num(); ++ClusterIndex) { FSurfelCluster& Cluster = Clusters[ClusterIndex]; if (Cluster.BestSurfelIndex >= 0 && SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex]) { Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster); } if (Cluster.BestSurfelIndex >= 0 && Cluster.BestSurfelWeight > BestSurfelToAddWeight) { BestClusterToAddIndex = ClusterIndex; BestSurfelToAddWeight = Cluster.BestSurfelWeight; } } if (BestClusterToAddIndex >= 0) { FSurfelCluster& Cluster = Clusters[BestClusterToAddIndex]; Cluster.AddSurfel(ClusteringParams, SurfelScene, Cluster.BestSurfelIndex); SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex] = true; Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster); } } while (BestClusterToAddIndex >= 0); } struct FMeshCardsLODLevel { TArray Clusters; float SurfaceCoverage; float ClusterArea; }; void UpdateLODLevelCoverage(const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel) { LODLevel.ClusterArea = 0.0f; TArray SurfelCoverage; SurfelCoverage.SetNumZeroed(SurfelScene.Surfels.Num()); for (const FSurfelCluster& Cluster : LODLevel.Clusters) { for (int32 SurfelIndex : Cluster.SurfelIndices) { const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex]; const float SurfelWeight = SurfelNormalWeight(Surfel, Cluster.Normal, ClusteringParams); SurfelCoverage[SurfelIndex] = FMath::Max(SurfelCoverage[SurfelIndex], SurfelWeight); } const FVector3f BoundsSize = Cluster.Bounds.GetSize(); LODLevel.ClusterArea += BoundsSize.X * BoundsSize.Y; } float SurfaceCoverageSum = 0.0f; for (int32 SurfelIndex = 0; SurfelIndex < SurfelScene.Surfels.Num(); ++SurfelIndex) { SurfaceCoverageSum += SurfelCoverage[SurfelIndex]; } LODLevel.SurfaceCoverage = 0.0f; if (SurfelScene.Surfels.Num() > 0) { LODLevel.SurfaceCoverage = SurfaceCoverageSum / SurfelScene.Surfels.Num(); } } // Cluster only by direction void BuildMeshCardsLOD0(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel) { FSurfelCluster TempCluster; for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < NumAxisAlignedDirections; ++AxisAlignedDirectionIndex) { TempCluster.Reset(); TempCluster.SetDirection(AxisAlignedDirectionIndex); for (int32 SurfelIndex : SurfelScene.SurfelIndicesPerDirection[AxisAlignedDirectionIndex]) { const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex]; const float SurfelWeight = SurfelNormalWeight(Surfel, TempCluster.Normal, ClusteringParams); if (Surfel.RayCache[TempCluster.AxisAlignedDirectionIndex] == FLT_MAX && SurfelWeight > 0.0f) { const FVector3f LocalPositon = Surfel.LocalSurfelPosition[TempCluster.AxisAlignedDirectionIndex]; TempCluster.SurfelIndices.Add(SurfelIndex); TempCluster.Bounds += LocalPositon - ClusteringParams.SurfelExtendRadius; TempCluster.Bounds += LocalPositon + ClusteringParams.SurfelExtendRadius; } } if (TempCluster.IsValid(ClusteringParams)) { LODLevel.Clusters.Add(TempCluster); } } UpdateLODLevelCoverage(SurfelScene, ClusteringParams, LODLevel); } // Cluster by direction and position void BuildMeshCardsLOD1(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel) { static const auto CVarMeshCardRepresentationSeedIterations = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MeshCardRepresentation.SeedIterations")); static const auto CVarMeshCardRepresentationGrowIterations = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MeshCardRepresentation.GrowIterations")); const int32 NumSeedIterations = FMath::Clamp(CVarMeshCardRepresentationSeedIterations->GetValueOnAnyThread(), 1, 32); const int32 NumGrowIterations = FMath::Clamp(CVarMeshCardRepresentationGrowIterations->GetValueOnAnyThread(), 0, 32); // Init list of cluster seeds TArray ClusterSeeds; ClusterSeeds.SetNumUninitialized(SurfelScene.Surfels.Num()); for (int32 SurfelIndex = 0; SurfelIndex < SurfelScene.Surfels.Num(); ++SurfelIndex) { ClusterSeeds[SurfelIndex] = SurfelIndex; } FRandomStream ClusterSeedRandomStream(0); TBitArray<> SurfelAssignedToAnyCluster(false, SurfelScene.Surfels.Num()); FSurfelCluster TempCluster; // Generate initial list of clusters while (ClusterSeeds.Num() > 0) { // Select next seed int32 NextSeedSurfelIndex = -1; while (ClusterSeeds.Num() > 0) { int32 RandomIndex = ClusterSeedRandomStream.RandHelper(ClusterSeeds.Num()); const int32 ClusterSeed = ClusterSeeds[RandomIndex]; ClusterSeeds.RemoveAtSwap(RandomIndex); if (!SurfelAssignedToAnyCluster[ClusterSeed]) { NextSeedSurfelIndex = ClusterSeed; break; } } // Try to build a cluster using selected seed if (NextSeedSurfelIndex >= 0) { for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumSeedIterations; ++ClusteringIterationIndex) { TempCluster.Reset(); TempCluster.SetDirection(NormalToAxisAlignedDirectionIndex(SurfelScene.Surfels[NextSeedSurfelIndex].Normal)); TempCluster.AddSurfel(ClusteringParams, SurfelScene, NextSeedSurfelIndex); SurfelAssignedToAnyCluster[NextSeedSurfelIndex] = true; GrowSingleCluster( ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster, TempCluster); // Restore global state for (int32 SurfelIndex : TempCluster.SurfelIndices) { SurfelAssignedToAnyCluster[SurfelIndex] = false; } if (TempCluster.IsValid(ClusteringParams)) { const int32 AverageSurfelIndex = FindBestSeed(SurfelScene, TempCluster); if (AverageSurfelIndex != NextSeedSurfelIndex) { ClusterSeeds.RemoveSwap(AverageSurfelIndex); NextSeedSurfelIndex = AverageSurfelIndex; } else { break; } } else { break; } } // Add new cluster only if it has least MinSurfelsPerCluster points if (TempCluster.IsValid(ClusteringParams)) { LODLevel.Clusters.Add(TempCluster); for (int32 SurfelIndex : TempCluster.SurfelIndices) { SurfelAssignedToAnyCluster[SurfelIndex] = true; } } } } // Grow all clusters simultaneously from the best seed for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumGrowIterations; ++ClusteringIterationIndex) { GrowAllClusters( ClusteringParams, LODLevel.Clusters, SurfelScene, SurfelAssignedToAnyCluster ); bool bAnyClusterSeedChanged = false; for (FSurfelCluster& Cluster : LODLevel.Clusters) { const int32 ClusterSeedIndex = FindBestSeed(SurfelScene, Cluster); if (ClusterSeedIndex != Cluster.SurfelIndices[0]) { bAnyClusterSeedChanged = true; break; } } if (!bAnyClusterSeedChanged) { break; } } UpdateLODLevelCoverage(SurfelScene, ClusteringParams, LODLevel); } void SerializeLOD(const FGenerateCardMeshContext& Context, const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, FMeshCardsLODLevel const& LODLevel, int32 LODLevelIndex, FMeshCardsBuildData& MeshCardsBuildData) { MeshCardsBuildData.MaxLODLevel = FMath::Max(MeshCardsBuildData.MaxLODLevel, LODLevelIndex); #if DEBUG_MESH_CARD_VISUALIZATION TBitArray<> DebugSurfelInCluster; TBitArray<> DebugSurfelInAnyCluster(false, SurfelScene.Surfels.Num()); UE_LOG(LogMeshUtilities, Log, TEXT("CardGen Mesh:%s LOD:%d Surfels:%d Clusters:%d SurfaceCoverage:%f ClusterArea:%f"), *Context.MeshName, LODLevelIndex, SurfelScene.Surfels.Num(), LODLevel.Clusters.Num(), LODLevel.SurfaceCoverage, LODLevel.ClusterArea); #endif for (const FSurfelCluster& Cluster : LODLevel.Clusters) { check(Cluster.AxisAlignedDirectionIndex < NumAxisAlignedDirections); if (Cluster.IsValid(ClusteringParams)) { const FMatrix LocalToWorld = Cluster.WorldToLocal.Inverse(); FLumenCardBuildData BuiltData; BuiltData.OBB.Origin = (FVector4f)LocalToWorld.TransformPosition(Cluster.Bounds.GetCenter()); BuiltData.OBB.Extent = Cluster.Bounds.GetExtent(); BuiltData.OBB.AxisX = LocalToWorld.GetScaledAxis(EAxis::X); BuiltData.OBB.AxisY = LocalToWorld.GetScaledAxis(EAxis::Y); BuiltData.OBB.AxisZ = LocalToWorld.GetScaledAxis(EAxis::Z); BuiltData.LODLevel = LODLevelIndex; BuiltData.AxisAlignedDirectionIndex = Cluster.AxisAlignedDirectionIndex; MeshCardsBuildData.CardBuildData.Add(BuiltData); #if DEBUG_MESH_CARD_VISUALIZATION DebugSurfelInCluster.Reset(); DebugSurfelInCluster.Add(false, SurfelScene.Surfels.Num()); FLumenCardBuildDebugData::FSurfelCluster& DebugCluster = MeshCardsBuildData.DebugData.Clusters.AddDefaulted_GetRef(); DebugCluster.Surfels.Reserve(SurfelScene.Surfels.Num()); // Cluster seed { FLumenCardBuildDebugData::FSurfel DebugSurfel; DebugSurfel.Position = SurfelScene.Surfels[Cluster.SurfelIndices[0]].Position; DebugSurfel.Normal = SurfelScene.Surfels[Cluster.SurfelIndices[0]].Normal; DebugSurfel.SourceSurfelIndex = 0; DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Seed; DebugCluster.Surfels.Add(DebugSurfel); } { const int32 AverageSurfelIndex = FindBestSeed(SurfelScene, Cluster); FLumenCardBuildDebugData::FSurfel DebugSurfel; DebugSurfel.Position = SurfelScene.Surfels[AverageSurfelIndex].Position; DebugSurfel.Normal = SurfelScene.Surfels[AverageSurfelIndex].Normal; DebugSurfel.SourceSurfelIndex = AverageSurfelIndex; DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Seed2; DebugCluster.Surfels.Add(DebugSurfel); } for (int32 SurfelIndex : Cluster.SurfelIndices) { FLumenCardBuildDebugData::FSurfel DebugSurfel; DebugSurfel.Position = SurfelScene.Surfels[SurfelIndex].Position; DebugSurfel.Normal = SurfelScene.Surfels[SurfelIndex].Normal; DebugSurfel.SourceSurfelIndex = SurfelIndex; DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Cluster; DebugCluster.Surfels.Add(DebugSurfel); const float RayT = SurfelScene.Surfels[SurfelIndex].RayCache[Cluster.AxisAlignedDirectionIndex]; if (RayT < FLT_MAX) { FLumenCardBuildDebugData::FRay DebugRay; DebugRay.RayStart = DebugSurfel.Position; DebugRay.RayEnd = DebugRay.RayStart + RayT * Cluster.Normal; DebugRay.bHit = false; DebugCluster.Rays.Add(DebugRay); } DebugSurfelInAnyCluster[SurfelIndex] = true; DebugSurfelInCluster[SurfelIndex] = true; } for (int32 SurfelIndex = 0; SurfelIndex < SurfelScene.Surfels.Num(); ++SurfelIndex) { if (!DebugSurfelInCluster[SurfelIndex]) { FLumenCardBuildDebugData::FSurfel DebugSurfel; DebugSurfel.Position = SurfelScene.Surfels[SurfelIndex].Position; DebugSurfel.Normal = SurfelScene.Surfels[SurfelIndex].Normal; DebugSurfel.SourceSurfelIndex = SurfelIndex; DebugSurfel.Type = DebugSurfelInAnyCluster[SurfelIndex] ? FLumenCardBuildDebugData::ESurfelType::Used : FLumenCardBuildDebugData::ESurfelType::Idle; DebugCluster.Surfels.Add(DebugSurfel); } } #endif } } } void BuildMeshCards(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, FCardRepresentationData& OutData) { // Make sure BBox isn't empty and we can generate card representation for it. This handles e.g. infinitely thin planes. const FVector MeshCardsBoundsCenter = MeshBounds.GetCenter(); const FVector MeshCardsBoundsExtent = FVector::Max(MeshBounds.GetExtent() + 1.0f, FVector(5.0f)); const FBox MeshCardsBounds(MeshCardsBoundsCenter - MeshCardsBoundsExtent, MeshCardsBoundsCenter + MeshCardsBoundsExtent); // Prepare a list of surfels for cluster fitting FSurfelScene SurfelScene; FClusteringParams ClusteringParamsLOD1; GenerateSurfels(Context, MeshCardsBounds, SurfelScene, ClusteringParamsLOD1, OutData.MeshCardsBuildData.DebugData); FClusteringParams ClusteringParamsLOD0 = ClusteringParamsLOD1; ClusteringParamsLOD0.MinSurfelsPerCluster /= 2; FMeshCardsLODLevel MeshCardsLOD0; BuildMeshCardsLOD0(MeshBounds, Context, SurfelScene, ClusteringParamsLOD0, MeshCardsLOD0); FMeshCardsLODLevel MeshCardsLOD1; BuildMeshCardsLOD1(MeshBounds, Context, SurfelScene, ClusteringParamsLOD1, MeshCardsLOD1); OutData.MeshCardsBuildData.Bounds = MeshCardsBounds; OutData.MeshCardsBuildData.MaxLODLevel = 0; OutData.MeshCardsBuildData.CardBuildData.Reset(); SerializeLOD(Context, ClusteringParamsLOD0, SurfelScene, MeshCardsLOD0, 0, OutData.MeshCardsBuildData); // Optionally serialize LOD1 if it's of higher quality without exceeding the budget if (MeshCardsLOD1.Clusters.Num() <= MaxCardsPerMesh && MeshCardsLOD1.SurfaceCoverage > MeshCardsLOD0.SurfaceCoverage) { float WeightedSurfaceCoverageIncrease = (MeshCardsLOD1.SurfaceCoverage - MeshCardsLOD0.SurfaceCoverage) / FMath::Max(MeshCardsLOD1.Clusters.Num() - MeshCardsLOD0.Clusters.Num(), 1); if (WeightedSurfaceCoverageIncrease > 0.02f * (1.0f + SurfelScene.TwoSidedTriangleRatio)) { SerializeLOD(Context, ClusteringParamsLOD1, SurfelScene, MeshCardsLOD1, 1, OutData.MeshCardsBuildData); } } } #endif // #if USE_EMBREE bool FMeshUtilities::GenerateCardRepresentationData( FString MeshName, const FSourceMeshDataForDerivedDataTask& SourceMeshData, const FStaticMeshLODResources& LODModel, class FQueuedThreadPool& ThreadPool, const TArray& MaterialBlendModes, const FBoxSphereBounds& Bounds, const FDistanceFieldVolumeData* DistanceFieldVolumeData, bool bGenerateAsIfTwoSided, FCardRepresentationData& OutData) { #if USE_EMBREE TRACE_CPUPROFILER_EVENT_SCOPE(FMeshUtilities::GenerateCardRepresentationData); const double StartTime = FPlatformTime::Seconds(); FEmbreeScene EmbreeScene; MeshRepresentation::SetupEmbreeScene(MeshName, SourceMeshData, LODModel, MaterialBlendModes, bGenerateAsIfTwoSided, EmbreeScene); if (!EmbreeScene.EmbreeScene) { return false; } FGenerateCardMeshContext Context(MeshName, EmbreeScene, OutData); // Note: must operate on the SDF bounds because SDF generation can expand the mesh's bounds BuildMeshCards(DistanceFieldVolumeData ? DistanceFieldVolumeData->LocalSpaceMeshBounds : Bounds.GetBox(), Context, OutData); MeshRepresentation::DeleteEmbreeScene(EmbreeScene); const float TimeElapsed = (float)(FPlatformTime::Seconds() - StartTime); if (TimeElapsed > 1.0f) { UE_LOG(LogMeshUtilities, Log, TEXT("Finished mesh card build in %.1fs %s tris:%d"), TimeElapsed, *MeshName, EmbreeScene.NumIndices / 3); } return true; #else UE_LOG(LogMeshUtilities, Warning, TEXT("Platform did not set USE_EMBREE, GenerateCardRepresentationData failed.")); return false; #endif }