// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryProcessing/ApproximateActorsImpl.h" #include "GameFramework/Actor.h" #include "Scene/MeshSceneAdapter.h" #include "DynamicMesh/DynamicMesh3.h" #include "DynamicMesh/MeshNormals.h" #include "DynamicMesh/MeshTangents.h" #include "DynamicMesh/ColliderMesh.h" #include "MeshSimplification.h" #include "MeshConstraintsUtil.h" #include "Implicit/Solidify.h" #include "Implicit/Morphology.h" #include "Operations/RemoveOccludedTriangles.h" #include "Operations/MeshPlaneCut.h" #include "ConstrainedDelaunay2.h" #include "ParameterizationOps/ParameterizeMeshOp.h" #include "Parameterization/MeshUVPacking.h" #include "MeshQueries.h" #include "ProjectionTargets.h" #include "Selections/MeshFaceSelection.h" #include "Generators/RectangleMeshGenerator.h" #include "Parameterization/DynamicMeshUVEditor.h" #include "AssetUtils/CreateStaticMeshUtil.h" #include "AssetUtils/CreateTexture2DUtil.h" #include "AssetUtils/CreateMaterialUtil.h" #include "AssetUtils/Texture2DUtil.h" #include "UObject/UObjectGlobals.h" // for CreatePackage #include "ImageUtils.h" #include "Image/ImageInfilling.h" #include "Sampling/MeshGenericWorldPositionBaker.h" #include "Scene/SceneCapturePhotoSet.h" #include "AssetUtils/Texture2DBuilder.h" #include "Engine/StaticMesh.h" #include "MaterialDomain.h" #include "Materials/Material.h" #include "Materials/MaterialInstanceConstant.h" #include "MaterialUtilities.h" #include "ShaderCore.h" #include "MeshMerge/MeshApproximationSettings.h" #include "Async/Async.h" #include "Misc/ScopedSlowTask.h" #include "RenderCaptureInterface.h" using namespace UE::Geometry; using namespace UE::AssetUtils; DEFINE_LOG_CATEGORY_STATIC(LogApproximateActors, Log, All); #define LOCTEXT_NAMESPACE "ApproximateActorsImpl" static TAutoConsoleVariable CVarApproximateActorsRDOCCapture( TEXT("ApproximateActors.RenderCapture"), 0, TEXT("Determines whether or not to trigger a render capture.\n") TEXT("0: Turned Off\n") TEXT("1: Turned On"), ECVF_Default); struct FGeneratedResultTextures { UTexture2D* BaseColorMap = nullptr; UTexture2D* RoughnessMap = nullptr; UTexture2D* MetallicMap = nullptr; UTexture2D* SpecularMap = nullptr; UTexture2D* PackedMRSMap = nullptr; UTexture2D* EmissiveMap = nullptr; UTexture2D* NormalMap = nullptr; }; static TUniquePtr CapturePhotoSet( const IGeometryProcessing_ApproximateActors::FInput& Input, const IGeometryProcessing_ApproximateActors::FOptions& Options ) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Captures); double FieldOfView = Options.FieldOfViewDegrees; double NearPlaneDist = Options.NearPlaneDist; FImageDimensions CaptureDimensions(Options.RenderCaptureImageSize, Options.RenderCaptureImageSize); TUniquePtr SceneCapture = MakeUnique(); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::BaseColor, Options.bBakeBaseColor); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::WorldNormal, Options.bBakeNormalMap); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Emissive, Options.bBakeEmissive); bool bMetallic = Options.bBakeMetallic; bool bRoughness = Options.bBakeRoughness; bool bSpecular = Options.bBakeSpecular; if (Options.bUsePackedMRS && (bMetallic || bRoughness || bSpecular ) ) { SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::CombinedMRS, true); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Roughness, false); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Metallic, false); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Specular, false); } else { SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::CombinedMRS, false); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Roughness, bRoughness); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Metallic, bMetallic); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Specular, bSpecular); } // These capture types aren't yet supported by the Approximate Actors interface SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Opacity, false); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::SubsurfaceColor, false); UWorld* World = Input.Actors.IsEmpty() ? (Input.Components.IsEmpty() ? nullptr : Input.Components[0]->GetWorld()) : Input.Actors[0]->GetWorld(); SceneCapture->SetCaptureSceneActorsAndComponents(World, Input.Actors, Input.Components); const TArray SpatialParams = ComputeStandardExteriorSpatialPhotoParameters( World, Input.Actors, Input.Components, CaptureDimensions, FieldOfView, NearPlaneDist, true, true, true, true, true); SceneCapture->SetSpatialPhotoParams(SpatialParams); SceneCapture->Compute(); return SceneCapture; } static void BakeTexturesFromPhotoCapture( TUniquePtr& SceneCapture, const IGeometryProcessing_ApproximateActors::FOptions& Options, FGeneratedResultTextures& GeneratedTextures, const FDynamicMesh3* WorldTargetMesh, const FMeshTangentsd* MeshTangents ) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures); int32 UVLayer = 0; int32 Supersample = FMath::Max(1, Options.AntiAliasMultiSampling); if ( (Options.TextureImageSize * Supersample) > 16384) { UE_LOG(LogApproximateActors, Warning, TEXT("Ignoring requested supersampling rate %d because it would require image buffers with resolution %d, please try lower value."), Supersample, Options.TextureImageSize * Supersample); Supersample = 1; } FImageDimensions OutputDimensions(Options.TextureImageSize*Supersample, Options.TextureImageSize*Supersample); FScopedSlowTask Progress(8.f, LOCTEXT("BakingTextures", "Baking Textures...")); Progress.MakeDialog(true); Progress.EnterProgressFrame(1.f, LOCTEXT("BakingSetup", "Setup...")); FDynamicMeshAABBTree3 Spatial(WorldTargetMesh, true); FMeshImageBakingCache TempBakeCache; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_MakeCache); TempBakeCache.SetDetailMesh(WorldTargetMesh, &Spatial); TempBakeCache.SetBakeTargetMesh(WorldTargetMesh); TempBakeCache.SetDimensions(OutputDimensions); TempBakeCache.SetUVLayer(UVLayer); TempBakeCache.SetThickness(0.1); TempBakeCache.SetCorrespondenceStrategy(FMeshImageBakingCache::ECorrespondenceStrategy::Identity); TempBakeCache.ValidateCache(); } Progress.EnterProgressFrame(1.f, LOCTEXT("BakingBaseColor", "Baking Base Color...")); FAxisAlignedBox3d TargetBounds = WorldTargetMesh->GetBounds(); double RayOffsetHackDist = (double)(100.0f * FMathf::ZeroTolerance * TargetBounds.MinDim() ); auto VisibilityFunction = [&Spatial, RayOffsetHackDist](const FVector3d& SurfPos, const FVector3d& ImagePosWorld) { FVector3d RayDir = ImagePosWorld - SurfPos; double Dist = Normalize(RayDir); FVector3d RayOrigin = SurfPos + RayOffsetHackDist * RayDir; int32 HitTID = Spatial.FindNearestHitTriangle(FRay3d(RayOrigin, RayDir), IMeshSpatial::FQueryOptions(Dist)); return (HitTID == IndexConstants::InvalidID); }; auto GetNumDownsampleSteps = [&](int32 TargetImageSize) { return (Options.TextureImageSize / TargetImageSize) * Supersample; }; FSceneCapturePhotoSet::FSceneSample DefaultSample; FVector4f InvalidColor(0, -1, 0, 1); DefaultSample.BaseColor = FVector3f(InvalidColor.X, InvalidColor.Y, InvalidColor.Z); FMeshGenericWorldPositionColorBaker BaseColorBaker; BaseColorBaker.SetCache(&TempBakeCache); BaseColorBaker.ColorSampleFunction = [&](FVector3d Position, FVector3d Normal) { FSceneCapturePhotoSet::FSceneSample Sample = DefaultSample; SceneCapture->ComputeSample(FRenderCaptureTypeFlags::BaseColor(), Position, Normal, VisibilityFunction, Sample); return Sample.GetValue4f(ERenderCaptureType::BaseColor); }; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_BakeColor); BaseColorBaker.Bake(); } // find "hole" pixels TArray MissingPixels; TUniquePtr> ColorImage = BaseColorBaker.TakeResult(); TMarchingPixelInfill Infill; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_ComputeInfill); TempBakeCache.FindSamplingHoles([&](const FVector2i& Coords) { return ColorImage->GetPixel(Coords) == InvalidColor; }, MissingPixels); // solve infill for the holes while also caching infill information Infill.ComputeInfill(*ColorImage, MissingPixels, InvalidColor, [](FVector4f SumValue, int32 Count) { float InvSum = (Count == 0) ? 1.0f : (1.0f / Count); return FVector4f(SumValue.X * InvSum, SumValue.Y * InvSum, SumValue.Z * InvSum, 1.0f); }); } // downsample the image if necessary int32 BaseColorDownsampleSteps = GetNumDownsampleSteps(Options.CustomTextureSizeBaseColor); if (BaseColorDownsampleSteps > 1) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_Downsample); TImageBuilder Downsampled = ColorImage->FastDownsample(BaseColorDownsampleSteps, FVector4f::Zero(), [](FVector4f V, int N) { return V / (float)N; }); *ColorImage = MoveTemp(Downsampled); } // this lambda is used to process the per-channel images. It does the bake, applies infill, and downsamples if necessary auto ProcessChannelFunc = [&](ERenderCaptureType CaptureType, int32 CaptureTextureSize) { FVector4f DefaultValue(0, 0, 0, 0); FMeshGenericWorldPositionColorBaker ChannelBaker; ChannelBaker.SetCache(&TempBakeCache); ChannelBaker.ColorSampleFunction = [&](FVector3d Position, FVector3d Normal) { FSceneCapturePhotoSet::FSceneSample Sample = DefaultSample; SceneCapture->ComputeSample(FRenderCaptureTypeFlags::Single(CaptureType), Position, Normal, VisibilityFunction, Sample); return Sample.GetValue4f(CaptureType); }; ChannelBaker.Bake(); TUniquePtr> Image = ChannelBaker.TakeResult(); Infill.ApplyInfill(*Image, [](FVector4f SumValue, int32 Count) { float InvSum = (Count == 0) ? 1.0f : (1.0f / Count); return FVector4f(SumValue.X * InvSum, SumValue.Y * InvSum, SumValue.Z * InvSum, 1.0f); }); int32 DownsampleSteps = GetNumDownsampleSteps(CaptureTextureSize); if (DownsampleSteps > 1) { TImageBuilder Downsampled = Image->FastDownsample(DownsampleSteps, FVector4f::Zero(), [](FVector4f V, int N) { return V / (float)N; }); *Image = MoveTemp(Downsampled); } return MoveTemp(Image); }; bool bMetallic = Options.bBakeMetallic; bool bRoughness = Options.bBakeRoughness; bool bSpecular = Options.bBakeSpecular; TUniquePtr> RoughnessImage, MetallicImage, SpecularImage, PackedMRSImage, EmissiveImage; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_OtherChannels); if (Options.bUsePackedMRS && (bMetallic || bRoughness || bSpecular)) { int32 MRSTextureSize = FMath::Max(Options.CustomTextureSizeRoughness, FMath::Max(Options.CustomTextureSizeMetallic, Options.CustomTextureSizeSpecular)); PackedMRSImage = ProcessChannelFunc(ERenderCaptureType::CombinedMRS, MRSTextureSize); } else { if (bRoughness) { RoughnessImage = ProcessChannelFunc(ERenderCaptureType::Roughness, Options.CustomTextureSizeRoughness); } if (bMetallic) { MetallicImage = ProcessChannelFunc(ERenderCaptureType::Metallic, Options.CustomTextureSizeMetallic); } if (bSpecular) { SpecularImage = ProcessChannelFunc(ERenderCaptureType::Specular, Options.CustomTextureSizeSpecular); } } if (Options.bBakeEmissive) { EmissiveImage = ProcessChannelFunc(ERenderCaptureType::Emissive, Options.CustomTextureSizeEmissive); } } Progress.EnterProgressFrame(1.f, LOCTEXT("BakingNormals", "Baking Normals...")); TUniquePtr> NormalImage; if (Options.bBakeNormalMap) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_NormalMapBake); // no infill on normal map for now, doesn't make sense to do after mapping to tangent space! // (should we build baked normal map in world space, and then resample to tangent space??) FVector4f DefaultNormalValue(0, 0, 1, 1); FMeshGenericWorldPositionNormalBaker NormalMapBaker; NormalMapBaker.SetCache(&TempBakeCache); NormalMapBaker.BaseMeshTangents = MeshTangents; NormalMapBaker.NormalSampleFunction = [&](FVector3d Position, FVector3d Normal) { FSceneCapturePhotoSet::FSceneSample Sample = DefaultSample; SceneCapture->ComputeSample(FRenderCaptureTypeFlags::WorldNormal(), Position, Normal, VisibilityFunction, Sample); FVector3f NormalColor = Sample.WorldNormal; float x = (NormalColor.X - 0.5f) * 2.0f; float y = (NormalColor.Y - 0.5f) * 2.0f; float z = (NormalColor.Z - 0.5f) * 2.0f; return FVector3f(x, y, z); }; NormalMapBaker.Bake(); NormalImage = NormalMapBaker.TakeResult(); int32 NormalMapDownsampleSteps = GetNumDownsampleSteps(Options.CustomTextureSizeNormalMap); if (NormalMapDownsampleSteps > 1) { TImageBuilder Downsampled = NormalImage->FastDownsample(NormalMapDownsampleSteps, FVector3f::Zero(), [](FVector3f V, int N) { return V / (float)N; }); *NormalImage = MoveTemp(Downsampled); } } // build textures Progress.EnterProgressFrame(1.f, LOCTEXT("BuildingTextures", "Building Textures...")); { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Textures_BuildTextures); FScopedSlowTask BuildTexProgress(6.f, LOCTEXT("BuildingTextures", "Building Textures...")); BuildTexProgress.MakeDialog(true); if (Options.bBakeBaseColor && ColorImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.BaseColorMap = FTexture2DBuilder::BuildTextureFromImage(*ColorImage, FTexture2DBuilder::ETextureType::Color, true, false); } if (Options.bBakeEmissive && EmissiveImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.EmissiveMap = FTexture2DBuilder::BuildTextureFromImage(*EmissiveImage, FTexture2DBuilder::ETextureType::EmissiveHDR, false, false); GeneratedTextures.EmissiveMap->CompressionSettings = TC_HDR_Compressed; } if (Options.bBakeNormalMap && NormalImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.NormalMap = FTexture2DBuilder::BuildTextureFromImage(*NormalImage, FTexture2DBuilder::ETextureType::NormalMap, false, false); } if ( (bRoughness || bMetallic || bSpecular) && PackedMRSImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.PackedMRSMap = FTexture2DBuilder::BuildTextureFromImage(*PackedMRSImage, FTexture2DBuilder::ETextureType::ColorLinear, false, false); } else { if (bRoughness && RoughnessImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.RoughnessMap = FTexture2DBuilder::BuildTextureFromImage(*RoughnessImage, FTexture2DBuilder::ETextureType::Roughness, false, false); } if (bMetallic && MetallicImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.MetallicMap = FTexture2DBuilder::BuildTextureFromImage(*MetallicImage, FTexture2DBuilder::ETextureType::Metallic, false, false); } if (bSpecular && SpecularImage.IsValid()) { BuildTexProgress.EnterProgressFrame(1.f); GeneratedTextures.SpecularMap = FTexture2DBuilder::BuildTextureFromImage(*SpecularImage, FTexture2DBuilder::ETextureType::Specular, false, false); } } } } struct FApproximationMeshData { IGeometryProcessing_ApproximateActors::EResultCode ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::UnknownError; bool bHaveMesh = false; FDynamicMesh3 Mesh; bool bHaveTangents = false; FMeshTangentsd Tangents; }; static TSharedPtr GenerateApproximationMesh( TUniquePtr Scene, const IGeometryProcessing_ApproximateActors::FOptions& Options, double ApproxAccuracy ) { FScopedSlowTask Progress(8.f, LOCTEXT("Generating Mesh", "Generating Mesh..")); TRACE_BOOKMARK(TEXT("ApproximateActors-Collect Seed Points")); TSharedPtr Result = MakeShared(); // collect seed points TArray SeedPoints; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_SeedPoints); Scene->CollectMeshSeedPoints(SeedPoints); } FAxisAlignedBox3d SceneBounds = Scene->GetBoundingBox(); // calculate a voxel size based on target world-space approximation accuracy float WorldBoundsSize = SceneBounds.DiagonalLength(); int32 VoxelDimTarget = (int)(WorldBoundsSize / ApproxAccuracy) + 1; if (VoxelDimTarget < 64) { VoxelDimTarget = 64; // use a sane minimum in case the parameter is super-wrong } // avoid insane memory usage if (VoxelDimTarget > Options.ClampVoxelDimension) { UE_LOG(LogApproximateActors, Log, TEXT("very large voxel size %d clamped to %d"), VoxelDimTarget, Options.ClampVoxelDimension); VoxelDimTarget = Options.ClampVoxelDimension; } // make ground plane FVector3d GroundPlaneOrigin = FVector3d::ZeroVector; FPlane3d GroundClipPlane; bool bHaveGroundClipPlane = false; if (Options.GroundPlanePolicy == IGeometryProcessing_ApproximateActors::EGroundPlanePolicy::FixedZHeightGroundPlane) { GroundPlaneOrigin = FVector3d(SceneBounds.Center().X, SceneBounds.Center().Y, Options.GroundPlaneZHeight); GroundClipPlane = FPlane3d(FVector3d::UnitZ(), GroundPlaneOrigin); bHaveGroundClipPlane = true; } Progress.EnterProgressFrame(1.f, LOCTEXT("SolidifyMesh", "Approximating Mesh...")); TRACE_BOOKMARK(TEXT("ApproximateActors-Solidify")); FDynamicMesh3 SolidMesh; { // Do in local scope so that memory allocated in Solidify is released after SolidMesh is available // TODO: SolidMesh could be replaced with a FColliderMesh if TImplicitMorphology would allow it TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Solidify); FWindingNumberBasedSolidify Solidify( [&Scene](const FVector3d& Position) { return Scene->FastWindingNumber(Position, true); }, SceneBounds, SeedPoints); Solidify.SetCellSizeAndExtendBounds(SceneBounds, 2.0 * ApproxAccuracy, VoxelDimTarget); Solidify.WindingThreshold = Options.WindingThreshold; SolidMesh = FDynamicMesh3(&Solidify.Generate()); SolidMesh.DiscardAttributes(); } // if solid mesh has no triangles, something has gone wrong if (SolidMesh.TriangleCount() == 0) { UE_LOG(LogApproximateActors, Warning, TEXT("Solidify mesh has no triangles - unable to generate mesh")); Result->ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::MeshGenerationFailed; return Result; } // CurResultMesh will point to the "current" result and we will update this pointer as we // step through various stages of the generation process FDynamicMesh3* CurResultMesh = &SolidMesh; if (Options.bVerbose) { UE_LOG(LogApproximateActors, Log, TEXT("Solidify mesh has %d triangles"), CurResultMesh->TriangleCount()); } // we are done w/ the FMeshSceneAdapter now and can free it's memory Scene.Reset(); SeedPoints.Empty(); Progress.EnterProgressFrame(1.f, LOCTEXT("ClosingMesh", "Topological Operations...")); // do topological closure to fix small gaps/etc FDynamicMesh3 MorphologyMesh; if (Options.bApplyMorphology) { TRACE_BOOKMARK(TEXT("ApproximateActors-Morphology")); TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Morphology); double MorphologyDistance = Options.MorphologyDistanceMeters * 100.0; // convert to cm FAxisAlignedBox3d MorphologyBounds = CurResultMesh->GetBounds(); FDynamicMeshAABBTree3 MorphologyBVTree(CurResultMesh); TImplicitMorphology ImplicitMorphology; ImplicitMorphology.MorphologyOp = TImplicitMorphology::EMorphologyOp::Close; ImplicitMorphology.Source = CurResultMesh; ImplicitMorphology.SourceSpatial = &MorphologyBVTree; ImplicitMorphology.SetCellSizesAndDistance(MorphologyBounds, MorphologyDistance, VoxelDimTarget, VoxelDimTarget); MorphologyMesh = FDynamicMesh3(&ImplicitMorphology.Generate()); MorphologyMesh.DiscardAttributes(); CurResultMesh = &MorphologyMesh; SolidMesh = FDynamicMesh3(); // we are done with SolidMesh now, release it's memory if (Options.bVerbose) { UE_LOG(LogApproximateActors, Log, TEXT("Morphology mesh has %d triangles"), CurResultMesh->TriangleCount()); } } // TODO: try doing base clipping here to speed up simplification? slight risk of introducing border issues... // if mesh has no triangles, something has gone wrong if (CurResultMesh == nullptr || CurResultMesh->TriangleCount() == 0) { Result->ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::MeshGenerationFailed; return Result; } Progress.EnterProgressFrame(1.f, LOCTEXT("SimplifyingMesh", "Simplifying Mesh...")); TRACE_BOOKMARK(TEXT("ApproximateActors-Simplify")); FVolPresMeshSimplification Simplifier(CurResultMesh); Simplifier.ProjectionMode = FVolPresMeshSimplification::ETargetProjectionMode::NoProjection; Simplifier.DEBUG_CHECK_LEVEL = 0; Simplifier.bAllowSeamCollapse = false; int32 BaseTargeTriCount = Options.FixedTriangleCount; { int32 BeforeCount = CurResultMesh->TriangleCount(); TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification); if (Options.MeshSimplificationPolicy == IGeometryProcessing_ApproximateActors::ESimplificationPolicy::TrianglesPerUnitSqMeter) { FVector2d VolArea = TMeshQueries::GetVolumeArea(*CurResultMesh); double MeshAreaMeterSqr = VolArea.Y * 0.0001; int32 AreaBaseTargetTriCount = MeshAreaMeterSqr * Options.SimplificationTargetMetric; // do initial fast-collapse pass if enabled if (Options.bEnableFastSimplifyPrePass && CurResultMesh->TriangleCount() > 10 * AreaBaseTargetTriCount) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification_PrePass); Simplifier.FastCollapsePass(ApproxAccuracy, 10, false, 5 * AreaBaseTargetTriCount); } { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification_Pass1); Simplifier.SimplifyToTriangleCount(AreaBaseTargetTriCount); } } else if (Options.MeshSimplificationPolicy == IGeometryProcessing_ApproximateActors::ESimplificationPolicy::GeometricTolerance) { double UseTargetTolerance = Options.SimplificationTargetMetric * 100.0; // convert to cm (UE Units) // do initial fast-collapse pass if enabled if (Options.bEnableFastSimplifyPrePass && CurResultMesh->TriangleCount() > 1000000) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification_PrePass); Simplifier.FastCollapsePass(ApproxAccuracy, 10, false, 1000000); } // simplify down to a reasonable tri count, as geometric metric is (relatively) expensive // (still, this is all incredibly cheap compared to the cost of the rest of this method in practice) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification_Pass1); Simplifier.SimplifyToTriangleCount(50000); } // make copy of mesh geometry to use for geometric error measurement FColliderMesh Collider; Collider.Initialize(*CurResultMesh); FColliderMeshProjectionTarget ColliderTarget(&Collider); Simplifier.SetProjectionTarget(&ColliderTarget); Simplifier.GeometricErrorConstraint = FVolPresMeshSimplification::EGeometricErrorCriteria::PredictedPointToProjectionTarget; Simplifier.GeometricErrorTolerance = UseTargetTolerance; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification_Pass2); Simplifier.SimplifyToTriangleCount(8); } } else { // do initial fast-collapse pass if enabled int32 FastCollapseThresh = FMath::Max(1000000, 2 * BaseTargeTriCount); if (Options.bEnableFastSimplifyPrePass && CurResultMesh->TriangleCount() > FastCollapseThresh) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Simplification_PrePass); Simplifier.FastCollapsePass(ApproxAccuracy, 10, false, FastCollapseThresh); } Simplifier.SimplifyToTriangleCount(BaseTargeTriCount); } int32 AfterCount = CurResultMesh->TriangleCount(); if (Options.bVerbose) { UE_LOG(LogApproximateActors, Log, TEXT("Simplified mesh from %d to %d triangles"), BeforeCount, AfterCount); } } Progress.EnterProgressFrame(1.f, LOCTEXT("RemoveHidden", "Removing Hidden Geometry...")); TRACE_BOOKMARK(TEXT("ApproximateActors-Remove Hidden")); if (Options.OcclusionPolicy == IGeometryProcessing_ApproximateActors::EOcclusionPolicy::VisibilityBased) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Occlusion); TRemoveOccludedTriangles Remover(CurResultMesh); Remover.InsideMode = EOcclusionCalculationMode::SimpleOcclusionTest; Remover.TriangleSamplingMethod = EOcclusionTriangleSampling::VerticesAndCentroids; Remover.AddTriangleSamples = 50; Remover.AddRandomRays = 50; FDynamicMeshAABBTree3 CurResultMeshSpatial(CurResultMesh, false); { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Occlusion_Spatial); CurResultMeshSpatial.Build(); } TArray NoTransforms; NoTransforms.Add(FTransformSRT3d::Identity()); TArray Spatials; Spatials.Add(&CurResultMeshSpatial); FAxisAlignedBox3d Bounds = CurResultMesh->GetBounds(); FDynamicMesh3 BasePlaneOccluderMesh; FDynamicMeshAABBTree3 BasePlaneOccluderSpatial; if (Options.bAddDownwardFacesOccluder) { FRectangleMeshGenerator RectGen; RectGen.Origin = Bounds.Center(); RectGen.Origin.Z = Bounds.Min.Z - 1.0; RectGen.Normal = FVector3f::UnitZ(); RectGen.Width = RectGen.Height = 10.0 * Bounds.MaxDim(); BasePlaneOccluderMesh.Copy(&RectGen.Generate()); BasePlaneOccluderSpatial.SetMesh(&BasePlaneOccluderMesh, true); NoTransforms.Add(FTransformSRT3d::Identity()); Spatials.Add(&BasePlaneOccluderSpatial); } { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Occlusion_Compute); Remover.Select(NoTransforms, Spatials, {}, NoTransforms); } int32 NumRemoved = 0; if (Remover.RemovedT.Num() > 0) { FMeshFaceSelection Selection(CurResultMesh); { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Occlusion_Clean); Selection.Select(Remover.RemovedT); Selection.ExpandToOneRingNeighbours(1); Selection.ContractBorderByOneRingNeighbours(2); // select any tris w/ all verts below clip plane if (Options.GroundPlaneClippingPolicy == IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::DiscardFullyHiddenFaces) { if (bHaveGroundClipPlane) { for (int32 tid : CurResultMesh->TriangleIndicesItr()) { FVector3d A, B, C; CurResultMesh->GetTriVertices(tid, A, B, C); if (GroundClipPlane.WhichSide(A) <= 0 && GroundClipPlane.WhichSide(B) <= 0 && GroundClipPlane.WhichSide(C) <= 0) { Selection.Select(tid); } } } else { UE_LOG(LogApproximateActors, Warning, TEXT("DiscardFullyHiddenFaces Ground Plane Clipping Policy ignored because no Ground Clip Plane is set")); } } } FDynamicMeshEditor Editor(CurResultMesh); { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Occlusion_Delete); TArray SelectionArray(Selection.AsArray()); NumRemoved = SelectionArray.Num(); Editor.RemoveTriangles(SelectionArray, true); } } if (Options.bVerbose) { UE_LOG(LogApproximateActors, Log, TEXT("Occlusion-Filtered mesh has %d triangles (removed %d)"), CurResultMesh->TriangleCount(), NumRemoved); } } // if occlusion removed all the triangles, fail if (CurResultMesh->TriangleCount() == 0) { Result->ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::MeshGenerationFailed; return Result; } TRACE_BOOKMARK(TEXT("ApproximateActors-Clip Ground")); if (Options.GroundPlaneClippingPolicy == IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::CutFaces || Options.GroundPlaneClippingPolicy == IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::CutFacesAndFill) { if (bHaveGroundClipPlane) { FMeshPlaneCut PlaneCut(CurResultMesh, GroundPlaneOrigin, -GroundClipPlane.Normal); PlaneCut.Cut(); if (Options.GroundPlaneClippingPolicy == IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::CutFacesAndFill) { PlaneCut.HoleFill(ConstrainedDelaunayTriangulate, true); } } else { UE_LOG(LogApproximateActors, Warning, TEXT("Ground Plane Cut/Fill Policy ignored because no Ground Clip Plane is set")); } } // if clipping removed all the triangles, fail if (CurResultMesh->TriangleCount() == 0) { Result->ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::MeshGenerationFailed; return Result; } TRACE_BOOKMARK(TEXT("ApproximateActors-Normals and UVs")); // re-enable attributes CurResultMesh->EnableAttributes(); // TODO: clip hidden triangles against occluder geo like landscape // compute normals { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Normals); if (Options.bCalculateHardNormals) { FMeshNormals::InitializeOverlayTopologyFromOpeningAngle(CurResultMesh, CurResultMesh->Attributes()->PrimaryNormals(), Options.HardNormalsAngleDeg); FMeshNormals::QuickRecomputeOverlayNormals(*CurResultMesh); } else { FMeshNormals::InitializeOverlayToPerVertexNormals(CurResultMesh->Attributes()->PrimaryNormals()); } } // exit here if we are just generating a merged collision mesh if (Options.BasePolicy == IGeometryProcessing_ApproximateActors::EApproximationPolicy::CollisionMesh) { Result->ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::Success; Result->bHaveMesh = true; Result->Mesh = MoveTemp(*CurResultMesh); return Result; } Progress.EnterProgressFrame(1.f, LOCTEXT("ComputingUVs", "Computing UVs...")); // compute UVs bool bHaveValidUVs = true; TSharedPtr UVInputMesh = MakeShared(); // PatchBuilder AutoUV currently requires compact input mesh. TODO: fix that, then we can just re-use CurResultMesh here. UVInputMesh->CompactCopy(*CurResultMesh); //*UVInputMesh = MoveTemp(*CurResultMesh); FParameterizeMeshOp ParameterizeMeshOp; ParameterizeMeshOp.Stretch = Options.UVAtlasStretchTarget; // UVAtlas parameters ParameterizeMeshOp.NumCharts = 0; // PatchBuilder generation parameters ParameterizeMeshOp.InitialPatchCount = Options.PatchBuilderInitialPatchCount; ParameterizeMeshOp.bRespectInputGroups = false; ParameterizeMeshOp.PatchCurvatureAlignmentWeight = Options.PatchBuilderCurvatureAlignment; ParameterizeMeshOp.PatchMergingMetricThresh = Options.PatchBuilderMergingThreshold; ParameterizeMeshOp.PatchMergingAngleThresh = Options.PatchBuilderMaxNormalDeviationDeg; ParameterizeMeshOp.ExpMapNormalSmoothingSteps = 5; ParameterizeMeshOp.ExpMapNormalSmoothingAlpha = 0.25; ParameterizeMeshOp.InputMesh = UVInputMesh; ParameterizeMeshOp.Method = UE::Geometry::EParamOpBackend::XAtlas; if (Options.UVPolicy == IGeometryProcessing_ApproximateActors::EUVGenerationPolicy::PreferUVAtlas) { ParameterizeMeshOp.Method = UE::Geometry::EParamOpBackend::UVAtlas; } else if (Options.UVPolicy == IGeometryProcessing_ApproximateActors::EUVGenerationPolicy::PreferPatchBuilder) { ParameterizeMeshOp.Method = UE::Geometry::EParamOpBackend::PatchBuilder; } FProgressCancel UVProgressCancel; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_GenerateUVs); ParameterizeMeshOp.CalculateResult(&UVProgressCancel); } TUniquePtr FinalMesh; FGeometryResult UVResultInfo = ParameterizeMeshOp.GetResultInfo(); if (! UVResultInfo.HasResult()) { UE_LOG(LogApproximateActors, Warning, TEXT("UV Auto-Generation Failed for target path %s"), *Options.BasePackagePath); bHaveValidUVs = false; FinalMesh = MakeUnique(MoveTemp(*UVInputMesh)); } else { FinalMesh = ParameterizeMeshOp.ExtractResult(); } // if UVs failed, fall back to box projection if (!bHaveValidUVs) { FDynamicMeshUVEditor UVEditor(FinalMesh.Get(), 0, true); TArray AllTriangles; for (int32 tid : FinalMesh->TriangleIndicesItr()) { AllTriangles.Add(tid); } UVEditor.SetTriangleUVsFromBoxProjection(AllTriangles, [&](const FVector3d& P) { return P; }, FFrame3d(FinalMesh->GetBounds().Center()), FVector3d::One()); bHaveValidUVs = true; } Progress.EnterProgressFrame(1.f, LOCTEXT("PackingUVs", "Packing UVs...")); // repack UVs if (bHaveValidUVs) { FDynamicMeshUVOverlay* RepackUVLayer = FinalMesh->Attributes()->PrimaryUV(); RepackUVLayer->SplitBowties(); FDynamicMeshUVPacker Packer(RepackUVLayer); Packer.TextureResolution = Options.TextureImageSize / 4; // maybe too conservative? We don't have gutter control currently. Packer.GutterSize = 1.0; // not clear this works Packer.bAllowFlips = false; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_PackUVs); bool bPackingOK = Packer.StandardPack(); if (!bPackingOK) { UE_LOG(LogApproximateActors, Warning, TEXT("UV Packing Failed for target path %s"), *Options.BasePackagePath); } } } Progress.EnterProgressFrame(1.f, LOCTEXT("ComputingTangents", "Computing Tangents...")); Result->ResultCode = IGeometryProcessing_ApproximateActors::EResultCode::Success; Result->bHaveMesh = true; Result->Mesh = MoveTemp(*FinalMesh); // compute tangents Result->bHaveTangents = true; Result->Tangents.SetMesh(&Result->Mesh); FComputeTangentsOptions TangentsOptions; TangentsOptions.bAveraged = true; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Tangents); Result->Tangents.ComputeTriVertexTangents( Result->Mesh.Attributes()->PrimaryNormals(), Result->Mesh.Attributes()->PrimaryUV(), TangentsOptions); } return Result; } IGeometryProcessing_ApproximateActors::FOptions FApproximateActorsImpl::ConstructOptions(const FMeshApproximationSettings& UseSettings) { // // Construct options for ApproximateActors operation // FOptions Options; Options.BasePolicy = (UseSettings.OutputType == EMeshApproximationType::MeshShapeOnly) ? IGeometryProcessing_ApproximateActors::EApproximationPolicy::CollisionMesh : IGeometryProcessing_ApproximateActors::EApproximationPolicy::MeshAndGeneratedMaterial; Options.MeshDataLODPolicy = (UseSettings.bUseRenderLODMeshes) ? EMeshDataSourceLODPolicy::LOD0RenderMeshes : EMeshDataSourceLODPolicy::LOD0SourceMeshes; Options.WorldSpaceApproximationAccuracyMeters = UseSettings.ApproximationAccuracy; Options.bAutoThickenThinParts = UseSettings.bAttemptAutoThickening; Options.AutoThickenThicknessMeters = UseSettings.TargetMinThicknessMultiplier * UseSettings.ApproximationAccuracy; Options.bIgnoreTinyParts = UseSettings.bIgnoreTinyParts; Options.TinyPartMaxDimensionMeters = UseSettings.TinyPartSizeMultiplier * UseSettings.ApproximationAccuracy; Options.BaseCappingPolicy = IGeometryProcessing_ApproximateActors::EBaseCappingPolicy::NoBaseCapping; if (UseSettings.BaseCapping == EMeshApproximationBaseCappingType::ConvexPolygon) { Options.BaseCappingPolicy = IGeometryProcessing_ApproximateActors::EBaseCappingPolicy::ConvexPolygon; } else if (UseSettings.BaseCapping == EMeshApproximationBaseCappingType::ConvexSolid) { Options.BaseCappingPolicy = IGeometryProcessing_ApproximateActors::EBaseCappingPolicy::ConvexSolid; } Options.ClampVoxelDimension = UseSettings.ClampVoxelDimension; Options.WindingThreshold = UseSettings.WindingThreshold; Options.bApplyMorphology = UseSettings.bFillGaps; Options.MorphologyDistanceMeters = UseSettings.GapDistance; if (UseSettings.GroundClipping == EMeshApproximationGroundPlaneClippingPolicy::NoGroundClipping) { Options.GroundPlanePolicy = EGroundPlanePolicy::NoGroundPlane; Options.GroundPlaneClippingPolicy = IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::NoClipping; } else if (UseSettings.GroundClipping == EMeshApproximationGroundPlaneClippingPolicy::DiscardWithZPlane) { Options.GroundPlanePolicy = EGroundPlanePolicy::FixedZHeightGroundPlane; Options.GroundPlaneZHeight = UseSettings.GroundClippingZHeight; Options.GroundPlaneClippingPolicy = IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::DiscardFullyHiddenFaces; } else if (UseSettings.GroundClipping == EMeshApproximationGroundPlaneClippingPolicy::CutWithZPlane) { Options.GroundPlanePolicy = EGroundPlanePolicy::FixedZHeightGroundPlane; Options.GroundPlaneZHeight = UseSettings.GroundClippingZHeight; Options.GroundPlaneClippingPolicy = IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::CutFaces; } else if (UseSettings.GroundClipping == EMeshApproximationGroundPlaneClippingPolicy::CutAndFillWithZPlane) { Options.GroundPlanePolicy = EGroundPlanePolicy::FixedZHeightGroundPlane; Options.GroundPlaneZHeight = UseSettings.GroundClippingZHeight; Options.GroundPlaneClippingPolicy = IGeometryProcessing_ApproximateActors::EGroundPlaneClippingPolicy::CutFacesAndFill; } Options.OcclusionPolicy = (UseSettings.OcclusionMethod == EOccludedGeometryFilteringPolicy::VisibilityBasedFiltering) ? IGeometryProcessing_ApproximateActors::EOcclusionPolicy::VisibilityBased : IGeometryProcessing_ApproximateActors::EOcclusionPolicy::None; Options.bAddDownwardFacesOccluder = UseSettings.bOccludeFromBottom; Options.FixedTriangleCount = UseSettings.TargetTriCount; if (UseSettings.SimplifyMethod == EMeshApproximationSimplificationPolicy::TrianglesPerArea) { Options.MeshSimplificationPolicy = IGeometryProcessing_ApproximateActors::ESimplificationPolicy::TrianglesPerUnitSqMeter; Options.SimplificationTargetMetric = UseSettings.TrianglesPerM; } else if (UseSettings.SimplifyMethod == EMeshApproximationSimplificationPolicy::GeometricTolerance) { Options.MeshSimplificationPolicy = IGeometryProcessing_ApproximateActors::ESimplificationPolicy::GeometricTolerance; Options.SimplificationTargetMetric = UseSettings.GeometricDeviation; } else { Options.MeshSimplificationPolicy = IGeometryProcessing_ApproximateActors::ESimplificationPolicy::FixedTriangleCount; } Options.bEnableFastSimplifyPrePass = UseSettings.bEnableSimplifyPrePass; Options.UVPolicy = IGeometryProcessing_ApproximateActors::EUVGenerationPolicy::PreferXAtlas; if (UseSettings.UVGenerationMethod == EMeshApproximationUVGenerationPolicy::PreferUVAtlas) { Options.UVPolicy = IGeometryProcessing_ApproximateActors::EUVGenerationPolicy::PreferUVAtlas; } else if (UseSettings.UVGenerationMethod == EMeshApproximationUVGenerationPolicy::PreferPatchBuilder) { Options.UVPolicy = IGeometryProcessing_ApproximateActors::EUVGenerationPolicy::PreferPatchBuilder; } Options.PatchBuilderInitialPatchCount = FMath::Clamp(UseSettings.InitialPatchCount, 1, 99999); Options.PatchBuilderCurvatureAlignment = FMath::Clamp(UseSettings.CurvatureAlignment, 0.001, 1000.0); Options.PatchBuilderMergingThreshold = FMath::Clamp(UseSettings.MergingThreshold, 1.0, 9999.0); Options.PatchBuilderMaxNormalDeviationDeg = FMath::Clamp(UseSettings.MaxAngleDeviation, 0.0, 180); Options.bCalculateHardNormals = UseSettings.bEstimateHardNormals; Options.HardNormalsAngleDeg = FMath::Clamp(UseSettings.HardNormalAngle, 0.001f, 89.99f); Options.TextureImageSize = UseSettings.MaterialSettings.TextureSize.X; if (UseSettings.MaterialSettings.TextureSizingType == TextureSizingType_UseSingleTextureSize) { Options.TextureSizePolicy = ETextureSizePolicy::TextureSize; } else if (UseSettings.MaterialSettings.TextureSizingType == TextureSizingType_UseAutomaticBiasedSizes) { Options.TextureSizePolicy = ETextureSizePolicy::CustomTextureSize; int32 NormalSize = Options.TextureImageSize; int32 BaseColorSize = FMath::Max(Options.TextureImageSize >> 1, 32); int32 PropertiesSize = FMath::Max(Options.TextureImageSize >> 2, 16); Options.CustomTextureSizeBaseColor = BaseColorSize; Options.CustomTextureSizeRoughness = PropertiesSize; Options.CustomTextureSizeMetallic = PropertiesSize; Options.CustomTextureSizeSpecular = PropertiesSize; Options.CustomTextureSizeEmissive = PropertiesSize; Options.CustomTextureSizeNormalMap = NormalSize; } else if (UseSettings.MaterialSettings.TextureSizingType == TextureSizingType_UseManualOverrideTextureSize) { Options.TextureSizePolicy = ETextureSizePolicy::CustomTextureSize; Options.CustomTextureSizeBaseColor = UseSettings.MaterialSettings.DiffuseTextureSize.X; Options.CustomTextureSizeRoughness = UseSettings.MaterialSettings.RoughnessTextureSize.X; Options.CustomTextureSizeMetallic = UseSettings.MaterialSettings.MetallicTextureSize.X; Options.CustomTextureSizeSpecular = UseSettings.MaterialSettings.SpecularTextureSize.X; Options.CustomTextureSizeEmissive = UseSettings.MaterialSettings.EmissiveTextureSize.X; Options.CustomTextureSizeNormalMap = UseSettings.MaterialSettings.NormalTextureSize.X; } Options.AntiAliasMultiSampling = FMath::Max(1, UseSettings.MultiSamplingAA); Options.RenderCaptureImageSize = (UseSettings.RenderCaptureResolution == 0) ? Options.TextureImageSize : UseSettings.RenderCaptureResolution; Options.FieldOfViewDegrees = UseSettings.CaptureFieldOfView; Options.NearPlaneDist = UseSettings.NearPlaneDist; Options.bMaximizeBakeParallelism = UseSettings.bEnableParallelBaking; Options.bVerbose = UseSettings.bPrintDebugMessages; Options.bWriteDebugMesh = UseSettings.bEmitFullDebugMesh; // Nanite settings Options.bGenerateNaniteEnabledMesh = UseSettings.bGenerateNaniteEnabledMesh; Options.NaniteFallbackTarget = UseSettings.NaniteFallbackTarget == ::ENaniteFallbackTarget::Auto ? IGeometryProcessing_ApproximateActors::ENaniteFallbackTarget::Auto : (UseSettings.NaniteFallbackTarget == ::ENaniteFallbackTarget::PercentTriangles ? IGeometryProcessing_ApproximateActors::ENaniteFallbackTarget::PercentTriangles : IGeometryProcessing_ApproximateActors::ENaniteFallbackTarget::RelativeError); Options.NaniteFallbackPercentTriangles = UseSettings.NaniteFallbackPercentTriangles; Options.NaniteFallbackRelativeError = UseSettings.NaniteFallbackRelativeError; // Distance field Options.bAllowDistanceField = UseSettings.bAllowDistanceField; // Ray tracing Options.bSupportRayTracing = UseSettings.bSupportRayTracing; // Material properties baking Options.bBakeBaseColor = true; Options.bBakeRoughness = UseSettings.MaterialSettings.bRoughnessMap; Options.bBakeMetallic = UseSettings.MaterialSettings.bMetallicMap; Options.bBakeSpecular = UseSettings.MaterialSettings.bSpecularMap; Options.bBakeEmissive = UseSettings.MaterialSettings.bEmissiveMap; Options.bBakeNormalMap = UseSettings.MaterialSettings.bNormalMap; return Options; } void FApproximateActorsImpl::ApproximateActors(const FInput& Input, const FOptions& Options, FResults& ResultsOut) { int32 ActorClusters = 1; FScopedSlowTask Progress(1.f, LOCTEXT("ApproximatingActors", "Generating Actor Approximation...")); Progress.MakeDialog(true); Progress.EnterProgressFrame(1.f); GenerateApproximationForActorSet(Input, Options, ResultsOut); } void FApproximateActorsImpl::GenerateApproximationForActorSet(const FInput& Input, const FOptions& Options, FResults& ResultsOut) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate); RenderCaptureInterface::FScopedCapture RenderCapture(CVarApproximateActorsRDOCCapture.GetValueOnAnyThread() == 1, TEXT("ApproximateActors")); if (Options.BasePolicy == IGeometryProcessing_ApproximateActors::EApproximationPolicy::MeshAndGeneratedMaterial) { // The scene capture photoset part of this process relies on debug view modes being available. // If it ain't the case, fail immediatelly if (!AllowDebugViewmodes()) { UE_LOG(LogApproximateActors, Error, TEXT("Debug view modes not are available - unable to generate material")); ResultsOut.ResultCode = EResultCode::MaterialGenerationFailed; return; } } // // extract all visible meshes from the list of Actors and build up a "mesh scene" that represents the // assembly of geometry, taking advantage of instancing where possible, but also pulling those meshes // apart and processing them if necessary to (eg) thicken them, etc, so that later processing works better. // FMeshSceneAdapter does all the heavy lifting, here it is just being configured and built // FScopedSlowTask Progress(11.f, LOCTEXT("ApproximatingActors", "Generating Actor Approximation...")); Progress.EnterProgressFrame(1.f, LOCTEXT("BuildingScene", "Building Scene...")); float ApproxAccuracy = Options.WorldSpaceApproximationAccuracyMeters * 100.0; // convert to cm (UE Units) TUniquePtr Scene = MakeUnique(); FMeshSceneAdapterBuildOptions SceneBuildOptions; SceneBuildOptions.bIgnoreStaticMeshSourceData = (Options.MeshDataLODPolicy == EMeshDataSourceLODPolicy::LOD0RenderMeshes); // default is false SceneBuildOptions.bThickenThinMeshes = Options.bAutoThickenThinParts; SceneBuildOptions.DesiredMinThickness = Options.AutoThickenThicknessMeters * 100.0; // convert to cm (UE Units) // filter out objects smaller than 10% of voxel size SceneBuildOptions.bFilterTinyObjects = Options.bIgnoreTinyParts; SceneBuildOptions.TinyObjectBoxMaxDimension = Options.TinyPartMaxDimensionMeters; SceneBuildOptions.bOnlySurfaceMaterials = true; // don't include decal geometry in 3D mesh scene (will be included in renderings) SceneBuildOptions.bEnableUVQueries = SceneBuildOptions.bEnableNormalsQueries = false; // not required in this context, will reduce memory usage SceneBuildOptions.bPrintDebugMessages = Options.bVerbose; { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_BuildScene); TRACE_BOOKMARK(TEXT("ApproximateActors-Adding Actors")); Scene->AddActors(Input.Actors); TRACE_BOOKMARK(TEXT("ApproximateActors-Adding Components")); Scene->AddComponents(Input.Components); } if (!Scene->IsValid()) { UE_LOG(LogApproximateActors, Warning, TEXT("No valid input actors/components - unable to generate mesh")); ResultsOut.ResultCode = EResultCode::MeshGenerationFailed; return; } { TRACE_BOOKMARK(TEXT("ApproximateActors-Building Scene")); Scene->Build(SceneBuildOptions); } if (Options.bVerbose) { FMeshSceneAdapter::FStatistics Stats; Scene->GetGeometryStatistics(Stats); UE_LOG(LogApproximateActors, Log, TEXT("%lld triangles in %lld unique meshes, total %lld triangles in %lld instances"), Stats.UniqueMeshTriangleCount, Stats.UniqueMeshCount, Stats.InstanceMeshTriangleCount, Stats.InstanceMeshCount); } // add a "base cap" if desired, this helps with (eg) large meshes with no base like a 3D scan of a mountain, etc if (Options.BaseCappingPolicy != EBaseCappingPolicy::NoBaseCapping) { TRACE_BOOKMARK(TEXT("ApproximateActors-Capping")); TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_Capping); double UseThickness = 0.0; if (Options.BaseCappingPolicy == EBaseCappingPolicy::ConvexSolid) { UseThickness = (Options.BaseThicknessOverrideMeters != 0) ? (Options.BaseThicknessOverrideMeters * 100.0) : (Options.bAutoThickenThinParts ? SceneBuildOptions.DesiredMinThickness : 1.25 * ApproxAccuracy); } double UseHeight = (Options.BaseHeightOverrideMeters != 0) ? (Options.BaseHeightOverrideMeters * 100.0) : (2.0 * ApproxAccuracy); Scene->GenerateBaseClosingMesh(UseHeight, UseThickness); } FDynamicMesh3 DebugMesh; FDynamicMesh3* WriteDebugMesh = nullptr; if (Options.bWriteDebugMesh) { TRACE_CPUPROFILER_EVENT_SCOPE(ApproximateActorsImpl_Generate_DebugMesh); DebugMesh.EnableAttributes(); Scene->GetAccumulatedMesh(DebugMesh); FMeshNormals::InitializeMeshToPerTriangleNormals(&DebugMesh); WriteDebugMesh = &DebugMesh; } // build spatial evaluation cache in the FMeshSceneAdapter, necessary for the mesh build below TRACE_BOOKMARK(TEXT("ApproximateActors-Building Cache")); Scene->BuildSpatialEvaluationCache(); // // Generate a new mesh that approximates the entire scene represented by FMeshSceneAdapter. // TRACE_BOOKMARK(TEXT("ApproximateActors-Building Approx Mesh")); // Pass ownership of the Scene to GenerateApproximationMesh() so that it can delete it as soon // as possible (because it is often very large, memory-wise) TFuture> GenerateMeshFuture = Async(EAsyncExecution::ThreadPool, [&Scene, Options, ApproxAccuracy]() { return GenerateApproximationMesh( MoveTemp(Scene), Options, ApproxAccuracy); }); FDynamicMesh3 FinalMesh; FMeshTangentsd FinalMeshTangents; auto WaitForMeshAvailable = [&]() { TSharedPtr ApproximationMeshData = GenerateMeshFuture.Get(); ResultsOut.ResultCode = ApproximationMeshData->ResultCode; FinalMesh = MoveTemp(ApproximationMeshData->Mesh); FinalMeshTangents = MoveTemp(ApproximationMeshData->Tangents); }; // if we are only generating collision mesh, we are done now if (Options.BasePolicy == IGeometryProcessing_ApproximateActors::EApproximationPolicy::CollisionMesh) { WaitForMeshAvailable(); if (ResultsOut.ResultCode == EResultCode::Success) { EmitGeneratedMeshAsset(Options, ResultsOut, &FinalMesh, nullptr, WriteDebugMesh); } return; } // if parallel capture is not allowed, force mesh computation to finish now if (Options.bMaximizeBakeParallelism == false) { WaitForMeshAvailable(); if (ResultsOut.ResultCode != EResultCode::Success) { return; } } // // create a set of spatially located render captures of the scene ("photo set"). // Progress.EnterProgressFrame(1.f, LOCTEXT("CapturingScene", "Capturing Scene...")); TRACE_BOOKMARK(TEXT("ApproximateActors-Capture Photos")); TUniquePtr SceneCapture = CapturePhotoSet(Input, Options); // if parallel capture was allowed, need to force the mesh compute to finish now to be able to proceed if (Options.bMaximizeBakeParallelism == true) { WaitForMeshAvailable(); if (ResultsOut.ResultCode != EResultCode::Success) { return; } } // // bake textures onto the generated approximation mesh by projecting/sampling // the set of captured photos // Progress.EnterProgressFrame(1.f, LOCTEXT("BakingTextures", "Baking Textures...")); TRACE_BOOKMARK(TEXT("ApproximateActors-Bake Textures")); FOptions OverridenOptions = Options; // evaluate required texture size if needed if (OverridenOptions.TextureSizePolicy == ETextureSizePolicy::TexelDensity) { OverridenOptions.TextureSizePolicy = ETextureSizePolicy::TextureSize; OverridenOptions.TextureImageSize = FMaterialUtilities::GetTextureSizeFromTargetTexelDensity(FinalMesh, OverridenOptions.MeshTexelDensity); } if (OverridenOptions.TextureSizePolicy == ETextureSizePolicy::TextureSize) { // Assign the same texture size to all properties OverridenOptions.TextureImageSize = FMath::RoundUpToPowerOfTwo(OverridenOptions.TextureImageSize); OverridenOptions.CustomTextureSizeBaseColor = OverridenOptions.TextureImageSize; OverridenOptions.CustomTextureSizeRoughness = OverridenOptions.TextureImageSize; OverridenOptions.CustomTextureSizeMetallic = OverridenOptions.TextureImageSize; OverridenOptions.CustomTextureSizeSpecular = OverridenOptions.TextureImageSize; OverridenOptions.CustomTextureSizeEmissive = OverridenOptions.TextureImageSize; OverridenOptions.CustomTextureSizeNormalMap = OverridenOptions.TextureImageSize; } else { OverridenOptions.CustomTextureSizeBaseColor = FMath::RoundUpToPowerOfTwo(OverridenOptions.CustomTextureSizeBaseColor); OverridenOptions.CustomTextureSizeRoughness = FMath::RoundUpToPowerOfTwo(OverridenOptions.CustomTextureSizeRoughness); OverridenOptions.CustomTextureSizeMetallic = FMath::RoundUpToPowerOfTwo(OverridenOptions.CustomTextureSizeMetallic); OverridenOptions.CustomTextureSizeSpecular = FMath::RoundUpToPowerOfTwo(OverridenOptions.CustomTextureSizeSpecular); OverridenOptions.CustomTextureSizeEmissive = FMath::RoundUpToPowerOfTwo(OverridenOptions.CustomTextureSizeEmissive); OverridenOptions.CustomTextureSizeNormalMap = FMath::RoundUpToPowerOfTwo(OverridenOptions.CustomTextureSizeNormalMap); // Store the maximum texture size in TextureImageSize. This will serve as the capture size for all properties in the baking cache. // Images will then be downscaled as needed for each property. OverridenOptions.TextureImageSize = OverridenOptions.CustomTextureSizeBaseColor; OverridenOptions.TextureImageSize = FMath::Max(OverridenOptions.TextureImageSize, OverridenOptions.CustomTextureSizeRoughness); OverridenOptions.TextureImageSize = FMath::Max(OverridenOptions.TextureImageSize, OverridenOptions.CustomTextureSizeMetallic); OverridenOptions.TextureImageSize = FMath::Max(OverridenOptions.TextureImageSize, OverridenOptions.CustomTextureSizeSpecular); OverridenOptions.TextureImageSize = FMath::Max(OverridenOptions.TextureImageSize, OverridenOptions.CustomTextureSizeEmissive); OverridenOptions.TextureImageSize = FMath::Max(OverridenOptions.TextureImageSize, OverridenOptions.CustomTextureSizeNormalMap); } // bake textures for Actor FGeneratedResultTextures GeneratedTextures; BakeTexturesFromPhotoCapture(SceneCapture, OverridenOptions, GeneratedTextures, &FinalMesh, &FinalMeshTangents); Progress.EnterProgressFrame(1.f, LOCTEXT("Writing Assets", "Writing Assets...")); TRACE_BOOKMARK(TEXT("ApproximateActors-Create Material")); // Make material for textures by creating MIC of input material, or fall back to known material UMaterialInterface* UseBaseMaterial = (Options.BakeMaterial != nullptr) ? Options.BakeMaterial : LoadObject(nullptr, TEXT("/MeshModelingToolsetExp/Materials/FullMaterialBakePreviewMaterial_PackedMRS")); FMaterialAssetOptions MatOptions; MatOptions.NewAssetPath = Options.BasePackagePath + TEXT("_Material"); FMaterialAssetResults MatResults; ECreateMaterialResult MatResult = UE::AssetUtils::CreateDerivedMaterialInstance(UseBaseMaterial, MatOptions, MatResults); UMaterialInstanceConstant* NewMaterial = nullptr; if (ensure(MatResult == ECreateMaterialResult::Ok)) { NewMaterial = MatResults.NewMaterialInstance; ResultsOut.NewMaterials.Add(NewMaterial); } // this lambda converts a generated texture to an Asset, and then assigns it to a parameter of the Material FString BaseTexturePath = MatOptions.NewAssetPath; auto WriteTextureLambda = [BaseTexturePath, NewMaterial, &ResultsOut]( UTexture2D* Texture, FString TextureTypeSuffix, FTexture2DBuilder::ETextureType Type, FName MaterialParamName ) { if (ensure(Texture != nullptr) == false) return; FTexture2DBuilder::CopyPlatformDataToSourceData(Texture, Type); if (Type == FTexture2DBuilder::ETextureType::Roughness || Type == FTexture2DBuilder::ETextureType::Metallic || Type == FTexture2DBuilder::ETextureType::Specular) { UE::AssetUtils::ConvertToSingleChannel(Texture); } // Make sure the texture is a VT if required by the material sampler if (NewMaterial != nullptr) { UTexture* DefaultTexture = nullptr; NewMaterial->GetTextureParameterValue(MaterialParamName, DefaultTexture); if (ensure(DefaultTexture)) { Texture->VirtualTextureStreaming = DefaultTexture->VirtualTextureStreaming; } } FTexture2DAssetOptions TexOptions; TexOptions.NewAssetPath = BaseTexturePath + TextureTypeSuffix; FTexture2DAssetResults Results; ECreateTexture2DResult TexResult = UE::AssetUtils::SaveGeneratedTexture2DAsset(Texture, TexOptions, Results); if (ensure(TexResult == ECreateTexture2DResult::Ok)) { ResultsOut.NewTextures.Add(Texture); if (NewMaterial != nullptr) { NewMaterial->SetTextureParameterValueEditorOnly(MaterialParamName, Texture); } } }; TRACE_BOOKMARK(TEXT("ApproximateActors-Write Textures")); // process the generated textures if (Options.bBakeBaseColor && GeneratedTextures.BaseColorMap) { WriteTextureLambda(GeneratedTextures.BaseColorMap, TEXT("_BaseColor"), FTexture2DBuilder::ETextureType::Color, Options.BaseColorTexParamName); } if (Options.bBakeEmissive && GeneratedTextures.EmissiveMap) { WriteTextureLambda(GeneratedTextures.EmissiveMap, TEXT("_Emissive"), FTexture2DBuilder::ETextureType::EmissiveHDR, Options.EmissiveTexParamName); } if (Options.bBakeNormalMap && GeneratedTextures.NormalMap) { WriteTextureLambda(GeneratedTextures.NormalMap, TEXT("_Normal"), FTexture2DBuilder::ETextureType::NormalMap, Options.NormalTexParamName); } if ((Options.bBakeRoughness || Options.bBakeMetallic || Options.bBakeSpecular) && Options.bUsePackedMRS && GeneratedTextures.PackedMRSMap) { WriteTextureLambda(GeneratedTextures.PackedMRSMap, TEXT("_PackedMRS"), FTexture2DBuilder::ETextureType::ColorLinear, Options.PackedMRSTexParamName); } if (Options.bBakeRoughness && GeneratedTextures.RoughnessMap) { WriteTextureLambda(GeneratedTextures.RoughnessMap, TEXT("_Roughness"), FTexture2DBuilder::ETextureType::Roughness, Options.RoughnessTexParamName); } if (Options.bBakeMetallic && GeneratedTextures.MetallicMap) { WriteTextureLambda(GeneratedTextures.MetallicMap, TEXT("_Metallic"), FTexture2DBuilder::ETextureType::Metallic, Options.MetallicTexParamName); } if (Options.bBakeSpecular && GeneratedTextures.SpecularMap) { WriteTextureLambda(GeneratedTextures.SpecularMap, TEXT("_Specular"), FTexture2DBuilder::ETextureType::Specular, Options.SpecularTexParamName); } // force material update now that we have updated texture parameters // (does this do that? Let calling code do it?) if (NewMaterial != nullptr) { NewMaterial->PostEditChange(); } EmitGeneratedMeshAsset(Options, ResultsOut, &FinalMesh, NewMaterial, WriteDebugMesh); ResultsOut.ResultCode = EResultCode::Success; } UStaticMesh* FApproximateActorsImpl::EmitGeneratedMeshAsset( const FOptions& Options, FResults& ResultsOut, FDynamicMesh3* FinalMesh, UMaterialInterface* Material, FDynamicMesh3* DebugMesh) { FStaticMeshAssetOptions MeshAssetOptions; MeshAssetOptions.CollisionType = ECollisionTraceFlag::CTF_UseSimpleAsComplex; MeshAssetOptions.bEnableRecomputeTangents = false; MeshAssetOptions.NewAssetPath = Options.BasePackagePath; MeshAssetOptions.SourceMeshes.DynamicMeshes.Add(FinalMesh); MeshAssetOptions.bGenerateNaniteEnabledMesh = Options.bGenerateNaniteEnabledMesh; MeshAssetOptions.NaniteSettings.bEnabled = Options.bGenerateNaniteEnabledMesh; MeshAssetOptions.NaniteSettings.FallbackTarget = Options.NaniteFallbackTarget == IGeometryProcessing_ApproximateActors::ENaniteFallbackTarget::Auto ? ::ENaniteFallbackTarget::Auto : (Options.NaniteFallbackTarget == IGeometryProcessing_ApproximateActors::ENaniteFallbackTarget::PercentTriangles ? ::ENaniteFallbackTarget::PercentTriangles : ::ENaniteFallbackTarget::RelativeError); MeshAssetOptions.NaniteSettings.FallbackPercentTriangles = Options.NaniteFallbackPercentTriangles; MeshAssetOptions.NaniteSettings.FallbackRelativeError = Options.NaniteFallbackRelativeError; MeshAssetOptions.bSupportRayTracing = Options.bSupportRayTracing; MeshAssetOptions.bAllowDistanceField = Options.bAllowDistanceField; MeshAssetOptions.bGenerateLightmapUVs = Options.bGenerateLightmapUVs; MeshAssetOptions.bCreatePhysicsBody = Options.bCreatePhysicsBody; MeshAssetOptions.bBuildReversedIndexBuffer = Options.bBuildReversedIndexBuffer; if (Material) { MeshAssetOptions.AssetMaterials.Add(Material); } else { MeshAssetOptions.AssetMaterials.Add(UMaterial::GetDefaultMaterial(MD_Surface)); } MeshAssetOptions.bDeferPostEditChange = true; // Defer static mesh build to control options FStaticMeshResults MeshAssetOutputs; ECreateStaticMeshResult ResultCode = UE::AssetUtils::CreateStaticMeshAsset(MeshAssetOptions, MeshAssetOutputs); ensure(ResultCode == ECreateStaticMeshResult::Ok); UStaticMesh::FBuildParameters BuildParameters; // Nanite meshes can sometime contain smaller triangles that could be discarded in the proxy mesh. // If those are found on the outskirt of a merged mesh, the static mesh build will complain about // the bounds delta being too high. BuildParameters.bIgnoreBoundsDiff = Options.bGenerateNaniteEnabledMesh; MeshAssetOutputs.StaticMesh->Build(BuildParameters); ResultsOut.NewMeshAssets.Add(MeshAssetOutputs.StaticMesh); if (DebugMesh != nullptr) { FStaticMeshAssetOptions DebugMeshAssetOptions = MeshAssetOptions; DebugMeshAssetOptions.NewAssetPath = Options.BasePackagePath + TEXT("_DEBUG"); DebugMeshAssetOptions.SourceMeshes.DynamicMeshes.Reset(1); DebugMeshAssetOptions.SourceMeshes.DynamicMeshes.Add(DebugMesh); FStaticMeshResults DebugMeshAssetOutputs; UE::AssetUtils::CreateStaticMeshAsset(DebugMeshAssetOptions, DebugMeshAssetOutputs); } return MeshAssetOutputs.StaticMesh; } #undef LOCTEXT_NAMESPACE