Files
UnrealEngineUWP/Engine/Source/Developer/MeshUtilities/Private/MeshCardRepresentationUtilities.cpp

1022 lines
35 KiB
C++
Raw Normal View History

// 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<FSurfel> Surfels;
TArray<uint16> SurfelIndicesPerDirection[NumAxisAlignedDirections];
float TwoSidedTriangleRatio = 0.0f;
};
class FSurfelCluster
{
public:
FMatrix44f WorldToLocal;
FVector3f Normal;
FBox Bounds;
TArray<int32> 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<FVector3f> 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<float> TriangleArea;
TArray<FVector3f> TriangleNormal;
TArray<int32> 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<float> 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<uint16> 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<FSurfelCluster>& 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<FSurfelCluster> Clusters;
float SurfaceCoverage;
float ClusterArea;
};
void UpdateLODLevelCoverage(const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
{
LODLevel.ClusterArea = 0.0f;
TArray<float> 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<int32> 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<FSignedDistanceFieldBuildMaterialData>& 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);
Sparse, narrow band, streamed Mesh Signed Distance Fields * SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding). * Tracing must load the brick index from the indirection table, and only bricks near the surface are stored * 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed * SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface * The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy * Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator * Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances * Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases * Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2 * Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead * Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread * Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation * Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects * Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup * Upgraded mesh utilities to Embree 3.12.2 to get point queries * Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation * Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key. Original costs on 1080 GTX (full updates on everything and no screen traces) 10.60ms UpdateGlobalDistanceField 3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 1.73ms VoxelizeCards Clipmaps=[0,1,2,3] 0.38ms TraceCards 1 dispatch 1 groups 0.51ms TraceCards 1 dispatch 1 groups Sparse SDF costs 12.06ms UpdateGlobalDistanceField 4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 2.30ms VoxelizeCards Clipmaps=[0,1,2,3] 0.69ms TraceCards 1 dispatch 1 groups 0.77ms TraceCards 1 dispatch 1 groups Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed #rb Krzysztof.Narkowicz #ROBOMERGE-OWNER: Daniel.Wright #ROBOMERGE-AUTHOR: daniel.wright #ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/... #ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269) #ROBOMERGE-CONFLICT from-shelf [CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
// 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);
Sparse, narrow band, streamed Mesh Signed Distance Fields * SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding). * Tracing must load the brick index from the indirection table, and only bricks near the surface are stored * 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed * SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface * The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy * Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator * Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances * Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases * Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2 * Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead * Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread * Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation * Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects * Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup * Upgraded mesh utilities to Embree 3.12.2 to get point queries * Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation * Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key. Original costs on 1080 GTX (full updates on everything and no screen traces) 10.60ms UpdateGlobalDistanceField 3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 1.73ms VoxelizeCards Clipmaps=[0,1,2,3] 0.38ms TraceCards 1 dispatch 1 groups 0.51ms TraceCards 1 dispatch 1 groups Sparse SDF costs 12.06ms UpdateGlobalDistanceField 4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 2.30ms VoxelizeCards Clipmaps=[0,1,2,3] 0.69ms TraceCards 1 dispatch 1 groups 0.77ms TraceCards 1 dispatch 1 groups Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed #rb Krzysztof.Narkowicz #ROBOMERGE-OWNER: Daniel.Wright #ROBOMERGE-AUTHOR: daniel.wright #ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/... #ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269) #ROBOMERGE-CONFLICT from-shelf [CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
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"),
Sparse, narrow band, streamed Mesh Signed Distance Fields * SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding). * Tracing must load the brick index from the indirection table, and only bricks near the surface are stored * 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed * SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface * The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy * Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator * Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances * Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases * Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2 * Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead * Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread * Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation * Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects * Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup * Upgraded mesh utilities to Embree 3.12.2 to get point queries * Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation * Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key. Original costs on 1080 GTX (full updates on everything and no screen traces) 10.60ms UpdateGlobalDistanceField 3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 1.73ms VoxelizeCards Clipmaps=[0,1,2,3] 0.38ms TraceCards 1 dispatch 1 groups 0.51ms TraceCards 1 dispatch 1 groups Sparse SDF costs 12.06ms UpdateGlobalDistanceField 4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 2.30ms VoxelizeCards Clipmaps=[0,1,2,3] 0.69ms TraceCards 1 dispatch 1 groups 0.77ms TraceCards 1 dispatch 1 groups Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed #rb Krzysztof.Narkowicz #ROBOMERGE-OWNER: Daniel.Wright #ROBOMERGE-AUTHOR: daniel.wright #ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/... #ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269) #ROBOMERGE-CONFLICT from-shelf [CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
TimeElapsed,
*MeshName,
EmbreeScene.NumIndices / 3);
Sparse, narrow band, streamed Mesh Signed Distance Fields * SDFs are now generated, allocated from the atlas and uploaded in 8^3 bricks (7^3 unique data, half voxel padding). * Tracing must load the brick index from the indirection table, and only bricks near the surface are stored * 3 mips are now generated, with the lowest resolution always loaded and the other 2 streamed * SDFs are now G8 narrow band. Lower resolution mips must be traversed when querying distance to nearest surface far away from the surface * The Distance Field Brick Atlas is now stored for each FScene and dynamically resized based on needs with a GPU memcopy * Brick atlas uses a 1d pooled allocator which has no fragmentation and greatly reduces packing waste over the 3d allocator * Added new indirection for Distance Field Asset data, so that only a single entry needs to be updated when a mip is streamed in or out in scenes with millions of instances * Compute shaders operating on distance field instances generate streaming requests, which are async read back to CPU, turned into IO requests, which are polled and when complete uploaded to atlases * Any mesh instance inside the Global SDF extent (200m) requests mip1, and at 50m requests mip2 * Now using a batched compute scatter to upload to the distance field atlas instead of RHIUpdateTexture3d, to bypass alignment restrictions and per-upload overhead * Distance Field streaming uses an async task to move Memcpy and IO request overhead off of the Rendering Thread * Distance Field Visualization now computes a normal from the SDF gradient and does simple lighting to better visualize the scene representation * Increased r.DistanceFields.MaxPerMeshResolution from 128 to 512, to better represent large objects * Mesh SDF generation now uses an Embree point query to calculate closest unsigned distance, and then a much smaller set of rays to count backfaces for negative region determination, for a 11x speedup * Upgraded mesh utilities to Embree 3.12.2 to get point queries * Fixed wrong transform used for SDF normals in Lumen, causing non-uniformly scaled meshes to have incorrect Surface Cache interpolation * Fixed Static Mesh materials not getting PostLoaded before SDF build, causing their blend modes to be wrong for the build, which corrupts the DDC. Also included those blend modes in the DDC key. Original costs on 1080 GTX (full updates on everything and no screen traces) 10.60ms UpdateGlobalDistanceField 3.62ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 1.73ms VoxelizeCards Clipmaps=[0,1,2,3] 0.38ms TraceCards 1 dispatch 1 groups 0.51ms TraceCards 1 dispatch 1 groups Sparse SDF costs 12.06ms UpdateGlobalDistanceField 4.35ms LumenReflectiveTest.DirectionalLight_1 Shadowmap 1 2.30ms VoxelizeCards Clipmaps=[0,1,2,3] 0.69ms TraceCards 1 dispatch 1 groups 0.77ms TraceCards 1 dispatch 1 groups Tested: TopazEntry PC, Reverb PC and PS5, EngineTests, QAGame, Rift, Frosty P_Construct_WP, FortGPUTestbed #rb Krzysztof.Narkowicz #ROBOMERGE-OWNER: Daniel.Wright #ROBOMERGE-AUTHOR: daniel.wright #ROBOMERGE-SOURCE: CL 15784493 in //UE5/Release-5.0-EarlyAccess/... #ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v783-15756269) #ROBOMERGE-CONFLICT from-shelf [CL 15790658 by Daniel Wright in ue5-main branch]
2021-03-23 22:40:05 -04:00
}
return true;
#else
UE_LOG(LogMeshUtilities, Warning, TEXT("Platform did not set USE_EMBREE, GenerateCardRepresentationData failed."));
return false;
#endif
}