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.mzph.cn/news/110568.shtml

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

相关文章

华为智选SF5,AITO问界的车怎么样

#华为智选 #赛力斯SF5 #aito问界m5 #aito问界m7 #华为汽车 华为的车&#xff0c;后杠焊两点&#xff0c;拉车的时候&#xff0c;拖车钩断了&#xff0c;后杠拉出来了&#xff0c;这质量可以吗&#xff1f;是否应该全部召回&#xff1f;M5&#xff0c;M7是不是也这样&#xff1f…

蓝桥杯(跳跃 C++)

思路&#xff1a; 1、根据题目很容易知道可以用深度搜索、广度搜索、动态规划的思想解题。 2、这里利用深度搜素&#xff0c;由题目可知&#xff0c;可以往九个方向走。 3、这里的判断边界就是走到终点。 #include<iostream> using namespace std; int max1 0; int …

增加并行度后,发现Flink窗口不会计算的问题。

文章目录 前言一、现象二、结论三、解决 前言 窗口没有关闭计算的问题&#xff0c;一直困扰了很久&#xff0c;经过多次验证&#xff0c;确定了问题的根源。 一、现象 Flink使用了window&#xff0c;同时使用了watermark &#xff0c;并且还设置了较高的并行度。生产是设置了…

微服务负载均衡实践

概述 本文介绍微服务的服务调用和负载均衡&#xff0c;使用spring cloud的loadbalancer及openfeign两种技术来实现。 本文的操作是在微服务的初步使用的基础上进行。 环境说明 jdk1.8 maven3.6.3 mysql8 spring cloud2021.0.8 spring boot2.7.12 idea2022 步骤 改造Eu…

Windows下定时下载Linux服务器的数据库备份文件(pscp+bat脚本+定时任务)

下载传输软件pscp Download PuTTY: latest release (0.79) 创建bat执行脚本 echo 删除旧的备份文件 del D:\db_bk\*.dbecho 下载新的备份文件 D:\Programs\pscp -P 22 -pw youPassword youName192.168.1.1:/home/backup/test.db D:\db_bk\ 设置定时任务 1.使用任务计划程…

Halcon 中查看算子和函数的执行时间

1、在Halcol主窗口的底栏中的第一个图标显示算子或函数的执行时间&#xff0c;如下图&#xff1a; 2、在Halcon的菜单栏中选择【窗口】&#xff0c;在下拉框中选择【打开输出控制台】&#xff0c;进行查看算子或函数的执行时间&#xff0c;如下图&#xff1a;

音视频技术开发周刊 | 315

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 OpenAI科学家最新演讲&#xff1a;GPT-4即将超越拐点&#xff0c;1000倍性能必定涌现&#xff01; GPT-4参数规模扩大1000倍&#xff0c;如何实现&#xff1f;OpenAI科学家…

Spring核心扩展点BeanDefinitionRegistryPostProcessor源码分析

我们知道&#xff0c;只要在一个Java类上加上Component、Service、Controller等注解&#xff0c;就可以被加载到Spring容器中&#xff0c;除了以上方式&#xff0c;加了Bean和Import好像也可以将对象添加到Spring容器中&#xff0c;究竟Spring是如何实现这些功能的呢&#xff1…

Redis 主从复制,哨兵,集群——(1)主从复制篇

目录 1. Redis 主从复制是什么&#xff1f; 2. Redis 主动复制能干嘛&#xff1f; 2.1 读写分离 2.2 容灾恢复 2.3 数据备份 2.4 水平扩展支撑高并发 3. Redis 主从复制配置项 3.1 配从库不配主库 3.2 权限密码配置 3.3 基本操作命令 4. 案例演示 4.1 案例说明 4.…

tika解压遇到压缩炸弹如何继续解压

1.问题 项目中要对10层压缩的zip、7z等文件用tika解压遇到错误&#xff1a;tika zip bomb detected 也就是说tika认为这是个压缩炸弹。 “压缩炸弹”是一个压缩包文件的木马程序&#xff0c;通常只有几百KB&#xff0c;解压后会变成上百MB或者上GB庞然大物。把你本地磁盘占满…

迁移conda环境后,非root用户执行pip命令和jupyter命令报错/bad interpreter: Permission denied

移动conda环境&#xff0c;在移动的环境执行pip和jupyter 报错-bash: /data/home/用户名/anaconda3/envs/llm/bin/pip: /root/anaconda3/envs/llm/bin/python: bad interpreter: Permission denied 报错信息 一、原因 原因是当前的这个data/home/用户名/anaconda3/envs/环境名…

2022年亚太杯APMCM数学建模大赛A题结晶器熔剂熔融结晶过程序列图像特征提取及建模分析求解全过程文档及程序

2022年亚太杯APMCM数学建模大赛 A题 结晶器熔剂熔融结晶过程序列图像特征提取及建模分析 原题再现&#xff1a; 连铸过程中的保护渣使钢水弯液面隔热&#xff0c;防止钢水在连铸过程中再次氧化&#xff0c;控制传热&#xff0c;为铸坯提供润滑&#xff0c;并吸收非金属夹杂物…

37 WEB漏洞-反序列化之PHPJAVA全解(上)

目录 PHP反序列化演示案例&#xff1a;先搞一把PHP反序列化热身题稳住-无类问题-本地在撸一把CTF反序列化小真题压压惊-无类执行-实例最后顶一把网鼎杯2020青龙大真题舒服下-有类魔术方法触发-实例 https://www.cnblogs.com/zhengna/p/15661109.html 代码在线测试平台&#xff…

k8s-20 hpa控制器

hpa可通过metrics-server所提供pod的cpu 或者内存的负载情况&#xff0c;从而动态拉伸控制器的副本数&#xff0c;从而达到后端的自动弹缩 官网&#xff1a;https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/ 上传镜像 压测 po…

IPV6 ND协议--源码解析【根源分析】

ND协议介绍 ND介绍请阅读上一篇文章&#xff1a;IPv6知识 - ND协议【一文通透】11.NDP协议分析与实践_router solicitation报文中不携带source link-layer address-CSDN博客 ND协议定义了5种ICMPv6报文类型&#xff0c;如下表所示&#xff1a; NS/NA报文主要用于地址解析RS/…

【数之道 08】走进“卷积神经网络“,了解图像识别背后的原理

卷积神经网络 CNN模型的架构Cnn 的流程第一步 提取图片特征提取特征的计算规则 第二步 最大池化第三步 扁平化处理第四步 数据条录入全连接隐藏层 b站视频 CNN模型的架构 图片由像素点组成&#xff0c;最终成像效果由背后像素的颜色数值所决定的 有这样的一个66的区域&#x…

Datawhale-新能源时间序列赛事学习笔记(1)

1.赛题描述 在电动汽车充电站运营管理中&#xff0c;准确预测充电站的电量需求对于提高充电站运营服务水平和优化区域电网供给能力非常关键。本次赛题旨在建立站点充电量预测模型&#xff0c;根据充电站的相关信息和历史电量数据&#xff0c;准确预测未来某段时间内充电站的充电…

Java基础20问(6-10)

6.Java接口和抽象类的区别&#xff1f; 不同点 1.接口在Java8之前不能写方法实现逻辑&#xff0c;Java8及以后的版本&#xff0c;可以用default关键字写方法的实现。 2.接口中方法都是public的&#xff0c;public可以省略&#xff0c;而抽象类没有这个限制。 3.接口用inter…

【MATLAB第79期】基于MATLAB的数据抽样合集(sobol、LHS、Halton、正交、随机函数)更新中

【MATLAB第79期】基于MATLAB的数据抽样合集&#xff08;sobol、LHS、Halton、正交、随机函数&#xff09;更新中 一、随机函数 1.指定区间随机生成数据&#xff08;小数&#xff09; [a b]区间随机数生成: Aa(b-a)rand(m,n) m&#xff1a;待生成矩阵A的行数 n: 待生成矩阵A…

物联网AI MicroPython传感器学习 之 AS608指纹识别模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 AS608指纹识别模块是一款高性能的光学指纹识别模块。它采用的是指纹识别芯片公司杭州晟元芯片技术有限公司生产的AS608指纹识别芯片。该芯片内置DSP运算单元&#xff0c;集成了指纹识别算法&am…