2020-07-06 18:58:26 -04:00
|
|
|
// 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"
|
2021-01-20 11:34:55 -04:00
|
|
|
#include "MeshRepresentationCommon.h"
|
2021-07-21 18:05:08 -04:00
|
|
|
#include "Containers/BinaryHeap.h"
|
|
|
|
|
|
|
|
|
|
// Debug option for investigating card generation issues
|
|
|
|
|
#define DEBUG_MESH_CARD_VISUALIZATION 0
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
static TAutoConsoleVariable<int32> CVarCardRepresentationParallelBuild(
|
|
|
|
|
TEXT("r.MeshCardRepresentation.ParallelBuild"),
|
|
|
|
|
1,
|
|
|
|
|
TEXT("Whether to use task for mesh card building."),
|
|
|
|
|
ECVF_Default);
|
|
|
|
|
|
|
|
|
|
namespace MeshCardGen
|
|
|
|
|
{
|
|
|
|
|
int32 constexpr NumAxisAlignedDirections = 6;
|
|
|
|
|
int32 constexpr MaxCardsPerMesh = 32;
|
|
|
|
|
int32 constexpr NumSurfelSamples = 16;
|
|
|
|
|
int32 constexpr MinSurfelSamples = 2;
|
|
|
|
|
};
|
2020-07-06 18:58:26 -04:00
|
|
|
|
|
|
|
|
class FGenerateCardMeshContext
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
const FString& MeshName;
|
2021-07-21 18:05:08 -04:00
|
|
|
const FEmbreeScene& EmbreeScene;
|
2020-07-06 18:58:26 -04:00
|
|
|
FCardRepresentationData& OutData;
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
FGenerateCardMeshContext(const FString& InMeshName, const FEmbreeScene& InEmbreeScene, FCardRepresentationData& InOutData) :
|
2020-07-06 18:58:26 -04:00
|
|
|
MeshName(InMeshName),
|
2021-07-21 18:05:08 -04:00
|
|
|
EmbreeScene(InEmbreeScene),
|
2020-07-06 18:58:26 -04:00
|
|
|
OutData(InOutData)
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FIntBox
|
|
|
|
|
{
|
|
|
|
|
FIntBox()
|
|
|
|
|
: Min(INT32_MAX)
|
|
|
|
|
, Max(-INT32_MAX)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
FIntBox(const FIntVector& InMin, const FIntVector& InMax)
|
|
|
|
|
: Min(InMin)
|
|
|
|
|
, Max(InMax)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void Init()
|
|
|
|
|
{
|
|
|
|
|
Min = FIntVector(INT32_MAX);
|
|
|
|
|
Max = FIntVector(-INT32_MAX);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Add(const FIntVector& Point)
|
|
|
|
|
{
|
|
|
|
|
Min = FIntVector(FMath::Min(Min.X, Point.X), FMath::Min(Min.Y, Point.Y), FMath::Min(Min.Z, Point.Z));
|
|
|
|
|
Max = FIntVector(FMath::Max(Max.X, Point.X), FMath::Max(Max.Y, Point.Y), FMath::Max(Max.Z, Point.Z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector2 GetFaceXY() const
|
|
|
|
|
{
|
|
|
|
|
return FIntVector2(Max.X + 1 - Min.X, Max.Y + 1 - Min.Y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 GetFaceArea() const
|
|
|
|
|
{
|
|
|
|
|
return (Max.X + 1 - Min.X) * (Max.Y + 1 - Min.Y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Contains(const FIntBox& Other) const
|
|
|
|
|
{
|
|
|
|
|
if (Other.Min.X >= Min.X && Other.Max.X <= Max.X
|
|
|
|
|
&& Other.Min.Y >= Min.Y && Other.Max.Y <= Max.Y
|
|
|
|
|
&& Other.Min.Z >= Min.Z && Other.Max.Z <= Max.Z)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector GetAxisDistanceFromBox(const FIntBox& Box)
|
|
|
|
|
{
|
|
|
|
|
const FIntVector CenterDelta2 = (Max - Min) - (Box.Max - Box.Min);
|
|
|
|
|
const FIntVector ExtentSum2 = (Max + Min) + (Box.Max + Box.Min);
|
|
|
|
|
|
|
|
|
|
FIntVector AxisDistance;
|
|
|
|
|
AxisDistance.X = FMath::Max(FMath::Abs(CenterDelta2.X) - ExtentSum2.X, 0) / 2;
|
|
|
|
|
AxisDistance.Y = FMath::Max(FMath::Abs(CenterDelta2.Y) - ExtentSum2.Y, 0) / 2;
|
|
|
|
|
AxisDistance.Z = FMath::Max(FMath::Abs(CenterDelta2.Z) - ExtentSum2.Z, 0) / 2;
|
|
|
|
|
return AxisDistance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector GetAxisDistanceFromPoint(const FIntVector& Point)
|
|
|
|
|
{
|
|
|
|
|
FIntVector AxisDistance;
|
|
|
|
|
AxisDistance.X = FMath::Max(FMath::Max(Min.X - Point.X, Point.X - Max.X), 0);
|
|
|
|
|
AxisDistance.Y = FMath::Max(FMath::Max(Min.Y - Point.Y, Point.Y - Max.Y), 0);
|
|
|
|
|
AxisDistance.Z = FMath::Max(FMath::Max(Min.Z - Point.Z, Point.Z - Max.Z), 0);
|
|
|
|
|
return AxisDistance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIntVector Min;
|
|
|
|
|
FIntVector Max;
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-06 18:58:26 -04:00
|
|
|
#if USE_EMBREE
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
typedef uint16 FSurfelIndex;
|
|
|
|
|
constexpr FSurfelIndex INVALID_SURFEL_INDEX = UINT16_MAX;
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
struct FSurfel
|
2020-07-06 18:58:26 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntVector Coord;
|
|
|
|
|
int32 MinRayZ;
|
|
|
|
|
};
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FSurfelScenePerDirection
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfel> Surfels;
|
|
|
|
|
FLumenCardBuildDebugData DebugData;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FSurfelScene
|
|
|
|
|
{
|
|
|
|
|
FSurfelScenePerDirection Directions[MeshCardGen::NumAxisAlignedDirections];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FAxisAlignedDirectionBasis
|
|
|
|
|
{
|
|
|
|
|
FMatrix44f LocalToWorldRotation;
|
|
|
|
|
FVector3f LocalToWorldOffset;
|
|
|
|
|
FIntVector VolumeSize;
|
|
|
|
|
float VoxelSize;
|
|
|
|
|
|
|
|
|
|
FVector3f TransformSurfel(FIntVector SurfelCoord) const
|
|
|
|
|
{
|
|
|
|
|
return LocalToWorldRotation.TransformPosition(FVector3f(SurfelCoord.X + 0.5f, SurfelCoord.Y + 0.5f, SurfelCoord.Z)) * VoxelSize + LocalToWorldOffset;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
struct FClusteringParams
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
float VoxelSize = 0.0f;
|
|
|
|
|
int32 MaxSurfelDistanceXY = 0;
|
|
|
|
|
int32 MaxSurfelDistanceZ = 0;
|
|
|
|
|
float SurfelDistanceZMult = 1.0f;
|
2021-07-21 18:05:08 -04:00
|
|
|
int32 MinSurfelsPerCluster = 0;
|
|
|
|
|
float MinDensityPerCluster = 0.0f;
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 MaxLumenMeshCards = 0;
|
|
|
|
|
|
|
|
|
|
FAxisAlignedDirectionBasis ClusterBasis[MeshCardGen::NumAxisAlignedDirections];
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class FSurfelCluster
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntBox Bounds;
|
|
|
|
|
TArray<FSurfelIndex> SurfelIndices;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntBox PotentialSurfelsBounds;
|
|
|
|
|
TArray<int32> PotentialSurfels;
|
|
|
|
|
|
|
|
|
|
int32 MinRayZ = 0;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
// Best surfels to add to this cluster
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
float BestSurfelDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
float WeightedCoverage = 0.0f;
|
|
|
|
|
|
|
|
|
|
void Init()
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
Bounds.Init();
|
2022-01-06 16:44:09 +00:00
|
|
|
PotentialSurfelsBounds.Init();
|
2021-07-21 18:05:08 -04:00
|
|
|
SurfelIndices.Reset();
|
2022-01-06 16:44:09 +00:00
|
|
|
PotentialSurfels.Reset();
|
|
|
|
|
MinRayZ = 0;
|
|
|
|
|
BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
BestSurfelDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsValid(const FClusteringParams& ClusteringParams) const
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
return SurfelIndices.Num() >= ClusteringParams.MinSurfelsPerCluster
|
|
|
|
|
&& GetDensity() > ClusteringParams.MinDensityPerCluster;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
float GetDensity() const
|
|
|
|
|
{
|
|
|
|
|
const float Density = SurfelIndices.Num() / (float)Bounds.GetFaceArea();
|
|
|
|
|
return Density;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float GetDensityAfterAdd(const FIntVector& Expand) const
|
|
|
|
|
{
|
|
|
|
|
FIntVector2 FaceXY = Bounds.GetFaceXY();
|
|
|
|
|
FaceXY.X += Expand.X;
|
|
|
|
|
FaceXY.Y += Expand.Y;
|
|
|
|
|
|
|
|
|
|
const float Density = (SurfelIndices.Num() + 1) / float(FaceXY.X * FaceXY.Y);
|
|
|
|
|
return Density;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddSurfel(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, FSurfelIndex SurfelToAddIndex);
|
|
|
|
|
void UpdateBestSurfel(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, const TBitArray<>& SurfelAssignedToAnyCluster);
|
|
|
|
|
void UpdateWeightedCoverage(const TArray<FSurfel>& Surfels);
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void FSurfelCluster::AddSurfel(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, FSurfelIndex SurfelToAddIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
const FSurfel& SurfelToAdd = SurfelScene.Surfels[SurfelToAddIndex];
|
|
|
|
|
SurfelIndices.Add(SurfelToAddIndex);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
Bounds.Add(SurfelToAdd.Coord);
|
2021-10-20 08:48:24 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
MinRayZ = FMath::Max(MinRayZ, SurfelToAdd.MinRayZ);
|
2022-01-05 10:30:05 -05:00
|
|
|
|
2022-01-05 20:41:17 +00:00
|
|
|
// Check if all surfels are visible after add
|
2022-01-06 16:44:09 +00:00
|
|
|
check(MinRayZ <= Bounds.Min.Z);
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void FSurfelCluster::UpdateBestSurfel(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
|
|
|
|
const TBitArray<>& SurfelAssignedToAnyCluster)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
BestSurfelDistance = FLT_MAX;
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Update potential surfel array if required
|
|
|
|
|
if (!PotentialSurfelsBounds.Contains(Bounds))
|
|
|
|
|
{
|
|
|
|
|
const FIntVector PotentialSurfelMargin(2, 2, 8);
|
|
|
|
|
|
|
|
|
|
PotentialSurfels.Reset();
|
|
|
|
|
PotentialSurfelsBounds.Min = Bounds.Min - FIntVector(PotentialSurfelMargin);
|
|
|
|
|
PotentialSurfelsBounds.Max = Bounds.Max + FIntVector(PotentialSurfelMargin);
|
|
|
|
|
|
|
|
|
|
for (int32 SurfelIndex = 0; SurfelIndex < SurfelScene.Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
if (!SurfelAssignedToAnyCluster[SurfelIndex])
|
|
|
|
|
{
|
|
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
|
|
|
|
if (Surfel.Coord.Z >= MinRayZ && Surfel.MinRayZ <= Bounds.Min.Z)
|
|
|
|
|
{
|
|
|
|
|
const FIntVector AxisDistances = PotentialSurfelsBounds.GetAxisDistanceFromPoint(Surfel.Coord);
|
|
|
|
|
float ManhattanDistance = AxisDistances.X + AxisDistances.Y + AxisDistances.Z * ClusteringParams.SurfelDistanceZMult;
|
|
|
|
|
|
|
|
|
|
if (ManhattanDistance <= ClusteringParams.MaxSurfelDistanceXY)
|
|
|
|
|
{
|
|
|
|
|
PotentialSurfels.Add(SurfelIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 SurfelIndex : PotentialSurfels)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
if (!SurfelAssignedToAnyCluster[SurfelIndex])
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const FIntVector AxisDistances = Bounds.GetAxisDistanceFromPoint(Surfel.Coord);
|
|
|
|
|
float ManhattanDistance = AxisDistances.X + AxisDistances.Y + AxisDistances.Z * ClusteringParams.SurfelDistanceZMult;
|
|
|
|
|
|
|
|
|
|
const float DensityAfterAdd = GetDensityAfterAdd(AxisDistances);
|
|
|
|
|
|
|
|
|
|
const bool bPassDistanceTest = ManhattanDistance <= ClusteringParams.MaxSurfelDistanceXY;
|
|
|
|
|
const bool bPassMinZTest = Surfel.Coord.Z >= MinRayZ && Surfel.MinRayZ <= Bounds.Min.Z;
|
|
|
|
|
const bool bPassDensityTest = DensityAfterAdd > ClusteringParams.MinDensityPerCluster;
|
|
|
|
|
|
|
|
|
|
if (bPassDistanceTest && bPassMinZTest && bPassDensityTest)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
// Weight by distance
|
|
|
|
|
float SurfelDistance = ManhattanDistance;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Weight by aspect ratio
|
|
|
|
|
const FIntVector2 FaceXY = Bounds.GetFaceXY();
|
|
|
|
|
if (FaceXY.X > FaceXY.Y && AxisDistances.X < AxisDistances.Y)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
SurfelDistance -= 0.5f;
|
|
|
|
|
}
|
|
|
|
|
else if (FaceXY.X < FaceXY.Y && AxisDistances.X > AxisDistances.Y)
|
|
|
|
|
{
|
|
|
|
|
SurfelDistance -= 0.5f;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Weight by density
|
|
|
|
|
{
|
|
|
|
|
SurfelDistance -= GetDensityAfterAdd(AxisDistances);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Weight by visibility
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Surfel.MinRayZ > 0)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
SurfelDistance += FMath::Clamp(1.0f - (Surfel.Coord.Z - Surfel.MinRayZ) / 10.0f, 0.0f, 1.0f);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (SurfelDistance < BestSurfelDistance)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelIndex = SurfelIndex;
|
|
|
|
|
BestSurfelDistance = SurfelDistance;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void FSurfelCluster::UpdateWeightedCoverage(const TArray<FSurfel>& Surfels)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
WeightedCoverage = 0.0f;
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex : SurfelIndices)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfel& Surfel = Surfels[SurfelIndex];
|
|
|
|
|
|
|
|
|
|
if (Surfel.MinRayZ > 0)
|
|
|
|
|
{
|
|
|
|
|
WeightedCoverage += 0.5f * FMath::Clamp((Surfel.Coord.Z - Surfel.MinRayZ) / 10.0f, 0.1f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
WeightedCoverage += 1.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FSurfelCandidate
|
|
|
|
|
{
|
|
|
|
|
FVector3f Position;
|
|
|
|
|
FVector3f Normal;
|
|
|
|
|
int32 MinRayZ;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FSurfelCandidateCell
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfelCandidate, TFixedAllocator<MeshCardGen::NumSurfelSamples>> Candidates;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Trace rays over the hemisphere and discard surfels which mostly hit back faces
|
|
|
|
|
bool CheckIfSurfelIsValid(
|
|
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const TArray<FSurfelCandidate, TFixedAllocator<MeshCardGen::NumSurfelSamples>>& Candidates,
|
|
|
|
|
const TArray<FVector3f>& RayDirectionsOverHemisphere,
|
|
|
|
|
FLumenCardBuildDebugData& DebugData)
|
|
|
|
|
{
|
|
|
|
|
uint32 CandidateIndex = 0;
|
|
|
|
|
uint32 NumHits = 0;
|
|
|
|
|
uint32 NumBackFaceHits = 0;
|
|
|
|
|
const float SurfaceRayBias = 0.1f;
|
|
|
|
|
|
|
|
|
|
for (int32 RayIndex = 0; RayIndex < RayDirectionsOverHemisphere.Num(); ++RayIndex)
|
|
|
|
|
{
|
|
|
|
|
const FMatrix44f SurfaceBasis = MeshRepresentation::GetTangentBasisFrisvad(Candidates[CandidateIndex].Normal);
|
|
|
|
|
const FVector3f RayOrigin = Candidates[CandidateIndex].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;
|
|
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
if (FVector::DotProduct((FVector)RayDirection, (FVector)EmbreeRay.GetHitNormal()) > 0.0f && !EmbreeContext.IsHitTwoSided())
|
2022-01-06 16:44:09 +00:00
|
|
|
{
|
|
|
|
|
++NumBackFaceHits;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
FLumenCardBuildDebugData::FRay& SurfelRay = DebugData.SurfelRays.AddDefaulted_GetRef();
|
|
|
|
|
SurfelRay.RayStart = RayOrigin;
|
|
|
|
|
SurfelRay.RayEnd = RayOrigin + RayDirection * (EmbreeRay.ray.tfar < FLT_MAX ? EmbreeRay.ray.tfar : 200.0f);
|
|
|
|
|
SurfelRay.bHit = EmbreeRay.ray.tfar < FLT_MAX;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
CandidateIndex = (CandidateIndex + 1) % Candidates.Num();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool bInsideGeometry = NumHits > 0 && NumBackFaceHits > RayDirectionsOverHemisphere.Num() * 0.2f;
|
|
|
|
|
return !bInsideGeometry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenerateSurfelsForDirection(
|
|
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const FAxisAlignedDirectionBasis& ClusterBasis,
|
|
|
|
|
const TArray<FVector3f>& RayDirectionsOverHemisphere,
|
|
|
|
|
float VoxelSize,
|
|
|
|
|
FSurfelScenePerDirection& SurfelScenePerDirection)
|
|
|
|
|
{
|
|
|
|
|
const float NormalWeightTreshold = MeshCardRepresentation::GetNormalTreshold();
|
|
|
|
|
const FVector3f RayDirection = ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::Type::Z);
|
|
|
|
|
|
|
|
|
|
TArray<FSurfelCandidateCell> SurfelCandidateCells;
|
|
|
|
|
SurfelCandidateCells.SetNum(ClusterBasis.VolumeSize.Z);
|
|
|
|
|
|
|
|
|
|
for (int32 CoordY = 0; CoordY < ClusterBasis.VolumeSize.Y; ++CoordY)
|
|
|
|
|
{
|
|
|
|
|
for (int32 CoordX = 0; CoordX < ClusterBasis.VolumeSize.X; ++CoordX)
|
|
|
|
|
{
|
|
|
|
|
for (int32 CoordZ = 0; CoordZ < ClusterBasis.VolumeSize.Z; ++CoordZ)
|
|
|
|
|
{
|
|
|
|
|
SurfelCandidateCells[CoordZ].Candidates.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trace multiple rays per cell and mark cells which need to spawn a surfel
|
|
|
|
|
for (uint32 SampleIndex = 0; SampleIndex < MeshCardGen::NumSurfelSamples; ++SampleIndex)
|
|
|
|
|
{
|
|
|
|
|
FVector3f Jitter;
|
|
|
|
|
Jitter.X = (SampleIndex + 0.5f) / MeshCardGen::NumSurfelSamples;
|
|
|
|
|
Jitter.Y = (double)ReverseBits(SampleIndex) / (double)0x100000000LL;
|
|
|
|
|
|
|
|
|
|
FVector3f RayOrigin = ClusterBasis.LocalToWorldRotation.TransformPosition(FVector3f(CoordX + Jitter.X, CoordY + Jitter.Y, 0.0f)) * VoxelSize + ClusterBasis.LocalToWorldOffset;
|
|
|
|
|
|
|
|
|
|
int32 LastHitCoordZ = -2;
|
|
|
|
|
while (LastHitCoordZ < ClusterBasis.VolumeSize.Z)
|
|
|
|
|
{
|
|
|
|
|
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 = (LastHitCoordZ + 1) * VoxelSize;
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
const int32 HitCoordZ = FMath::Clamp(EmbreeRay.ray.tfar / VoxelSize, 0, ClusterBasis.VolumeSize.Z);
|
|
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
FVector SurfaceNormal = (FVector)EmbreeRay.GetHitNormal();
|
|
|
|
|
float NdotD = FVector::DotProduct((FVector)-RayDirection, SurfaceNormal);
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
// Handle two sided hits
|
|
|
|
|
if (NdotD < 0.0f && EmbreeContext.IsHitTwoSided())
|
|
|
|
|
{
|
|
|
|
|
NdotD = -NdotD;
|
|
|
|
|
SurfaceNormal = -SurfaceNormal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool bPassProjectionTest = NdotD >= NormalWeightTreshold;
|
|
|
|
|
if (bPassProjectionTest && HitCoordZ > LastHitCoordZ + 1 && HitCoordZ < ClusterBasis.VolumeSize.Z)
|
|
|
|
|
{
|
|
|
|
|
FSurfelCandidate& SurfelCandidate = SurfelCandidateCells[HitCoordZ].Candidates.AddDefaulted_GetRef();
|
|
|
|
|
SurfelCandidate.Position = RayOrigin + RayDirection * EmbreeRay.ray.tfar;
|
2022-02-02 07:59:31 -05:00
|
|
|
SurfelCandidate.Normal = (FVector3f)SurfaceNormal;
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
SurfelCandidate.MinRayZ = 0;
|
|
|
|
|
if (LastHitCoordZ >= 0)
|
|
|
|
|
{
|
|
|
|
|
SurfelCandidate.MinRayZ = FMath::Max(SurfelCandidate.MinRayZ, LastHitCoordZ + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LastHitCoordZ = HitCoordZ + 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
LastHitCoordZ = INT32_MAX;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert surfel candidates into actual surfels
|
|
|
|
|
for (int32 CoordZ = 0; CoordZ < ClusterBasis.VolumeSize.Z; ++CoordZ)
|
|
|
|
|
{
|
|
|
|
|
FSurfelCandidateCell& SurfelCandidateCell = SurfelCandidateCells[CoordZ];
|
|
|
|
|
if (SurfelCandidateCell.Candidates.Num() >= MeshCardGen::MinSurfelSamples)
|
|
|
|
|
{
|
|
|
|
|
const bool bValidSurfel = CheckIfSurfelIsValid(
|
|
|
|
|
Context,
|
|
|
|
|
SurfelCandidateCell.Candidates,
|
|
|
|
|
RayDirectionsOverHemisphere,
|
|
|
|
|
SurfelScenePerDirection.DebugData);
|
|
|
|
|
|
|
|
|
|
if (bValidSurfel)
|
|
|
|
|
{
|
|
|
|
|
struct FSortByMinZ
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()(const FSurfelCandidate& A, const FSurfelCandidate& B) const
|
|
|
|
|
{
|
|
|
|
|
return A.MinRayZ > B.MinRayZ;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SurfelCandidateCell.Candidates.Sort(FSortByMinZ());
|
|
|
|
|
const int32 MedianMinRayZ = SurfelCandidateCell.Candidates[SurfelCandidateCell.Candidates.Num() / 2].MinRayZ;
|
|
|
|
|
|
|
|
|
|
FSurfel& Surfel = SurfelScenePerDirection.Surfels.AddDefaulted_GetRef();
|
|
|
|
|
Surfel.Coord = FIntVector(CoordX, CoordY, CoordZ);
|
|
|
|
|
Surfel.MinRayZ = MedianMinRayZ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if DEBUG_MESH_CARD_VISUALIZATION
|
|
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel& DebugSurfel = SurfelScenePerDirection.DebugData.Surfels.AddDefaulted_GetRef();
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(FIntVector(CoordX, CoordY, CoordZ));
|
|
|
|
|
DebugSurfel.Normal = -RayDirection;
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SurfelScenePerDirection.Surfels.Num() - 1;
|
|
|
|
|
DebugSurfel.Type = bValidSurfel ? FLumenCardBuildDebugData::ESurfelType::Valid : FLumenCardBuildDebugData::ESurfelType::Invalid;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GenerateSurfels(
|
|
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const FBox& MeshCardsBounds,
|
|
|
|
|
FSurfelScene& SurfelScene,
|
2022-01-06 16:44:09 +00:00
|
|
|
FClusteringParams& ClusteringParams)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
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;
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const float TargetVoxelSize = 20.0f;
|
|
|
|
|
const int32 MaxVoxels = 64;
|
|
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
const FVector3f MeshCardsBoundsSize = 2.0f * (FVector3f)MeshCardsBounds.GetExtent();
|
2022-01-06 16:44:09 +00:00
|
|
|
const float MaxMeshCardsBounds = MeshCardsBoundsSize.GetMax();
|
|
|
|
|
const float MaxSizeInVoxels = FMath::Clamp(MaxMeshCardsBounds / TargetVoxelSize + 0.5f, 1, MaxVoxels);
|
|
|
|
|
const float VoxelSize = FMath::Max(TargetVoxelSize, MaxMeshCardsBounds / MaxSizeInVoxels);
|
|
|
|
|
|
|
|
|
|
FIntVector SizeInVoxels;
|
|
|
|
|
SizeInVoxels.X = FMath::Clamp(FMath::CeilToFloat(MeshCardsBoundsSize.X / VoxelSize), 1, MaxVoxels);
|
|
|
|
|
SizeInVoxels.Y = FMath::Clamp(FMath::CeilToFloat(MeshCardsBoundsSize.Y / VoxelSize), 1, MaxVoxels);
|
|
|
|
|
SizeInVoxels.Z = FMath::Clamp(FMath::CeilToFloat(MeshCardsBoundsSize.Z / VoxelSize), 1, MaxVoxels);
|
|
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
const FVector3f VoxelBoundsCenter = (FVector3f)MeshCardsBounds.GetCenter();
|
2022-01-06 16:44:09 +00:00
|
|
|
const FVector3f VoxelBoundsExtent = FVector3f(SizeInVoxels) * VoxelSize * 0.5f;
|
|
|
|
|
const FVector3f VoxelBoundsMin = VoxelBoundsCenter - VoxelBoundsExtent;
|
|
|
|
|
const FVector3f VoxelBoundsMax = VoxelBoundsCenter + VoxelBoundsExtent;
|
|
|
|
|
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
FAxisAlignedDirectionBasis& ClusterBasis = ClusteringParams.ClusterBasis[AxisAlignedDirectionIndex];
|
|
|
|
|
ClusterBasis.VoxelSize = VoxelSize;
|
|
|
|
|
|
|
|
|
|
FVector3f XAxis = FVector3f(1.0f, 0.0f, 0.0f);
|
|
|
|
|
FVector3f YAxis = FVector3f(0.0f, 1.0f, 0.0f);
|
|
|
|
|
switch (AxisAlignedDirectionIndex / 2)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
XAxis = FVector3f(0.0f, 1.0f, 0.0f);
|
|
|
|
|
YAxis = FVector3f(0.0f, 0.0f, 1.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
XAxis = FVector3f(1.0f, 0.0f, 0.0f);
|
|
|
|
|
YAxis = FVector3f(0.0f, 0.0f, 1.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
XAxis = FVector3f(1.0f, 0.0f, 0.0f);
|
|
|
|
|
YAxis = FVector3f(0.0f, 1.0f, 0.0f);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector3f ZAxis = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
|
|
|
|
|
ClusterBasis.LocalToWorldRotation = FMatrix44f(XAxis, YAxis, -ZAxis, FVector3f::ZeroVector);
|
|
|
|
|
|
|
|
|
|
ClusterBasis.LocalToWorldOffset = VoxelBoundsMin;
|
|
|
|
|
if (AxisAlignedDirectionIndex & 1)
|
|
|
|
|
{
|
|
|
|
|
ClusterBasis.LocalToWorldOffset[AxisAlignedDirectionIndex / 2] = VoxelBoundsMax[AxisAlignedDirectionIndex / 2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (AxisAlignedDirectionIndex / 2)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
ClusterBasis.VolumeSize.X = SizeInVoxels.Y;
|
|
|
|
|
ClusterBasis.VolumeSize.Y = SizeInVoxels.Z;
|
|
|
|
|
ClusterBasis.VolumeSize.Z = SizeInVoxels.X;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
ClusterBasis.VolumeSize.X = SizeInVoxels.X;
|
|
|
|
|
ClusterBasis.VolumeSize.Y = SizeInVoxels.Z;
|
|
|
|
|
ClusterBasis.VolumeSize.Z = SizeInVoxels.Y;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
ClusterBasis.VolumeSize.X = SizeInVoxels.X;
|
|
|
|
|
ClusterBasis.VolumeSize.Y = SizeInVoxels.Y;
|
|
|
|
|
ClusterBasis.VolumeSize.Z = SizeInVoxels.Z;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
// Generate random ray directions over a hemisphere
|
|
|
|
|
constexpr uint32 NumRayDirectionsOverHemisphere = 64;
|
|
|
|
|
TArray<FVector3f> RayDirectionsOverHemisphere;
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
FRandomStream RandomStream(0);
|
|
|
|
|
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumRayDirectionsOverHemisphere, RandomStream, RayDirectionsOverHemisphere);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const int32 DebugSurfelDirection = MeshCardRepresentation::GetDebugSurfelDirection();
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const bool bSingleThreaded = CVarCardRepresentationParallelBuild.GetValueOnAnyThread() == 0;
|
|
|
|
|
ParallelFor(MeshCardGen::NumAxisAlignedDirections,
|
|
|
|
|
[&](int32 AxisAlignedDirectionIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
if (DebugSurfelDirection < 0 || DebugSurfelDirection == AxisAlignedDirectionIndex)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
GenerateSurfelsForDirection(
|
|
|
|
|
Context,
|
|
|
|
|
ClusteringParams.ClusterBasis[AxisAlignedDirectionIndex],
|
|
|
|
|
RayDirectionsOverHemisphere,
|
|
|
|
|
VoxelSize,
|
|
|
|
|
SurfelScenePerDirection
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}, bSingleThreaded);
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
ClusteringParams.VoxelSize = VoxelSize;
|
|
|
|
|
ClusteringParams.MaxSurfelDistanceXY = MeshCardRepresentation::GetMaxSurfelDistanceXY();
|
|
|
|
|
ClusteringParams.MaxSurfelDistanceZ = MeshCardRepresentation::GetMaxSurfelDistanceZ();
|
|
|
|
|
ClusteringParams.SurfelDistanceZMult = ClusteringParams.MaxSurfelDistanceXY / float(ClusteringParams.MaxSurfelDistanceZ);
|
|
|
|
|
ClusteringParams.MinSurfelsPerCluster = 20;
|
|
|
|
|
ClusteringParams.MinDensityPerCluster = MeshCardRepresentation::GetMinDensity();
|
2022-01-05 20:41:17 +00:00
|
|
|
|
|
|
|
|
#if DEBUG_MESH_CARD_VISUALIZATION
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FLumenCardBuildDebugData& MergedDebugData = Context.OutData.MeshCardsBuildData.DebugData;
|
|
|
|
|
FLumenCardBuildDebugData& DirectionDebugData = SurfelScene.Directions[AxisAlignedDirectionIndex].DebugData;
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const int32 SurfelOffset = MergedDebugData.Surfels.Num();
|
|
|
|
|
|
|
|
|
|
MergedDebugData.Surfels.Append(DirectionDebugData.Surfels);
|
|
|
|
|
MergedDebugData.SurfelRays.Append(DirectionDebugData.SurfelRays);
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex = SurfelOffset; SurfelIndex < MergedDebugData.Surfels.Num(); ++SurfelIndex)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
MergedDebugData.Surfels[SurfelIndex].SourceSurfelIndex += SurfelOffset;
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
#endif
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GrowSingleCluster(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
2021-07-21 18:05:08 -04:00
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster,
|
|
|
|
|
FSurfelCluster& Cluster)
|
|
|
|
|
{
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
Cluster.AddSurfel(ClusteringParams, SurfelScene, Cluster.BestSurfelIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
} while (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex FindBestSeed(const FClusteringParams& ClusteringParams, const FSurfelScenePerDirection& SurfelScene, const FSurfelCluster& Cluster)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FIntVector CoordSum = FIntVector(0, 0, 0);
|
|
|
|
|
for (FSurfelIndex SurfelIndex : Cluster.SurfelIndices)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
2022-01-06 16:44:09 +00:00
|
|
|
CoordSum += Surfel.Coord;
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-05 10:30:05 -05:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const FVector3f AverageCoord = FVector3f(CoordSum) / Cluster.SurfelIndices.Num();
|
2022-01-05 10:30:05 -05:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex BestSurfelIndex = INVALID_SURFEL_INDEX;
|
|
|
|
|
float BestSurfelDistance = FLT_MAX;
|
|
|
|
|
for (FSurfelIndex SurfelIndex : Cluster.SurfelIndices)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
|
|
|
|
const FSurfel& Surfel = SurfelScene.Surfels[SurfelIndex];
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FVector3f AxisDistances = (FVector3f(Surfel.Coord) - AverageCoord).GetAbs();
|
|
|
|
|
AxisDistances.Z *= ClusteringParams.SurfelDistanceZMult;
|
|
|
|
|
float SurfelDistance = AxisDistances.X * AxisDistances.X + AxisDistances.Y * AxisDistances.Y + AxisDistances.Z * AxisDistances.Z;
|
|
|
|
|
|
|
|
|
|
float SurfelWeight = 10.0f * FMath::Clamp(1.0f - (Surfel.Coord.Z - Surfel.MinRayZ) / 10.0f, 0.0f, 1.0f);
|
|
|
|
|
SurfelDistance += SurfelWeight * SurfelWeight;
|
|
|
|
|
|
|
|
|
|
if (SurfelDistance < BestSurfelDistance)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
BestSurfelIndex = SurfelIndex;
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelDistance = SurfelDistance;
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return BestSurfelIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GrowAllClusters(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 AxisAlignedDirectionIndex,
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster,
|
|
|
|
|
TArray<FSurfelCluster>& Clusters)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
// Reset all clusters and find their new best seeds
|
2022-01-06 16:44:09 +00:00
|
|
|
SurfelAssignedToAnyCluster.Init(false, SurfelScene.Surfels.Num());
|
2021-07-21 18:05:08 -04:00
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelIndex ClusterSeedIndex = FindBestSeed(ClusteringParams, SurfelScene, Cluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
Cluster.Init();
|
2021-07-21 18:05:08 -04:00
|
|
|
Cluster.AddSurfel(ClusteringParams, SurfelScene, ClusterSeedIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[ClusterSeedIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cluster surfels
|
|
|
|
|
int32 BestClusterToAddIndex = -1;
|
2022-01-06 16:44:09 +00:00
|
|
|
float BestSurfelToAddDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
BestClusterToAddIndex = -1;
|
2022-01-06 16:44:09 +00:00
|
|
|
BestSurfelToAddDistance = FLT_MAX;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Pick best surfel across all clusters
|
2021-07-21 18:05:08 -04:00
|
|
|
for (int32 ClusterIndex = 0; ClusterIndex < Clusters.Num(); ++ClusterIndex)
|
|
|
|
|
{
|
|
|
|
|
FSurfelCluster& Cluster = Clusters[ClusterIndex];
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX && SurfelAssignedToAnyCluster[Cluster.BestSurfelIndex])
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
Cluster.UpdateBestSurfel(ClusteringParams, SurfelScene, SurfelAssignedToAnyCluster);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelIndex != INVALID_SURFEL_INDEX)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.BestSurfelDistance < BestSurfelToAddDistance)
|
|
|
|
|
{
|
|
|
|
|
BestClusterToAddIndex = ClusterIndex;
|
|
|
|
|
BestSurfelToAddDistance = Cluster.BestSurfelDistance;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
struct FMeshCardsLODLevelPerDirection
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfelCluster> Clusters;
|
|
|
|
|
bool bNeedsToReGrow = false;
|
|
|
|
|
};
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
struct FMeshCardsLODLevel
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
FMeshCardsLODLevelPerDirection Directions[MeshCardGen::NumAxisAlignedDirections];
|
|
|
|
|
float WeightedSurfaceCoverage = 0.0f;
|
|
|
|
|
float SurfaceArea = 0.0f;
|
|
|
|
|
int32 NumSurfels = 0;
|
|
|
|
|
int32 NumClusters = 0;
|
|
|
|
|
|
|
|
|
|
bool NeedToReGrow() const
|
|
|
|
|
{
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
if (Directions[AxisAlignedDirectionIndex].bNeedsToReGrow)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void UpdateLODLevelCoverage(const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
LODLevel.WeightedSurfaceCoverage = 0.0f;
|
|
|
|
|
LODLevel.SurfaceArea = 0.0f;
|
|
|
|
|
LODLevel.NumSurfels = 0;
|
|
|
|
|
LODLevel.NumClusters = 0;
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
const TArray<FSurfel>& Surfels = SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels;
|
|
|
|
|
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
Cluster.UpdateWeightedCoverage(Surfels);
|
|
|
|
|
LODLevel.WeightedSurfaceCoverage += Cluster.WeightedCoverage;
|
|
|
|
|
LODLevel.SurfaceArea += Cluster.Bounds.GetFaceArea();
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
LODLevel.NumSurfels += Surfels.Num();
|
|
|
|
|
LODLevel.NumClusters += Clusters.Num();
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cluster only by direction
|
|
|
|
|
void BuildMeshCardsLOD0(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const TArray<FSurfel>& Surfels = SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelCluster TempCluster;
|
|
|
|
|
TempCluster.Init();
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex = 0; SurfelIndex < Surfels.Num(); ++SurfelIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfel& Surfel = Surfels[SurfelIndex];
|
|
|
|
|
if (Surfel.MinRayZ == 0)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
TempCluster.SurfelIndices.Add(SurfelIndex);
|
2022-01-06 16:44:09 +00:00
|
|
|
TempCluster.Bounds.Add(Surfel.Coord);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (TempCluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
LODLevel.Directions[AxisAlignedDirectionIndex].Clusters.Add(TempCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateLODLevelCoverage(SurfelScene, ClusteringParams, LODLevel);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Process cluster seeds one by one and try to grow clusters from each one of them
|
|
|
|
|
void GenerateClustersFromSeeds(
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
|
|
|
|
int32 AxisAlignedDirectionIndex,
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScene,
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster,
|
|
|
|
|
TArray<FSurfelIndex>& ClusterSeeds,
|
|
|
|
|
TArray<FSurfelCluster>& Clusters)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const int32 NumSeedIterations = MeshCardRepresentation::GetSeedIterations();
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
FRandomStream ClusterSeedRandomStream(0);
|
|
|
|
|
FSurfelCluster TempCluster;
|
|
|
|
|
|
|
|
|
|
while (ClusterSeeds.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
// Select next seed
|
2022-01-06 16:44:09 +00:00
|
|
|
FSurfelIndex NextSeedSurfelIndex = INVALID_SURFEL_INDEX;
|
2021-07-21 18:05:08 -04:00
|
|
|
while (ClusterSeeds.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
int32 RandomIndex = ClusterSeedRandomStream.RandHelper(ClusterSeeds.Num());
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelIndex ClusterSeed = ClusterSeeds[RandomIndex];
|
2021-07-21 18:05:08 -04:00
|
|
|
ClusterSeeds.RemoveAtSwap(RandomIndex);
|
|
|
|
|
|
|
|
|
|
if (!SurfelAssignedToAnyCluster[ClusterSeed])
|
|
|
|
|
{
|
|
|
|
|
NextSeedSurfelIndex = ClusterSeed;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to build a cluster using selected seed
|
2022-01-06 16:44:09 +00:00
|
|
|
if (NextSeedSurfelIndex != INVALID_SURFEL_INDEX)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumSeedIterations; ++ClusteringIterationIndex)
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
TempCluster.Init();
|
2021-07-21 18:05:08 -04:00
|
|
|
TempCluster.AddSurfel(ClusteringParams, SurfelScene, NextSeedSurfelIndex);
|
|
|
|
|
SurfelAssignedToAnyCluster[NextSeedSurfelIndex] = true;
|
|
|
|
|
|
|
|
|
|
GrowSingleCluster(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
SurfelScene,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
TempCluster);
|
|
|
|
|
|
|
|
|
|
// Restore global state
|
2022-01-06 16:44:09 +00:00
|
|
|
for (FSurfelIndex SurfelIndex : TempCluster.SurfelIndices)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
SurfelAssignedToAnyCluster[SurfelIndex] = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (TempCluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelIndex AverageSurfelIndex = FindBestSeed(ClusteringParams, SurfelScene, TempCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
if (AverageSurfelIndex != NextSeedSurfelIndex)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
ClusterSeeds.RemoveSwap(AverageSurfelIndex);
|
|
|
|
|
NextSeedSurfelIndex = AverageSurfelIndex;
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
break;
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add new cluster only if it has least MinSurfelsPerCluster points
|
|
|
|
|
if (TempCluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
Clusters.Add(TempCluster);
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (FSurfelIndex SurfelIndex : TempCluster.SurfelIndices)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
|
|
|
|
SurfelAssignedToAnyCluster[SurfelIndex] = true;
|
|
|
|
|
}
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
2021-01-20 11:34:55 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Limit number of clusters to fit in user provided limit
|
|
|
|
|
void LimitClusters(const FClusteringParams& ClusteringParams, const FSurfelScene& SurfelScene, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
|
|
|
|
int32 NumClusters = 0;
|
|
|
|
|
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
const TArray<FSurfel>& Surfels = SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
Cluster.UpdateWeightedCoverage(Surfels);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct FSortByClusterWeightedCoverage
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE bool operator()(const FSurfelCluster& A, const FSurfelCluster& B) const
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
return A.WeightedCoverage > B.WeightedCoverage;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Clusters.Sort(FSortByClusterWeightedCoverage());
|
|
|
|
|
NumClusters += Clusters.Num();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (NumClusters > ClusteringParams.MaxLumenMeshCards)
|
|
|
|
|
{
|
|
|
|
|
float SmallestClusterWeightedCoverage = FLT_MAX;
|
|
|
|
|
int32 SmallestClusterDirectionIndex = 0;
|
|
|
|
|
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
const TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
if (Clusters.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelCluster& Cluster = Clusters.Last();
|
|
|
|
|
if (Cluster.WeightedCoverage < SmallestClusterWeightedCoverage)
|
|
|
|
|
{
|
|
|
|
|
SmallestClusterDirectionIndex = AxisAlignedDirectionIndex;
|
|
|
|
|
SmallestClusterWeightedCoverage = Cluster.WeightedCoverage;
|
|
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FMeshCardsLODLevelPerDirection& MeshCardsLODLevelPerDirection = LODLevel.Directions[SmallestClusterDirectionIndex];
|
|
|
|
|
MeshCardsLODLevelPerDirection.Clusters.Pop();
|
|
|
|
|
MeshCardsLODLevelPerDirection.bNeedsToReGrow = true;
|
|
|
|
|
--NumClusters;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cluster by direction and position
|
|
|
|
|
void BuildMeshCardsLOD1(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, const FSurfelScene& SurfelScene, const FClusteringParams& ClusteringParams, FMeshCardsLODLevel& LODLevel)
|
|
|
|
|
{
|
|
|
|
|
const int32 NumGrowIterations = MeshCardRepresentation::GetGrowIterations();
|
|
|
|
|
|
|
|
|
|
TBitArray<> SurfelAssignedToAnyClusterArray[MeshCardGen::NumAxisAlignedDirections];
|
|
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
SurfelAssignedToAnyClusterArray[AxisAlignedDirectionIndex].Init(false, SurfelScene.Directions[AxisAlignedDirectionIndex].Surfels.Num());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate initial list of clusters
|
|
|
|
|
const bool bSingleThreaded = CVarCardRepresentationParallelBuild.GetValueOnAnyThread() == 0;
|
|
|
|
|
ParallelFor(MeshCardGen::NumAxisAlignedDirections,
|
|
|
|
|
[&](int32 AxisAlignedDirectionIndex)
|
2021-07-21 18:05:08 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
const FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster = SurfelAssignedToAnyClusterArray[AxisAlignedDirectionIndex];
|
|
|
|
|
|
|
|
|
|
TArray<FSurfelIndex> ClusterSeeds;
|
|
|
|
|
ClusterSeeds.SetNumUninitialized(SurfelScenePerDirection.Surfels.Num());
|
|
|
|
|
for (FSurfelIndex SurfelIndex = 0; SurfelIndex < SurfelScenePerDirection.Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
ClusterSeeds[SurfelIndex] = SurfelIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GenerateClustersFromSeeds(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
AxisAlignedDirectionIndex,
|
|
|
|
|
SurfelScenePerDirection,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
ClusterSeeds,
|
|
|
|
|
Clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Grow all clusters simultaneously from the best seed
|
|
|
|
|
for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumGrowIterations; ++ClusteringIterationIndex)
|
|
|
|
|
{
|
|
|
|
|
GrowAllClusters(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
AxisAlignedDirectionIndex,
|
|
|
|
|
SurfelScenePerDirection,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
Clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bool bAnyClusterSeedChanged = false;
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelIndex ClusterSeedIndex = FindBestSeed(ClusteringParams, SurfelScenePerDirection, Cluster);
|
|
|
|
|
if (ClusterSeedIndex != Cluster.SurfelIndices[0])
|
|
|
|
|
{
|
|
|
|
|
bAnyClusterSeedChanged = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bAnyClusterSeedChanged)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, bSingleThreaded);
|
|
|
|
|
|
|
|
|
|
LimitClusters(ClusteringParams, SurfelScene, LODLevel);
|
|
|
|
|
|
|
|
|
|
if (LODLevel.NeedToReGrow())
|
|
|
|
|
{
|
|
|
|
|
ParallelFor(MeshCardGen::NumAxisAlignedDirections,
|
|
|
|
|
[&](int32 AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
if (LODLevel.Directions[AxisAlignedDirectionIndex].bNeedsToReGrow)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
TBitArray<>& SurfelAssignedToAnyCluster = SurfelAssignedToAnyClusterArray[AxisAlignedDirectionIndex];
|
|
|
|
|
|
|
|
|
|
// Grow all clusters simultaneously from the best seed
|
|
|
|
|
for (int32 ClusteringIterationIndex = 0; ClusteringIterationIndex < NumGrowIterations; ++ClusteringIterationIndex)
|
|
|
|
|
{
|
|
|
|
|
GrowAllClusters(
|
|
|
|
|
ClusteringParams,
|
|
|
|
|
AxisAlignedDirectionIndex,
|
|
|
|
|
SurfelScenePerDirection,
|
|
|
|
|
SurfelAssignedToAnyCluster,
|
|
|
|
|
Clusters
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bool bAnyClusterSeedChanged = false;
|
|
|
|
|
for (FSurfelCluster& Cluster : Clusters)
|
|
|
|
|
{
|
|
|
|
|
const FSurfelIndex ClusterSeedIndex = FindBestSeed(ClusteringParams, SurfelScenePerDirection, Cluster);
|
|
|
|
|
if (ClusterSeedIndex != Cluster.SurfelIndices[0])
|
|
|
|
|
{
|
|
|
|
|
bAnyClusterSeedChanged = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bAnyClusterSeedChanged)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, bSingleThreaded);
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
UpdateLODLevelCoverage(SurfelScene, ClusteringParams, LODLevel);
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void SerializeLOD(
|
|
|
|
|
const FGenerateCardMeshContext& Context,
|
|
|
|
|
const FClusteringParams& ClusteringParams,
|
|
|
|
|
const FSurfelScene& SurfelScene,
|
|
|
|
|
FMeshCardsLODLevel const& LODLevel,
|
|
|
|
|
int32 LODLevelIndex,
|
|
|
|
|
const FBox& MeshCardsBounds,
|
|
|
|
|
FMeshCardsBuildData& MeshCardsBuildData)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
MeshCardsBuildData.MaxLODLevel = FMath::Max(MeshCardsBuildData.MaxLODLevel, LODLevelIndex);
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 SourceSurfelOffset = 0;
|
2021-07-21 18:05:08 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < MeshCardGen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
|
|
|
|
|
{
|
|
|
|
|
const FAxisAlignedDirectionBasis& ClusterBasis = ClusteringParams.ClusterBasis[AxisAlignedDirectionIndex];
|
|
|
|
|
const FSurfelScenePerDirection& SurfelScenePerDirection = SurfelScene.Directions[AxisAlignedDirectionIndex];
|
|
|
|
|
const TArray<FSurfel>& Surfels = SurfelScenePerDirection.Surfels;
|
|
|
|
|
const TArray<FSurfelCluster>& Clusters = LODLevel.Directions[AxisAlignedDirectionIndex].Clusters;
|
|
|
|
|
|
|
|
|
|
#if DEBUG_MESH_CARD_VISUALIZATION
|
|
|
|
|
TBitArray<> DebugSurfelInCluster;
|
|
|
|
|
TBitArray<> DebugSurfelInAnyCluster(false, Surfels.Num());
|
2022-01-05 10:30:05 -05:00
|
|
|
#endif
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-02-02 07:59:31 -05:00
|
|
|
const FBox3f LocalMeshCardsBounds = FBox3f(MeshCardsBounds.ShiftBy((FVector)-ClusterBasis.LocalToWorldOffset).TransformBy(FMatrix(ClusterBasis.LocalToWorldRotation.GetTransposed())));
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
for (const FSurfelCluster& Cluster : Clusters)
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
if (Cluster.IsValid(ClusteringParams))
|
|
|
|
|
{
|
|
|
|
|
// Clamp to mesh bounds
|
|
|
|
|
FVector3f ClusterBoundsMin = (FVector3f(Cluster.Bounds.Min) - FVector3f(0.0f, 0.0f, 0.5f)) * ClusteringParams.VoxelSize;
|
|
|
|
|
FVector3f ClusterBoundsMax = (FVector3f(Cluster.Bounds.Max) + FVector3f(1.0f, 1.0f, 1.0f)) * ClusteringParams.VoxelSize;
|
|
|
|
|
ClusterBoundsMin = FVector3f::Max(ClusterBoundsMin, LocalMeshCardsBounds.Min);
|
|
|
|
|
ClusterBoundsMax = FVector3f::Min(ClusterBoundsMax, LocalMeshCardsBounds.Max);
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
const FVector3f ClusterBoundsOrigin = (ClusterBoundsMax + ClusterBoundsMin) * 0.5f;
|
|
|
|
|
const FVector3f ClusterBoundsExtent = (ClusterBoundsMax - ClusterBoundsMin) * 0.5f;
|
|
|
|
|
const FVector3f MeshClusterBoundsOrigin = ClusterBasis.LocalToWorldRotation.TransformPosition(ClusterBoundsOrigin) + ClusterBasis.LocalToWorldOffset;
|
|
|
|
|
|
|
|
|
|
FLumenCardBuildData BuiltData;
|
|
|
|
|
BuiltData.OBB.Origin = MeshClusterBoundsOrigin;
|
|
|
|
|
BuiltData.OBB.Extent = ClusterBoundsExtent;
|
|
|
|
|
BuiltData.OBB.AxisX = ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::X);
|
|
|
|
|
BuiltData.OBB.AxisY = ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::Y);
|
|
|
|
|
BuiltData.OBB.AxisZ = -ClusterBasis.LocalToWorldRotation.GetScaledAxis(EAxis::Z);
|
|
|
|
|
BuiltData.LODLevel = LODLevelIndex;
|
|
|
|
|
BuiltData.AxisAlignedDirectionIndex = AxisAlignedDirectionIndex;
|
|
|
|
|
MeshCardsBuildData.CardBuildData.Add(BuiltData);
|
2022-01-05 20:41:17 +00:00
|
|
|
|
|
|
|
|
#if DEBUG_MESH_CARD_VISUALIZATION
|
2022-01-06 16:44:09 +00:00
|
|
|
DebugSurfelInCluster.Reset();
|
|
|
|
|
DebugSurfelInCluster.Add(false, Surfels.Num());
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
FLumenCardBuildDebugData::FSurfelCluster& DebugCluster = MeshCardsBuildData.DebugData.Clusters.AddDefaulted_GetRef();
|
|
|
|
|
DebugCluster.Surfels.Reserve(DebugCluster.Surfels.Num() + Surfels.Num());
|
2022-01-05 20:41:17 +00:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
// Cluster seed
|
2022-01-05 20:41:17 +00:00
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
2022-01-06 16:44:09 +00:00
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[Cluster.SurfelIndices[0]].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + Cluster.SurfelIndices[0];
|
|
|
|
|
DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Seed;
|
2022-01-05 20:41:17 +00:00
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
|
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const FSurfelIndex AverageSurfelIndex = FindBestSeed(ClusteringParams, SurfelScenePerDirection, Cluster);
|
|
|
|
|
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[AverageSurfelIndex].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + AverageSurfelIndex;
|
|
|
|
|
DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Seed2;
|
|
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex : Cluster.SurfelIndices)
|
|
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[SurfelIndex].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + SurfelIndex;
|
|
|
|
|
DebugSurfel.Type = FLumenCardBuildDebugData::ESurfelType::Cluster;
|
|
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
|
|
|
|
|
|
|
|
|
const int32 SurfelMinRayZ = Surfels[SurfelIndex].MinRayZ;
|
|
|
|
|
if (SurfelMinRayZ > 0)
|
|
|
|
|
{
|
|
|
|
|
FIntVector MinRayZCoord = Surfels[SurfelIndex].Coord;
|
|
|
|
|
MinRayZCoord.Z = SurfelMinRayZ;
|
|
|
|
|
|
|
|
|
|
FLumenCardBuildDebugData::FRay DebugRay;
|
|
|
|
|
DebugRay.RayStart = DebugSurfel.Position;
|
|
|
|
|
DebugRay.RayEnd = ClusterBasis.TransformSurfel(MinRayZCoord);
|
|
|
|
|
DebugRay.bHit = false;
|
|
|
|
|
DebugCluster.Rays.Add(DebugRay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DebugSurfelInAnyCluster[SurfelIndex] = true;
|
|
|
|
|
DebugSurfelInCluster[SurfelIndex] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FSurfelIndex SurfelIndex = 0; SurfelIndex < Surfels.Num(); ++SurfelIndex)
|
|
|
|
|
{
|
|
|
|
|
if (!DebugSurfelInCluster[SurfelIndex])
|
|
|
|
|
{
|
|
|
|
|
FLumenCardBuildDebugData::FSurfel DebugSurfel;
|
|
|
|
|
DebugSurfel.Position = ClusterBasis.TransformSurfel(Surfels[SurfelIndex].Coord);
|
|
|
|
|
DebugSurfel.Normal = AxisAlignedDirectionIndexToNormal(AxisAlignedDirectionIndex);
|
|
|
|
|
DebugSurfel.SourceSurfelIndex = SourceSurfelOffset + SurfelIndex;
|
|
|
|
|
DebugSurfel.Type = DebugSurfelInAnyCluster[SurfelIndex] ? FLumenCardBuildDebugData::ESurfelType::Used : FLumenCardBuildDebugData::ESurfelType::Idle;
|
|
|
|
|
DebugCluster.Surfels.Add(DebugSurfel);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-05 20:41:17 +00:00
|
|
|
#endif
|
2022-01-06 16:44:09 +00:00
|
|
|
}
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
SourceSurfelOffset += Surfels.Num();
|
2022-01-05 20:41:17 +00:00
|
|
|
}
|
2022-01-06 16:44:09 +00:00
|
|
|
|
|
|
|
|
#if DEBUG_MESH_CARD_VISUALIZATION
|
|
|
|
|
UE_LOG(LogMeshUtilities, Log, TEXT("CardGen Mesh:%s LOD:%d Surfels:%d Clusters:%d WeightedSurfaceCoverage:%f ClusterArea:%f"),
|
|
|
|
|
*Context.MeshName,
|
|
|
|
|
LODLevelIndex,
|
|
|
|
|
LODLevel.NumSurfels,
|
|
|
|
|
LODLevel.NumClusters,
|
|
|
|
|
LODLevel.WeightedSurfaceCoverage,
|
|
|
|
|
LODLevel.SurfaceArea);
|
|
|
|
|
#endif
|
2021-01-20 11:34:55 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
void BuildMeshCards(const FBox& MeshBounds, const FGenerateCardMeshContext& Context, int32 MaxLumenMeshCards, FCardRepresentationData& OutData)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2021-01-23 01:26:01 -04:00
|
|
|
// Make sure BBox isn't empty and we can generate card representation for it. This handles e.g. infinitely thin planes.
|
2021-02-11 17:59:41 -04:00
|
|
|
const FVector MeshCardsBoundsCenter = MeshBounds.GetCenter();
|
|
|
|
|
const FVector MeshCardsBoundsExtent = FVector::Max(MeshBounds.GetExtent() + 1.0f, FVector(5.0f));
|
|
|
|
|
const FBox MeshCardsBounds(MeshCardsBoundsCenter - MeshCardsBoundsExtent, MeshCardsBoundsCenter + MeshCardsBoundsExtent);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
// Prepare a list of surfels for cluster fitting
|
|
|
|
|
FSurfelScene SurfelScene;
|
|
|
|
|
FClusteringParams ClusteringParamsLOD1;
|
2022-01-06 16:44:09 +00:00
|
|
|
ClusteringParamsLOD1.MaxLumenMeshCards = MaxLumenMeshCards;
|
|
|
|
|
GenerateSurfels(Context, MeshCardsBounds, SurfelScene, ClusteringParamsLOD1);
|
2021-07-21 18:05:08 -04:00
|
|
|
|
|
|
|
|
FClusteringParams ClusteringParamsLOD0 = ClusteringParamsLOD1;
|
|
|
|
|
ClusteringParamsLOD0.MinSurfelsPerCluster /= 2;
|
|
|
|
|
|
|
|
|
|
FMeshCardsLODLevel MeshCardsLOD0;
|
|
|
|
|
BuildMeshCardsLOD0(MeshBounds, Context, SurfelScene, ClusteringParamsLOD0, MeshCardsLOD0);
|
|
|
|
|
|
|
|
|
|
FMeshCardsLODLevel MeshCardsLOD1;
|
|
|
|
|
BuildMeshCardsLOD1(MeshBounds, Context, SurfelScene, ClusteringParamsLOD1, MeshCardsLOD1);
|
|
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
OutData.MeshCardsBuildData.Bounds = MeshCardsBounds;
|
2021-07-21 18:05:08 -04:00
|
|
|
OutData.MeshCardsBuildData.MaxLODLevel = 0;
|
2021-01-20 11:34:55 -04:00
|
|
|
OutData.MeshCardsBuildData.CardBuildData.Reset();
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2022-01-06 16:44:09 +00:00
|
|
|
SerializeLOD(Context, ClusteringParamsLOD0, SurfelScene, MeshCardsLOD0, 0, MeshCardsBounds, OutData.MeshCardsBuildData);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
// Optionally serialize LOD1 if it's of higher quality without exceeding the budget
|
2022-01-06 16:44:09 +00:00
|
|
|
if (MeshCardsLOD1.NumClusters <= MeshCardGen::MaxCardsPerMesh
|
|
|
|
|
&& MeshCardsLOD1.WeightedSurfaceCoverage > MeshCardsLOD0.WeightedSurfaceCoverage)
|
2021-01-20 11:34:55 -04:00
|
|
|
{
|
2022-01-06 16:44:09 +00:00
|
|
|
SerializeLOD(Context, ClusteringParamsLOD1, SurfelScene, MeshCardsLOD1, 1, MeshCardsBounds, OutData.MeshCardsBuildData);
|
2020-07-06 18:58:26 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif // #if USE_EMBREE
|
|
|
|
|
|
|
|
|
|
bool FMeshUtilities::GenerateCardRepresentationData(
|
|
|
|
|
FString MeshName,
|
2020-09-22 23:04:05 -04:00
|
|
|
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
|
2020-07-06 18:58:26 -04:00
|
|
|
const FStaticMeshLODResources& LODModel,
|
|
|
|
|
class FQueuedThreadPool& ThreadPool,
|
2021-01-20 11:34:55 -04:00
|
|
|
const TArray<FSignedDistanceFieldBuildMaterialData>& MaterialBlendModes,
|
2020-07-06 18:58:26 -04:00
|
|
|
const FBoxSphereBounds& Bounds,
|
|
|
|
|
const FDistanceFieldVolumeData* DistanceFieldVolumeData,
|
2022-01-06 16:44:09 +00:00
|
|
|
int32 MaxLumenMeshCards,
|
2021-01-20 11:34:55 -04:00
|
|
|
bool bGenerateAsIfTwoSided,
|
2020-07-06 18:58:26 -04:00
|
|
|
FCardRepresentationData& OutData)
|
|
|
|
|
{
|
|
|
|
|
#if USE_EMBREE
|
2020-07-28 09:09:18 -04:00
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FMeshUtilities::GenerateCardRepresentationData);
|
2021-01-20 11:34:55 -04:00
|
|
|
const double StartTime = FPlatformTime::Seconds();
|
2020-07-28 09:09:18 -04:00
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
FEmbreeScene EmbreeScene;
|
|
|
|
|
MeshRepresentation::SetupEmbreeScene(MeshName,
|
|
|
|
|
SourceMeshData,
|
|
|
|
|
LODModel,
|
|
|
|
|
MaterialBlendModes,
|
|
|
|
|
bGenerateAsIfTwoSided,
|
|
|
|
|
EmbreeScene);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
if (!EmbreeScene.EmbreeScene)
|
2020-07-06 18:58:26 -04:00
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-21 18:05:08 -04:00
|
|
|
FGenerateCardMeshContext Context(MeshName, EmbreeScene, OutData);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
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
|
2022-01-06 16:44:09 +00:00
|
|
|
BuildMeshCards(DistanceFieldVolumeData ? DistanceFieldVolumeData->LocalSpaceMeshBounds : Bounds.GetBox(), Context, MaxLumenMeshCards, OutData);
|
2020-07-06 18:58:26 -04:00
|
|
|
|
2021-01-20 11:34:55 -04:00
|
|
|
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)
|
|
|
|
|
{
|
2021-07-21 18:05:08 -04:00
|
|
|
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,
|
2021-07-21 18:05:08 -04:00
|
|
|
*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
|
|
|
}
|
2020-07-06 18:58:26 -04:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
#else
|
|
|
|
|
UE_LOG(LogMeshUtilities, Warning, TEXT("Platform did not set USE_EMBREE, GenerateCardRepresentationData failed."));
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|