• UE5 运行时生成距离场数据


    1.背景

    最近有在运行时加载模型的需求,使用DatasmithRuntimeActor可以实现,但是跟在编辑器里加载的模型对比起来,室内没有Lumen的光照效果。

    图1 编辑器下加载模型的效果

    图2 运行时下加载模型的效果

    然后查看了距离场的数据,发现运行时并没有生成距离场的数据

    图3 编辑器下的距离场数据

    图4 运行时的距离场数据(并没有看到导入的模型)

    2.生成距离场的源码

    通过跟踪源码发现,找到编辑器模式下生成距离场数据的代码

    MeshUtilities->GenerateSignedDistanceFieldVolumeData();

    Editor模式下,添加一个Fbx资源,生成距离场的代码执行的过程如下:

    1. void FDistanceFieldVolumeData::CacheDerivedData()
    2. {
    3. FAsyncDistanceFieldTask* NewTask = new FAsyncDistanceFieldTask;
    4. ... ...
    5. GDistanceFieldAsyncQueue->AddTask(NewTask);
    6. }
    7. void FAsyncDistanceFieldTaskWorker::DoWork()
    8. {
    9. // Put on background thread to avoid interfering with game-thread bound tasks
    10. FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
    11. GDistanceFieldAsyncQueue->Build(&Task, TaskGraphWrapper);
    12. }
    13. void FDistanceFieldAsyncQueue::Build(FAsyncDistanceFieldTask* Task, FQueuedThreadPool& BuildThreadPool)
    14. {
    15. #if WITH_EDITOR
    16. // Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
    17. if (Task->StaticMesh && Task->GenerateSource)
    18. {
    19. const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
    20. MeshUtilities->GenerateSignedDistanceFieldVolumeData();
    21. }
    22. #endif
    23. }

    3.运行时生成距离场数据

    参考GenerateSignedDistanceFieldVolumeData函数,将源码中的代码挖到自己的项目中

    3.1 添加的模块

    1. PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RenderCore", "InputCore", "DatasmithContent" });
    2. AddEngineThirdPartyPrivateStaticDependencies(Target, "Embree3");

    3.2 距离场生成的代码

    下面代码在编辑器中以game模式启动可以在运行时生成距离场的数据,但是需要打包的话,还需要修改源码,因为其中调用的很多函数是限定在WITH_EDITOR宏中的,需要将其脱离限定宏。

    1. void UMainWidget::ButtonSDFClicked()
    2. {
    3. TArray OutMeshActor;
    4. UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), OutMeshActor);
    5. UE_LOG(LogTemp, Log, TEXT("start Generate SDF data"));
    6. GEngine->AddOnScreenDebugMessage(0, 400.0f, FColor::Red, TEXT("start Generate SDF data"));
    7. TSet StaticMeshSet;
    8. int32 Index = 0;
    9. for (AActor* OutActor : OutMeshActor)
    10. {
    11. int tagNum = OutActor->Tags.Num();
    12. if (tagNum > 0)
    13. {
    14. UDatasmithAssetUserData* DatasmithAssetUserData = OutActor->GetRootComponent()->GetAssetUserData();
    15. if (DatasmithAssetUserData)
    16. {
    17. const FString CreationPhaseKey = PHASE_CREATION;
    18. const FString CreationPhase = *DatasmithAssetUserData->MetaData.FindRef(*CreationPhaseKey);
    19. if (CreationPhase == ELECTROMECHANICAL)
    20. {
    21. continue;
    22. }
    23. //FString familyName = *DatasmithAssetUserData->MetaData.FindRef(TEXT("Element*Family"));
    24. //if (familyName == TEXT("基本墙") || familyName == TEXT("天花板") || familyName == TEXT("楼板"))
    25. {
    26. UStaticMesh* StaticMesh = nullptr;
    27. if (AStaticMeshActor* StaticMeshActor = Cast(OutActor))
    28. {
    29. auto component = StaticMeshActor->GetStaticMeshComponent();
    30. component->UnregisterComponent();
    31. component->RegisterComponent();
    32. StaticMesh = component->GetStaticMesh();
    33. if (StaticMesh->IsValidLowLevel())
    34. {
    35. StaticMeshSet.Emplace(StaticMesh);
    36. }
    37. }
    38. }
    39. }
    40. }
    41. }
    42. static int i;
    43. USignedDistanceFieldUtilities* MyClass = NewObject();
    44. for (UStaticMesh* Mesh : StaticMeshSet)
    45. {
    46. MyClass->GenerateSDF(Mesh);
    47. i++;
    48. }
    49. UE_LOG(LogTemp, Log, TEXT("end Generate SDF data"));
    50. UE_LOG(LogTemp, Log, TEXT(" Generate SDF data count : %d "), i);
    51. GEngine->AddOnScreenDebugMessage(0, 400.0f, FColor::Red, TEXT("end Generate SDF data"));
    52. }

    SignedDistanceFieldUtilities.h

    1. // Fill out your copyright notice in the Description page of Project Settings.
    2. #pragma once
    3. #include "CoreMinimal.h"
    4. #include "UObject/NoExportTypes.h"
    5. #include "kDOP.h"
    6. #if USE_EMBREE
    7. #include
    8. #include
    9. #else
    10. typedef void* RTCDevice;
    11. typedef void* RTCScene;
    12. typedef void* RTCGeometry;
    13. #endif
    14. #include "SignedDistanceFieldUtilities.generated.h"
    15. class FSourceMeshDataForDerivedDataTask;
    16. class FDistanceFieldVolumeData;
    17. class FMeshBuildDataProvider
    18. {
    19. public:
    20. /** Initialization constructor. */
    21. FMeshBuildDataProvider(
    22. const TkDOPTree<const FMeshBuildDataProvider, uint32>& InkDopTree) :
    23. kDopTree(InkDopTree)
    24. {}
    25. // kDOP data provider interface.
    26. FORCEINLINE const TkDOPTree<const FMeshBuildDataProvider, uint32>& GetkDOPTree(void) const
    27. {
    28. return kDopTree;
    29. }
    30. FORCEINLINE const FMatrix& GetLocalToWorld(void) const
    31. {
    32. return FMatrix::Identity;
    33. }
    34. FORCEINLINE const FMatrix& GetWorldToLocal(void) const
    35. {
    36. return FMatrix::Identity;
    37. }
    38. FORCEINLINE FMatrix GetLocalToWorldTransposeAdjoint(void) const
    39. {
    40. return FMatrix::Identity;
    41. }
    42. FORCEINLINE float GetDeterminant(void) const
    43. {
    44. return 1.0f;
    45. }
    46. private:
    47. const TkDOPTree<const FMeshBuildDataProvider, uint32>& kDopTree;
    48. };
    49. struct FEmbreeTriangleDesc
    50. {
    51. int16 ElementIndex;
    52. bool IsTwoSided() const
    53. {
    54. // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
    55. return ElementIndex == 1;
    56. }
    57. };
    58. // Mapping between Embree Geometry Id and engine Mesh/LOD Id
    59. struct FEmbreeGeometry
    60. {
    61. TArray IndexArray;
    62. TArray VertexArray;
    63. TArray TriangleDescs; // The material ID of each triangle.
    64. RTCGeometry InternalGeometry;
    65. };
    66. class FEmbreeScene
    67. {
    68. public:
    69. bool bUseEmbree = false;
    70. int32 NumIndices = 0;
    71. bool bMostlyTwoSided = false;
    72. // Embree
    73. RTCDevice EmbreeDevice = nullptr;
    74. RTCScene EmbreeScene = nullptr;
    75. FEmbreeGeometry Geometry;
    76. // DOP tree fallback
    77. TkDOPTree<const FMeshBuildDataProvider, uint32> kDopTree;
    78. };
    79. #if USE_EMBREE
    80. struct FEmbreeRay : public RTCRayHit
    81. {
    82. FEmbreeRay() :
    83. ElementIndex(-1)
    84. {
    85. hit.u = hit.v = 0;
    86. ray.time = 0;
    87. ray.mask = 0xFFFFFFFF;
    88. hit.geomID = RTC_INVALID_GEOMETRY_ID;
    89. hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
    90. hit.primID = RTC_INVALID_GEOMETRY_ID;
    91. }
    92. FVector3f GetHitNormal() const
    93. {
    94. return FVector3f(-hit.Ng_x, -hit.Ng_y, -hit.Ng_z).GetSafeNormal();
    95. }
    96. bool IsHitTwoSided() const
    97. {
    98. // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
    99. return ElementIndex == 1;
    100. }
    101. // Additional Outputs.
    102. int32 ElementIndex; // Material Index
    103. };
    104. struct FEmbreeIntersectionContext : public RTCIntersectContext
    105. {
    106. FEmbreeIntersectionContext() :
    107. ElementIndex(-1)
    108. {}
    109. bool IsHitTwoSided() const
    110. {
    111. // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
    112. return ElementIndex == 1;
    113. }
    114. // Hit against this primitive will be ignored
    115. int32 SkipPrimId = RTC_INVALID_GEOMETRY_ID;
    116. // Additional Outputs.
    117. int32 ElementIndex; // Material Index
    118. };
    119. #endif
    120. /**
    121. *
    122. */
    123. UCLASS()
    124. class DISTANCEFIELDTEST_API USignedDistanceFieldUtilities : public UObject
    125. {
    126. GENERATED_BODY()
    127. public:
    128. USignedDistanceFieldUtilities();
    129. class FSignedDistanceFieldBuildMaterialData
    130. {
    131. public:
    132. EBlendMode BlendMode = BLEND_Opaque;
    133. bool bTwoSided = false;
    134. bool bAffectDistanceFieldLighting = true;
    135. };
    136. /** Generates unit length, stratified and uniformly distributed direction samples in a hemisphere. */
    137. void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray& Samples);
    138. /**
    139. * [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
    140. */
    141. FMatrix44f GetTangentBasisFrisvad(FVector3f TangentZ);
    142. void SetupEmbreeScene(FString MeshName,
    143. const FSourceMeshDataForDerivedDataTask& SourceMeshData,
    144. const FStaticMeshLODResources& LODModel,
    145. const TArray& MaterialBlendModes,
    146. bool bGenerateAsIfTwoSided,
    147. FEmbreeScene& EmbreeScene);
    148. void DeleteEmbreeScene(FEmbreeScene& EmbreeScene);
    149. void GenerateSignedDistanceFieldVolumeData(
    150. FString MeshName,
    151. const FSourceMeshDataForDerivedDataTask& SourceMeshData,
    152. const FStaticMeshLODResources& LODModel,
    153. const TArray& MaterialBlendModes,
    154. const FBoxSphereBounds& Bounds,
    155. float DistanceFieldResolutionScale,
    156. bool bGenerateAsIfTwoSided,
    157. FDistanceFieldVolumeData& OutData);
    158. bool GenerateSDF(UStaticMesh* StaticMesh);
    159. };

    SignedDistanceFieldUtilities.cpp

    1. // Fill out your copyright notice in the Description page of Project Settings.
    2. #include "SignedDistanceFieldUtilities.h"
    3. #include "Kismet/GameplayStatics.h"
    4. #include "Engine/StaticMeshActor.h"
    5. #include "DistanceFieldAtlas.h"
    6. #include "MeshCardRepresentation.h"
    7. #include "ObjectCacheContext.h"
    8. static FVector3f UniformSampleHemisphere(FVector2D Uniforms)
    9. {
    10. Uniforms = Uniforms * 2.0f - 1.0f;
    11. if (Uniforms == FVector2D::ZeroVector)
    12. {
    13. return FVector3f::ZeroVector;
    14. }
    15. float R;
    16. float Theta;
    17. if (FMath::Abs(Uniforms.X) > FMath::Abs(Uniforms.Y))
    18. {
    19. R = Uniforms.X;
    20. Theta = (float)PI / 4 * (Uniforms.Y / Uniforms.X);
    21. }
    22. else
    23. {
    24. R = Uniforms.Y;
    25. Theta = (float)PI / 2 - (float)PI / 4 * (Uniforms.X / Uniforms.Y);
    26. }
    27. // concentric disk sample
    28. const float U = R * FMath::Cos(Theta);
    29. const float V = R * FMath::Sin(Theta);
    30. const float R2 = R * R;
    31. // map to hemisphere [P. Shirley, Kenneth Chiu; 1997; A Low Distortion Map Between Disk and Square]
    32. return FVector3f(U * FMath::Sqrt(2 - R2), V * FMath::Sqrt(2 - R2), 1.0f - R2);
    33. }
    34. #if USE_EMBREE
    35. void EmbreeFilterFunc(const struct RTCFilterFunctionNArguments* args)
    36. {
    37. FEmbreeGeometry* EmbreeGeometry = (FEmbreeGeometry*)args->geometryUserPtr;
    38. FEmbreeTriangleDesc Desc = EmbreeGeometry->TriangleDescs[RTCHitN_primID(args->hit, 1, 0)];
    39. FEmbreeIntersectionContext& IntersectionContext = *static_cast(args->context);
    40. IntersectionContext.ElementIndex = Desc.ElementIndex;
    41. const RTCHit& EmbreeHit = *(RTCHit*)args->hit;
    42. if (IntersectionContext.SkipPrimId != RTC_INVALID_GEOMETRY_ID && IntersectionContext.SkipPrimId == EmbreeHit.primID)
    43. {
    44. // Ignore hit in order to continue tracing
    45. args->valid[0] = 0;
    46. }
    47. }
    48. void EmbreeErrorFunc(void* userPtr, RTCError code, const char* str)
    49. {
    50. FString ErrorString;
    51. TArray& ErrorStringArray = ErrorString.GetCharArray();
    52. ErrorStringArray.Empty();
    53. int32 StrLen = FCStringAnsi::Strlen(str);
    54. int32 Length = FUTF8ToTCHAR_Convert::ConvertedLength(str, StrLen);
    55. ErrorStringArray.AddUninitialized(Length + 1); // +1 for the null terminator
    56. FUTF8ToTCHAR_Convert::Convert(ErrorStringArray.GetData(), ErrorStringArray.Num(), reinterpret_cast<const ANSICHAR*>(str), StrLen);
    57. ErrorStringArray[Length] = TEXT('\0');
    58. UE_LOG(LogTemp, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code);
    59. }
    60. #endif
    61. USignedDistanceFieldUtilities::USignedDistanceFieldUtilities()
    62. {
    63. }
    64. void USignedDistanceFieldUtilities::GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray& Samples)
    65. {
    66. const int32 NumSamplesDim = FMath::TruncToInt(FMath::Sqrt((float)NumSamples));
    67. Samples.Empty(NumSamplesDim * NumSamplesDim);
    68. for (int32 IndexX = 0; IndexX < NumSamplesDim; IndexX++)
    69. {
    70. for (int32 IndexY = 0; IndexY < NumSamplesDim; IndexY++)
    71. {
    72. const float U1 = RandomStream.GetFraction();
    73. const float U2 = RandomStream.GetFraction();
    74. const float Fraction1 = (IndexX + U1) / (float)NumSamplesDim;
    75. const float Fraction2 = (IndexY + U2) / (float)NumSamplesDim;
    76. Samples.Add(UniformSampleHemisphere(FVector2D(Fraction1, Fraction2)));
    77. }
    78. }
    79. }
    80. FMatrix44f USignedDistanceFieldUtilities::GetTangentBasisFrisvad(FVector3f TangentZ)
    81. {
    82. FVector3f TangentX;
    83. FVector3f TangentY;
    84. if (TangentZ.Z < -0.9999999f)
    85. {
    86. TangentX = FVector3f(0, -1, 0);
    87. TangentY = FVector3f(-1, 0, 0);
    88. }
    89. else
    90. {
    91. float A = 1.0f / (1.0f + TangentZ.Z);
    92. float B = -TangentZ.X * TangentZ.Y * A;
    93. TangentX = FVector3f(1.0f - TangentZ.X * TangentZ.X * A, B, -TangentZ.X);
    94. TangentY = FVector3f(B, 1.0f - TangentZ.Y * TangentZ.Y * A, -TangentZ.Y);
    95. }
    96. FMatrix44f LocalBasis;
    97. LocalBasis.SetIdentity();
    98. LocalBasis.SetAxis(0, TangentX);
    99. LocalBasis.SetAxis(1, TangentY);
    100. LocalBasis.SetAxis(2, TangentZ);
    101. return LocalBasis;
    102. }
    103. void USignedDistanceFieldUtilities::SetupEmbreeScene(FString MeshName, const FSourceMeshDataForDerivedDataTask& SourceMeshData, const FStaticMeshLODResources& LODModel, const TArray& MaterialBlendModes, bool bGenerateAsIfTwoSided, FEmbreeScene& EmbreeScene)
    104. {
    105. const uint32 NumIndices = SourceMeshData.IsValid() ? SourceMeshData.GetNumIndices() : LODModel.IndexBuffer.GetNumIndices();
    106. const int32 NumTriangles = NumIndices / 3;
    107. const uint32 NumVertices = SourceMeshData.IsValid() ? SourceMeshData.GetNumVertices() : LODModel.VertexBuffers.PositionVertexBuffer.GetNumVertices();
    108. EmbreeScene.NumIndices = NumTriangles;
    109. TArray > BuildTriangles;
    110. #if USE_EMBREE
    111. EmbreeScene.bUseEmbree = true;
    112. if (EmbreeScene.bUseEmbree)
    113. {
    114. EmbreeScene.EmbreeDevice = rtcNewDevice(nullptr);
    115. rtcSetDeviceErrorFunction(EmbreeScene.EmbreeDevice, EmbreeErrorFunc, nullptr);
    116. RTCError ReturnErrorNewDevice = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
    117. if (ReturnErrorNewDevice != RTC_ERROR_NONE)
    118. {
    119. UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewDevice failed. Code: %d"), *MeshName, (int32)ReturnErrorNewDevice);
    120. return;
    121. }
    122. EmbreeScene.EmbreeScene = rtcNewScene(EmbreeScene.EmbreeDevice);
    123. rtcSetSceneFlags(EmbreeScene.EmbreeScene, RTC_SCENE_FLAG_NONE);
    124. RTCError ReturnErrorNewScene = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
    125. if (ReturnErrorNewScene != RTC_ERROR_NONE)
    126. {
    127. UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewScene failed. Code: %d"), *MeshName, (int32)ReturnErrorNewScene);
    128. rtcReleaseDevice(EmbreeScene.EmbreeDevice);
    129. return;
    130. }
    131. }
    132. #endif
    133. TArray FilteredTriangles;
    134. FilteredTriangles.Empty(NumTriangles);
    135. if (SourceMeshData.IsValid())
    136. {
    137. for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
    138. {
    139. const uint32 I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
    140. const uint32 I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
    141. const uint32 I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
    142. const FVector3f V0 = SourceMeshData.VertexPositions[I0];
    143. const FVector3f V1 = SourceMeshData.VertexPositions[I1];
    144. const FVector3f V2 = SourceMeshData.VertexPositions[I2];
    145. const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
    146. const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
    147. if (!bDegenerateTriangle)
    148. {
    149. FilteredTriangles.Add(TriangleIndex);
    150. }
    151. }
    152. }
    153. else
    154. {
    155. for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
    156. {
    157. const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
    158. const uint32 I0 = Indices[TriangleIndex * 3 + 0];
    159. const uint32 I1 = Indices[TriangleIndex * 3 + 1];
    160. const uint32 I2 = Indices[TriangleIndex * 3 + 2];
    161. const FVector3f V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
    162. const FVector3f V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
    163. const FVector3f V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
    164. const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
    165. const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
    166. if (!bDegenerateTriangle)
    167. {
    168. bool bTriangleIsOpaqueOrMasked = false;
    169. for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
    170. {
    171. const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
    172. if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
    173. {
    174. if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
    175. {
    176. bTriangleIsOpaqueOrMasked = !IsTranslucentBlendMode(MaterialBlendModes[Section.MaterialIndex].BlendMode) && MaterialBlendModes[Section.MaterialIndex].bAffectDistanceFieldLighting;
    177. }
    178. break;
    179. }
    180. }
    181. if (bTriangleIsOpaqueOrMasked)
    182. {
    183. FilteredTriangles.Add(TriangleIndex);
    184. }
    185. }
    186. }
    187. }
    188. const int32 NumBufferVerts = 1; // Reserve extra space at the end of the array, as embree has an internal bug where they read and discard 4 bytes off the end of the array
    189. EmbreeScene.Geometry.VertexArray.Empty(NumVertices + NumBufferVerts);
    190. EmbreeScene.Geometry.VertexArray.AddUninitialized(NumVertices + NumBufferVerts);
    191. const int32 NumFilteredIndices = FilteredTriangles.Num() * 3;
    192. EmbreeScene.Geometry.IndexArray.Empty(NumFilteredIndices);
    193. EmbreeScene.Geometry.IndexArray.AddUninitialized(NumFilteredIndices);
    194. FVector3f* EmbreeVertices = EmbreeScene.Geometry.VertexArray.GetData();
    195. uint32* EmbreeIndices = EmbreeScene.Geometry.IndexArray.GetData();
    196. EmbreeScene.Geometry.TriangleDescs.Empty(FilteredTriangles.Num());
    197. for (int32 FilteredTriangleIndex = 0; FilteredTriangleIndex < FilteredTriangles.Num(); FilteredTriangleIndex++)
    198. {
    199. uint32 I0, I1, I2;
    200. FVector3f V0, V1, V2;
    201. const int32 TriangleIndex = FilteredTriangles[FilteredTriangleIndex];
    202. if (SourceMeshData.IsValid())
    203. {
    204. I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
    205. I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
    206. I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];
    207. V0 = SourceMeshData.VertexPositions[I0];
    208. V1 = SourceMeshData.VertexPositions[I1];
    209. V2 = SourceMeshData.VertexPositions[I2];
    210. }
    211. else
    212. {
    213. const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
    214. I0 = Indices[TriangleIndex * 3 + 0];
    215. I1 = Indices[TriangleIndex * 3 + 1];
    216. I2 = Indices[TriangleIndex * 3 + 2];
    217. V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
    218. V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
    219. V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
    220. }
    221. bool bTriangleIsTwoSided = false;
    222. for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
    223. {
    224. const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
    225. if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
    226. {
    227. if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
    228. {
    229. bTriangleIsTwoSided = MaterialBlendModes[Section.MaterialIndex].bTwoSided;
    230. }
    231. break;
    232. }
    233. }
    234. if (EmbreeScene.bUseEmbree)
    235. {
    236. EmbreeIndices[FilteredTriangleIndex * 3 + 0] = I0;
    237. EmbreeIndices[FilteredTriangleIndex * 3 + 1] = I1;
    238. EmbreeIndices[FilteredTriangleIndex * 3 + 2] = I2;
    239. EmbreeVertices[I0] = V0;
    240. EmbreeVertices[I1] = V1;
    241. EmbreeVertices[I2] = V2;
    242. FEmbreeTriangleDesc Desc;
    243. // Store bGenerateAsIfTwoSided in material index
    244. Desc.ElementIndex = bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0;
    245. EmbreeScene.Geometry.TriangleDescs.Add(Desc);
    246. }
    247. else
    248. {
    249. BuildTriangles.Add(FkDOPBuildCollisionTriangle(
    250. // Store bGenerateAsIfTwoSided in material index
    251. bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0,
    252. FVector(V0),
    253. FVector(V1),
    254. FVector(V2)));
    255. }
    256. }
    257. #if USE_EMBREE
    258. if (EmbreeScene.bUseEmbree)
    259. {
    260. RTCGeometry Geometry = rtcNewGeometry(EmbreeScene.EmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE);
    261. EmbreeScene.Geometry.InternalGeometry = Geometry;
    262. rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, EmbreeVertices, 0, sizeof(FVector3f), NumVertices);
    263. rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, EmbreeIndices, 0, sizeof(uint32) * 3, FilteredTriangles.Num());
    264. rtcSetGeometryUserData(Geometry, &EmbreeScene.Geometry);
    265. rtcSetGeometryIntersectFilterFunction(Geometry, EmbreeFilterFunc);
    266. rtcCommitGeometry(Geometry);
    267. rtcAttachGeometry(EmbreeScene.EmbreeScene, Geometry);
    268. rtcReleaseGeometry(Geometry);
    269. rtcCommitScene(EmbreeScene.EmbreeScene);
    270. RTCError ReturnError = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
    271. if (ReturnError != RTC_ERROR_NONE)
    272. {
    273. UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcCommitScene failed. Code: %d"), *MeshName, (int32)ReturnError);
    274. return;
    275. }
    276. }
    277. else
    278. #endif
    279. {
    280. EmbreeScene.kDopTree.Build(BuildTriangles);
    281. }
    282. // bMostlyTwoSided
    283. {
    284. uint32 NumTrianglesTotal = 0;
    285. uint32 NumTwoSidedTriangles = 0;
    286. for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
    287. {
    288. const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
    289. if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
    290. {
    291. NumTrianglesTotal += Section.NumTriangles;
    292. if (MaterialBlendModes[Section.MaterialIndex].bTwoSided)
    293. {
    294. NumTwoSidedTriangles += Section.NumTriangles;
    295. }
    296. }
    297. }
    298. EmbreeScene.bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
    299. }
    300. }
    301. void USignedDistanceFieldUtilities::DeleteEmbreeScene(FEmbreeScene& EmbreeScene)
    302. {
    303. #if USE_EMBREE
    304. if (EmbreeScene.bUseEmbree)
    305. {
    306. rtcReleaseScene(EmbreeScene.EmbreeScene);
    307. rtcReleaseDevice(EmbreeScene.EmbreeDevice);
    308. }
    309. #endif
    310. }
    311. #if USE_EMBREE
    312. class FEmbreePointQueryContext : public RTCPointQueryContext
    313. {
    314. public:
    315. RTCGeometry MeshGeometry;
    316. int32 NumTriangles;
    317. };
    318. bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
    319. {
    320. const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;
    321. check(args->userPtr);
    322. float& ClosestDistanceSq = *(float*)(args->userPtr);
    323. const int32 TriangleIndex = args->primID;
    324. check(TriangleIndex < Context->NumTriangles);
    325. const FVector3f* VertexBuffer = (const FVector3f*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_VERTEX, 0);
    326. const uint32* IndexBuffer = (const uint32*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_INDEX, 0);
    327. const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
    328. const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
    329. const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];
    330. const FVector3f V0 = VertexBuffer[I0];
    331. const FVector3f V1 = VertexBuffer[I1];
    332. const FVector3f V2 = VertexBuffer[I2];
    333. const FVector3f QueryPosition(args->query->x, args->query->y, args->query->z);
    334. const FVector3f ClosestPoint = (FVector3f)FMath::ClosestPointOnTriangleToPoint((FVector)QueryPosition, (FVector)V0, (FVector)V1, (FVector)V2);
    335. const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared();
    336. if (QueryDistanceSq < ClosestDistanceSq)
    337. {
    338. ClosestDistanceSq = QueryDistanceSq;
    339. bool bShrinkQuery = true;
    340. if (bShrinkQuery)
    341. {
    342. args->query->radius = FMath::Sqrt(ClosestDistanceSq);
    343. // Return true to indicate that the query radius has shrunk
    344. return true;
    345. }
    346. }
    347. // Return false to indicate that the query radius hasn't changed
    348. return false;
    349. }
    350. static int32 ComputeLinearVoxelIndex(FIntVector VoxelCoordinate, FIntVector VolumeDimensions)
    351. {
    352. return (VoxelCoordinate.Z * VolumeDimensions.Y + VoxelCoordinate.Y) * VolumeDimensions.X + VoxelCoordinate.X;
    353. }
    354. class FSparseMeshDistanceFieldAsyncTask
    355. {
    356. public:
    357. FSparseMeshDistanceFieldAsyncTask(
    358. const FEmbreeScene& InEmbreeScene,
    359. const TArray* InSampleDirections,
    360. float InLocalSpaceTraceDistance,
    361. FBox InVolumeBounds,
    362. float InLocalToVolumeScale,
    363. FVector2D InDistanceFieldToVolumeScaleBias,
    364. FIntVector InBrickCoordinate,
    365. FIntVector InIndirectionSize,
    366. bool bInUsePointQuery)
    367. :
    368. EmbreeScene(InEmbreeScene),
    369. SampleDirections(InSampleDirections),
    370. LocalSpaceTraceDistance(InLocalSpaceTraceDistance),
    371. VolumeBounds(InVolumeBounds),
    372. LocalToVolumeScale(InLocalToVolumeScale),
    373. DistanceFieldToVolumeScaleBias(InDistanceFieldToVolumeScaleBias),
    374. BrickCoordinate(InBrickCoordinate),
    375. IndirectionSize(InIndirectionSize),
    376. bUsePointQuery(bInUsePointQuery),
    377. BrickMaxDistance(MIN_uint8),
    378. BrickMinDistance(MAX_uint8)
    379. {}
    380. void DoWork();
    381. // Readonly inputs
    382. const FEmbreeScene& EmbreeScene;
    383. const TArray* SampleDirections;
    384. float LocalSpaceTraceDistance;
    385. FBox VolumeBounds;
    386. float LocalToVolumeScale;
    387. FVector2D DistanceFieldToVolumeScaleBias;
    388. FIntVector BrickCoordinate;
    389. FIntVector IndirectionSize;
    390. bool bUsePointQuery;
    391. // Output
    392. uint8 BrickMaxDistance;
    393. uint8 BrickMinDistance;
    394. TArray DistanceFieldVolume;
    395. };
    396. int32 DebugX = 0;
    397. int32 DebugY = 0;
    398. int32 DebugZ = 0;
    399. void FSparseMeshDistanceFieldAsyncTask::DoWork()
    400. {
    401. TRACE_CPUPROFILER_EVENT_SCOPE(FSparseMeshDistanceFieldAsyncTask::DoWork);
    402. const FVector IndirectionVoxelSize = VolumeBounds.GetSize() / FVector(IndirectionSize);
    403. const FVector DistanceFieldVoxelSize = IndirectionVoxelSize / FVector(DistanceField::UniqueDataBrickSize);
    404. const FVector BrickMinPosition = VolumeBounds.Min + FVector(BrickCoordinate) * IndirectionVoxelSize;
    405. DistanceFieldVolume.Empty(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
    406. DistanceFieldVolume.AddZeroed(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
    407. for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++)
    408. {
    409. for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++)
    410. {
    411. for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++)
    412. {
    413. if (XIndex == DebugX && YIndex == DebugY && ZIndex == DebugZ)
    414. {
    415. int32 DebugBreak = 0;
    416. }
    417. const FVector VoxelPosition = FVector(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + BrickMinPosition;
    418. const int32 Index = (ZIndex * DistanceField::BrickSize * DistanceField::BrickSize + YIndex * DistanceField::BrickSize + XIndex);
    419. float MinLocalSpaceDistance = LocalSpaceTraceDistance;
    420. bool bTraceRays = true;
    421. if (bUsePointQuery)
    422. {
    423. RTCPointQuery PointQuery;
    424. PointQuery.x = VoxelPosition.X;
    425. PointQuery.y = VoxelPosition.Y;
    426. PointQuery.z = VoxelPosition.Z;
    427. PointQuery.time = 0;
    428. PointQuery.radius = LocalSpaceTraceDistance;
    429. FEmbreePointQueryContext QueryContext;
    430. rtcInitPointQueryContext(&QueryContext);
    431. QueryContext.MeshGeometry = EmbreeScene.Geometry.InternalGeometry;
    432. QueryContext.NumTriangles = EmbreeScene.Geometry.TriangleDescs.Num();
    433. float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
    434. rtcPointQuery(EmbreeScene.EmbreeScene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq);
    435. const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq);
    436. bTraceRays = ClosestDistance <= LocalSpaceTraceDistance;
    437. MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance);
    438. }
    439. if (bTraceRays)
    440. {
    441. int32 Hit = 0;
    442. int32 HitBack = 0;
    443. for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++)
    444. {
    445. const FVector UnitRayDirection = (FVector)(*SampleDirections)[SampleIndex];
    446. const float PullbackEpsilon = 1.e-4f;
    447. // Pull back the starting position slightly to make sure we hit a triangle that VoxelPosition is exactly on.
    448. // This happens a lot with boxes, since we trace from voxel corners.
    449. const FVector StartPosition = VoxelPosition - PullbackEpsilon * LocalSpaceTraceDistance * UnitRayDirection;
    450. const FVector EndPosition = VoxelPosition + UnitRayDirection * LocalSpaceTraceDistance;
    451. if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection))
    452. {
    453. FEmbreeRay EmbreeRay;
    454. FVector RayDirection = EndPosition - VoxelPosition;
    455. EmbreeRay.ray.org_x = StartPosition.X;
    456. EmbreeRay.ray.org_y = StartPosition.Y;
    457. EmbreeRay.ray.org_z = StartPosition.Z;
    458. EmbreeRay.ray.dir_x = RayDirection.X;
    459. EmbreeRay.ray.dir_y = RayDirection.Y;
    460. EmbreeRay.ray.dir_z = RayDirection.Z;
    461. EmbreeRay.ray.tnear = 0;
    462. EmbreeRay.ray.tfar = 1.0f;
    463. FEmbreeIntersectionContext EmbreeContext;
    464. rtcInitIntersectContext(&EmbreeContext);
    465. rtcIntersect1(EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);
    466. if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
    467. {
    468. check(EmbreeContext.ElementIndex != -1);
    469. Hit++;
    470. const FVector HitNormal = (FVector)EmbreeRay.GetHitNormal();
    471. if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeContext.IsHitTwoSided())
    472. {
    473. HitBack++;
    474. }
    475. if (!bUsePointQuery)
    476. {
    477. const float CurrentDistance = EmbreeRay.ray.tfar * LocalSpaceTraceDistance;
    478. if (CurrentDistance < MinLocalSpaceDistance)
    479. {
    480. MinLocalSpaceDistance = CurrentDistance;
    481. }
    482. }
    483. }
    484. }
    485. }
    486. // Consider this voxel 'inside' an object if we hit a significant number of backfaces
    487. if (Hit > 0 && HitBack > .25f * SampleDirections->Num())
    488. {
    489. MinLocalSpaceDistance *= -1;
    490. }
    491. }
    492. // Transform to the tracing shader's Volume space
    493. const float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;
    494. // Transform to the Distance Field texture's space
    495. const float RescaledDistance = (VolumeSpaceDistance - DistanceFieldToVolumeScaleBias.Y) / DistanceFieldToVolumeScaleBias.X;
    496. check(DistanceField::DistanceFieldFormat == PF_G8);
    497. const uint8 QuantizedDistance = FMath::Clamp(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
    498. DistanceFieldVolume[Index] = QuantizedDistance;
    499. BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
    500. BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
    501. }
    502. }
    503. }
    504. }
    505. void USignedDistanceFieldUtilities::GenerateSignedDistanceFieldVolumeData(
    506. FString MeshName,
    507. const FSourceMeshDataForDerivedDataTask& SourceMeshData,
    508. const FStaticMeshLODResources& LODModel,
    509. const TArray& MaterialBlendModes,
    510. const FBoxSphereBounds& Bounds,
    511. float DistanceFieldResolutionScale,
    512. bool bGenerateAsIfTwoSided,
    513. FDistanceFieldVolumeData& OutData)
    514. {
    515. TRACE_CPUPROFILER_EVENT_SCOPE(GenerateSignedDistanceFieldVolumeData);
    516. if (DistanceFieldResolutionScale > 0)
    517. {
    518. const double StartTime = FPlatformTime::Seconds();
    519. FEmbreeScene EmbreeScene;
    520. SetupEmbreeScene(MeshName,
    521. SourceMeshData,
    522. LODModel,
    523. MaterialBlendModes,
    524. bGenerateAsIfTwoSided,
    525. EmbreeScene);
    526. check(EmbreeScene.bUseEmbree);
    527. // Whether to use an Embree Point Query to compute the closest unsigned distance. Rays will only be traced to determine backfaces visible for sign.
    528. const bool bUsePointQuery = true;
    529. TArray SampleDirections;
    530. {
    531. const int32 NumVoxelDistanceSamples = bUsePointQuery ? 49 : 576;
    532. FRandomStream RandomStream(0);
    533. GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
    534. TArray OtherHemisphereSamples;
    535. GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, OtherHemisphereSamples);
    536. for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++)
    537. {
    538. FVector3f Sample = OtherHemisphereSamples[i];
    539. Sample.Z *= -1.0f;
    540. SampleDirections.Add(Sample);
    541. }
    542. }
    543. static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution"));
    544. const int32 PerMeshMax = CVar->GetValueOnAnyThread();
    545. // Meshes with explicit artist-specified scale can go higher
    546. const int32 MaxNumBlocksOneDim = FMath::Min(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);
    547. static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
    548. const float VoxelDensity = CVarDensity->GetValueOnAnyThread();
    549. const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
    550. FBox LocalSpaceMeshBounds(Bounds.GetBox());
    551. // Make sure the mesh bounding box has positive extents to handle planes
    552. {
    553. FVector MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
    554. FVector MeshBoundsExtent = FVector::Max(LocalSpaceMeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f));
    555. LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
    556. LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
    557. }
    558. // 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
    559. // Expand the mesh bounds by a fraction of a voxel to allow room for a pullback on the hit location for computing the gradient.
    560. // Only expand for two sided meshes as this adds significant Mesh SDF tracing cost
    561. if (EmbreeScene.bMostlyTwoSided)
    562. {
    563. const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
    564. const FIntVector Mip0IndirectionDimensions = FIntVector(
    565. FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
    566. FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
    567. FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
    568. const float CentralDifferencingExpandInVoxels = .25f;
    569. const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * CentralDifferencingExpandInVoxels));
    570. LocalSpaceMeshBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
    571. }
    572. // The tracing shader uses a Volume space that is normalized by the maximum extent, to keep Volume space within [-1, 1], we must match that behavior when encoding
    573. const float LocalToVolumeScale = 1.0f / LocalSpaceMeshBounds.GetExtent().GetMax();
    574. const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
    575. const FIntVector Mip0IndirectionDimensions = FIntVector(
    576. FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
    577. FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
    578. FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));
    579. TArray StreamableMipData;
    580. for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++)
    581. {
    582. const FIntVector IndirectionDimensions = FIntVector(
    583. FMath::DivideAndRoundUp(Mip0IndirectionDimensions.X, 1 << MipIndex),
    584. FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Y, 1 << MipIndex),
    585. FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Z, 1 << MipIndex));
    586. // Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering
    587. const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
    588. const FBox DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
    589. const FVector IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector(IndirectionDimensions);
    590. const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();
    591. const FVector VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector(DistanceField::UniqueDataBrickSize);
    592. const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
    593. const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
    594. const FVector2D DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);
    595. TArray AsyncTasks;
    596. AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z / 8);
    597. for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++)
    598. {
    599. for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++)
    600. {
    601. for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++)
    602. {
    603. AsyncTasks.Emplace(
    604. EmbreeScene,
    605. &SampleDirections,
    606. LocalSpaceTraceDistance,
    607. DistanceFieldVolumeBounds,
    608. LocalToVolumeScale,
    609. DistanceFieldToVolumeScaleBias,
    610. FIntVector(XIndex, YIndex, ZIndex),
    611. IndirectionDimensions,
    612. bUsePointQuery);
    613. }
    614. }
    615. }
    616. static bool bMultiThreaded = true;
    617. if (bMultiThreaded)
    618. {
    619. EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;
    620. ParallelForTemplate(
    621. TEXT("GenerateSignedDistanceFieldVolumeData.PF"),
    622. AsyncTasks.Num(), 1, [&AsyncTasks](int32 TaskIndex)
    623. {
    624. AsyncTasks[TaskIndex].DoWork();
    625. }, Flags);
    626. }
    627. else
    628. {
    629. for (FSparseMeshDistanceFieldAsyncTask& AsyncTask : AsyncTasks)
    630. {
    631. AsyncTask.DoWork();
    632. }
    633. }
    634. FSparseDistanceFieldMip& OutMip = OutData.Mips[MipIndex];
    635. TArray IndirectionTable;
    636. IndirectionTable.Empty(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
    637. IndirectionTable.AddUninitialized(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
    638. for (int32 i = 0; i < IndirectionTable.Num(); i++)
    639. {
    640. IndirectionTable[i] = DistanceField::InvalidBrickIndex;
    641. }
    642. TArray ValidBricks;
    643. ValidBricks.Empty(AsyncTasks.Num());
    644. for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++)
    645. {
    646. if (AsyncTasks[TaskIndex].BrickMinDistance < MAX_uint8 && AsyncTasks[TaskIndex].BrickMaxDistance > MIN_uint8)
    647. {
    648. ValidBricks.Add(&AsyncTasks[TaskIndex]);
    649. }
    650. }
    651. const uint32 NumBricks = ValidBricks.Num();
    652. const uint32 BrickSizeBytes = DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
    653. TArray DistanceFieldBrickData;
    654. DistanceFieldBrickData.Empty(BrickSizeBytes * NumBricks);
    655. DistanceFieldBrickData.AddUninitialized(BrickSizeBytes * NumBricks);
    656. for (int32 BrickIndex = 0; BrickIndex < ValidBricks.Num(); BrickIndex++)
    657. {
    658. const FSparseMeshDistanceFieldAsyncTask& Brick = *ValidBricks[BrickIndex];
    659. const int32 IndirectionIndex = ComputeLinearVoxelIndex(Brick.BrickCoordinate, IndirectionDimensions);
    660. IndirectionTable[IndirectionIndex] = BrickIndex;
    661. check(BrickSizeBytes == Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
    662. FPlatformMemory::Memcpy(&DistanceFieldBrickData[BrickIndex * BrickSizeBytes], Brick.DistanceFieldVolume.GetData(), Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
    663. }
    664. const int32 IndirectionTableBytes = IndirectionTable.Num() * IndirectionTable.GetTypeSize();
    665. const int32 MipDataBytes = IndirectionTableBytes + DistanceFieldBrickData.Num();
    666. if (MipIndex == DistanceField::NumMips - 1)
    667. {
    668. OutData.AlwaysLoadedMip.Empty(MipDataBytes);
    669. OutData.AlwaysLoadedMip.AddUninitialized(MipDataBytes);
    670. FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[0], IndirectionTable.GetData(), IndirectionTableBytes);
    671. if (DistanceFieldBrickData.Num() > 0)
    672. {
    673. FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
    674. }
    675. }
    676. else
    677. {
    678. OutMip.BulkOffset = StreamableMipData.Num();
    679. StreamableMipData.AddUninitialized(MipDataBytes);
    680. OutMip.BulkSize = StreamableMipData.Num() - OutMip.BulkOffset;
    681. checkf(OutMip.BulkSize > 0, TEXT("BulkSize was 0 for %s with %ux%ux%u indirection"), *MeshName, IndirectionDimensions.X, IndirectionDimensions.Y, IndirectionDimensions.Z);
    682. FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset], IndirectionTable.GetData(), IndirectionTableBytes);
    683. if (DistanceFieldBrickData.Num() > 0)
    684. {
    685. FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset + IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
    686. }
    687. }
    688. OutMip.IndirectionDimensions = IndirectionDimensions;
    689. OutMip.DistanceFieldToVolumeScaleBias = DistanceFieldToVolumeScaleBias;
    690. OutMip.NumDistanceFieldBricks = NumBricks;
    691. // Account for the border voxels we added
    692. const FVector VirtualUVMin = FVector(DistanceField::MeshDistanceFieldObjectBorder) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
    693. const FVector VirtualUVSize = FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
    694. const FVector VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;
    695. // [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
    696. OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
    697. OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
    698. }
    699. DeleteEmbreeScene(EmbreeScene);
    700. OutData.bMostlyTwoSided = EmbreeScene.bMostlyTwoSided;
    701. OutData.LocalSpaceMeshBounds = LocalSpaceMeshBounds;
    702. OutData.StreamableMips.Lock(LOCK_READ_WRITE);
    703. uint8* Ptr = (uint8*)OutData.StreamableMips.Realloc(StreamableMipData.Num());
    704. FMemory::Memcpy(Ptr, StreamableMipData.GetData(), StreamableMipData.Num());
    705. OutData.StreamableMips.Unlock();
    706. OutData.StreamableMips.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);
    707. const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);
    708. if (BuildTime > 1.0f)
    709. {
    710. 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"),
    711. BuildTime,
    712. Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize,
    713. Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize,
    714. Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize,
    715. (OutData.GetResourceSizeBytes() + OutData.StreamableMips.GetBulkDataSize()) / 1024.0f / 1024.0f,
    716. (OutData.AlwaysLoadedMip.GetAllocatedSize()) / 1024.0f / 1024.0f,
    717. FMath::RoundToInt(100.0f * OutData.Mips[0].NumDistanceFieldBricks / (float)(Mip0IndirectionDimensions.X * Mip0IndirectionDimensions.Y * Mip0IndirectionDimensions.Z)),
    718. EmbreeScene.NumIndices / 3,
    719. *MeshName);
    720. }
    721. }
    722. }
    723. bool USignedDistanceFieldUtilities::GenerateSDF(UStaticMesh* StaticMesh)
    724. {
    725. if (!StaticMesh->IsValidLowLevel())
    726. return false;
    727. const TArray& StaticMaterials = StaticMesh->GetStaticMaterials();
    728. TArray BuildMaterialData;
    729. BuildMaterialData.SetNum(StaticMaterials.Num());
    730. FMeshSectionInfoMap& SectionInfoMap = StaticMesh->GetSectionInfoMap();
    731. const uint32 LODIndex = 0;
    732. for (int32 SectionIndex = 0; SectionIndex < SectionInfoMap.GetSectionNumber(LODIndex); SectionIndex++)
    733. {
    734. const FMeshSectionInfo& Section = SectionInfoMap.Get(LODIndex, SectionIndex);
    735. if (!BuildMaterialData.IsValidIndex(Section.MaterialIndex))
    736. {
    737. continue;
    738. }
    739. USignedDistanceFieldUtilities::FSignedDistanceFieldBuildMaterialData& MaterialData = BuildMaterialData[Section.MaterialIndex];
    740. MaterialData.bAffectDistanceFieldLighting = Section.bAffectDistanceFieldLighting;
    741. UMaterialInterface* MaterialInterface = StaticMaterials[Section.MaterialIndex].MaterialInterface;
    742. if (MaterialInterface)
    743. {
    744. MaterialData.BlendMode = MaterialInterface->GetBlendMode();
    745. MaterialData.bTwoSided = MaterialInterface->IsTwoSided();
    746. }
    747. }
    748. //FString DistanceFieldKey = BuildDistanceFieldDerivedDataKey(InStaticMeshDerivedDataKey);
    749. //for (int32 MaterialIndex = 0; MaterialIndex < Mesh->GetStaticMaterials().Num(); MaterialIndex++)
    750. //{
    751. // DistanceFieldKey += FString::Printf(TEXT("_M%u_%u_%u"),
    752. // (uint32)BuildMaterialData[MaterialIndex].BlendMode,
    753. // BuildMaterialData[MaterialIndex].bTwoSided ? 1 : 0,
    754. // BuildMaterialData[MaterialIndex].bAffectDistanceFieldLighting ? 1 : 0);
    755. //}
    756. FString MeshName = StaticMesh->GetName();
    757. const FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
    758. UStaticMesh* GenerateSource = BuildSettings.DistanceFieldReplacementMesh ? ToRawPtr(BuildSettings.DistanceFieldReplacementMesh) : StaticMesh;
    759. float DistanceFieldResolutionScale = BuildSettings.DistanceFieldResolutionScale;
    760. bool bGenerateDistanceFieldAsIfTwoSided = BuildSettings.bGenerateDistanceFieldAsIfTwoSided;
    761. FDistanceFieldVolumeData* GeneratedVolumeData = new FDistanceFieldVolumeData();
    762. FSourceMeshDataForDerivedDataTask SourceMeshData{};
    763. if (GenerateSource->GetRenderData())
    764. {
    765. const FStaticMeshLODResources& LODModel = GenerateSource->GetRenderData()->LODResources[0];
    766. //USignedDistanceFieldUtilities* MyClass = NewObject();
    767. /*MyClass->*/GenerateSignedDistanceFieldVolumeData(
    768. MeshName,
    769. SourceMeshData,
    770. LODModel,
    771. MoveTemp(BuildMaterialData),
    772. GenerateSource->GetRenderData()->Bounds,
    773. DistanceFieldResolutionScale,
    774. bGenerateDistanceFieldAsIfTwoSided,
    775. *GeneratedVolumeData
    776. );
    777. // Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
    778. //if (Task->StaticMesh)
    779. {
    780. FObjectCacheContextScope ObjectCacheScope;
    781. check(!StaticMesh->IsCompiling());
    782. GeneratedVolumeData->bAsyncBuilding = false;
    783. FStaticMeshRenderData* RenderData = StaticMesh->GetRenderData();
    784. FDistanceFieldVolumeData* OldVolumeData = RenderData->LODResources[0].DistanceFieldData;
    785. // Assign the new volume data, this is safe because the render thread makes a copy of the pointer at scene proxy creation time.
    786. RenderData->LODResources[0].DistanceFieldData = GeneratedVolumeData;
    787. // Renderstates are not initialized between UStaticMesh::PreEditChange() and UStaticMesh::PostEditChange()
    788. if (RenderData->IsInitialized())
    789. {
    790. for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(StaticMesh))
    791. {
    792. if (Component->IsRegistered() && Component->IsRenderStateCreated())
    793. {
    794. Component->MarkRenderStateDirty();
    795. }
    796. }
    797. }
    798. if (OldVolumeData)
    799. {
    800. // Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
    801. BeginCleanup(OldVolumeData);
    802. }
    803. // Need also to update platform render data if it's being cached
    804. FStaticMeshRenderData* PlatformRenderData = RenderData->NextCachedRenderData.Get();
    805. while (PlatformRenderData)
    806. {
    807. if (PlatformRenderData->LODResources[0].DistanceFieldData)
    808. {
    809. *PlatformRenderData->LODResources[0].DistanceFieldData = *GeneratedVolumeData;
    810. // The old bulk data assignment operator doesn't copy over flags
    811. PlatformRenderData->LODResources[0].DistanceFieldData->StreamableMips.ResetBulkDataFlags(GeneratedVolumeData->StreamableMips.GetBulkDataFlags());
    812. }
    813. PlatformRenderData = PlatformRenderData->NextCachedRenderData.Get();
    814. }
    815. //{
    816. // TArray DerivedData;
    817. // // Save built distance field volume to DDC
    818. // FMemoryWriter Ar(DerivedData, /*bIsPersistent=*/ true);
    819. // StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData->Serialize(Ar, Task->StaticMesh);
    820. // GetDerivedDataCacheRef().Put(*Task->DDCKey, DerivedData, Task->StaticMesh->GetPathName());
    821. // COOK_STAT(Timer.AddMiss(DerivedData.Num()));
    822. //}
    823. //BeginCacheMeshCardRepresentation(
    824. // Task->TargetPlatform,
    825. // Task->StaticMesh,
    826. // Task->StaticMesh->GetPlatformStaticMeshRenderData(Task->StaticMesh, Task->TargetPlatform),
    827. // Task->DDCKey,
    828. // &Task->SourceMeshData);
    829. }
    830. return true;
    831. }
    832. return false;
    833. }
    834. #else
    835. //
    836. //void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
    837. // FString MeshName,
    838. // const FSourceMeshDataForDerivedDataTask& SourceMeshData,
    839. // const FStaticMeshLODResources& LODModel,
    840. // class FQueuedThreadPool& ThreadPool,
    841. // const TArray& MaterialBlendModes,
    842. // const FBoxSphereBounds& Bounds,
    843. // float DistanceFieldResolutionScale,
    844. // bool bGenerateAsIfTwoSided,
    845. // FDistanceFieldVolumeData& OutData)
    846. //{
    847. // if (DistanceFieldResolutionScale > 0)
    848. // {
    849. // UE_LOG(LogTemp, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
    850. // }
    851. //}
    852. #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)

  • 相关阅读:
    讲座学习截图——《CAD/CAE/CAM几何引擎-软件概述》:概念阐述,几何内核,CAD软件介绍
    【数据分析】基于matlab GUI齿轮箱振动数据分析【含Matlab源码 2122期】
    c 摄像头利用v4l2直接生成avi视频(不利用ffmpeg)
    java计算机毕业设计基于springboot 网上商城购物系统
    【极客时间-系列教程】深入剖析Kubernetes-预习篇 · 小鲸鱼大事记(二):崭露头角
    Linux之权限【读、写、执行】【详细总结】
    Java中如何检测HashMap中是否存在指定Key呢?
    软件测试之测试程序开发
    1000+节点的cdh集群主服务迁移全过程
    认识通讯协议——TCP/IP、UDP协议的区别,HTTP通讯协议的理解
  • 原文地址:https://blog.csdn.net/zouzouol/article/details/133886636