You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Generate surfels directly from the triangles instead of relying on ray tracing, to fix coverage issues on some meshes * Surfels which are inside meshes (surrounded by back faces) or are too close to geometry are discarded * New surfel clustering algorithm, which inserts one seed after another and tries to iteratively grow clusters in order to find the best set of seeds. Final step is to reset all clusters and grow all simultaneously from previously selected seeds. * Cluster growing is based on normal, distance and surfel visibility (don�t cluster surfels near geometry first, as it can cause algorithm to be stuck in a local minimum) * Runtime sampling has strict culling based on the angle and card AABB. Additionally, the tri-planar blending zone was tightened. This improves performance. 0.62-0.5 ms. * Added various visualizations and CVars for card generation debugging * Fixed card visibility bug, where card could influence outside of it�s range due to negative shadow map visibility [FYI] Daniel.Wright, Patrick.Kelly #ROBOMERGE-SOURCE: CL 16897632 #ROBOMERGE-BOT: (v836-16769935) [CL 16916104 by krzysztof narkowicz in ue5-main branch]
558 lines
22 KiB
C++
558 lines
22 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"
|
|
|
|
#if USE_EMBREE
|
|
|
|
|
|
class FEmbreePointQueryContext : public RTCPointQueryContext
|
|
{
|
|
public:
|
|
RTCGeometry MeshGeometry;
|
|
int32 NumTriangles;
|
|
};
|
|
|
|
bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
|
|
{
|
|
const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;
|
|
|
|
check(args->userPtr);
|
|
float& ClosestDistanceSq = *(float*)(args->userPtr);
|
|
|
|
const int32 TriangleIndex = args->primID;
|
|
check(TriangleIndex < Context->NumTriangles);
|
|
|
|
const FVector3f* VertexBuffer = (const FVector3f*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_VERTEX, 0);
|
|
const uint32* IndexBuffer = (const uint32*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_INDEX, 0);
|
|
|
|
const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
|
|
const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
|
|
const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];
|
|
|
|
const FVector3f V0 = VertexBuffer[I0];
|
|
const FVector3f V1 = VertexBuffer[I1];
|
|
const FVector3f V2 = VertexBuffer[I2];
|
|
|
|
const FVector3f QueryPosition(args->query->x, args->query->y, args->query->z);
|
|
const FVector3f ClosestPoint = FMath::ClosestPointOnTriangleToPoint(QueryPosition, V0, V1, 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,
|
|
FBox InVolumeBounds,
|
|
float InLocalToVolumeScale,
|
|
FVector2D InDistanceFieldToVolumeScaleBias,
|
|
FIntVector InBrickCoordinate,
|
|
FIntVector 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);
|
|
|
|
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.MeshGeometry = EmbreeScene.Geometry.InternalGeometry;
|
|
QueryContext.NumTriangles = EmbreeScene.Geometry.TriangleDescs.Num();
|
|
float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
|
|
rtcPointQuery(EmbreeScene.EmbreeScene, &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 = (*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;
|
|
|
|
FEmbreeIntersectionContext EmbreeContext;
|
|
rtcInitIntersectContext(&EmbreeContext);
|
|
rtcIntersect1(EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);
|
|
|
|
if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
|
|
{
|
|
check(EmbreeContext.ElementIndex != -1);
|
|
Hit++;
|
|
|
|
const FVector HitNormal = 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
|
|
FString MeshName,
|
|
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
|
|
const FStaticMeshLODResources& LODModel,
|
|
class FQueuedThreadPool& ThreadPool,
|
|
const TArray<FSignedDistanceFieldBuildMaterialData>& MaterialBlendModes,
|
|
const FBoxSphereBounds& Bounds,
|
|
float DistanceFieldResolutionScale,
|
|
bool bGenerateAsIfTwoSided,
|
|
FDistanceFieldVolumeData& OutData)
|
|
{
|
|
if (DistanceFieldResolutionScale > 0)
|
|
{
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
FEmbreeScene EmbreeScene;
|
|
MeshRepresentation::SetupEmbreeScene(MeshName,
|
|
SourceMeshData,
|
|
LODModel,
|
|
MaterialBlendModes,
|
|
bGenerateAsIfTwoSided,
|
|
EmbreeScene);
|
|
|
|
check(EmbreeScene.bUseEmbree);
|
|
|
|
bool bMostlyTwoSided;
|
|
{
|
|
uint32 NumTrianglesTotal = 0;
|
|
uint32 NumTwoSidedTriangles = 0;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
|
|
{
|
|
const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
|
|
|
|
if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
|
|
{
|
|
NumTrianglesTotal += Section.NumTriangles;
|
|
|
|
if (MaterialBlendModes[Section.MaterialIndex].bTwoSided)
|
|
{
|
|
NumTwoSidedTriangles += Section.NumTriangles;
|
|
}
|
|
}
|
|
}
|
|
|
|
bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
|
|
}
|
|
|
|
// 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 ? 120 : 1200;
|
|
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;
|
|
FBox LocalSpaceMeshBounds(Bounds.GetBox());
|
|
|
|
// Make sure the mesh bounding box has positive extents to handle planes
|
|
{
|
|
FVector MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
|
|
FVector MeshBoundsExtent = FVector::Max(LocalSpaceMeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f));
|
|
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 (bMostlyTwoSided)
|
|
{
|
|
const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
|
|
const FIntVector Mip0IndirectionDimensions = FIntVector(
|
|
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 FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(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 FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
|
|
const FIntVector Mip0IndirectionDimensions = FIntVector(
|
|
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 FIntVector IndirectionDimensions = FIntVector(
|
|
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 FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
|
|
const FBox DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
|
|
|
|
const FVector IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector(IndirectionDimensions);
|
|
const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();
|
|
|
|
const FVector VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector(DistanceField::UniqueDataBrickSize);
|
|
const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
|
|
const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
|
|
const FVector2D DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);
|
|
|
|
TArray<FSparseMeshDistanceFieldAsyncTask> AsyncTasks;
|
|
AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z / 8);
|
|
|
|
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,
|
|
FIntVector(XIndex, YIndex, ZIndex),
|
|
IndirectionDimensions,
|
|
bUsePointQuery);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool bMultiThreaded = true;
|
|
|
|
if (bMultiThreaded)
|
|
{
|
|
EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;
|
|
|
|
ParallelForTemplate(AsyncTasks.Num(), [&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 FVector VirtualUVMin = FVector(DistanceField::MeshDistanceFieldObjectBorder) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
|
|
const FVector VirtualUVSize = FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
|
|
|
|
const FVector VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;
|
|
|
|
// [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
|
|
OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
|
|
OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
|
|
}
|
|
|
|
MeshRepresentation::DeleteEmbreeScene(EmbreeScene);
|
|
|
|
OutData.bMostlyTwoSided = 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);
|
|
|
|
const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);
|
|
|
|
if (BuildTime > 1.0f)
|
|
{
|
|
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.NumIndices / 3,
|
|
*MeshName);
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
|
|
FString MeshName,
|
|
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
|
|
const FStaticMeshLODResources& LODModel,
|
|
class FQueuedThreadPool& ThreadPool,
|
|
const TArray<FSignedDistanceFieldBuildMaterialData>& MaterialBlendModes,
|
|
const FBoxSphereBounds& Bounds,
|
|
float DistanceFieldResolutionScale,
|
|
bool bGenerateAsIfTwoSided,
|
|
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
|