UE5 运行时生成距离场数据

1.背景

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

UE5 运行时生成距离场数据_第1张图片

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

UE5 运行时生成距离场数据_第2张图片

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

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

UE5 运行时生成距离场数据_第3张图片

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

UE5 运行时生成距离场数据_第4张图片

图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 tasks
    FQueuedThreadPoolTaskGraphWrapper TaskGraphWrapper(ENamedThreads::AnyBackgroundThreadNormalTask);
    GDistanceFieldAsyncQueue->Build(&Task, TaskGraphWrapper);
}

void FDistanceFieldAsyncQueue::Build(FAsyncDistanceFieldTask* Task, FQueuedThreadPool& BuildThreadPool)
{
#if WITH_EDITOR
        // Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
        if (Task->StaticMesh && Task->GenerateSource)
        {    
            const FStaticMeshLODResources& LODModel = Task->GenerateSource->GetRenderData()->LODResources[0];
            MeshUtilities->GenerateSignedDistanceFieldVolumeData();
        }
#endif
}

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 OutMeshActor;
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), OutMeshActor);
    UE_LOG(LogTemp, Log, TEXT("start Generate SDF data"));
    GEngine->AddOnScreenDebugMessage(0, 400.0f, FColor::Red, TEXT("start Generate SDF data"));

    TSet StaticMeshSet;
    int32 Index = 0;
    for (AActor* OutActor : OutMeshActor)
    {
        int tagNum = OutActor->Tags.Num();
        if (tagNum > 0)
        {
            UDatasmithAssetUserData* DatasmithAssetUserData = OutActor->GetRootComponent()->GetAssetUserData();
            if (DatasmithAssetUserData)
            {
                const FString CreationPhaseKey = PHASE_CREATION;
                const FString CreationPhase = *DatasmithAssetUserData->MetaData.FindRef(*CreationPhaseKey);
                if (CreationPhase == ELECTROMECHANICAL)
                {
                    continue;
                }

                //FString familyName = *DatasmithAssetUserData->MetaData.FindRef(TEXT("Element*Family"));
                //if (familyName == TEXT("基本墙") || familyName == TEXT("天花板") || familyName == TEXT("楼板"))
                {
                    UStaticMesh* StaticMesh = nullptr;
                    if (AStaticMeshActor* StaticMeshActor = Cast(OutActor))
                    {
                        auto component = StaticMeshActor->GetStaticMeshComponent();
                        component->UnregisterComponent();
                        component->RegisterComponent();
                        StaticMesh = component->GetStaticMesh();
                        if (StaticMesh->IsValidLowLevel())
                        {
                            StaticMeshSet.Emplace(StaticMesh);
                        }
                    }
                }
            }
        }
    }

    static int i;

    USignedDistanceFieldUtilities* MyClass = NewObject();
    for (UStaticMesh* Mesh : StaticMeshSet)
    {
        MyClass->GenerateSDF(Mesh);
        i++;
    }

    UE_LOG(LogTemp, Log, TEXT("end Generate SDF data"));
    UE_LOG(LogTemp, Log, TEXT(" Generate SDF data count : %d "), i);
    GEngine->AddOnScreenDebugMessage(0, 400.0f, FColor::Red, TEXT("end Generate SDF data"));
}

SignedDistanceFieldUtilities.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"


#include "kDOP.h"

#if USE_EMBREE
#include 
#include 
#else
typedef void* RTCDevice;
typedef void* RTCScene;
typedef void* RTCGeometry;
#endif

#include "SignedDistanceFieldUtilities.generated.h"




class FSourceMeshDataForDerivedDataTask;
class FDistanceFieldVolumeData;


class FMeshBuildDataProvider
{
public:

    /** Initialization constructor. */
    FMeshBuildDataProvider(
        const TkDOPTree& InkDopTree) :
        kDopTree(InkDopTree)
    {}

    // kDOP data provider interface.

    FORCEINLINE const TkDOPTree& GetkDOPTree(void) const
    {
        return kDopTree;
    }

    FORCEINLINE const FMatrix& GetLocalToWorld(void) const
    {
        return FMatrix::Identity;
    }

    FORCEINLINE const FMatrix& GetWorldToLocal(void) const
    {
        return FMatrix::Identity;
    }

    FORCEINLINE FMatrix GetLocalToWorldTransposeAdjoint(void) const
    {
        return FMatrix::Identity;
    }

    FORCEINLINE float GetDeterminant(void) const
    {
        return 1.0f;
    }

private:

    const TkDOPTree& kDopTree;
};

struct FEmbreeTriangleDesc
{
    int16 ElementIndex;

    bool IsTwoSided() const
    {
        // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
        return ElementIndex == 1;
    }
};

// Mapping between Embree Geometry Id and engine Mesh/LOD Id
struct FEmbreeGeometry
{
    TArray IndexArray;
    TArray VertexArray;
    TArray TriangleDescs; // The material ID of each triangle.
    RTCGeometry InternalGeometry;
};

class FEmbreeScene
{
public:
    bool bUseEmbree = false;
    int32 NumIndices = 0;
    bool bMostlyTwoSided = false;

    // Embree
    RTCDevice EmbreeDevice = nullptr;
    RTCScene EmbreeScene = nullptr;
    FEmbreeGeometry Geometry;

    // DOP tree fallback
    TkDOPTree kDopTree;
};

#if USE_EMBREE
struct FEmbreeRay : public RTCRayHit
{
    FEmbreeRay() :
        ElementIndex(-1)
    {
        hit.u = hit.v = 0;
        ray.time = 0;
        ray.mask = 0xFFFFFFFF;
        hit.geomID = RTC_INVALID_GEOMETRY_ID;
        hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
        hit.primID = RTC_INVALID_GEOMETRY_ID;
    }

    FVector3f GetHitNormal() const
    {
        return FVector3f(-hit.Ng_x, -hit.Ng_y, -hit.Ng_z).GetSafeNormal();
    }

    bool IsHitTwoSided() const
    {
        // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
        return ElementIndex == 1;
    }

    // Additional Outputs.
    int32 ElementIndex; // Material Index
};

struct FEmbreeIntersectionContext : public RTCIntersectContext
{
    FEmbreeIntersectionContext() :
        ElementIndex(-1)
    {}

    bool IsHitTwoSided() const
    {
        // MaterialIndex on the build triangles was set to 1 if two-sided, or 0 if one-sided
        return ElementIndex == 1;
    }

    // Hit against this primitive will be ignored
    int32 SkipPrimId = RTC_INVALID_GEOMETRY_ID;

    // Additional Outputs.
    int32 ElementIndex; // Material Index
};

#endif



/**
 * 
 */
UCLASS()
class DISTANCEFIELDTEST_API USignedDistanceFieldUtilities : public UObject
{
    GENERATED_BODY()

public:
    USignedDistanceFieldUtilities();

    class FSignedDistanceFieldBuildMaterialData
    {
    public:
        EBlendMode BlendMode = BLEND_Opaque;
        bool bTwoSided = false;
        bool bAffectDistanceFieldLighting = true;
    };

    /** Generates unit length, stratified and uniformly distributed direction samples in a hemisphere. */
    void GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray& Samples);

    /**
     *    [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"]
     */
    FMatrix44f GetTangentBasisFrisvad(FVector3f TangentZ);

    void SetupEmbreeScene(FString MeshName,
        const FSourceMeshDataForDerivedDataTask& SourceMeshData,
        const FStaticMeshLODResources& LODModel,
        const TArray& MaterialBlendModes,
        bool bGenerateAsIfTwoSided,
        FEmbreeScene& EmbreeScene);

    void DeleteEmbreeScene(FEmbreeScene& EmbreeScene);

    void GenerateSignedDistanceFieldVolumeData(
        FString MeshName,
        const FSourceMeshDataForDerivedDataTask& SourceMeshData,
        const FStaticMeshLODResources& LODModel,
        const TArray& MaterialBlendModes,
        const FBoxSphereBounds& Bounds,
        float DistanceFieldResolutionScale,
        bool bGenerateAsIfTwoSided,
        FDistanceFieldVolumeData& OutData);



    bool GenerateSDF(UStaticMesh* StaticMesh);
};

SignedDistanceFieldUtilities.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "SignedDistanceFieldUtilities.h"

#include "Kismet/GameplayStatics.h"
#include "Engine/StaticMeshActor.h"
#include "DistanceFieldAtlas.h"
#include "MeshCardRepresentation.h"
#include "ObjectCacheContext.h"

static FVector3f UniformSampleHemisphere(FVector2D Uniforms)
{
    Uniforms = Uniforms * 2.0f - 1.0f;

    if (Uniforms == FVector2D::ZeroVector)
    {
        return FVector3f::ZeroVector;
    }

    float R;
    float Theta;

    if (FMath::Abs(Uniforms.X) > FMath::Abs(Uniforms.Y))
    {
        R = Uniforms.X;
        Theta = (float)PI / 4 * (Uniforms.Y / Uniforms.X);
    }
    else
    {
        R = Uniforms.Y;
        Theta = (float)PI / 2 - (float)PI / 4 * (Uniforms.X / Uniforms.Y);
    }

    // concentric disk sample
    const float U = R * FMath::Cos(Theta);
    const float V = R * FMath::Sin(Theta);
    const float R2 = R * R;

    // map to hemisphere [P. Shirley, Kenneth Chiu; 1997; A Low Distortion Map Between Disk and Square]
    return FVector3f(U * FMath::Sqrt(2 - R2), V * FMath::Sqrt(2 - R2), 1.0f - R2);
}

#if USE_EMBREE
void EmbreeFilterFunc(const struct RTCFilterFunctionNArguments* args)
{
    FEmbreeGeometry* EmbreeGeometry = (FEmbreeGeometry*)args->geometryUserPtr;
    FEmbreeTriangleDesc Desc = EmbreeGeometry->TriangleDescs[RTCHitN_primID(args->hit, 1, 0)];

    FEmbreeIntersectionContext& IntersectionContext = *static_cast(args->context);
    IntersectionContext.ElementIndex = Desc.ElementIndex;

    const RTCHit& EmbreeHit = *(RTCHit*)args->hit;
    if (IntersectionContext.SkipPrimId != RTC_INVALID_GEOMETRY_ID && IntersectionContext.SkipPrimId == EmbreeHit.primID)
    {
        // Ignore hit in order to continue tracing
        args->valid[0] = 0;
    }
}

void EmbreeErrorFunc(void* userPtr, RTCError code, const char* str)
{
    FString ErrorString;
    TArray& ErrorStringArray = ErrorString.GetCharArray();
    ErrorStringArray.Empty();

    int32 StrLen = FCStringAnsi::Strlen(str);
    int32 Length = FUTF8ToTCHAR_Convert::ConvertedLength(str, StrLen);
    ErrorStringArray.AddUninitialized(Length + 1); // +1 for the null terminator
    FUTF8ToTCHAR_Convert::Convert(ErrorStringArray.GetData(), ErrorStringArray.Num(), reinterpret_cast(str), StrLen);
    ErrorStringArray[Length] = TEXT('\0');

    UE_LOG(LogTemp, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code);
}
#endif

USignedDistanceFieldUtilities::USignedDistanceFieldUtilities()
{
}

void USignedDistanceFieldUtilities::GenerateStratifiedUniformHemisphereSamples(int32 NumSamples, FRandomStream& RandomStream, TArray& Samples)
{
    const int32 NumSamplesDim = FMath::TruncToInt(FMath::Sqrt((float)NumSamples));

    Samples.Empty(NumSamplesDim * NumSamplesDim);

    for (int32 IndexX = 0; IndexX < NumSamplesDim; IndexX++)
    {
        for (int32 IndexY = 0; IndexY < NumSamplesDim; IndexY++)
        {
            const float U1 = RandomStream.GetFraction();
            const float U2 = RandomStream.GetFraction();

            const float Fraction1 = (IndexX + U1) / (float)NumSamplesDim;
            const float Fraction2 = (IndexY + U2) / (float)NumSamplesDim;

            Samples.Add(UniformSampleHemisphere(FVector2D(Fraction1, Fraction2)));
        }
    }
}

FMatrix44f USignedDistanceFieldUtilities::GetTangentBasisFrisvad(FVector3f TangentZ)
{
    FVector3f TangentX;
    FVector3f TangentY;

    if (TangentZ.Z < -0.9999999f)
    {
        TangentX = FVector3f(0, -1, 0);
        TangentY = FVector3f(-1, 0, 0);
    }
    else
    {
        float A = 1.0f / (1.0f + TangentZ.Z);
        float B = -TangentZ.X * TangentZ.Y * A;
        TangentX = FVector3f(1.0f - TangentZ.X * TangentZ.X * A, B, -TangentZ.X);
        TangentY = FVector3f(B, 1.0f - TangentZ.Y * TangentZ.Y * A, -TangentZ.Y);
    }

    FMatrix44f LocalBasis;
    LocalBasis.SetIdentity();
    LocalBasis.SetAxis(0, TangentX);
    LocalBasis.SetAxis(1, TangentY);
    LocalBasis.SetAxis(2, TangentZ);
    return LocalBasis;
}

void USignedDistanceFieldUtilities::SetupEmbreeScene(FString MeshName, const FSourceMeshDataForDerivedDataTask& SourceMeshData, const FStaticMeshLODResources& LODModel, const TArray& MaterialBlendModes, bool bGenerateAsIfTwoSided, FEmbreeScene& EmbreeScene)
{
    const uint32 NumIndices = SourceMeshData.IsValid() ? SourceMeshData.GetNumIndices() : LODModel.IndexBuffer.GetNumIndices();
    const int32 NumTriangles = NumIndices / 3;
    const uint32 NumVertices = SourceMeshData.IsValid() ? SourceMeshData.GetNumVertices() : LODModel.VertexBuffers.PositionVertexBuffer.GetNumVertices();
    EmbreeScene.NumIndices = NumTriangles;

    TArray > BuildTriangles;

#if USE_EMBREE
    EmbreeScene.bUseEmbree = true;

    if (EmbreeScene.bUseEmbree)
    {
        EmbreeScene.EmbreeDevice = rtcNewDevice(nullptr);
        rtcSetDeviceErrorFunction(EmbreeScene.EmbreeDevice, EmbreeErrorFunc, nullptr);

        RTCError ReturnErrorNewDevice = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
        if (ReturnErrorNewDevice != RTC_ERROR_NONE)
        {
            UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewDevice failed. Code: %d"), *MeshName, (int32)ReturnErrorNewDevice);
            return;
        }

        EmbreeScene.EmbreeScene = rtcNewScene(EmbreeScene.EmbreeDevice);
        rtcSetSceneFlags(EmbreeScene.EmbreeScene, RTC_SCENE_FLAG_NONE);

        RTCError ReturnErrorNewScene = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
        if (ReturnErrorNewScene != RTC_ERROR_NONE)
        {
            UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcNewScene failed. Code: %d"), *MeshName, (int32)ReturnErrorNewScene);
            rtcReleaseDevice(EmbreeScene.EmbreeDevice);
            return;
        }
    }
#endif

    TArray FilteredTriangles;
    FilteredTriangles.Empty(NumTriangles);

    if (SourceMeshData.IsValid())
    {
        for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
        {
            const uint32 I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
            const uint32 I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
            const uint32 I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];

            const FVector3f V0 = SourceMeshData.VertexPositions[I0];
            const FVector3f V1 = SourceMeshData.VertexPositions[I1];
            const FVector3f V2 = SourceMeshData.VertexPositions[I2];

            const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
            const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
            if (!bDegenerateTriangle)
            {
                FilteredTriangles.Add(TriangleIndex);
            }
        }
    }
    else
    {
        for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex)
        {
            const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
            const uint32 I0 = Indices[TriangleIndex * 3 + 0];
            const uint32 I1 = Indices[TriangleIndex * 3 + 1];
            const uint32 I2 = Indices[TriangleIndex * 3 + 2];

            const FVector3f V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
            const FVector3f V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
            const FVector3f V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);

            const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2));
            const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER;
            if (!bDegenerateTriangle)
            {
                bool bTriangleIsOpaqueOrMasked = false;

                for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
                {
                    const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];

                    if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
                    {
                        if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
                        {
                            bTriangleIsOpaqueOrMasked = !IsTranslucentBlendMode(MaterialBlendModes[Section.MaterialIndex].BlendMode) && MaterialBlendModes[Section.MaterialIndex].bAffectDistanceFieldLighting;
                        }

                        break;
                    }
                }

                if (bTriangleIsOpaqueOrMasked)
                {
                    FilteredTriangles.Add(TriangleIndex);
                }
            }
        }
    }

    const int32 NumBufferVerts = 1; // Reserve extra space at the end of the array, as embree has an internal bug where they read and discard 4 bytes off the end of the array
    EmbreeScene.Geometry.VertexArray.Empty(NumVertices + NumBufferVerts);
    EmbreeScene.Geometry.VertexArray.AddUninitialized(NumVertices + NumBufferVerts);

    const int32 NumFilteredIndices = FilteredTriangles.Num() * 3;

    EmbreeScene.Geometry.IndexArray.Empty(NumFilteredIndices);
    EmbreeScene.Geometry.IndexArray.AddUninitialized(NumFilteredIndices);

    FVector3f* EmbreeVertices = EmbreeScene.Geometry.VertexArray.GetData();
    uint32* EmbreeIndices = EmbreeScene.Geometry.IndexArray.GetData();
    EmbreeScene.Geometry.TriangleDescs.Empty(FilteredTriangles.Num());

    for (int32 FilteredTriangleIndex = 0; FilteredTriangleIndex < FilteredTriangles.Num(); FilteredTriangleIndex++)
    {
        uint32 I0, I1, I2;
        FVector3f V0, V1, V2;

        const int32 TriangleIndex = FilteredTriangles[FilteredTriangleIndex];
        if (SourceMeshData.IsValid())
        {
            I0 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 0];
            I1 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 1];
            I2 = SourceMeshData.TriangleIndices[TriangleIndex * 3 + 2];

            V0 = SourceMeshData.VertexPositions[I0];
            V1 = SourceMeshData.VertexPositions[I1];
            V2 = SourceMeshData.VertexPositions[I2];
        }
        else
        {
            const FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView();
            I0 = Indices[TriangleIndex * 3 + 0];
            I1 = Indices[TriangleIndex * 3 + 1];
            I2 = Indices[TriangleIndex * 3 + 2];

            V0 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I0);
            V1 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I1);
            V2 = LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(I2);
        }

        bool bTriangleIsTwoSided = false;

        for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
        {
            const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];

            if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3)
            {
                if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
                {
                    bTriangleIsTwoSided = MaterialBlendModes[Section.MaterialIndex].bTwoSided;
                }

                break;
            }
        }

        if (EmbreeScene.bUseEmbree)
        {
            EmbreeIndices[FilteredTriangleIndex * 3 + 0] = I0;
            EmbreeIndices[FilteredTriangleIndex * 3 + 1] = I1;
            EmbreeIndices[FilteredTriangleIndex * 3 + 2] = I2;

            EmbreeVertices[I0] = V0;
            EmbreeVertices[I1] = V1;
            EmbreeVertices[I2] = V2;

            FEmbreeTriangleDesc Desc;
            // Store bGenerateAsIfTwoSided in material index
            Desc.ElementIndex = bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0;
            EmbreeScene.Geometry.TriangleDescs.Add(Desc);
        }
        else
        {
            BuildTriangles.Add(FkDOPBuildCollisionTriangle(
                // Store bGenerateAsIfTwoSided in material index
                bGenerateAsIfTwoSided || bTriangleIsTwoSided ? 1 : 0,
                FVector(V0),
                FVector(V1),
                FVector(V2)));
        }
    }

#if USE_EMBREE
    if (EmbreeScene.bUseEmbree)
    {
        RTCGeometry Geometry = rtcNewGeometry(EmbreeScene.EmbreeDevice, RTC_GEOMETRY_TYPE_TRIANGLE);
        EmbreeScene.Geometry.InternalGeometry = Geometry;

        rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, EmbreeVertices, 0, sizeof(FVector3f), NumVertices);
        rtcSetSharedGeometryBuffer(Geometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, EmbreeIndices, 0, sizeof(uint32) * 3, FilteredTriangles.Num());

        rtcSetGeometryUserData(Geometry, &EmbreeScene.Geometry);
        rtcSetGeometryIntersectFilterFunction(Geometry, EmbreeFilterFunc);

        rtcCommitGeometry(Geometry);
        rtcAttachGeometry(EmbreeScene.EmbreeScene, Geometry);
        rtcReleaseGeometry(Geometry);

        rtcCommitScene(EmbreeScene.EmbreeScene);

        RTCError ReturnError = rtcGetDeviceError(EmbreeScene.EmbreeDevice);
        if (ReturnError != RTC_ERROR_NONE)
        {
            UE_LOG(LogTemp, Warning, TEXT("GenerateSignedDistanceFieldVolumeData failed for %s. Embree rtcCommitScene failed. Code: %d"), *MeshName, (int32)ReturnError);
            return;
        }
    }
    else
#endif
    {
        EmbreeScene.kDopTree.Build(BuildTriangles);
    }

    // bMostlyTwoSided
    {
        uint32 NumTrianglesTotal = 0;
        uint32 NumTwoSidedTriangles = 0;

        for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
        {
            const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];

            if (MaterialBlendModes.IsValidIndex(Section.MaterialIndex))
            {
                NumTrianglesTotal += Section.NumTriangles;

                if (MaterialBlendModes[Section.MaterialIndex].bTwoSided)
                {
                    NumTwoSidedTriangles += Section.NumTriangles;
                }
            }
        }

        EmbreeScene.bMostlyTwoSided = NumTwoSidedTriangles * 4 >= NumTrianglesTotal || bGenerateAsIfTwoSided;
    }
}

void USignedDistanceFieldUtilities::DeleteEmbreeScene(FEmbreeScene& EmbreeScene)
{
#if USE_EMBREE
    if (EmbreeScene.bUseEmbree)
    {
        rtcReleaseScene(EmbreeScene.EmbreeScene);
        rtcReleaseDevice(EmbreeScene.EmbreeDevice);
    }
#endif
}





#if USE_EMBREE


class FEmbreePointQueryContext : public RTCPointQueryContext
{
public:
    RTCGeometry MeshGeometry;
    int32 NumTriangles;
};

bool EmbreePointQueryFunction(RTCPointQueryFunctionArguments* args)
{
    const FEmbreePointQueryContext* Context = (const FEmbreePointQueryContext*)args->context;

    check(args->userPtr);
    float& ClosestDistanceSq = *(float*)(args->userPtr);

    const int32 TriangleIndex = args->primID;
    check(TriangleIndex < Context->NumTriangles);

    const FVector3f* VertexBuffer = (const FVector3f*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_VERTEX, 0);
    const uint32* IndexBuffer = (const uint32*)rtcGetGeometryBufferData(Context->MeshGeometry, RTC_BUFFER_TYPE_INDEX, 0);

    const uint32 I0 = IndexBuffer[TriangleIndex * 3 + 0];
    const uint32 I1 = IndexBuffer[TriangleIndex * 3 + 1];
    const uint32 I2 = IndexBuffer[TriangleIndex * 3 + 2];

    const FVector3f V0 = VertexBuffer[I0];
    const FVector3f V1 = VertexBuffer[I1];
    const FVector3f V2 = VertexBuffer[I2];

    const FVector3f QueryPosition(args->query->x, args->query->y, args->query->z);
    const FVector3f ClosestPoint = (FVector3f)FMath::ClosestPointOnTriangleToPoint((FVector)QueryPosition, (FVector)V0, (FVector)V1, (FVector)V2);
    const float QueryDistanceSq = (ClosestPoint - QueryPosition).SizeSquared();

    if (QueryDistanceSq < ClosestDistanceSq)
    {
        ClosestDistanceSq = QueryDistanceSq;

        bool bShrinkQuery = true;

        if (bShrinkQuery)
        {
            args->query->radius = FMath::Sqrt(ClosestDistanceSq);
            // Return true to indicate that the query radius has shrunk
            return true;
        }
    }

    // Return false to indicate that the query radius hasn't changed
    return false;
}

static int32 ComputeLinearVoxelIndex(FIntVector VoxelCoordinate, FIntVector VolumeDimensions)
{
    return (VoxelCoordinate.Z * VolumeDimensions.Y + VoxelCoordinate.Y) * VolumeDimensions.X + VoxelCoordinate.X;
}


class FSparseMeshDistanceFieldAsyncTask
{
public:
    FSparseMeshDistanceFieldAsyncTask(
        const FEmbreeScene& InEmbreeScene,
        const TArray* InSampleDirections,
        float InLocalSpaceTraceDistance,
        FBox InVolumeBounds,
        float InLocalToVolumeScale,
        FVector2D InDistanceFieldToVolumeScaleBias,
        FIntVector InBrickCoordinate,
        FIntVector InIndirectionSize,
        bool bInUsePointQuery)
        :
        EmbreeScene(InEmbreeScene),
        SampleDirections(InSampleDirections),
        LocalSpaceTraceDistance(InLocalSpaceTraceDistance),
        VolumeBounds(InVolumeBounds),
        LocalToVolumeScale(InLocalToVolumeScale),
        DistanceFieldToVolumeScaleBias(InDistanceFieldToVolumeScaleBias),
        BrickCoordinate(InBrickCoordinate),
        IndirectionSize(InIndirectionSize),
        bUsePointQuery(bInUsePointQuery),
        BrickMaxDistance(MIN_uint8),
        BrickMinDistance(MAX_uint8)
    {}

    void DoWork();

    // Readonly inputs
    const FEmbreeScene& EmbreeScene;
    const TArray* SampleDirections;
    float LocalSpaceTraceDistance;
    FBox VolumeBounds;
    float LocalToVolumeScale;
    FVector2D DistanceFieldToVolumeScaleBias;
    FIntVector BrickCoordinate;
    FIntVector IndirectionSize;
    bool bUsePointQuery;

    // Output
    uint8 BrickMaxDistance;
    uint8 BrickMinDistance;
    TArray DistanceFieldVolume;
};

int32 DebugX = 0;
int32 DebugY = 0;
int32 DebugZ = 0;

void FSparseMeshDistanceFieldAsyncTask::DoWork()
{
    TRACE_CPUPROFILER_EVENT_SCOPE(FSparseMeshDistanceFieldAsyncTask::DoWork);

    const FVector IndirectionVoxelSize = VolumeBounds.GetSize() / FVector(IndirectionSize);
    const FVector DistanceFieldVoxelSize = IndirectionVoxelSize / FVector(DistanceField::UniqueDataBrickSize);
    const FVector BrickMinPosition = VolumeBounds.Min + FVector(BrickCoordinate) * IndirectionVoxelSize;

    DistanceFieldVolume.Empty(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);
    DistanceFieldVolume.AddZeroed(DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize);

    for (int32 ZIndex = 0; ZIndex < DistanceField::BrickSize; ZIndex++)
    {
        for (int32 YIndex = 0; YIndex < DistanceField::BrickSize; YIndex++)
        {
            for (int32 XIndex = 0; XIndex < DistanceField::BrickSize; XIndex++)
            {
                if (XIndex == DebugX && YIndex == DebugY && ZIndex == DebugZ)
                {
                    int32 DebugBreak = 0;
                }

                const FVector VoxelPosition = FVector(XIndex, YIndex, ZIndex) * DistanceFieldVoxelSize + BrickMinPosition;
                const int32 Index = (ZIndex * DistanceField::BrickSize * DistanceField::BrickSize + YIndex * DistanceField::BrickSize + XIndex);

                float MinLocalSpaceDistance = LocalSpaceTraceDistance;

                bool bTraceRays = true;

                if (bUsePointQuery)
                {
                    RTCPointQuery PointQuery;
                    PointQuery.x = VoxelPosition.X;
                    PointQuery.y = VoxelPosition.Y;
                    PointQuery.z = VoxelPosition.Z;
                    PointQuery.time = 0;
                    PointQuery.radius = LocalSpaceTraceDistance;

                    FEmbreePointQueryContext QueryContext;
                    rtcInitPointQueryContext(&QueryContext);
                    QueryContext.MeshGeometry = EmbreeScene.Geometry.InternalGeometry;
                    QueryContext.NumTriangles = EmbreeScene.Geometry.TriangleDescs.Num();
                    float ClosestUnsignedDistanceSq = (LocalSpaceTraceDistance * 2.0f) * (LocalSpaceTraceDistance * 2.0f);
                    rtcPointQuery(EmbreeScene.EmbreeScene, &PointQuery, &QueryContext, EmbreePointQueryFunction, &ClosestUnsignedDistanceSq);

                    const float ClosestDistance = FMath::Sqrt(ClosestUnsignedDistanceSq);
                    bTraceRays = ClosestDistance <= LocalSpaceTraceDistance;
                    MinLocalSpaceDistance = FMath::Min(MinLocalSpaceDistance, ClosestDistance);
                }

                if (bTraceRays)
                {
                    int32 Hit = 0;
                    int32 HitBack = 0;

                    for (int32 SampleIndex = 0; SampleIndex < SampleDirections->Num(); SampleIndex++)
                    {
                        const FVector UnitRayDirection = (FVector)(*SampleDirections)[SampleIndex];
                        const float PullbackEpsilon = 1.e-4f;
                        // Pull back the starting position slightly to make sure we hit a triangle that VoxelPosition is exactly on.  
                        // This happens a lot with boxes, since we trace from voxel corners.
                        const FVector StartPosition = VoxelPosition - PullbackEpsilon * LocalSpaceTraceDistance * UnitRayDirection;
                        const FVector EndPosition = VoxelPosition + UnitRayDirection * LocalSpaceTraceDistance;

                        if (FMath::LineBoxIntersection(VolumeBounds, VoxelPosition, EndPosition, UnitRayDirection))
                        {
                            FEmbreeRay EmbreeRay;

                            FVector RayDirection = EndPosition - VoxelPosition;
                            EmbreeRay.ray.org_x = StartPosition.X;
                            EmbreeRay.ray.org_y = StartPosition.Y;
                            EmbreeRay.ray.org_z = StartPosition.Z;
                            EmbreeRay.ray.dir_x = RayDirection.X;
                            EmbreeRay.ray.dir_y = RayDirection.Y;
                            EmbreeRay.ray.dir_z = RayDirection.Z;
                            EmbreeRay.ray.tnear = 0;
                            EmbreeRay.ray.tfar = 1.0f;

                            FEmbreeIntersectionContext EmbreeContext;
                            rtcInitIntersectContext(&EmbreeContext);
                            rtcIntersect1(EmbreeScene.EmbreeScene, &EmbreeContext, &EmbreeRay);

                            if (EmbreeRay.hit.geomID != RTC_INVALID_GEOMETRY_ID && EmbreeRay.hit.primID != RTC_INVALID_GEOMETRY_ID)
                            {
                                check(EmbreeContext.ElementIndex != -1);
                                Hit++;

                                const FVector HitNormal = (FVector)EmbreeRay.GetHitNormal();

                                if (FVector::DotProduct(UnitRayDirection, HitNormal) > 0 && !EmbreeContext.IsHitTwoSided())
                                {
                                    HitBack++;
                                }

                                if (!bUsePointQuery)
                                {
                                    const float CurrentDistance = EmbreeRay.ray.tfar * LocalSpaceTraceDistance;

                                    if (CurrentDistance < MinLocalSpaceDistance)
                                    {
                                        MinLocalSpaceDistance = CurrentDistance;
                                    }
                                }
                            }
                        }
                    }

                    // Consider this voxel 'inside' an object if we hit a significant number of backfaces
                    if (Hit > 0 && HitBack > .25f * SampleDirections->Num())
                    {
                        MinLocalSpaceDistance *= -1;
                    }
                }

                // Transform to the tracing shader's Volume space
                const float VolumeSpaceDistance = MinLocalSpaceDistance * LocalToVolumeScale;
                // Transform to the Distance Field texture's space
                const float RescaledDistance = (VolumeSpaceDistance - DistanceFieldToVolumeScaleBias.Y) / DistanceFieldToVolumeScaleBias.X;
                check(DistanceField::DistanceFieldFormat == PF_G8);
                const uint8 QuantizedDistance = FMath::Clamp(FMath::FloorToInt(RescaledDistance * 255.0f + .5f), 0, 255);
                DistanceFieldVolume[Index] = QuantizedDistance;
                BrickMaxDistance = FMath::Max(BrickMaxDistance, QuantizedDistance);
                BrickMinDistance = FMath::Min(BrickMinDistance, QuantizedDistance);
            }
        }
    }
}

void USignedDistanceFieldUtilities::GenerateSignedDistanceFieldVolumeData(
    FString MeshName,
    const FSourceMeshDataForDerivedDataTask& SourceMeshData,
    const FStaticMeshLODResources& LODModel,
    const TArray& MaterialBlendModes,
    const FBoxSphereBounds& Bounds,
    float DistanceFieldResolutionScale,
    bool bGenerateAsIfTwoSided,
    FDistanceFieldVolumeData& OutData)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(GenerateSignedDistanceFieldVolumeData);

    if (DistanceFieldResolutionScale > 0)
    {
        const double StartTime = FPlatformTime::Seconds();

        FEmbreeScene EmbreeScene;
        SetupEmbreeScene(MeshName,
            SourceMeshData,
            LODModel,
            MaterialBlendModes,
            bGenerateAsIfTwoSided,
            EmbreeScene);

        check(EmbreeScene.bUseEmbree);

        // Whether to use an Embree Point Query to compute the closest unsigned distance.  Rays will only be traced to determine backfaces visible for sign.
        const bool bUsePointQuery = true;

        TArray SampleDirections;
        {
            const int32 NumVoxelDistanceSamples = bUsePointQuery ? 49 : 576;
            FRandomStream RandomStream(0);
            GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, SampleDirections);
            TArray OtherHemisphereSamples;
            GenerateStratifiedUniformHemisphereSamples(NumVoxelDistanceSamples, RandomStream, OtherHemisphereSamples);

            for (int32 i = 0; i < OtherHemisphereSamples.Num(); i++)
            {
                FVector3f Sample = OtherHemisphereSamples[i];
                Sample.Z *= -1.0f;
                SampleDirections.Add(Sample);
            }
        }

        static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DistanceFields.MaxPerMeshResolution"));
        const int32 PerMeshMax = CVar->GetValueOnAnyThread();

        // Meshes with explicit artist-specified scale can go higher
        const int32 MaxNumBlocksOneDim = FMath::Min(FMath::DivideAndRoundNearest(DistanceFieldResolutionScale <= 1 ? PerMeshMax / 2 : PerMeshMax, DistanceField::UniqueDataBrickSize), DistanceField::MaxIndirectionDimension - 1);

        static const auto CVarDensity = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.DistanceFields.DefaultVoxelDensity"));
        const float VoxelDensity = CVarDensity->GetValueOnAnyThread();

        const float NumVoxelsPerLocalSpaceUnit = VoxelDensity * DistanceFieldResolutionScale;
        FBox LocalSpaceMeshBounds(Bounds.GetBox());

        // Make sure the mesh bounding box has positive extents to handle planes
        {
            FVector MeshBoundsCenter = LocalSpaceMeshBounds.GetCenter();
            FVector MeshBoundsExtent = FVector::Max(LocalSpaceMeshBounds.GetExtent(), FVector(1.0f, 1.0f, 1.0f));
            LocalSpaceMeshBounds.Min = MeshBoundsCenter - MeshBoundsExtent;
            LocalSpaceMeshBounds.Max = MeshBoundsCenter + MeshBoundsExtent;
        }

        // We sample on voxel corners and use central differencing for gradients, so a box mesh using two-sided materials whose vertices lie on LocalSpaceMeshBounds produces a zero gradient on intersection
        // Expand the mesh bounds by a fraction of a voxel to allow room for a pullback on the hit location for computing the gradient.
        // Only expand for two sided meshes as this adds significant Mesh SDF tracing cost
        if (EmbreeScene.bMostlyTwoSided)
        {
            const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
            const FIntVector Mip0IndirectionDimensions = FIntVector(
                FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
                FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
                FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));

            const float CentralDifferencingExpandInVoxels = .25f;
            const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(Mip0IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * CentralDifferencingExpandInVoxels));
            LocalSpaceMeshBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);
        }

        // The tracing shader uses a Volume space that is normalized by the maximum extent, to keep Volume space within [-1, 1], we must match that behavior when encoding
        const float LocalToVolumeScale = 1.0f / LocalSpaceMeshBounds.GetExtent().GetMax();

        const FVector DesiredDimensions = FVector(LocalSpaceMeshBounds.GetSize() * FVector(NumVoxelsPerLocalSpaceUnit / (float)DistanceField::UniqueDataBrickSize));
        const FIntVector Mip0IndirectionDimensions = FIntVector(
            FMath::Clamp(FMath::RoundToInt(DesiredDimensions.X), 1, MaxNumBlocksOneDim),
            FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Y), 1, MaxNumBlocksOneDim),
            FMath::Clamp(FMath::RoundToInt(DesiredDimensions.Z), 1, MaxNumBlocksOneDim));

        TArray StreamableMipData;

        for (int32 MipIndex = 0; MipIndex < DistanceField::NumMips; MipIndex++)
        {
            const FIntVector IndirectionDimensions = FIntVector(
                FMath::DivideAndRoundUp(Mip0IndirectionDimensions.X, 1 << MipIndex),
                FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Y, 1 << MipIndex),
                FMath::DivideAndRoundUp(Mip0IndirectionDimensions.Z, 1 << MipIndex));

            // Expand to guarantee one voxel border for gradient reconstruction using bilinear filtering
            const FVector TexelObjectSpaceSize = LocalSpaceMeshBounds.GetSize() / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder));
            const FBox DistanceFieldVolumeBounds = LocalSpaceMeshBounds.ExpandBy(TexelObjectSpaceSize);

            const FVector IndirectionVoxelSize = DistanceFieldVolumeBounds.GetSize() / FVector(IndirectionDimensions);
            const float IndirectionVoxelRadius = IndirectionVoxelSize.Size();

            const FVector VolumeSpaceDistanceFieldVoxelSize = IndirectionVoxelSize * LocalToVolumeScale / FVector(DistanceField::UniqueDataBrickSize);
            const float MaxDistanceForEncoding = VolumeSpaceDistanceFieldVoxelSize.Size() * DistanceField::BandSizeInVoxels;
            const float LocalSpaceTraceDistance = MaxDistanceForEncoding / LocalToVolumeScale;
            const FVector2D DistanceFieldToVolumeScaleBias(2.0f * MaxDistanceForEncoding, -MaxDistanceForEncoding);

            TArray AsyncTasks;
            AsyncTasks.Reserve(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z / 8);

            for (int32 ZIndex = 0; ZIndex < IndirectionDimensions.Z; ZIndex++)
            {
                for (int32 YIndex = 0; YIndex < IndirectionDimensions.Y; YIndex++)
                {
                    for (int32 XIndex = 0; XIndex < IndirectionDimensions.X; XIndex++)
                    {
                        AsyncTasks.Emplace(
                            EmbreeScene,
                            &SampleDirections,
                            LocalSpaceTraceDistance,
                            DistanceFieldVolumeBounds,
                            LocalToVolumeScale,
                            DistanceFieldToVolumeScaleBias,
                            FIntVector(XIndex, YIndex, ZIndex),
                            IndirectionDimensions,
                            bUsePointQuery);
                    }
                }
            }

            static bool bMultiThreaded = true;

            if (bMultiThreaded)
            {
                EParallelForFlags Flags = EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced;

                ParallelForTemplate(
                    TEXT("GenerateSignedDistanceFieldVolumeData.PF"),
                    AsyncTasks.Num(), 1, [&AsyncTasks](int32 TaskIndex)
                    {
                        AsyncTasks[TaskIndex].DoWork();
                    }, Flags);
            }
            else
            {
                for (FSparseMeshDistanceFieldAsyncTask& AsyncTask : AsyncTasks)
                {
                    AsyncTask.DoWork();
                }
            }

            FSparseDistanceFieldMip& OutMip = OutData.Mips[MipIndex];
            TArray IndirectionTable;
            IndirectionTable.Empty(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);
            IndirectionTable.AddUninitialized(IndirectionDimensions.X * IndirectionDimensions.Y * IndirectionDimensions.Z);

            for (int32 i = 0; i < IndirectionTable.Num(); i++)
            {
                IndirectionTable[i] = DistanceField::InvalidBrickIndex;
            }

            TArray ValidBricks;
            ValidBricks.Empty(AsyncTasks.Num());

            for (int32 TaskIndex = 0; TaskIndex < AsyncTasks.Num(); TaskIndex++)
            {
                if (AsyncTasks[TaskIndex].BrickMinDistance < MAX_uint8 && AsyncTasks[TaskIndex].BrickMaxDistance > MIN_uint8)
                {
                    ValidBricks.Add(&AsyncTasks[TaskIndex]);
                }
            }

            const uint32 NumBricks = ValidBricks.Num();

            const uint32 BrickSizeBytes = DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;

            TArray DistanceFieldBrickData;
            DistanceFieldBrickData.Empty(BrickSizeBytes * NumBricks);
            DistanceFieldBrickData.AddUninitialized(BrickSizeBytes * NumBricks);

            for (int32 BrickIndex = 0; BrickIndex < ValidBricks.Num(); BrickIndex++)
            {
                const FSparseMeshDistanceFieldAsyncTask& Brick = *ValidBricks[BrickIndex];
                const int32 IndirectionIndex = ComputeLinearVoxelIndex(Brick.BrickCoordinate, IndirectionDimensions);
                IndirectionTable[IndirectionIndex] = BrickIndex;

                check(BrickSizeBytes == Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
                FPlatformMemory::Memcpy(&DistanceFieldBrickData[BrickIndex * BrickSizeBytes], Brick.DistanceFieldVolume.GetData(), Brick.DistanceFieldVolume.Num() * Brick.DistanceFieldVolume.GetTypeSize());
            }

            const int32 IndirectionTableBytes = IndirectionTable.Num() * IndirectionTable.GetTypeSize();
            const int32 MipDataBytes = IndirectionTableBytes + DistanceFieldBrickData.Num();

            if (MipIndex == DistanceField::NumMips - 1)
            {
                OutData.AlwaysLoadedMip.Empty(MipDataBytes);
                OutData.AlwaysLoadedMip.AddUninitialized(MipDataBytes);

                FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[0], IndirectionTable.GetData(), IndirectionTableBytes);

                if (DistanceFieldBrickData.Num() > 0)
                {
                    FPlatformMemory::Memcpy(&OutData.AlwaysLoadedMip[IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
                }
            }
            else
            {
                OutMip.BulkOffset = StreamableMipData.Num();
                StreamableMipData.AddUninitialized(MipDataBytes);
                OutMip.BulkSize = StreamableMipData.Num() - OutMip.BulkOffset;
                checkf(OutMip.BulkSize > 0, TEXT("BulkSize was 0 for %s with %ux%ux%u indirection"), *MeshName, IndirectionDimensions.X, IndirectionDimensions.Y, IndirectionDimensions.Z);

                FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset], IndirectionTable.GetData(), IndirectionTableBytes);

                if (DistanceFieldBrickData.Num() > 0)
                {
                    FPlatformMemory::Memcpy(&StreamableMipData[OutMip.BulkOffset + IndirectionTableBytes], DistanceFieldBrickData.GetData(), DistanceFieldBrickData.Num());
                }
            }

            OutMip.IndirectionDimensions = IndirectionDimensions;
            OutMip.DistanceFieldToVolumeScaleBias = DistanceFieldToVolumeScaleBias;
            OutMip.NumDistanceFieldBricks = NumBricks;

            // Account for the border voxels we added
            const FVector VirtualUVMin = FVector(DistanceField::MeshDistanceFieldObjectBorder) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);
            const FVector VirtualUVSize = FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize - FIntVector(2 * DistanceField::MeshDistanceFieldObjectBorder)) / FVector(IndirectionDimensions * DistanceField::UniqueDataBrickSize);

            const FVector VolumePositionExtent = LocalSpaceMeshBounds.GetExtent() * LocalToVolumeScale;

            // [-VolumePositionExtent, VolumePositionExtent] -> [VirtualUVMin, VirtualUVMin + VirtualUVSize]
            OutMip.VolumeToVirtualUVScale = VirtualUVSize / (2 * VolumePositionExtent);
            OutMip.VolumeToVirtualUVAdd = VolumePositionExtent * OutMip.VolumeToVirtualUVScale + VirtualUVMin;
        }

        DeleteEmbreeScene(EmbreeScene);

        OutData.bMostlyTwoSided = EmbreeScene.bMostlyTwoSided;
        OutData.LocalSpaceMeshBounds = LocalSpaceMeshBounds;

        OutData.StreamableMips.Lock(LOCK_READ_WRITE);
        uint8* Ptr = (uint8*)OutData.StreamableMips.Realloc(StreamableMipData.Num());
        FMemory::Memcpy(Ptr, StreamableMipData.GetData(), StreamableMipData.Num());
        OutData.StreamableMips.Unlock();
        OutData.StreamableMips.SetBulkDataFlags(BULKDATA_Force_NOT_InlinePayload);

        const float BuildTime = (float)(FPlatformTime::Seconds() - StartTime);

        if (BuildTime > 1.0f)
        {
            UE_LOG(LogTemp, Log, TEXT("Finished distance field build in %.1fs - %ux%ux%u sparse distance field, %.1fMb total, %.1fMb always loaded, %u%% occupied, %u triangles, %s"),
                BuildTime,
                Mip0IndirectionDimensions.X * DistanceField::UniqueDataBrickSize,
                Mip0IndirectionDimensions.Y * DistanceField::UniqueDataBrickSize,
                Mip0IndirectionDimensions.Z * DistanceField::UniqueDataBrickSize,
                (OutData.GetResourceSizeBytes() + OutData.StreamableMips.GetBulkDataSize()) / 1024.0f / 1024.0f,
                (OutData.AlwaysLoadedMip.GetAllocatedSize()) / 1024.0f / 1024.0f,
                FMath::RoundToInt(100.0f * OutData.Mips[0].NumDistanceFieldBricks / (float)(Mip0IndirectionDimensions.X * Mip0IndirectionDimensions.Y * Mip0IndirectionDimensions.Z)),
                EmbreeScene.NumIndices / 3,
                *MeshName);
        }
    }
}

bool USignedDistanceFieldUtilities::GenerateSDF(UStaticMesh* StaticMesh)
{
    if (!StaticMesh->IsValidLowLevel())
        return false;

    const TArray& StaticMaterials = StaticMesh->GetStaticMaterials();

    TArray BuildMaterialData;
    BuildMaterialData.SetNum(StaticMaterials.Num());

     FMeshSectionInfoMap& SectionInfoMap = StaticMesh->GetSectionInfoMap();
    const uint32 LODIndex = 0;

    for (int32 SectionIndex = 0; SectionIndex < SectionInfoMap.GetSectionNumber(LODIndex); SectionIndex++)
    {
        const FMeshSectionInfo& Section = SectionInfoMap.Get(LODIndex, SectionIndex);

        if (!BuildMaterialData.IsValidIndex(Section.MaterialIndex))
        {
            continue;
        }

        USignedDistanceFieldUtilities::FSignedDistanceFieldBuildMaterialData& MaterialData = BuildMaterialData[Section.MaterialIndex];
        MaterialData.bAffectDistanceFieldLighting = Section.bAffectDistanceFieldLighting;

        UMaterialInterface* MaterialInterface = StaticMaterials[Section.MaterialIndex].MaterialInterface;
        if (MaterialInterface)
        {
            MaterialData.BlendMode = MaterialInterface->GetBlendMode();
            MaterialData.bTwoSided = MaterialInterface->IsTwoSided();
        }
    }

    //FString DistanceFieldKey = BuildDistanceFieldDerivedDataKey(InStaticMeshDerivedDataKey);

    //for (int32 MaterialIndex = 0; MaterialIndex < Mesh->GetStaticMaterials().Num(); MaterialIndex++)
    //{
    //    DistanceFieldKey += FString::Printf(TEXT("_M%u_%u_%u"),
    //        (uint32)BuildMaterialData[MaterialIndex].BlendMode,
    //        BuildMaterialData[MaterialIndex].bTwoSided ? 1 : 0,
    //        BuildMaterialData[MaterialIndex].bAffectDistanceFieldLighting ? 1 : 0);
    //}

    FString MeshName = StaticMesh->GetName();

    const FMeshBuildSettings& BuildSettings = StaticMesh->GetSourceModel(0).BuildSettings;
    UStaticMesh* GenerateSource = BuildSettings.DistanceFieldReplacementMesh ? ToRawPtr(BuildSettings.DistanceFieldReplacementMesh) : StaticMesh;
    float DistanceFieldResolutionScale = BuildSettings.DistanceFieldResolutionScale;
    bool bGenerateDistanceFieldAsIfTwoSided = BuildSettings.bGenerateDistanceFieldAsIfTwoSided;

    FDistanceFieldVolumeData* GeneratedVolumeData = new FDistanceFieldVolumeData();
    FSourceMeshDataForDerivedDataTask SourceMeshData{};
    if (GenerateSource->GetRenderData())
    {
        const FStaticMeshLODResources& LODModel = GenerateSource->GetRenderData()->LODResources[0];

        //USignedDistanceFieldUtilities* MyClass = NewObject();
        /*MyClass->*/GenerateSignedDistanceFieldVolumeData(
            MeshName,
            SourceMeshData,
            LODModel,
            MoveTemp(BuildMaterialData),
            GenerateSource->GetRenderData()->Bounds,
            DistanceFieldResolutionScale,
            bGenerateDistanceFieldAsIfTwoSided,
            *GeneratedVolumeData
        );


        // Editor 'force delete' can null any UObject pointers which are seen by reference collecting (eg FProperty or serialized)
        //if (Task->StaticMesh)
        {
            FObjectCacheContextScope ObjectCacheScope;

            check(!StaticMesh->IsCompiling());

            GeneratedVolumeData->bAsyncBuilding = false;

            FStaticMeshRenderData* RenderData = StaticMesh->GetRenderData();
            FDistanceFieldVolumeData* OldVolumeData = RenderData->LODResources[0].DistanceFieldData;

            // Assign the new volume data, this is safe because the render thread makes a copy of the pointer at scene proxy creation time.
            RenderData->LODResources[0].DistanceFieldData = GeneratedVolumeData;

            // Renderstates are not initialized between UStaticMesh::PreEditChange() and UStaticMesh::PostEditChange()
            if (RenderData->IsInitialized())
            {
                for (UStaticMeshComponent* Component : ObjectCacheScope.GetContext().GetStaticMeshComponents(StaticMesh))
                {
                    if (Component->IsRegistered() && Component->IsRenderStateCreated())
                    {
                        Component->MarkRenderStateDirty();
                    }
                }
            }

            if (OldVolumeData)
            {
                // Rendering thread may still be referencing the old one, use the deferred cleanup interface to delete it next frame when it is safe
                BeginCleanup(OldVolumeData);
            }

            // Need also to update platform render data if it's being cached
            FStaticMeshRenderData* PlatformRenderData = RenderData->NextCachedRenderData.Get();
            while (PlatformRenderData)
            {
                if (PlatformRenderData->LODResources[0].DistanceFieldData)
                {
                    *PlatformRenderData->LODResources[0].DistanceFieldData = *GeneratedVolumeData;
                    // The old bulk data assignment operator doesn't copy over flags
                    PlatformRenderData->LODResources[0].DistanceFieldData->StreamableMips.ResetBulkDataFlags(GeneratedVolumeData->StreamableMips.GetBulkDataFlags());
                }
                PlatformRenderData = PlatformRenderData->NextCachedRenderData.Get();
            }

            //{
            //    TArray DerivedData;
            //    // Save built distance field volume to DDC
            //    FMemoryWriter Ar(DerivedData, /*bIsPersistent=*/ true);
            //    StaticMesh->GetRenderData()->LODResources[0].DistanceFieldData->Serialize(Ar, Task->StaticMesh);
            //    GetDerivedDataCacheRef().Put(*Task->DDCKey, DerivedData, Task->StaticMesh->GetPathName());
            //    COOK_STAT(Timer.AddMiss(DerivedData.Num()));
            //}

            //BeginCacheMeshCardRepresentation(
            //    Task->TargetPlatform,
            //    Task->StaticMesh,
            //    Task->StaticMesh->GetPlatformStaticMeshRenderData(Task->StaticMesh, Task->TargetPlatform),
            //    Task->DDCKey,
            //    &Task->SourceMeshData);
        }

        return true;
    }

    return false;
}

#else
//
//void FMeshUtilities::GenerateSignedDistanceFieldVolumeData(
//    FString MeshName,
//    const FSourceMeshDataForDerivedDataTask& SourceMeshData,
//    const FStaticMeshLODResources& LODModel,
//    class FQueuedThreadPool& ThreadPool,
//    const TArray& MaterialBlendModes,
//    const FBoxSphereBounds& Bounds,
//    float DistanceFieldResolutionScale,
//    bool bGenerateAsIfTwoSided,
//    FDistanceFieldVolumeData& OutData)
//{
//    if (DistanceFieldResolutionScale > 0)
//    {
//        UE_LOG(LogTemp, Warning, TEXT("Couldn't generate distance field for mesh, platform is missing Embree support."));
//    }
//}

#endif // PLATFORM_ENABLE_VECTORINTRINSICS

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)

你可能感兴趣的:(UE,ue5,distance,filed,距离场,runtime,lumen,sdf)