Files
UnrealEngine/Engine/Source/Developer/MeshUtilities/Private/MeshDistanceFieldUtilities.cpp
2025-05-18 13:04:45 +08:00

663 lines
27 KiB
C++

// 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 "DistanceFieldAtlas.h"
#include "MeshRepresentationCommon.h"
#include "Async/ParallelFor.h"
#include "ProfilingDebugging/CookStats.h"
#if ENABLE_COOK_STATS
namespace DistanceFieldUtilityCookStats
{
FCookStats::FDDCResourceUsageStats UsageStats;
static FCookStatsManager::FAutoRegisterCallback RegisterCookStats([](FCookStatsManager::AddStatFuncRef AddStat)
{
UsageStats.LogStats(AddStat, TEXT("DistanceField.Usage"), TEXT(""));
});
}
#endif
static TAutoConsoleVariable<int32> CVarDistanceFieldsEstimateRatio(
TEXT("r.CookMemory.DistanceFieldsEstimateRatio"), 15,
TEXT("Configurable ratio for the distance field memory estimate"),
ECVF_ReadOnly
);
#if USE_EMBREE
class FEmbreePointQueryContext : public RTCPointQueryContext
{
public:
const FEmbreeScene* Scene = nullptr;
};
bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
{
const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;
check(args->userPtr);
float& ClosestDistanceSq = *(float*)(args->userPtr);
int32 GeometryIndex = args->geomID;
if (Context->instID[0] != RTC_INVALID_GEOMETRY_ID)
{
// when testing against a geometry instance use instID to index into Scene->Geometries
GeometryIndex = Context->instID[0];
}
const FEmbreeGeometryAsset* GeometryAsset = Context->Scene->Geometries[GeometryIndex].Asset;
const int32 NumTriangles = GeometryAsset->NumTriangles;
const int32 TriangleIndex = args->primID;
check(TriangleIndex < NumTriangles);
const FVector3f* VertexBuffer = (const FVector3f*)GeometryAsset->VertexArray.GetData();
const uint32* IndexBuffer = (const uint32*)GeometryAsset->IndexArray.GetData();
const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];
FVector3f V0 = VertexBuffer[I0];
FVector3f V1 = VertexBuffer[I1];
FVector3f V2 = VertexBuffer[I2];
if (Context->instID[0] != RTC_INVALID_GEOMETRY_ID)
{
// when testing against a geometry instance need to transform vertices to world space
FMatrix44f* InstToWorld = (FMatrix44f*)Context->inst2world[0];
V0 = InstToWorld->TransformPosition(V0);
V1 = InstToWorld->TransformPosition(V1);
V2 = InstToWorld->TransformPosition(V2);
}
const FVector3f QueryPosition(args->query->x, args->query->y, args->query->z);
const FVector3f ClosestPoint = (FVector3f)FMath::ClosestPointOnTriangleToPoint((FVector)QueryPosition, (FVector)V0, (FVector)V1, (FVector)V2);
const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared();
if (QueryDistanceSq < ClosestDistanceSq)
{
ClosestDistanceSq = QueryDistanceSq;
bool bShrinkQuery = true;
if (bShrinkQuery)
{
args->query->radius = FMath::Sqrt(ClosestDistanceSq);
// Return true to indicate that the query radius has shrunk
return true;
}
}
// Return false to indicate that the query radius hasn't changed
return false;
}
static int32 ComputeLinearVoxelIndex(FIntVector VoxelCoordinate, FIntVector VolumeDimensions)
{
return (VoxelCoordinate.Z * VolumeDimensions.Y + VoxelCoordinate.Y) * VolumeDimensions.X + VoxelCoordinate.X;
}
class FSparseMeshDistanceFieldAsyncTask
{
public:
FSparseMeshDistanceFieldAsyncTask(
const FEmbreeScene& InEmbreeScene,
const TArray<FVector3f>* InSampleDirections,
float InLocalSpaceTraceDistance,
FBox3f InVolumeBounds,
float InLocalToVolumeScale,
FVector2f InDistanceFieldToVolumeScaleBias,
FInt32Vector InBrickCoordinate,
FInt32Vector InIndirectionSize,
bool bInUsePointQuery)
:
EmbreeScene(InEmbreeScene),
SampleDirections(InSampleDirections),
LocalSpaceTraceDistance(InLocalSpaceTraceDistance),
VolumeBounds(InVolumeBounds),
LocalToVolumeScale(InLocalToVolumeScale),
DistanceFieldToVolumeScaleBias(InDistanceFieldToVolumeScaleBias),
BrickCoordinate(InBrickCoordinate),
IndirectionSize(InIndirectionSize),
bUsePointQuery(bInUsePointQuery),
BrickMaxDistance(MIN_uint8),
BrickMinDistance(MAX_uint8)
{}
void DoWork();
// Readonly inputs
const FEmbreeScene& EmbreeScene;
const TArray<FVector3f>* SampleDirections;
float LocalSpaceTraceDistance;
FBox VolumeBounds;
float LocalToVolumeScale;
FVector2D DistanceFieldToVolumeScaleBias;
FIntVector BrickCoordinate;
FIntVector IndirectionSize;
bool bUsePointQuery;
// Output
uint8 BrickMaxDistance;
uint8 BrickMinDistance;
TArray<uint8> DistanceFieldVolume;
};
int32 DebugX = 0;
int32 DebugY = 0;
int32 DebugZ = 0;
void FSparseMeshDistanceFieldAsyncTask::DoWork()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSparseMeshDistanceFieldAsyncTask::DoWork);
COOK_STAT(auto Timer = DistanceFieldUtilityCookStats::UsageStats.TimeSyncWork());
COOK_STAT(Timer.TrackCyclesOnly());
const FVector IndirectionVoxelSize = VolumeBounds.GetSize() / FVector(IndirectionSize);
const FVector DistanceFieldVoxelSize = IndirectionVoxelSize / FVector(DistanceField::UniqueDataBrickSize);
const FVector BrickMinPosition = VolumeBounds.Min + FVector(BrickCoordinate) * IndirectionVoxelSize;
DistanceFieldVolume.Empty(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
DistanceFieldVolume.AddZeroed(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++)
{
for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++)
{
for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++)
{
if (XIndex == DebugX && YIndex == DebugY && ZIndex == DebugZ)
{
int32 DebugBreak = 0;
}
const FVector VoxelPosition = FVector(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + BrickMinPosition;
const int32 Index = (ZIndex * DistanceField::BrickSize * DistanceField::BrickSize + YIndex * DistanceField::BrickSize + XIndex);
float MinLocalSpaceDistance = LocalSpaceTraceDistance;
bool bTraceRays = true;
if (bUsePointQuery)
{
RTCPointQuery PointQuery;
PointQuery.x = VoxelPosition.X;
PointQuery.y = VoxelPosition.Y;
PointQuery.z = VoxelPosition.Z;
PointQuery.time = 0;
PointQuery.radius = LocalSpaceTraceDistance;
FEmbreePointQueryContext QueryContext;
rtcInitPointQueryContext(&QueryContext);
QueryContext.Scene = &EmbreeScene;
float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
rtcPointQuery(EmbreeScene.Scene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq);
const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq);
bTraceRays = ClosestDistance <= LocalSpaceTraceDistance;
MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance);
}
if (bTraceRays)
{
int32 Hit = 0;
int32 HitBack = 0;
for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++)
{
const FVector UnitRayDirection = (FVector)(*SampleDirections)[SampleIndex];
const float PullbackEpsilon = 1.e-4f;
// Pull back the starting position slightly to make sure we hit a triangle that VoxelPosition is exactly on.
// This happens a lot with boxes, since we trace from voxel corners.
const FVector StartPosition = VoxelPosition - PullbackEpsilon * LocalSpaceTraceDistance * UnitRayDirection;
const FVector EndPosition = VoxelPosition + UnitRayDirection * LocalSpaceTraceDistance;
if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection))
{
FEmbreeRay EmbreeRay;
FVector RayDirection = EndPosition - VoxelPosition;
EmbreeRay.ray.org_x = StartPosition.X;
EmbreeRay.ray.org_y = StartPosition.Y;
EmbreeRay.ray.org_z = StartPosition.Z;
EmbreeRay.ray.dir_x = RayDirection.X;
EmbreeRay.ray.dir_y = RayDirection.Y;
EmbreeRay.ray.dir_z = RayDirection.Z;
EmbreeRay.ray.tnear = 0;
EmbreeRay.ray.tfar = 1.0f;
EmbreeRay.ray.mask = 0xFFFFFFFF;
#if USE_EMBREE_MAJOR_VERSION >= 4
FEmbreeRayQueryContext EmbreeContext;
rtcInitRayQueryContext(&EmbreeContext);
RTCIntersectArguments args;
rtcInitIntersectArguments(&args);
args.context = &EmbreeContext;
rtcIntersect1(EmbreeScene.Scene, &EmbreeRay, &args);
#else
FEmbreeIntersectionContext EmbreeContext;
rtcInitIntersectContext(&EmbreeContext);
rtcIntersect1(EmbreeScene.Scene, &EmbreeContext, &EmbreeRay);
#endif
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
{
check(EmbreeContext.ElementIndex != -1);
Hit++;
const FVector HitNormal = (FVector)EmbreeRay.GetHitNormal();
if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeContext.IsHitTwoSided())
{
HitBack++;
}
if (!bUsePointQuery)
{
const float CurrentDistance = EmbreeRay.ray.tfar * LocalSpaceTraceDistance;
if (CurrentDistance < MinLocalSpaceDistance)
{
MinLocalSpaceDistance = CurrentDistance;
}
}
}
}
}
// Consider this voxel 'inside' an object if we hit a significant number of backfaces
if (Hit > 0 && HitBack > .25f * SampleDirections->Num())
{
MinLocalSpaceDistance *= -1;
}
}
// Transform to the tracing shader's Volume space
const float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;
// Transform to the Distance Field texture's space
const float RescaledDistance = (VolumeSpaceDistance - DistanceFieldToVolumeScaleBias.Y) / DistanceFieldToVolumeScaleBias.X;
check(DistanceField::DistanceFieldFormat == PF_G8);
const uint8 QuantizedDistance = FMath::Clamp<int32>(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
DistanceFieldVolume[Index] = QuantizedDistance;
BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
}
}
}
}
int64_t FMeshUtilities::MemoryForGenerateSignedDistanceFieldVolumeData(
const FMeshDataForDerivedDataTask& MeshData,
float DistanceFieldResolutionScale
)
{
// ------------------
// Copied from GenerateSignedDistanceFieldVolumeData
// ------------------
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution"));
const int32 PerMeshMax = CVar->GetValueOnAnyThread();
// Meshes with explicit artist-specified scale can go higher
const int32 MaxNumBlocksOneDim = FMath::Min<int32>(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);
static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
const float VoxelDensity = CVarDensity->GetValueOnAnyThread();
const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
FBox3f LocalSpaceMeshBounds(MeshData.Bounds.GetBox());
// Make sure the mesh bounding box has positive extents to handle planes
{
FVector3f MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
FVector3f MeshBoundsExtent = FVector3f::Max(LocalSpaceMeshBounds.GetExtent(), FVector3f::OneVector);
LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
}
const FVector3f DesiredDimensions = FVector3f(LocalSpaceMeshBounds.GetSize() * NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize);
const FInt32Vector Mip0IndirectionDimensions = FInt32Vector(
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
// ------------------
const int64 TotalBrickCount = (Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize
* Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize
* Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize);
// Base and Ratio have been taken by calculating at a granular level the memory used and correlating
// it with the TotalBrickCount. An estimate was used in place of granular calculations for simplicity.
const int32 Base = 1000;
const int32 Ratio = CVarDistanceFieldsEstimateRatio.GetValueOnAnyThread();
const int64 MeshUtilTotal = Base + TotalBrickCount * Ratio;
// ------------------
const uint32 NumIndices = MeshData.SourceMeshData ? MeshData.SourceMeshData->GetNumIndices() : MeshData.LODModel->IndexBuffer.GetNumIndices();
const int64 EmbreeUsage = MeshRepresentation::MemoryEstimateForEmbreeScene(NumIndices);
// ------------------
const int64 Total = MeshUtilTotal + EmbreeUsage;
return Total;
}
static void BuildSignedDistanceField(
FString MeshName,
const FEmbreeScene& EmbreeScene,
const FBoxSphereBounds3f& Bounds,
float DistanceFieldResolutionScale,
FDistanceFieldVolumeData& OutData)
{
// Whether to use an Embree Point Query to compute the closest unsigned distance. Rays will only be traced to determine backfaces visible for sign.
const bool bUsePointQuery = true;
TArray<FVector3f> SampleDirections;
{
const int32 NumVoxelDistanceSamples = bUsePointQuery ? 49 : 576;
FRandomStream RandomStream(0);
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
TArray<FVector3f> OtherHemisphereSamples;
MeshUtilities::GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, OtherHemisphereSamples);
for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++)
{
FVector3f Sample = OtherHemisphereSamples[i];
Sample.Z *= -1.0f;
SampleDirections.Add(Sample);
}
}
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution"));
const int32 PerMeshMax = CVar->GetValueOnAnyThread();
// Meshes with explicit artist-specified scale can go higher
const int32 MaxNumBlocksOneDim = FMath::Min<int32>(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);
static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
const float VoxelDensity = CVarDensity->GetValueOnAnyThread();
const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
FBox3f LocalSpaceMeshBounds(Bounds.GetBox());
// Make sure the mesh bounding box has positive extents to handle planes
{
FVector3f MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
FVector3f MeshBoundsExtent = FVector3f::Max(LocalSpaceMeshBounds.GetExtent(), FVector3f::OneVector);
LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
}
// We sample on voxel corners and use central differencing for gradients, so a box mesh using two-sided materials whose vertices lie on LocalSpaceMeshBounds produces a zero gradient on intersection
// Expand the mesh bounds by a fraction of a voxel to allow room for a pullback on the hit location for computing the gradient.
// Only expand for two sided meshes as this adds significant Mesh SDF tracing cost
if (EmbreeScene.bMostlyTwoSided)
{
const FVector3f DesiredDimensions = LocalSpaceMeshBounds.GetSize() * FVector3f(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize);
const FInt32Vector Mip0IndirectionDimensions = FInt32Vector(
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
const float CentralDifferencingExpandInVoxels = .25f;
const FVector3f TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector3f(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FInt32Vector(2 * CentralDifferencingExpandInVoxels));
LocalSpaceMeshBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
}
// The tracing shader uses a Volume space that is normalized by the maximum extent, to keep Volume space within [-1, 1], we must match that behavior when encoding
const float LocalToVolumeScale = 1.0f / LocalSpaceMeshBounds.GetExtent().GetMax();
const FVector3f DesiredDimensions = FVector3f(LocalSpaceMeshBounds.GetSize() * NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize);
const FInt32Vector Mip0IndirectionDimensions = FInt32Vector(
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
TArray<uint8> StreamableMipData;
for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++)
{
const FInt32Vector IndirectionDimensions = FInt32Vector(
FMath::DivideAndRoundUp(Mip0IndirectionDimensions.X, 1 << MipIndex),
FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Y, 1 << MipIndex),
FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Z, 1 << MipIndex));
// Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering
const FVector3f TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector3f(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
const FBox3f DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
const FVector3f IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector3f(IndirectionDimensions);
const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();
const FVector3f VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector3f(DistanceField::UniqueDataBrickSize);
const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
const FVector2f DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);
TArray<FSparseMeshDistanceFieldAsyncTask> AsyncTasks;
AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++)
{
for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++)
{
for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++)
{
AsyncTasks.Emplace(
EmbreeScene,
&SampleDirections,
LocalSpaceTraceDistance,
DistanceFieldVolumeBounds,
LocalToVolumeScale,
DistanceFieldToVolumeScaleBias,
FInt32Vector(XIndex, YIndex, ZIndex),
IndirectionDimensions,
bUsePointQuery);
}
}
}
static bool bMultiThreaded = true;
if (bMultiThreaded)
{
EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;
COOK_STAT(auto AsyncTimer = DistanceFieldUtilityCookStats::UsageStats.TimeAsyncWait());
COOK_STAT(AsyncTimer.TrackCyclesOnly());
ParallelForTemplate(
TEXT("GenerateSignedDistanceFieldVolumeData.PF"),
AsyncTasks.Num(), 1, [&AsyncTasks](int32 TaskIndex)
{
AsyncTasks[TaskIndex].DoWork();
}, Flags);
}
else
{
for (FSparseMeshDistanceFieldAsyncTask& AsyncTask : AsyncTasks)
{
AsyncTask.DoWork();
}
}
FSparseDistanceFieldMip& OutMip = OutData.Mips[MipIndex];
TArray<uint32> IndirectionTable;
IndirectionTable.Empty(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
IndirectionTable.AddUninitialized(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
for (int32 i = 0; i < IndirectionTable.Num(); i++)
{
IndirectionTable[i] = DistanceField::InvalidBrickIndex;
}
TArray<FSparseMeshDistanceFieldAsyncTask*> ValidBricks;
ValidBricks.Empty(AsyncTasks.Num());
for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++)
{
if (AsyncTasks[TaskIndex].BrickMinDistance < MAX_uint8 && AsyncTasks[TaskIndex].BrickMaxDistance > MIN_uint8)
{
ValidBricks.Add(&AsyncTasks[TaskIndex]);
}
}
const uint32 NumBricks = ValidBricks.Num();
const uint32 BrickSizeBytes = DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
TArray<uint8> DistanceFieldBrickData;
DistanceFieldBrickData.Empty(BrickSizeBytes * NumBricks);
DistanceFieldBrickData.AddUninitialized(BrickSizeBytes * NumBricks);
for (int32 BrickIndex = 0; BrickIndex < ValidBricks.Num(); BrickIndex++)
{
const FSparseMeshDistanceFieldAsyncTask& Brick = *ValidBricks[BrickIndex];
const int32 IndirectionIndex = ComputeLinearVoxelIndex(Brick.BrickCoordinate, IndirectionDimensions);
IndirectionTable[IndirectionIndex] = BrickIndex;
check(BrickSizeBytes == Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
FPlatformMemory::Memcpy(&DistanceFieldBrickData[BrickIndex * BrickSizeBytes], Brick.DistanceFieldVolume.GetData(), Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
}
const int32 IndirectionTableBytes = IndirectionTable.Num() * IndirectionTable.GetTypeSize();
const int32 MipDataBytes = IndirectionTableBytes + DistanceFieldBrickData.Num();
if (MipIndex == DistanceField::NumMips - 1)
{
OutData.AlwaysLoadedMip.Empty(MipDataBytes);
OutData.AlwaysLoadedMip.AddUninitialized(MipDataBytes);
FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[0], IndirectionTable.GetData(), IndirectionTableBytes);
if (DistanceFieldBrickData.Num() > 0)
{
FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
}
}
else
{
OutMip.BulkOffset = StreamableMipData.Num();
StreamableMipData.AddUninitialized(MipDataBytes);
OutMip.BulkSize = StreamableMipData.Num() - OutMip.BulkOffset;
checkf(OutMip.BulkSize > 0, TEXT("BulkSize was 0 for %s with %ux%ux%u indirection"), *MeshName, IndirectionDimensions.X, IndirectionDimensions.Y, IndirectionDimensions.Z);
FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset], IndirectionTable.GetData(), IndirectionTableBytes);
if (DistanceFieldBrickData.Num() > 0)
{
FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset + IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
}
}
OutMip.IndirectionDimensions = IndirectionDimensions;
OutMip.DistanceFieldToVolumeScaleBias = DistanceFieldToVolumeScaleBias;
OutMip.NumDistanceFieldBricks = NumBricks;
// Account for the border voxels we added
const FVector3f VirtualUVMin = FVector3f(DistanceField::MeshDistanceFieldObjectBorder) / FVector3f(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
const FVector3f VirtualUVSize = FVector3f(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FInt32Vector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector3f(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
const FVector3f VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;
// [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
}
OutData.bMostlyTwoSided = EmbreeScene.bMostlyTwoSided;
OutData.LocalSpaceMeshBounds = LocalSpaceMeshBounds;
OutData.StreamableMips.Lock(LOCK_READ_WRITE);
uint8* Ptr = (uint8*)OutData.StreamableMips.Realloc(StreamableMipData.Num());
FMemory::Memcpy(Ptr, StreamableMipData.GetData(), StreamableMipData.Num());
OutData.StreamableMips.Unlock();
OutData.StreamableMips.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);
}
void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FMeshDataForDerivedDataTask& MeshData,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
class FDistanceFieldVolumeData& OutData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(GenerateSignedDistanceFieldVolumeData);
COOK_STAT(auto Timer = DistanceFieldUtilityCookStats::UsageStats.TimeSyncWork());
COOK_STAT(Timer.TrackCyclesOnly());
if (DistanceFieldResolutionScale > 0)
{
const double StartTime = FPlatformTime::Seconds();
const bool bIncludeTranslucentTriangles = false;
FEmbreeScene EmbreeScene;
MeshRepresentation::SetupEmbreeScene(MeshName, bGenerateAsIfTwoSided, EmbreeScene);
// If Embree setup fails, there will be no scene to operate on. Early out.
if (!EmbreeScene.Scene)
{
return;
}
if (!MeshRepresentation::AddMeshDataToEmbreeScene(EmbreeScene, MeshData, bIncludeTranslucentTriangles))
{
return;
}
EmbreeScene.Commit();
BuildSignedDistanceField(MeshName, EmbreeScene, MeshData.Bounds, DistanceFieldResolutionScale, OutData);
MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);
if (BuildTime > 1.0f)
{
const FInt32Vector Mip0IndirectionDimensions = OutData.Mips[0].IndirectionDimensions;
UE_LOG(LogMeshUtilities, Log, TEXT("Finished distance field build in %.1fs - %ux%ux%u sparse distance field, %.1fMb total, %.1fMb always loaded, %u%% occupied, %u triangles, %s"),
BuildTime,
Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize,
Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize,
Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize,
(OutData.GetResourceSizeBytes() + OutData.StreamableMips.GetBulkDataSize()) / 1024.0f / 1024.0f,
(OutData.AlwaysLoadedMip.GetAllocatedSize()) / 1024.0f / 1024.0f,
FMath::RoundToInt(100.0f * OutData.Mips[0].NumDistanceFieldBricks / (float)(Mip0IndirectionDimensions.X * Mip0IndirectionDimensions.Y * Mip0IndirectionDimensions.Z)),
EmbreeScene.NumTrianglesTotal,
*MeshName);
}
}
}
#else
void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FMeshDataForDerivedDataTask& MeshData,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
class FDistanceFieldVolumeData& OutData)
{
if (DistanceFieldResolutionScale > 0)
{
UE_LOG(LogMeshUtilities, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
}
}
#endif // PLATFORM_ENABLE_VECTORINTRINSICS