1.背景
最近有在运行时加载模型的需求,使用DatasmithRuntimeActor可以实现,但是跟在编辑器里加载的模型对比起来,室内没有Lumen的光照效果。
图1 编辑器下加载模型的效果
图2 运行时下加载模型的效果
然后查看了距离场的数据,发现运行时并没有生成距离场的数据
图3 编辑器下的距离场数据
图4 运行时的距离场数据(并没有看到导入的模型)
2.生成距离场的源码
通过跟踪源码发现,找到编辑器模式下生成距离场数据的代码
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 tasksFQueuedThreadPoolTaskGraphWrapper 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
}
3.运行时生成距离场数据
参考GenerateSignedDistanceFieldVolumeData函数,将源码中的代码挖到自己的项目中
3.1 添加的模块
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RenderCore", "InputCore", "DatasmithContent" });
AddEngineThirdPartyPrivateStaticDependencies(Target, "Embree3");
3.2 距离场生成的代码
下面代码在编辑器中以game模式启动可以在运行时生成距离场的数据,但是需要打包的话,还需要修改源码,因为其中调用的很多函数是限定在WITH_EDITOR宏中的,需要将其脱离限定宏。
void UMainWidget::ButtonSDFClicked()
{TArray<AActor*> 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<UStaticMesh*> StaticMeshSet;int32 Index = 0;for (AActor* OutActor : OutMeshActor){int tagNum = OutActor->Tags.Num();if (tagNum > 0){UDatasmithAssetUserData* DatasmithAssetUserData = OutActor->GetRootComponent()->GetAssetUserData<UDatasmithAssetUserData>();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<AStaticMeshActor>(OutActor)){auto component = StaticMeshActor->GetStaticMeshComponent();component->UnregisterComponent();component->RegisterComponent();StaticMesh = component->GetStaticMesh();if (StaticMesh->IsValidLowLevel()){StaticMeshSet.Emplace(StaticMesh);}}}}}}static int i;USignedDistanceFieldUtilities* MyClass = NewObject<USignedDistanceFieldUtilities>();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 <embree3/rtcore.h>
#include <embree3/rtcore_ray.h>
#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<const FMeshBuildDataProvider, uint32>& InkDopTree) :kDopTree(InkDopTree){}// kDOP data provider interface.FORCEINLINE const TkDOPTree<const FMeshBuildDataProvider, uint32>& 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<const FMeshBuildDataProvider, uint32>& kDopTree;
};struct FEmbreeTriangleDesc
{int16 ElementIndex;bool IsTwoSided() const{// MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sidedreturn ElementIndex == 1;}
};// Mapping between Embree Geometry Id and engine Mesh/LOD Id
struct FEmbreeGeometry
{TArray<uint32> IndexArray;TArray<FVector3f> VertexArray;TArray<FEmbreeTriangleDesc> TriangleDescs; // The material ID of each triangle.RTCGeometry InternalGeometry;
};class FEmbreeScene
{
public:bool bUseEmbree = false;int32 NumIndices = 0;bool bMostlyTwoSided = false;// EmbreeRTCDevice EmbreeDevice = nullptr;RTCScene EmbreeScene = nullptr;FEmbreeGeometry Geometry;// DOP tree fallbackTkDOPTree<const FMeshBuildDataProvider, uint32> 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-sidedreturn 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-sidedreturn ElementIndex == 1;}// Hit against this primitive will be ignoredint32 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<FVector3f>& 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<FSignedDistanceFieldBuildMaterialData>& MaterialBlendModes,bool bGenerateAsIfTwoSided,FEmbreeScene& EmbreeScene);void DeleteEmbreeScene(FEmbreeScene& EmbreeScene);void GenerateSignedDistanceFieldVolumeData(FString MeshName,const FSourceMeshDataForDerivedDataTask& SourceMeshData,const FStaticMeshLODResources& LODModel,const TArray<FSignedDistanceFieldBuildMaterialData>& 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 sampleconst 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<FEmbreeIntersectionContext*>(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 tracingargs->valid[0] = 0;}
}void EmbreeErrorFunc(void* userPtr, RTCError code, const char* str)
{FString ErrorString;TArray<TCHAR, FString::AllocatorType>& 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 terminatorFUTF8ToTCHAR_Convert::Convert(ErrorStringArray.GetData(), ErrorStringArray.Num(), reinterpret_cast<const ANSICHAR*>(str), StrLen);ErrorStringArray[Length] = TEXT('\0');UE_LOG(LogTemp, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code);
}
#endifUSignedDistanceFieldUtilities::USignedDistanceFieldUtilities()
{
}void USignedDistanceFieldUtilities::GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray<FVector3f>& 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<FSignedDistanceFieldBuildMaterialData>& 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<FkDOPBuildCollisionTriangle<uint32> > BuildTriangles;#if USE_EMBREEEmbreeScene.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;}}
#endifTArray<int32> 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 arrayEmbreeScene.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 indexDesc.ElementIndex = bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0;EmbreeScene.Geometry.TriangleDescs.Add(Desc);}else{BuildTriangles.Add(FkDOPBuildCollisionTriangle<uint32>(// Store bGenerateAsIfTwoSided in material indexbGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0,FVector(V0),FVector(V1),FVector(V2)));}}#if USE_EMBREEif (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_EMBREEif (EmbreeScene.bUseEmbree){rtcReleaseScene(EmbreeScene.EmbreeScene);rtcReleaseDevice(EmbreeScene.EmbreeDevice);}
#endif
}#if USE_EMBREEclass 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 shrunkreturn true;}}// Return false to indicate that the query radius hasn't changedreturn 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 inputsconst FEmbreeScene& EmbreeScene;const TArray<FVector3f>* SampleDirections;float LocalSpaceTraceDistance;FBox VolumeBounds;float LocalToVolumeScale;FVector2D DistanceFieldToVolumeScaleBias;FIntVector BrickCoordinate;FIntVector IndirectionSize;bool bUsePointQuery;// Outputuint8 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 = (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 backfacesif (Hit > 0 && HitBack > .25f * SampleDirections->Num()){MinLocalSpaceDistance *= -1;}}// Transform to the tracing shader's Volume spaceconst float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;// Transform to the Distance Field texture's spaceconst 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 USignedDistanceFieldUtilities::GenerateSignedDistanceFieldVolumeData(FString MeshName,const FSourceMeshDataForDerivedDataTask& SourceMeshData,const FStaticMeshLODResources& LODModel,const TArray<FSignedDistanceFieldBuildMaterialData>& 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<FVector3f> SampleDirections;{const int32 NumVoxelDistanceSamples = bUsePointQuery ? 49 : 576;FRandomStream RandomStream(0);GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);TArray<FVector3f> 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 higherconst 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 costif (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 encodingconst 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 filteringconst 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(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 addedconst 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<FStaticMaterial>& StaticMaterials = StaticMesh->GetStaticMaterials();TArray<USignedDistanceFieldUtilities::FSignedDistanceFieldBuildMaterialData> 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<USignedDistanceFieldUtilities>();/*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 safeBeginCleanup(OldVolumeData);}// Need also to update platform render data if it's being cachedFStaticMeshRenderData* 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 flagsPlatformRenderData->LODResources[0].DistanceFieldData->StreamableMips.ResetBulkDataFlags(GeneratedVolumeData->StreamableMips.GetBulkDataFlags());}PlatformRenderData = PlatformRenderData->NextCachedRenderData.Get();}//{// TArray<uint8> 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<FSignedDistanceFieldBuildMaterialData>& 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
4.参考
剖析虚幻渲染体系(06)- UE5特辑Part 2(Lumen和其它) - 0向往0 - 博客园 (cnblogs.com)
UE5 Lumen GI 实现分析 - 知乎 (zhihu.com)
游戏引擎随笔 0x29:UE5 Lumen 源码解析(一)原理篇 - 知乎 (zhihu.com)
UE5渲染--距离场简析 - 知乎 (zhihu.com)
距离场的生成与使用 - 知乎 (zhihu.com)