UE5 运行时生成距离场数据

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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/161266.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【扩散模型从原理到实战】Chapter2 Hugging Face简介

文章目录 Hugging Face的核心功能介绍Hugging Face开源库Hugging Face开源库Gradio工具介绍参考资料 Hugging Face是机器学习从业者协作和交流的平台&#xff0c;成立于2016年&#xff0c;在纽约和巴黎设有办事处&#xff0c;团队成员来自世界各地&#xff0c;远程办公。 致力于…

封装一个Element-ui生成一个可行内编辑的表格(vue2项目)

这个封装的是一个供整个项目使用的表格,可多次复用.放在一个全局使用的公共组件文件下. 大致功能介绍,封装自定义指令,点击获得焦点,显示输入框,失去焦点显示文本内容,类型是字典决定类型,图片可以显示图片名还是上传图片 子组件 <script> export default {props: {//生…

SpringCloud ---day1

认识微服务 单体架构 微服务架构 方便进行打包上线,版本的更新上线,降低项目之间的耦合度,减少代码量巨大导致的编译/打包时间长的问题 SpringCloud简介 项目熟悉 业务流程 项目模块划分 服务拆分原则 什么时候拆: 对于一个初创的项目&#xff0c;首先要做的是验证项目的可…

el-table :span-method合并单元格

<el-table :data"tableData" :span-method"objectSpanMethod" border style"width: 100%" :height"300"><el-table-column type"index" label"序号" width"50"></el-table-column>&l…

ajax同步与异步,json-serve的安装与使用,node.js的下载

20.ajax json 轻量级的数据格式做配置文件网络传输 xml 重量级的数据格式 可扩展标记语言做配置文件网络传输 现在目前主流就是大量采用json做网络传输数据格式 1.ajax的概念: 与服务器进行’通信’的一种技术,能够实现异步的刷新页面 **同步:**按照顺序一步步的执行,容易造…

算法通过村第十四关-堆|黄金笔记|中位数

文章目录 前言数据流中的中位数的问题总结 前言 提示&#xff1a;我独自度过了太多的时光&#xff0c;沉默已成一种习惯。 帕瑞尔马卡姆《夜航西飞》 这个是一个比较难的题目&#xff0c;要不尝试一下看看。 数据流中的中位数的问题 参考题目地址&#xff1a;295. 数据流的中位…

MyBatisPlus-02

一 查询条件的三种 1.按条件查询 //方式一&#xff1a;按条件查询QueryWrapper qw new QueryWrapper();qw.lt("age",18);List<User> userList userDao.selectList(qw);System.out.println(userList); 2.lambda格式按条件查询 //方式二&#xff1a;lambda格…

基于EtherCAT的机器人多轴同步运动控制

随着工业自动化的发展&#xff0c;机器人在生产线上的应用越来越广泛。为了实现高效、精确的运动控制&#xff0c;机器人的多轴运动必须能够实现同步操作&#xff0c;它能够提高机器人的运动精度和稳定性&#xff0c;实现更高效的生产线操作。同时&#xff0c;它也为机器人的协…

WebGPU入门一

1 WebGPU学习开发环境配置 WebGPU的环境配置比较简单&#xff0c;不需要vite或webpack配置一个复杂的开发环境&#xff0c;直接使用.html文件即可。 1.1 支持WebGPU的浏览器 Chrome 113 beta测试版开始默认支持WebGPU。 1.2 index.html文件 创建index.html文件&#xff0c…

C++进阶篇1---继承

一、继承的概念和定义 1.1概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称为派生类。继承呈现了面向对象程序设计的层次结构&#xff…

ExoPlayer架构详解与源码分析(5)——MediaSource

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

掌握Python爬虫实现网站关键词扩展提升曝光率

目录 一、关键词优化的重要性 二、关键词优化的基本方法 1、选择与网站内容相关的关键词 2、控制关键词的密度和分布 3、关键词的层次布局 三、Python爬虫实现网站关键词扩展 1、确定目标网站 2、分析目标网站的HTML结构 3、编写Python爬虫代码 4、分析爬取到的关键词…

【算法练习Day22】 组合总和 III电话号码的字母组合

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 组合总和 III剪枝 电话号码…

深入理解React中的useEffect钩子函数

引言&#xff1a; React是一种流行的JavaScript库&#xff0c;它通过组件化和声明式编程的方式简化了前端开发。在React中&#xff0c;一个核心概念是组件的生命周期&#xff0c;其中包含了许多钩子函数&#xff0c;用于管理组件的不同阶段。其中之一就是useEffect钩子函数&…

LuaJit交叉编译移植到ARM Linux

简述 Lua与LuaJit的主要区别在于LuaJIT是基于JIT&#xff08;Just-In-Time&#xff09;技术开发的&#xff0c;可以实现动态编译和执行代码&#xff0c;从而提高了程序的运行效率。而Lua是基于解释器技术开发的&#xff0c;不能像LuaJIT那样进行代码的即时编译和执行。因此&…

系统架构与Tomcat的安装和配置

2023.10.16 今天是学习javaweb的第一天&#xff0c;主要学习了系统架构的相关知识和原理&#xff0c;下载了web服务器软件&#xff1a;Tomcat&#xff0c;并对其进行了配置。 系统架构 包括&#xff1a;C/S架构 和 B/S架构。 C/S架构&#xff1a; Client / Server&#xff0…

离散数学 学习 之 递推方程和生成函数

递推方程 注意这里的特征根一定不是相等 特解的话一般要去设出基本的形式 这是0 次多项式 生成函数

【JAVA】集合与背后的逻辑框架,包装类,List,Map,Set,静态内部类

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 collectionCollection创建collection使用泛型collection方法 Map 接口Map的存储结构HashMap和Tr…

lenovo联想笔记本ThinkPad系列T15p或P15v Gen3(21DA,21DB,21D8,21D9)原厂Win11系统镜像

下载链接&#xff1a;https://pan.baidu.com/s/1V4UXFhYZUNy2ZQ8u4x1AFg?pwdqz0s 系统自带指纹驱动、人脸识别驱动、显卡、声卡等所有驱动、出厂主题壁纸、Office办公软件、Lenovo联想电脑管家等预装程序 所需要工具&#xff1a;32G或以上的U盘 文件格式&#xff1a;ISO …

一篇文章带你搞定所有二叉树题型的递归思维(思路超详细)

文章目录 &#x1f380;前言&#xff1a;&#x1f3c5;先在开头总结一下&#xff0c;二叉树解题的思维模式分两类&#xff1a;&#x1f387;先解释一下“前序位置”&#xff0c;“后序位置”的意思&#x1f3e8;举一个简单的例子&#xff1a; &#x1fa80;下面通过两道例题&am…