最近有在运行时加载模型的需求,使用DatasmithRuntimeActor可以实现,但是跟在编辑器里加载的模型对比起来,室内没有Lumen的光照效果。
图1 编辑器下加载模型的效果
图2 运行时下加载模型的效果
然后查看了距离场的数据,发现运行时并没有生成距离场的数据
图3 编辑器下的距离场数据
图4 运行时的距离场数据(并没有看到导入的模型)
通过跟踪源码发现,找到编辑器模式下生成距离场数据的代码
MeshUtilities->GenerateSignedDistanceFieldVolumeData();
Editor模式下,添加一个Fbx资源,生成距离场的代码执行的过程如下:
void FDistanceFieldVolumeData::CacheDerivedData()
{
FAsyncDistanceFieldTask* NewTask = new FAsyncDistanceFieldTask;
... ...
GDistanceFieldAsyncQueue->AddTask(NewTask);
}
void FAsyncDistanceFieldTaskWorker::DoWork()
{
// Put on background thread to avoid interfering with game-thread bound tasks
FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
GDistanceFieldAsyncQueue->Build(&Task, TaskGraphWrapper);
}
void FDistanceFieldAsyncQueue::Build(FAsyncDistanceFieldTask* Task, FQueuedThreadPool& BuildThreadPool)
{
#if WITH_EDITOR
// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
if (Task->StaticMesh && Task->GenerateSource)
{
const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
MeshUtilities->GenerateSignedDistanceFieldVolumeData();
}
#endif
}
参考GenerateSignedDistanceFieldVolumeData函数,将源码中的代码挖到自己的项目中
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RenderCore", "InputCore", "DatasmithContent" });
AddEngineThirdPartyPrivateStaticDependencies(Target, "Embree3");
下面代码在编辑器中以game模式启动可以在运行时生成距离场的数据,但是需要打包的话,还需要修改源码,因为其中调用的很多函数是限定在WITH_EDITOR宏中的,需要将其脱离限定宏。
void UMainWidget::ButtonSDFClicked()
{
TArray OutMeshActor;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), OutMeshActor);
UE_LOG(LogTemp, Log, TEXT("start Generate SDF data"));
GEngine->AddOnScreenDebugMessage(0, 400.0f, FColor::Red, TEXT("start Generate SDF data"));
TSet StaticMeshSet;
int32 Index = 0;
for (AActor* OutActor : OutMeshActor)
{
int tagNum = OutActor->Tags.Num();
if (tagNum > 0)
{
UDatasmithAssetUserData* DatasmithAssetUserData = OutActor->GetRootComponent()->GetAssetUserData();
if (DatasmithAssetUserData)
{
const FString CreationPhaseKey = PHASE_CREATION;
const FString CreationPhase = *DatasmithAssetUserData->MetaData.FindRef(*CreationPhaseKey);
if (CreationPhase == ELECTROMECHANICAL)
{
continue;
}
//FString familyName = *DatasmithAssetUserData->MetaData.FindRef(TEXT("Element*Family"));
//if (familyName == TEXT("基本墙") || familyName == TEXT("天花板") || familyName == TEXT("楼板"))
{
UStaticMesh* StaticMesh = nullptr;
if (AStaticMeshActor* StaticMeshActor = Cast(OutActor))
{
auto component = StaticMeshActor->GetStaticMeshComponent();
component->UnregisterComponent();
component->RegisterComponent();
StaticMesh = component->GetStaticMesh();
if (StaticMesh->IsValidLowLevel())
{
StaticMeshSet.Emplace(StaticMesh);
}
}
}
}
}
}
static int i;
USignedDistanceFieldUtilities* MyClass = NewObject();
for (UStaticMesh* Mesh : StaticMeshSet)
{
MyClass->GenerateSDF(Mesh);
i++;
}
UE_LOG(LogTemp, Log, TEXT("end Generate SDF data"));
UE_LOG(LogTemp, Log, TEXT(" Generate SDF data count : %d "), i);
GEngine->AddOnScreenDebugMessage(0, 400.0f, FColor::Red, TEXT("end Generate SDF data"));
}
SignedDistanceFieldUtilities.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "kDOP.h"
#if USE_EMBREE
#include
#include
#else
typedef void* RTCDevice;
typedef void* RTCScene;
typedef void* RTCGeometry;
#endif
#include "SignedDistanceFieldUtilities.generated.h"
class FSourceMeshDataForDerivedDataTask;
class FDistanceFieldVolumeData;
class FMeshBuildDataProvider
{
public:
/** Initialization constructor. */
FMeshBuildDataProvider(
const TkDOPTree& InkDopTree) :
kDopTree(InkDopTree)
{}
// kDOP data provider interface.
FORCEINLINE const TkDOPTree& GetkDOPTree(void) const
{
return kDopTree;
}
FORCEINLINE const FMatrix& GetLocalToWorld(void) const
{
return FMatrix::Identity;
}
FORCEINLINE const FMatrix& GetWorldToLocal(void) const
{
return FMatrix::Identity;
}
FORCEINLINE FMatrix GetLocalToWorldTransposeAdjoint(void) const
{
return FMatrix::Identity;
}
FORCEINLINE float GetDeterminant(void) const
{
return 1.0f;
}
private:
const TkDOPTree& kDopTree;
};
struct FEmbreeTriangleDesc
{
int16 ElementIndex;
bool IsTwoSided() const
{
// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
return ElementIndex == 1;
}
};
// Mapping between Embree Geometry Id and engine Mesh/LOD Id
struct FEmbreeGeometry
{
TArray IndexArray;
TArray VertexArray;
TArray TriangleDescs; // The material ID of each triangle.
RTCGeometry InternalGeometry;
};
class FEmbreeScene
{
public:
bool bUseEmbree = false;
int32 NumIndices = 0;
bool bMostlyTwoSided = false;
// Embree
RTCDevice EmbreeDevice = nullptr;
RTCScene EmbreeScene = nullptr;
FEmbreeGeometry Geometry;
// DOP tree fallback
TkDOPTree kDopTree;
};
#if USE_EMBREE
struct FEmbreeRay : public RTCRayHit
{
FEmbreeRay() :
ElementIndex(-1)
{
hit.u = hit.v = 0;
ray.time = 0;
ray.mask = 0xFFFFFFFF;
hit.geomID = RTC_INVALID_GEOMETRY_ID;
hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
hit.primID = RTC_INVALID_GEOMETRY_ID;
}
FVector3f GetHitNormal() const
{
return FVector3f(-hit.Ng_x, -hit.Ng_y, -hit.Ng_z).GetSafeNormal();
}
bool IsHitTwoSided() const
{
// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
return ElementIndex == 1;
}
// Additional Outputs.
int32 ElementIndex; // Material Index
};
struct FEmbreeIntersectionContext : public RTCIntersectContext
{
FEmbreeIntersectionContext() :
ElementIndex(-1)
{}
bool IsHitTwoSided() const
{
// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
return ElementIndex == 1;
}
// Hit against this primitive will be ignored
int32 SkipPrimId = RTC_INVALID_GEOMETRY_ID;
// Additional Outputs.
int32 ElementIndex; // Material Index
};
#endif
/**
*
*/
UCLASS()
class DISTANCEFIELDTEST_API USignedDistanceFieldUtilities : public UObject
{
GENERATED_BODY()
public:
USignedDistanceFieldUtilities();
class FSignedDistanceFieldBuildMaterialData
{
public:
EBlendMode BlendMode = BLEND_Opaque;
bool bTwoSided = false;
bool bAffectDistanceFieldLighting = true;
};
/** Generates unit length, stratified and uniformly distributed direction samples in a hemisphere. */
void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray& Samples);
/**
* [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
*/
FMatrix44f GetTangentBasisFrisvad(FVector3f TangentZ);
void SetupEmbreeScene(FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
const TArray& MaterialBlendModes,
bool bGenerateAsIfTwoSided,
FEmbreeScene& EmbreeScene);
void DeleteEmbreeScene(FEmbreeScene& EmbreeScene);
void GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
const TArray& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
FDistanceFieldVolumeData& OutData);
bool GenerateSDF(UStaticMesh* StaticMesh);
};
SignedDistanceFieldUtilities.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "SignedDistanceFieldUtilities.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/StaticMeshActor.h"
#include "DistanceFieldAtlas.h"
#include "MeshCardRepresentation.h"
#include "ObjectCacheContext.h"
static FVector3f UniformSampleHemisphere(FVector2D Uniforms)
{
Uniforms = Uniforms * 2.0f - 1.0f;
if (Uniforms == FVector2D::ZeroVector)
{
return FVector3f::ZeroVector;
}
float R;
float Theta;
if (FMath::Abs(Uniforms.X) > FMath::Abs(Uniforms.Y))
{
R = Uniforms.X;
Theta = (float)PI / 4 * (Uniforms.Y / Uniforms.X);
}
else
{
R = Uniforms.Y;
Theta = (float)PI / 2 - (float)PI / 4 * (Uniforms.X / Uniforms.Y);
}
// concentric disk sample
const float U = R * FMath::Cos(Theta);
const float V = R * FMath::Sin(Theta);
const float R2 = R * R;
// map to hemisphere [P. Shirley, Kenneth Chiu; 1997; A Low Distortion Map Between Disk and Square]
return FVector3f(U * FMath::Sqrt(2 - R2), V * FMath::Sqrt(2 - R2), 1.0f - R2);
}
#if USE_EMBREE
void EmbreeFilterFunc(const struct RTCFilterFunctionNArguments* args)
{
FEmbreeGeometry* EmbreeGeometry = (FEmbreeGeometry*)args->geometryUserPtr;
FEmbreeTriangleDesc Desc = EmbreeGeometry->TriangleDescs[RTCHitN_primID(args->hit, 1, 0)];
FEmbreeIntersectionContext& IntersectionContext = *static_cast(args->context);
IntersectionContext.ElementIndex = Desc.ElementIndex;
const RTCHit& EmbreeHit = *(RTCHit*)args->hit;
if (IntersectionContext.SkipPrimId != RTC_INVALID_GEOMETRY_ID && IntersectionContext.SkipPrimId == EmbreeHit.primID)
{
// Ignore hit in order to continue tracing
args->valid[0] = 0;
}
}
void EmbreeErrorFunc(void* userPtr, RTCError code, const char* str)
{
FString ErrorString;
TArray& ErrorStringArray = ErrorString.GetCharArray();
ErrorStringArray.Empty();
int32 StrLen = FCStringAnsi::Strlen(str);
int32 Length = FUTF8ToTCHAR_Convert::ConvertedLength(str, StrLen);
ErrorStringArray.AddUninitialized(Length + 1); // +1 for the null terminator
FUTF8ToTCHAR_Convert::Convert(ErrorStringArray.GetData(), ErrorStringArray.Num(), reinterpret_cast(str), StrLen);
ErrorStringArray[Length] = TEXT('\0');
UE_LOG(LogTemp, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code);
}
#endif
USignedDistanceFieldUtilities::USignedDistanceFieldUtilities()
{
}
void USignedDistanceFieldUtilities::GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray& Samples)
{
const int32 NumSamplesDim = FMath::TruncToInt(FMath::Sqrt((float)NumSamples));
Samples.Empty(NumSamplesDim * NumSamplesDim);
for (int32 IndexX = 0; IndexX < NumSamplesDim; IndexX++)
{
for (int32 IndexY = 0; IndexY < NumSamplesDim; IndexY++)
{
const float U1 = RandomStream.GetFraction();
const float U2 = RandomStream.GetFraction();
const float Fraction1 = (IndexX + U1) / (float)NumSamplesDim;
const float Fraction2 = (IndexY + U2) / (float)NumSamplesDim;
Samples.Add(UniformSampleHemisphere(FVector2D(Fraction1, Fraction2)));
}
}
}
FMatrix44f USignedDistanceFieldUtilities::GetTangentBasisFrisvad(FVector3f TangentZ)
{
FVector3f TangentX;
FVector3f TangentY;
if (TangentZ.Z < -0.9999999f)
{
TangentX = FVector3f(0, -1, 0);
TangentY = FVector3f(-1, 0, 0);
}
else
{
float A = 1.0f / (1.0f + TangentZ.Z);
float B = -TangentZ.X * TangentZ.Y * A;
TangentX = FVector3f(1.0f - TangentZ.X * TangentZ.X * A, B, -TangentZ.X);
TangentY = FVector3f(B, 1.0f - TangentZ.Y * TangentZ.Y * A, -TangentZ.Y);
}
FMatrix44f LocalBasis;
LocalBasis.SetIdentity();
LocalBasis.SetAxis(0, TangentX);
LocalBasis.SetAxis(1, TangentY);
LocalBasis.SetAxis(2, TangentZ);
return LocalBasis;
}
void USignedDistanceFieldUtilities::SetupEmbreeScene(FString MeshName, const FSourceMeshDataForDerivedDataTask& SourceMeshData, const FStaticMeshLODResources& LODModel, const TArray& MaterialBlendModes, bool bGenerateAsIfTwoSided, FEmbreeScene& EmbreeScene)
{
const uint32 NumIndices = SourceMeshData.IsValid() ? SourceMeshData.GetNumIndices() : LODModel.IndexBuffer.GetNumIndices();
const int32 NumTriangles = NumIndices / 3;
const uint32 NumVertices = SourceMeshData.IsValid() ? SourceMeshData.GetNumVertices() : LODModel.VertexBuffers.PositionVertexBuffer.GetNumVertices();
EmbreeScene.NumIndices = NumTriangles;
TArray > BuildTriangles;
#if USE_EMBREE
EmbreeScene.bUseEmbree = true;
if (EmbreeScene.bUseEmbree)
{
EmbreeScene.EmbreeDevice = rtcNewDevice(nullptr);
rtcSetDeviceErrorFunction(EmbreeScene.EmbreeDevice, EmbreeErrorFunc, nullptr);
RTCError ReturnErrorNewDevice = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
if (ReturnErrorNewDevice != RTC_ERROR_NONE)
{
UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewDevice failed. Code: %d"), *MeshName, (int32)ReturnErrorNewDevice);
return;
}
EmbreeScene.EmbreeScene = rtcNewScene(EmbreeScene.EmbreeDevice);
rtcSetSceneFlags(EmbreeScene.EmbreeScene, RTC_SCENE_FLAG_NONE);
RTCError ReturnErrorNewScene = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
if (ReturnErrorNewScene != RTC_ERROR_NONE)
{
UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewScene failed. Code: %d"), *MeshName, (int32)ReturnErrorNewScene);
rtcReleaseDevice(EmbreeScene.EmbreeDevice);
return;
}
}
#endif
TArray FilteredTriangles;
FilteredTriangles.Empty(NumTriangles);
if (SourceMeshData.IsValid())
{
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
const uint32 I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
const uint32 I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
const uint32 I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
const FVector3f V0 = SourceMeshData.VertexPositions[I0];
const FVector3f V1 = SourceMeshData.VertexPositions[I1];
const FVector3f V2 = SourceMeshData.VertexPositions[I2];
const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
if (!bDegenerateTriangle)
{
FilteredTriangles.Add(TriangleIndex);
}
}
}
else
{
for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
{
const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
const uint32 I0 = Indices[TriangleIndex * 3 + 0];
const uint32 I1 = Indices[TriangleIndex * 3 + 1];
const uint32 I2 = Indices[TriangleIndex * 3 + 2];
const FVector3f V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
const FVector3f V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
const FVector3f V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
if (!bDegenerateTriangle)
{
bool bTriangleIsOpaqueOrMasked = false;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
{
if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
{
bTriangleIsOpaqueOrMasked = !IsTranslucentBlendMode(MaterialBlendModes[Section.MaterialIndex].BlendMode) && MaterialBlendModes[Section.MaterialIndex].bAffectDistanceFieldLighting;
}
break;
}
}
if (bTriangleIsOpaqueOrMasked)
{
FilteredTriangles.Add(TriangleIndex);
}
}
}
}
const int32 NumBufferVerts = 1; // Reserve extra space at the end of the array, as embree has an internal bug where they read and discard 4 bytes off the end of the array
EmbreeScene.Geometry.VertexArray.Empty(NumVertices + NumBufferVerts);
EmbreeScene.Geometry.VertexArray.AddUninitialized(NumVertices + NumBufferVerts);
const int32 NumFilteredIndices = FilteredTriangles.Num() * 3;
EmbreeScene.Geometry.IndexArray.Empty(NumFilteredIndices);
EmbreeScene.Geometry.IndexArray.AddUninitialized(NumFilteredIndices);
FVector3f* EmbreeVertices = EmbreeScene.Geometry.VertexArray.GetData();
uint32* EmbreeIndices = EmbreeScene.Geometry.IndexArray.GetData();
EmbreeScene.Geometry.TriangleDescs.Empty(FilteredTriangles.Num());
for (int32 FilteredTriangleIndex = 0; FilteredTriangleIndex < FilteredTriangles.Num(); FilteredTriangleIndex++)
{
uint32 I0, I1, I2;
FVector3f V0, V1, V2;
const int32 TriangleIndex = FilteredTriangles[FilteredTriangleIndex];
if (SourceMeshData.IsValid())
{
I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
V0 = SourceMeshData.VertexPositions[I0];
V1 = SourceMeshData.VertexPositions[I1];
V2 = SourceMeshData.VertexPositions[I2];
}
else
{
const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
I0 = Indices[TriangleIndex * 3 + 0];
I1 = Indices[TriangleIndex * 3 + 1];
I2 = Indices[TriangleIndex * 3 + 2];
V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
}
bool bTriangleIsTwoSided = false;
for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
{
const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
{
if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
{
bTriangleIsTwoSided = MaterialBlendModes[Section.MaterialIndex].bTwoSided;
}
break;
}
}
if (EmbreeScene.bUseEmbree)
{
EmbreeIndices[FilteredTriangleIndex * 3 + 0] = I0;
EmbreeIndices[FilteredTriangleIndex * 3 + 1] = I1;
EmbreeIndices[FilteredTriangleIndex * 3 + 2] = I2;
EmbreeVertices[I0] = V0;
EmbreeVertices[I1] = V1;
EmbreeVertices[I2] = V2;
FEmbreeTriangleDesc Desc;
// Store bGenerateAsIfTwoSided in material index
Desc.ElementIndex = bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0;
EmbreeScene.Geometry.TriangleDescs.Add(Desc);
}
else
{
BuildTriangles.Add(FkDOPBuildCollisionTriangle(
// Store bGenerateAsIfTwoSided in material index
bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0,
FVector(V0),
FVector(V1),
FVector(V2)));
}
}
#if USE_EMBREE
if (EmbreeScene.bUseEmbree)
{
RTCGeometry Geometry = rtcNewGeometry(EmbreeScene.EmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE);
EmbreeScene.Geometry.InternalGeometry = Geometry;
rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, EmbreeVertices, 0, sizeof(FVector3f), NumVertices);
rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, EmbreeIndices, 0, sizeof(uint32) * 3, FilteredTriangles.Num());
rtcSetGeometryUserData(Geometry, &EmbreeScene.Geometry);
rtcSetGeometryIntersectFilterFunction(Geometry, EmbreeFilterFunc);
rtcCommitGeometry(Geometry);
rtcAttachGeometry(EmbreeScene.EmbreeScene, Geometry);
rtcReleaseGeometry(Geometry);
rtcCommitScene(EmbreeScene.EmbreeScene);
RTCError ReturnError = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
if (ReturnError != RTC_ERROR_NONE)
{
UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcCommitScene failed. Code: %d"), *MeshName, (int32)ReturnError);
return;
}
}
else
#endif
{
EmbreeScene.kDopTree.Build(BuildTriangles);
}
// 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;
}
}
}
EmbreeScene.bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
}
}
void USignedDistanceFieldUtilities::DeleteEmbreeScene(FEmbreeScene& EmbreeScene)
{
#if USE_EMBREE
if (EmbreeScene.bUseEmbree)
{
rtcReleaseScene(EmbreeScene.EmbreeScene);
rtcReleaseDevice(EmbreeScene.EmbreeDevice);
}
#endif
}
#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 = (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* 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* SampleDirections;
float LocalSpaceTraceDistance;
FBox VolumeBounds;
float LocalToVolumeScale;
FVector2D DistanceFieldToVolumeScaleBias;
FIntVector BrickCoordinate;
FIntVector IndirectionSize;
bool bUsePointQuery;
// Output
uint8 BrickMaxDistance;
uint8 BrickMinDistance;
TArray 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 = (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;
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 = (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(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
DistanceFieldVolume[Index] = QuantizedDistance;
BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
}
}
}
}
void USignedDistanceFieldUtilities::GenerateSignedDistanceFieldVolumeData(
FString MeshName,
const FSourceMeshDataForDerivedDataTask& SourceMeshData,
const FStaticMeshLODResources& LODModel,
const TArray& MaterialBlendModes,
const FBoxSphereBounds& Bounds,
float DistanceFieldResolutionScale,
bool bGenerateAsIfTwoSided,
FDistanceFieldVolumeData& OutData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(GenerateSignedDistanceFieldVolumeData);
if (DistanceFieldResolutionScale > 0)
{
const double StartTime = FPlatformTime::Seconds();
FEmbreeScene EmbreeScene;
SetupEmbreeScene(MeshName,
SourceMeshData,
LODModel,
MaterialBlendModes,
bGenerateAsIfTwoSided,
EmbreeScene);
check(EmbreeScene.bUseEmbree);
// 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 SampleDirections;
{
const int32 NumVoxelDistanceSamples = bUsePointQuery ? 49 : 576;
FRandomStream RandomStream(0);
GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
TArray OtherHemisphereSamples;
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(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 (EmbreeScene.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 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 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(
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 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 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 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;
}
DeleteEmbreeScene(EmbreeScene);
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);
const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);
if (BuildTime > 1.0f)
{
UE_LOG(LogTemp, 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);
}
}
}
bool USignedDistanceFieldUtilities::GenerateSDF(UStaticMesh* StaticMesh)
{
if (!StaticMesh->IsValidLowLevel())
return false;
const TArray& StaticMaterials = StaticMesh->GetStaticMaterials();
TArray BuildMaterialData;
BuildMaterialData.SetNum(StaticMaterials.Num());
FMeshSectionInfoMap& SectionInfoMap = StaticMesh->GetSectionInfoMap();
const uint32 LODIndex = 0;
for (int32 SectionIndex = 0; SectionIndex < SectionInfoMap.GetSectionNumber(LODIndex); SectionIndex++)
{
const FMeshSectionInfo& Section = SectionInfoMap.Get(LODIndex, SectionIndex);
if (!BuildMaterialData.IsValidIndex(Section.MaterialIndex))
{
continue;
}
USignedDistanceFieldUtilities::FSignedDistanceFieldBuildMaterialData& MaterialData = BuildMaterialData[Section.MaterialIndex];
MaterialData.bAffectDistanceFieldLighting = Section.bAffectDistanceFieldLighting;
UMaterialInterface* MaterialInterface = StaticMaterials[Section.MaterialIndex].MaterialInterface;
if (MaterialInterface)
{
MaterialData.BlendMode = MaterialInterface->GetBlendMode();
MaterialData.bTwoSided = MaterialInterface->IsTwoSided();
}
}
//FString DistanceFieldKey = BuildDistanceFieldDerivedDataKey(InStaticMeshDerivedDataKey);
//for (int32 MaterialIndex = 0; MaterialIndex < Mesh->GetStaticMaterials().Num(); MaterialIndex++)
//{
// DistanceFieldKey += FString::Printf(TEXT("_M%u_%u_%u"),
// (uint32)BuildMaterialData[MaterialIndex].BlendMode,
// BuildMaterialData[MaterialIndex].bTwoSided ? 1 : 0,
// BuildMaterialData[MaterialIndex].bAffectDistanceFieldLighting ? 1 : 0);
//}
FString MeshName = StaticMesh->GetName();
const FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
UStaticMesh* GenerateSource = BuildSettings.DistanceFieldReplacementMesh ? ToRawPtr(BuildSettings.DistanceFieldReplacementMesh) : StaticMesh;
float DistanceFieldResolutionScale = BuildSettings.DistanceFieldResolutionScale;
bool bGenerateDistanceFieldAsIfTwoSided = BuildSettings.bGenerateDistanceFieldAsIfTwoSided;
FDistanceFieldVolumeData* GeneratedVolumeData = new FDistanceFieldVolumeData();
FSourceMeshDataForDerivedDataTask SourceMeshData{};
if (GenerateSource->GetRenderData())
{
const FStaticMeshLODResources& LODModel = GenerateSource->GetRenderData()->LODResources[0];
//USignedDistanceFieldUtilities* MyClass = NewObject();
/*MyClass->*/GenerateSignedDistanceFieldVolumeData(
MeshName,
SourceMeshData,
LODModel,
MoveTemp(BuildMaterialData),
GenerateSource->GetRenderData()->Bounds,
DistanceFieldResolutionScale,
bGenerateDistanceFieldAsIfTwoSided,
*GeneratedVolumeData
);
// Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
//if (Task->StaticMesh)
{
FObjectCacheContextScope ObjectCacheScope;
check(!StaticMesh->IsCompiling());
GeneratedVolumeData->bAsyncBuilding = false;
FStaticMeshRenderData* RenderData = StaticMesh->GetRenderData();
FDistanceFieldVolumeData* OldVolumeData = RenderData->LODResources[0].DistanceFieldData;
// Assign the new volume data, this is safe because the render thread makes a copy of the pointer at scene proxy creation time.
RenderData->LODResources[0].DistanceFieldData = GeneratedVolumeData;
// Renderstates are not initialized between UStaticMesh::PreEditChange() and UStaticMesh::PostEditChange()
if (RenderData->IsInitialized())
{
for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(StaticMesh))
{
if (Component->IsRegistered() && Component->IsRenderStateCreated())
{
Component->MarkRenderStateDirty();
}
}
}
if (OldVolumeData)
{
// Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
BeginCleanup(OldVolumeData);
}
// Need also to update platform render data if it's being cached
FStaticMeshRenderData* PlatformRenderData = RenderData->NextCachedRenderData.Get();
while (PlatformRenderData)
{
if (PlatformRenderData->LODResources[0].DistanceFieldData)
{
*PlatformRenderData->LODResources[0].DistanceFieldData = *GeneratedVolumeData;
// The old bulk data assignment operator doesn't copy over flags
PlatformRenderData->LODResources[0].DistanceFieldData->StreamableMips.ResetBulkDataFlags(GeneratedVolumeData->StreamableMips.GetBulkDataFlags());
}
PlatformRenderData = PlatformRenderData->NextCachedRenderData.Get();
}
//{
// TArray DerivedData;
// // Save built distance field volume to DDC
// FMemoryWriter Ar(DerivedData, /*bIsPersistent=*/ true);
// StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData->Serialize(Ar, Task->StaticMesh);
// GetDerivedDataCacheRef().Put(*Task->DDCKey, DerivedData, Task->StaticMesh->GetPathName());
// COOK_STAT(Timer.AddMiss(DerivedData.Num()));
//}
//BeginCacheMeshCardRepresentation(
// Task->TargetPlatform,
// Task->StaticMesh,
// Task->StaticMesh->GetPlatformStaticMeshRenderData(Task->StaticMesh, Task->TargetPlatform),
// Task->DDCKey,
// &Task->SourceMeshData);
}
return true;
}
return false;
}
#else
//
//void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
// FString MeshName,
// const FSourceMeshDataForDerivedDataTask& SourceMeshData,
// const FStaticMeshLODResources& LODModel,
// class FQueuedThreadPool& ThreadPool,
// const TArray& MaterialBlendModes,
// const FBoxSphereBounds& Bounds,
// float DistanceFieldResolutionScale,
// bool bGenerateAsIfTwoSided,
// FDistanceFieldVolumeData& OutData)
//{
// if (DistanceFieldResolutionScale > 0)
// {
// UE_LOG(LogTemp, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
// }
//}
#endif // PLATFORM_ENABLE_VECTORINTRINSICS
剖析虚幻渲染体系(06)- UE5特辑Part 2(Lumen和其它) - 0向往0 - 博客园 (cnblogs.com)
UE5 Lumen GI 实现分析 - 知乎 (zhihu.com)
游戏引擎随笔 0x29:UE5 Lumen 源码解析(一)原理篇 - 知乎 (zhihu.com)
UE5渲染--距离场简析 - 知乎 (zhihu.com)
距离场的生成与使用 - 知乎 (zhihu.com)