// Copyright Epic Games, Inc. All Rights Reserved. #include "MeshRepresentationCommon.h" #include "HAL/PlatformMemory.h" #include "MaterialShared.h" #include "MeshUtilities.h" #include "MeshUtilitiesPrivate.h" #include "DerivedMeshDataTaskUtils.h" #if USE_EMBREE static TAutoConsoleVariable CVarMemoryEstimateFactor( TEXT("r.Embree.MemoryEstimateFactor"), 60, TEXT("Configurable ratio for the memory used by embree"), ECVF_ReadOnly ); #endif 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); } void MeshUtilities::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; FVector3f Tmp = UniformSampleHemisphere(FVector2D(Fraction1, Fraction2)); // Workaround issue with compiler optimization by using copy constructor here. Samples.Add(FVector3f(Tmp)); } } } // [Frisvad 2012, "Building an Orthonormal Basis from a 3D Unit Vector Without Normalization"] FMatrix44f MeshRepresentation::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; } #if USE_EMBREE void EmbreeFilterFunc(const struct RTCFilterFunctionNArguments* args) { const FEmbreeGeometryAsset* GeometryAsset = (const FEmbreeGeometryAsset*)args->geometryUserPtr; FEmbreeTriangleDesc Desc = GeometryAsset->TriangleDescs[RTCHitN_primID(args->hit, 1, 0)]; #if USE_EMBREE_MAJOR_VERSION >= 4 FEmbreeRayQueryContext& EmbreeContext = *static_cast(args->context); #else FEmbreeIntersectionContext& EmbreeContext = *static_cast(args->context); #endif EmbreeContext.ElementIndex = Desc.ElementIndex; const RTCHit& EmbreeHit = *(RTCHit*)args->hit; if (EmbreeContext.SkipPrimId != RTC_INVALID_GEOMETRY_ID && EmbreeContext.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(LogMeshUtilities, Error, TEXT("Embree error: %s Code=%u"), *ErrorString, (uint32)code); } LLM_DEFINE_TAG(Embree); // userPtr is provided during callback registration, it points to the associated FEmbreeScene static bool EmbreeMemoryMonitor(void* userPtr, ssize_t bytes, bool post) { LLM_SCOPE_BYTAG(Embree); LLM_IF_ENABLED(FLowLevelMemTracker::Get().OnLowLevelChangeInMemoryUse(ELLMTracker::Default, static_cast(bytes))); return true; } #endif int64_t MeshRepresentation::MemoryEstimateForEmbreeScene(uint64_t IndexCount) { #if USE_EMBREE // This value was observed by breakpointing VmAlloc and viewing the allocations made const int64_t EmbreeDeviceUsage = 1024 * 1024 * 16; // Estimate based of measuring and correlating actual memory usage with various mesh properties. // Strong correlation between number of indices and memory usage. 1mb bias added to catch edge cases with very low index counts. return (CVarMemoryEstimateFactor.GetValueOnAnyThread() * IndexCount) + (1024 * 1024) + EmbreeDeviceUsage; #else return 0; #endif } void MeshRepresentation::SetupEmbreeScene(FString MeshName, bool bGenerateAsIfTwoSided, FEmbreeScene& OutEmbreeScene) { OutEmbreeScene.MeshName = MeshName; OutEmbreeScene.bGenerateAsIfTwoSided = bGenerateAsIfTwoSided; #if USE_EMBREE OutEmbreeScene.Device = rtcNewDevice(nullptr); rtcSetDeviceErrorFunction(OutEmbreeScene.Device, EmbreeErrorFunc, nullptr); LLM_IF_ENABLED(rtcSetDeviceMemoryMonitorFunction(OutEmbreeScene.Device, EmbreeMemoryMonitor, &OutEmbreeScene)); RTCError ReturnErrorNewDevice = rtcGetDeviceError(OutEmbreeScene.Device); if (ReturnErrorNewDevice == RTC_ERROR_OUT_OF_MEMORY) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to created Embree device for %s (OUT_OF_MEMORY)."), *MeshName); FPlatformMemory::OnOutOfMemory(0, 16); return; } if (ReturnErrorNewDevice != RTC_ERROR_NONE) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to created Embree device for %s. Code: %d"), *MeshName, (int32)ReturnErrorNewDevice); return; } OutEmbreeScene.Scene = rtcNewScene(OutEmbreeScene.Device); rtcSetSceneFlags(OutEmbreeScene.Scene, RTC_SCENE_FLAG_NONE); RTCError ReturnErrorNewScene = rtcGetDeviceError(OutEmbreeScene.Device); if (ReturnErrorNewScene == RTC_ERROR_OUT_OF_MEMORY) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to created Embree scene for %s (OUT_OF_MEMORY)."), *MeshName); FPlatformMemory::OnOutOfMemory(0, 16); return; } if (ReturnErrorNewScene != RTC_ERROR_NONE) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to created Embree scene for %s. Code: %d"), *MeshName, (int32)ReturnErrorNewScene); rtcReleaseDevice(OutEmbreeScene.Device); return; } #endif } void MeshRepresentation::DeleteEmbreeScene(FEmbreeScene& EmbreeScene) { #if USE_EMBREE for (FEmbreeGeometryAsset& Asset : EmbreeScene.GeometryAssets) { if (Asset.Scene != nullptr) { rtcReleaseScene(Asset.Scene); } } rtcReleaseScene(EmbreeScene.Scene); rtcReleaseDevice(EmbreeScene.Device); #endif EmbreeScene = FEmbreeScene(); } bool MeshRepresentation::AddMeshDataToEmbreeScene(FEmbreeScene& EmbreeScene, const FMeshDataForDerivedDataTask& MeshData, bool bIncludeTranslucentTriangles) { if ((MeshData.SourceMeshData == nullptr || !MeshData.SourceMeshData->IsValid()) && MeshData.LODModel == nullptr) { UE_LOG(LogMeshUtilities, Warning, TEXT("Provided MeshData for %s doesn't contain any data."), *EmbreeScene.MeshName); return false; } const FEmbreeGeometryAsset* GeometryAsset = EmbreeScene.AddGeometryAsset(MeshData.SourceMeshData, MeshData.LODModel, MeshData.SectionData, bIncludeTranslucentTriangles, false); const FEmbreeGeometry* Geometry = EmbreeScene.AddGeometry(GeometryAsset); return true; } const FEmbreeGeometryAsset* FEmbreeScene::AddGeometryAsset( const FSourceMeshDataForDerivedDataTask* SourceMeshData, const FStaticMeshLODResources* LODModel, TConstArrayView SectionData, bool bIncludeTranslucentTriangles, bool bInstantiable) { check((SourceMeshData != nullptr && SourceMeshData->IsValid()) || (LODModel != nullptr)); #if USE_EMBREE const uint32 NumVertices = (SourceMeshData && SourceMeshData->IsValid()) ? SourceMeshData->GetNumVertices() : LODModel->VertexBuffers.PositionVertexBuffer.GetNumVertices(); const uint32 NumIndices = (SourceMeshData && SourceMeshData->IsValid()) ? SourceMeshData->GetNumIndices() : LODModel->IndexBuffer.GetNumIndices(); const int32 NumTriangles = NumIndices / 3; const FStaticMeshSectionArray& Sections = (SourceMeshData && SourceMeshData->IsValid()) ? SourceMeshData->Sections : LODModel->Sections; TArray FilteredTriangles; FilteredTriangles.Empty(NumTriangles); // TODO: investigate/fix what causes this warning and then enable it bool bWarnOnceSectionData = false; for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) { FVector3f V0, V1, V2; if (SourceMeshData && SourceMeshData->IsValid()) { const uint32 I0 = SourceMeshData->TriangleIndices[TriangleIndex * 3 + 0]; const uint32 I1 = SourceMeshData->TriangleIndices[TriangleIndex * 3 + 1]; const uint32 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(); const uint32 I0 = Indices[TriangleIndex * 3 + 0]; const uint32 I1 = Indices[TriangleIndex * 3 + 1]; const uint32 I2 = Indices[TriangleIndex * 3 + 2]; V0 = LODModel->VertexBuffers.PositionVertexBuffer.VertexPosition(I0); V1 = LODModel->VertexBuffers.PositionVertexBuffer.VertexPosition(I1); V2 = LODModel->VertexBuffers.PositionVertexBuffer.VertexPosition(I2); } const FVector3f TriangleNormal = ((V1 - V2) ^ (V0 - V2)); const bool bDegenerateTriangle = TriangleNormal.SizeSquared() < SMALL_NUMBER; if (!bDegenerateTriangle) { bool bIncludeTriangle = false; for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); SectionIndex++) { const FStaticMeshSection& Section = Sections[SectionIndex]; if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3) { if (SectionData.IsValidIndex(SectionIndex)) { const bool bIsOpaqueOrMasked = !IsTranslucentBlendMode(SectionData[SectionIndex].BlendMode); bIncludeTriangle = (bIsOpaqueOrMasked || bIncludeTranslucentTriangles) && SectionData[SectionIndex].bAffectDistanceFieldLighting; } else if (bWarnOnceSectionData) { UE_LOG(LogMeshUtilities, Warning, TEXT("Missing section data for %s, section = %d."), *MeshName, SectionIndex); bWarnOnceSectionData = false; } break; } } if (bIncludeTriangle) { FilteredTriangles.Add(TriangleIndex); } } } FEmbreeGeometryAsset& GeometryAsset = *new FEmbreeGeometryAsset; 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 GeometryAsset.VertexArray.Empty(NumVertices + NumBufferVerts); GeometryAsset.VertexArray.AddUninitialized(NumVertices + NumBufferVerts); const int32 NumFilteredIndices = FilteredTriangles.Num() * 3; GeometryAsset.IndexArray.Empty(NumFilteredIndices); GeometryAsset.IndexArray.AddUninitialized(NumFilteredIndices); GeometryAsset.TriangleDescs.Empty(FilteredTriangles.Num()); FVector3f* EmbreeVertices = GeometryAsset.VertexArray.GetData(); uint32* EmbreeIndices = GeometryAsset.IndexArray.GetData(); for (int32 FilteredTriangleIndex = 0; FilteredTriangleIndex < FilteredTriangles.Num(); FilteredTriangleIndex++) { uint32 I0, I1, I2; FVector3f V0, V1, V2; const int32 TriangleIndex = FilteredTriangles[FilteredTriangleIndex]; if (SourceMeshData && 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 < Sections.Num(); SectionIndex++) { const FStaticMeshSection& Section = Sections[SectionIndex]; if ((uint32)(TriangleIndex * 3) >= Section.FirstIndex && (uint32)(TriangleIndex * 3) < Section.FirstIndex + Section.NumTriangles * 3) { if (SectionData.IsValidIndex(SectionIndex)) { bTriangleIsTwoSided = SectionData[SectionIndex].bTwoSided; } else if (bWarnOnceSectionData) { UE_LOG(LogMeshUtilities, Warning, TEXT("Missing section data for %s, section = %d."), *MeshName, SectionIndex); bWarnOnceSectionData = false; } break; } } { 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; GeometryAsset.TriangleDescs.Add(Desc); } } { GeometryAsset.NumVertices = NumVertices; GeometryAsset.NumTriangles = FilteredTriangles.Num(); GeometryAsset.SectionNumTriangles = 0; GeometryAsset.SectionNumTwoSidedTriangles = 0; for (int32 SectionIndex = 0; SectionIndex < Sections.Num(); SectionIndex++) { const FStaticMeshSection& Section = Sections[SectionIndex]; if (SectionData.IsValidIndex(SectionIndex)) { GeometryAsset.SectionNumTriangles += Section.NumTriangles; if (SectionData[SectionIndex].bTwoSided) { GeometryAsset.SectionNumTwoSidedTriangles += Section.NumTriangles; } } else if (bWarnOnceSectionData) { UE_LOG(LogMeshUtilities, Warning, TEXT("Missing section data for %s, section = %d."), *MeshName, SectionIndex); bWarnOnceSectionData = false; } } } if (bInstantiable) { RTCGeometry ImplGeometry = rtcNewGeometry(Device, RTC_GEOMETRY_TYPE_TRIANGLE); rtcSetSharedGeometryBuffer(ImplGeometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, EmbreeVertices, 0, sizeof(FVector3f), GeometryAsset.NumVertices); rtcSetSharedGeometryBuffer(ImplGeometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, EmbreeIndices, 0, sizeof(uint32) * 3, GeometryAsset.NumTriangles); rtcSetGeometryUserData(ImplGeometry, &GeometryAsset); rtcSetGeometryIntersectFilterFunction(ImplGeometry, EmbreeFilterFunc); rtcCommitGeometry(ImplGeometry); GeometryAsset.Scene = rtcNewScene(Device); rtcAttachGeometry(GeometryAsset.Scene, ImplGeometry); rtcReleaseGeometry(ImplGeometry); rtcCommitScene(GeometryAsset.Scene); RTCError ReturnError = rtcGetDeviceError(Device); if (ReturnError == RTC_ERROR_OUT_OF_MEMORY) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to create instantiable Embree geometry for %s (OUT_OF_MEMORY)."), *MeshName); FPlatformMemory::OnOutOfMemory(0, 16); //return nullptr; // unreachable code } if (ReturnError != RTC_ERROR_NONE) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to create instantiable Embree geometry for %s. Code: %d"), *MeshName, (int32)ReturnError); return nullptr; } } GeometryAssets.Add(&GeometryAsset); return &GeometryAsset; #else return nullptr; #endif } const FEmbreeGeometry* FEmbreeScene::AddGeometry(const FEmbreeGeometryAsset* GeometryAsset) { #if USE_EMBREE const FVector3f* VerticesData = GeometryAsset->VertexArray.GetData(); const uint32* IndicesData = GeometryAsset->IndexArray.GetData(); RTCGeometry ImplGeometry = rtcNewGeometry(Device, RTC_GEOMETRY_TYPE_TRIANGLE); rtcSetSharedGeometryBuffer(ImplGeometry, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, VerticesData, 0, sizeof(FVector3f), GeometryAsset->NumVertices); rtcSetSharedGeometryBuffer(ImplGeometry, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, IndicesData, 0, sizeof(uint32) * 3, GeometryAsset->NumTriangles); rtcSetGeometryUserData(ImplGeometry, (void*)GeometryAsset); rtcSetGeometryIntersectFilterFunction(ImplGeometry, EmbreeFilterFunc); rtcCommitGeometry(ImplGeometry); uint32 GeometryId = rtcAttachGeometry(Scene, ImplGeometry); rtcReleaseGeometry(ImplGeometry); RTCError ReturnError = rtcGetDeviceError(Device); if (ReturnError == RTC_ERROR_OUT_OF_MEMORY) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to add geometry to Embree scene for %s (OUT_OF_MEMORY)."), *MeshName); FPlatformMemory::OnOutOfMemory(0, 16); //return nullptr; // unreachable code } if (ReturnError != RTC_ERROR_NONE) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to add geometry to Embree scene for %s. Code: %d"), *MeshName, (int32)ReturnError); return nullptr; } FEmbreeGeometry& Geometry = *new FEmbreeGeometry; Geometry.Asset = GeometryAsset; Geometry.GeometryId = GeometryId; Geometries.Add(&Geometry); return &Geometry; #else return nullptr; #endif } const FEmbreeGeometry* FEmbreeScene::AddGeometryInstance(const FEmbreeGeometryAsset* GeometryAsset, const FMatrix44f& Transform) { #if USE_EMBREE RTCGeometry ImplGeometry = rtcNewGeometry(Device, RTC_GEOMETRY_TYPE_INSTANCE); rtcSetGeometryInstancedScene(ImplGeometry, GeometryAsset->Scene); rtcSetGeometryTransform(ImplGeometry, 0, RTC_FORMAT_FLOAT4X4_COLUMN_MAJOR, (const float*)Transform.M); rtcCommitGeometry(ImplGeometry); uint32 GeometryId = rtcAttachGeometry(Scene, ImplGeometry); rtcReleaseGeometry(ImplGeometry); RTCError ReturnError = rtcGetDeviceError(Device); if (ReturnError == RTC_ERROR_OUT_OF_MEMORY) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to add geometry instance to Embree scene for %s (OUT_OF_MEMORY)."), *MeshName); FPlatformMemory::OnOutOfMemory(0, 16); //return nullptr; // unreachable code } if (ReturnError != RTC_ERROR_NONE) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to add geometry instance to Embree scene for %s. Code: %d"), *MeshName, (int32)ReturnError); return nullptr; } FEmbreeGeometry& Geometry = *new FEmbreeGeometry; Geometry.Asset = GeometryAsset; Geometry.GeometryId = GeometryId; Geometries.Add(&Geometry); return &Geometry; #else return nullptr; #endif } void FEmbreeScene::Commit() { #if USE_EMBREE NumTrianglesTotal = 0; uint32 SectionNumTwoSidedTriangles = 0; uint32 SectionNumTriangles = 0; for (FEmbreeGeometry& Geometry : Geometries) { NumTrianglesTotal += Geometry.Asset->NumTriangles; SectionNumTwoSidedTriangles += Geometry.Asset->SectionNumTwoSidedTriangles; SectionNumTriangles += Geometry.Asset->SectionNumTriangles; } bMostlyTwoSided = SectionNumTwoSidedTriangles * 4 >= SectionNumTriangles || bGenerateAsIfTwoSided; rtcCommitScene(Scene); RTCError ReturnError = rtcGetDeviceError(Device); if (ReturnError == RTC_ERROR_OUT_OF_MEMORY) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to commit Embree scene for %s (OUT_OF_MEMORY)."), *MeshName); FPlatformMemory::OnOutOfMemory(0, 16); return; } if (ReturnError != RTC_ERROR_NONE) { UE_LOG(LogMeshUtilities, Warning, TEXT("Failed to commit Embree scene for %s. Code: %d"), *MeshName, (int32)ReturnError); return; } #endif }