// Copyright Epic Games, Inc. All Rights Reserved. #include "BakeRenderCaptureTool.h" #include "TargetInterfaces/MaterialProvider.h" #include "TargetInterfaces/MeshDescriptionProvider.h" #include "TargetInterfaces/PrimitiveComponentBackedTarget.h" #include "TargetInterfaces/StaticMeshBackedTarget.h" #include "ToolTargetManager.h" #include "DynamicMesh/MeshTransforms.h" #include "ModelingToolTargetUtil.h" #include "ModelingObjectsCreationAPI.h" #include "EngineAnalytics.h" #include "Sampling/MeshImageBakingCache.h" #include "Sampling/MeshMapBaker.h" #include "Sampling/RenderCaptureMapEvaluator.h" #include "Image/ImageInfilling.h" #include "Algo/NoneOf.h" #include "Misc/ScopedSlowTask.h" using namespace UE::Geometry; #define LOCTEXT_NAMESPACE "UBakeRenderCaptureTool" // // Implementation details // class FSceneCapturePhotoSetSampler : public FMeshBakerDynamicMeshSampler { public: FSceneCapturePhotoSetSampler( FSceneCapturePhotoSet* SceneCapture, TFunctionRef VisibilityFunction, const FDynamicMesh3* Mesh, const FDynamicMeshAABBTree3* Spatial, const FMeshTangentsd* Tangents) : FMeshBakerDynamicMeshSampler(Mesh, Spatial, Tangents), SceneCapture(SceneCapture), VisibilityFunction(VisibilityFunction) { check(SceneCapture != nullptr); check(Mesh != nullptr); check(Spatial != nullptr); check(Tangents != nullptr); } virtual bool SupportsCustomCorrespondence() const override { return true; } // Warning: Expects that Sample.BaseSample.SurfacePoint and Sample.BaseNormal are set when the function is called virtual void* ComputeCustomCorrespondence(const FMeshUVSampleInfo& SampleInfo, FMeshMapEvaluator::FCorrespondenceSample& Sample) const override { // Perform a ray-cast to determine which photo/coordinate, if any, should be sampled int PhotoIndex; FVector2d PhotoCoords; SceneCapture->ComputeSampleLocation(Sample.BaseSample.SurfacePoint, Sample.BaseNormal, VisibilityFunction, PhotoIndex, PhotoCoords); // Store the photo coordinates and index in the correspondence sample Sample.DetailMesh = SceneCapture; Sample.DetailTriID = PhotoIndex; Sample.DetailBaryCoords.X = PhotoCoords.X; Sample.DetailBaryCoords.Y = PhotoCoords.Y; // This will be set to Sample.DetailMesh but we can already do that internally so it's kindof redundant return SceneCapture; } virtual bool IsValidCorrespondence(const FMeshMapEvaluator::FCorrespondenceSample& Sample) const override { return Sample.DetailTriID != IndexConstants::InvalidID; } public: FSceneCapturePhotoSet* SceneCapture = nullptr; TFunctionRef VisibilityFunction; }; static FString BaseColorTexParamName = TEXT("BaseColor"); static FString RoughnessTexParamName = TEXT("Roughness"); static FString MetallicTexParamName = TEXT("Metallic"); static FString SpecularTexParamName = TEXT("Specular"); static FString EmissiveTexParamName = TEXT("Emissive"); static FString NormalTexParamName = TEXT("NormalMap"); static FString PackedMRSTexParamName = TEXT("PackedMRS"); class FRenderCaptureSettings { public: enum class ETextureSizePolicy : uint8 { TextureSize = 0, TexelDensity = 1 }; /** * Input options to Actor Approximation process */ struct FOptions { // // Material approximation settings // int32 RenderCaptureImageSize = 1024; // render capture parameters double FieldOfViewDegrees = 45.0; double NearPlaneDist = 1.0; // // Material output settings // // A new MIC derived from this material will be created and assigned to the generated mesh UMaterialInterface* BakeMaterial = nullptr; // if null, will use /MeshModelingToolsetExp/Materials/FullMaterialBakePreviewMaterial_PackedMRS instead bool bBakeBaseColor = true; bool bBakeRoughness = true; bool bBakeMetallic = true; bool bBakeSpecular = true; bool bBakeEmissive = true; bool bBakeNormalMap = true; bool bUsePackedMRS = true; // // Mesh settings // // Which UV layer of the Target mesh (the one we're baking to) should be used int32 TargetUVLayer = 0; }; /** * Construct an FOptions from the provided FMeshApproximationSettings. */ static FOptions ConstructOptions( const URenderCaptureProperties& RenderCaptureProperties, const UBakeRenderCaptureInputToolProperties& InputMeshSettings) { // // Construct options for ApproximateActors operation // FOptions Options; Options.TargetUVLayer = InputMeshSettings.GetTargetUVLayerIndex(); Options.RenderCaptureImageSize = static_cast(RenderCaptureProperties.Resolution); Options.bBakeBaseColor = RenderCaptureProperties.bBaseColorMap; Options.bBakeRoughness = RenderCaptureProperties.bRoughnessMap; Options.bBakeMetallic = RenderCaptureProperties.bMetallicMap; Options.bBakeSpecular = RenderCaptureProperties.bSpecularMap; Options.bBakeEmissive = RenderCaptureProperties.bEmissiveMap; Options.bBakeNormalMap = RenderCaptureProperties.bNormalMap; Options.bUsePackedMRS = RenderCaptureProperties.bPackedMRSMap; Options.FieldOfViewDegrees = RenderCaptureProperties.CaptureFieldOfView; Options.NearPlaneDist = RenderCaptureProperties.NearPlaneDist; return Options; } }; static TUniquePtr CapturePhotoSet( const TArray>& Actors, const FRenderCaptureSettings::FOptions& Options, bool bAllowCancel ) { TRACE_CPUPROFILER_EVENT_SCOPE(CapturePhotoSet); FScopedSlowTask Progress(1.f, LOCTEXT("CapturingScene", "Capturing Scene...")); Progress.EnterProgressFrame(1.f); Progress.MakeDialog(bAllowCancel); double FieldOfView = Options.FieldOfViewDegrees; double NearPlaneDist = Options.NearPlaneDist; FImageDimensions CaptureDimensions(Options.RenderCaptureImageSize, Options.RenderCaptureImageSize); TUniquePtr SceneCapture = MakeUnique(); SceneCapture->SetAllowCancel(bAllowCancel); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::BaseColor, Options.bBakeBaseColor); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::WorldNormal, Options.bBakeNormalMap); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Emissive, Options.bBakeEmissive); if (Options.bUsePackedMRS) { 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, Options.bBakeRoughness); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Metallic, Options.bBakeMetallic); SceneCapture->SetCaptureTypeEnabled(ERenderCaptureType::Specular, Options.bBakeSpecular); } SceneCapture->SetCaptureSceneActors(Actors[0]->GetWorld(), Actors); // SceneCapture->SetEnableWriteDebugImages(true); SceneCapture->AddStandardExteriorCapturesFromBoundingBox( CaptureDimensions, FieldOfView, NearPlaneDist, true, true, true, true, true); return SceneCapture; } template TSharedPtr> MakeColorEvaluator(const FSceneCapturePhotoSet::FSceneSample& DefaultSample, const FSceneCapturePhotoSet* SceneCapture) { TSharedPtr> Evaluator = MakeShared>(); switch (CaptureType) { case ERenderCaptureType::BaseColor: Evaluator->Channel = ERenderCaptureChannel::BaseColor; break; case ERenderCaptureType::Roughness: Evaluator->Channel = ERenderCaptureChannel::Roughness; break; case ERenderCaptureType::Metallic: Evaluator->Channel = ERenderCaptureChannel::Metallic; break; case ERenderCaptureType::Specular: Evaluator->Channel = ERenderCaptureChannel::Specular; break; case ERenderCaptureType::Emissive: Evaluator->Channel = ERenderCaptureChannel::Emissive; break; case ERenderCaptureType::WorldNormal: Evaluator->Channel = ERenderCaptureChannel::WorldNormal; break; case ERenderCaptureType::CombinedMRS: Evaluator->Channel = ERenderCaptureChannel::CombinedMRS; break; } Evaluator->DefaultResult = DefaultSample.GetValue4f(CaptureType); Evaluator->EvaluateSampleCallback = [&DefaultSample, SceneCapture](const FMeshMapEvaluator::FCorrespondenceSample& Sample) { const int PhotoIndex = Sample.DetailTriID; const FVector2d PhotoCoords(Sample.DetailBaryCoords.X, Sample.DetailBaryCoords.Y); const FVector4f SampleColor = SceneCapture->ComputeSample(PhotoIndex, PhotoCoords, DefaultSample); return SampleColor; }; Evaluator->EvaluateColorCallback = [](const int DataIdx, float*& In) { const FVector4f Out(In[0], In[1], In[2], In[3]); In += 4; return Out; }; return Evaluator; } // // Tool Operator // class FRenderCaptureMapBakerOp : public TGenericDataOperator { public: UE::Geometry::FDynamicMesh3* BaseMesh = nullptr; TSharedPtr BaseMeshTangents; FRenderCaptureSettings::FOptions Options; int32 TextureImageSize; EBakeTextureSamplesPerPixel SamplesPerPixel; FSceneCapturePhotoSet* SceneCapture = nullptr; // Begin TGenericDataOperator interface virtual void CalculateResult(FProgressCancel* Progress) override; // End TGenericDataOperator interface }; // Bake textures onto the base/target mesh by projecting/sampling the set of captured photos void FRenderCaptureMapBakerOp::CalculateResult(FProgressCancel*) { TRACE_CPUPROFILER_EVENT_SCOPE(FRenderCaptureMapBakerOp_CalculateResult); check(BaseMesh != nullptr); check(BaseMeshTangents.IsValid()); check(SceneCapture != nullptr); FDynamicMeshAABBTree3 BaseMeshSpatial(BaseMesh, true); double RayOffsetHackDist = (double)(100.0 * FMathf::ZeroTolerance * BaseMesh->GetBounds().MinDim() ); auto VisibilityFunction = [&BaseMeshSpatial, RayOffsetHackDist](const FVector3d& SurfPos, const FVector3d& ImagePosWorld) { FVector3d RayDir = ImagePosWorld - SurfPos; double Dist = Normalize(RayDir); FVector3d RayOrigin = SurfPos + RayOffsetHackDist * RayDir; int32 HitTID = BaseMeshSpatial.FindNearestHitTriangle(FRay3d(RayOrigin, RayDir), IMeshSpatial::FQueryOptions(Dist)); return (HitTID == IndexConstants::InvalidID); }; struct FInfillData { struct FSampleStats { uint16 NumValid = 0; uint16 NumInvalid = 0; // The ==, !=, += operators and the Zero() function are required by the TMarchingPixelInfill implementation bool operator==(const FSampleStats& Other) const { return (NumValid == Other.NumValid) && (NumInvalid == Other.NumInvalid); } bool operator!=(const FSampleStats& Other) const { return !(*this == Other); } FSampleStats& operator+=(const FSampleStats& Other) { NumValid += Other.NumValid; NumInvalid += Other.NumInvalid; return *this; } static FSampleStats Zero() { return FSampleStats{0, 0}; } }; // Collect some sample stats per pixel, used to determine if a pixel requires infill or not TImageBuilder SampleStats; // The i-th element of this array indicates if the i-th evaluator needs infill TArray EvaluatorNeedsInfill; } InfillData; InfillData.SampleStats.SetDimensions(FImageDimensions(TextureImageSize, TextureImageSize)); InfillData.SampleStats.Clear(FInfillData::FSampleStats{}); auto RegisterSampleStats = [&InfillData](bool bSampleValid, const FMeshMapEvaluator::FCorrespondenceSample& Sample, const FVector2d& UVPosition, const FVector2i& ImageCoords) { checkSlow(InfillData.SampleStats.GetDimensions().IsValidCoords(ImageCoords)); if (bSampleValid) { InfillData.SampleStats.GetPixel(ImageCoords).NumValid += 1; } else { InfillData.SampleStats.GetPixel(ImageCoords).NumInvalid += 1; } }; auto ComputeAndApplyInfill = [&InfillData](TArray>>& BakeResults) { check(BakeResults.Num() == InfillData.EvaluatorNeedsInfill.Num()); if (BakeResults.IsEmpty() || Algo::NoneOf(InfillData.EvaluatorNeedsInfill)) { return; } // Find pixels that need infill TArray MissingPixels; FCriticalSection MissingPixelsLock; ParallelFor(InfillData.SampleStats.GetDimensions().GetHeight(), [&MissingPixels, &MissingPixelsLock, &InfillData](int32 Y) { for (int32 X = 0; X < InfillData.SampleStats.GetDimensions().GetWidth(); X++) { const FInfillData::FSampleStats& Stats = InfillData.SampleStats.GetPixel(X, Y); // TODO experiment with other classifications if (Stats.NumInvalid > 0 && Stats.NumValid == 0) { MissingPixelsLock.Lock(); MissingPixels.Add(FVector2i(X, Y)); MissingPixelsLock.Unlock(); } } }); auto NormalizeFunc = [](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); }; auto DummyNormalizeStatsFunc = [](FInfillData::FSampleStats SumValue, int32 Count) { // The return value must be different from MissingValue below so that ComputeInfill works correctly return FInfillData::FSampleStats{TNumericLimits::Max(), TNumericLimits::Max()}; }; TMarchingPixelInfill Infill; // This must be the same as the value of exterior pixels, otherwise infill will spread the exterior values into the texture FInfillData::FSampleStats MissingValue{0, 0}; Infill.ComputeInfill(InfillData.SampleStats, MissingPixels, MissingValue, DummyNormalizeStatsFunc); for (int EvaluatorIndex = 0; EvaluatorIndex < BakeResults.Num(); EvaluatorIndex++) { if (InfillData.EvaluatorNeedsInfill[EvaluatorIndex]) { Infill.ApplyInfill(*BakeResults[EvaluatorIndex], NormalizeFunc); } } }; Result->SetTargetMesh(BaseMesh); Result->SetTargetMeshTangents(BaseMeshTangents); Result->SetDimensions(FImageDimensions(TextureImageSize, TextureImageSize)); Result->SetSamplesPerPixel(static_cast(SamplesPerPixel)); Result->SetFilter(FMeshMapBaker::EBakeFilterType::BSpline); Result->SetTargetMeshUVLayer(Options.TargetUVLayer); Result->InteriorSampleCallback = RegisterSampleStats; Result->PostWriteToImageCallback = ComputeAndApplyInfill; FSceneCapturePhotoSetSampler Sampler(SceneCapture, VisibilityFunction, BaseMesh, &BaseMeshSpatial, BaseMeshTangents.Get()); Result->SetDetailSampler(&Sampler); Result->SetCorrespondenceStrategy(FMeshMapBaker::ECorrespondenceStrategy::Custom); // Pixels in the output textures which don't map onto the mesh have a light grey color (except the normal map which // will show a color corresponding to a unit z tangent space normal) const FVector4f InvalidColor(.42, .42, .42, 1); const FVector3f DefaultNormal = FVector3f::UnitZ(); FSceneCapturePhotoSet::FSceneSample DefaultColorSample; DefaultColorSample.BaseColor = FVector3f(InvalidColor.X, InvalidColor.Y, InvalidColor.Z); DefaultColorSample.Roughness = InvalidColor.X; DefaultColorSample.Specular = InvalidColor.X; DefaultColorSample.Metallic = InvalidColor.X; DefaultColorSample.Emissive = FVector3f(InvalidColor.X, InvalidColor.Y, InvalidColor.Z); DefaultColorSample.WorldNormal = FVector4f((DefaultNormal + FVector3f::One()) * .5f, InvalidColor.W); auto AddColorEvaluator = [this, &InfillData] (const TSharedPtr>& Evaluator) { Result->AddEvaluator(Evaluator); InfillData.EvaluatorNeedsInfill.Add(true); }; if (Options.bBakeBaseColor) { AddColorEvaluator(MakeColorEvaluator(DefaultColorSample, SceneCapture)); } if (Options.bUsePackedMRS) { AddColorEvaluator(MakeColorEvaluator(DefaultColorSample, SceneCapture)); } else { if (Options.bBakeRoughness) { AddColorEvaluator(MakeColorEvaluator(DefaultColorSample, SceneCapture)); } if (Options.bBakeMetallic) { AddColorEvaluator(MakeColorEvaluator(DefaultColorSample, SceneCapture)); } if (Options.bBakeSpecular) { AddColorEvaluator(MakeColorEvaluator(DefaultColorSample, SceneCapture)); } } if (Options.bBakeEmissive) { AddColorEvaluator(MakeColorEvaluator(DefaultColorSample, SceneCapture)); } if (Options.bBakeNormalMap) { TSharedPtr> Evaluator = MakeShared>(); Evaluator->Channel = ERenderCaptureChannel::WorldNormal; Evaluator->DefaultResult = DefaultNormal; Evaluator->EvaluateSampleCallback = [this, &DefaultColorSample](const FMeshMapEvaluator::FCorrespondenceSample& Sample) { const int32 TriangleID = Sample.BaseSample.TriangleIndex; const FVector3d BaryCoords = Sample.BaseSample.BaryCoords; const int PhotoIndex = Sample.DetailTriID; const FVector2d PhotoCoords(Sample.DetailBaryCoords.X, Sample.DetailBaryCoords.Y); const FVector4f NormalColor = SceneCapture->ComputeSample(PhotoIndex, PhotoCoords, DefaultColorSample); // Map from color components [0,1] to normal components [-1,1] const FVector3f WorldSpaceNormal( (NormalColor.X - 0.5f) * 2.0f, (NormalColor.Y - 0.5f) * 2.0f, (NormalColor.Z - 0.5f) * 2.0f); // Get tangents on base mesh FVector3d BaseTangentX, BaseTangentY; BaseMeshTangents->GetInterpolatedTriangleTangent(TriangleID, BaryCoords, BaseTangentX, BaseTangentY); // Compute normal in tangent space const FVector3f TangentSpaceNormal( (float)WorldSpaceNormal.Dot(FVector3f(BaseTangentX)), (float)WorldSpaceNormal.Dot(FVector3f(BaseTangentY)), (float)WorldSpaceNormal.Dot(FVector3f(Sample.BaseNormal))); return TangentSpaceNormal; }; Evaluator->EvaluateColorCallback = [](const int DataIdx, float*& In) { // Map normal space [-1,1] to color space [0,1] const FVector3f Normal(In[0], In[1], In[2]); const FVector3f Color = (Normal + FVector3f::One()) * 0.5f; const FVector4f Out(Color.X, Color.Y, Color.Z, 1.0f); In += 3; return Out; }; Result->AddEvaluator(Evaluator); // Note: 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??) InfillData.EvaluatorNeedsInfill.Add(false); } { TRACE_CPUPROFILER_EVENT_SCOPE(FRenderCaptureMapBakerOp_CalculateResult_Bake); Result->Bake(); } } // // Tool Builder // const FToolTargetTypeRequirements& UBakeRenderCaptureToolBuilder::GetTargetRequirements() const { static FToolTargetTypeRequirements TypeRequirements({ UMeshDescriptionProvider::StaticClass(), UPrimitiveComponentBackedTarget::StaticClass(), UStaticMeshBackedTarget::StaticClass(), // FMeshSceneAdapter currently only supports StaticMesh targets UMaterialProvider::StaticClass() }); return TypeRequirements; } bool UBakeRenderCaptureToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { const int32 NumTargets = SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()); return (NumTargets > 1); } UMultiSelectionMeshEditingTool* UBakeRenderCaptureToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const { return NewObject(SceneState.ToolManager); } // // Tool // void UBakeRenderCaptureTool::Setup() { TRACE_CPUPROFILER_EVENT_SCOPE(UBakeRenderCaptureTool::Setup); Super::Setup(); InitializePreviewMaterials(); // Initialize base mesh const FTransformSRT3d BaseToWorld = UE::ToolTarget::GetLocalToWorldTransform(Targets[0]); PreviewMesh->ProcessMesh([this, BaseToWorld](const FDynamicMesh3& Mesh) { TargetMesh.Copy(Mesh); TargetMeshTangents = MakeShared(&TargetMesh); TargetMeshTangents->CopyTriVertexTangents(Mesh); // FMeshSceneAdapter operates in world space, so ensure our mesh transformed to world. MeshTransforms::ApplyTransform(TargetMesh, BaseToWorld); TargetSpatial.SetMesh(&TargetMesh, true); }); // Initialize actors const int NumTargets = Targets.Num(); Actors.Empty(NumTargets - 1); for (int Idx = 1; Idx < NumTargets; ++Idx) { if (AActor* Actor = UE::ToolTarget::GetTargetActor(Targets[Idx])) { Actors.Add(Actor); } } UToolTarget* Target = Targets[0]; // Setup tool property sets Settings = NewObject(this); Settings->RestoreProperties(this); AddToolPropertySource(Settings); Settings->MapPreview = BaseColorTexParamName; // We always bake the base color Settings->WatchProperty(Settings->MapPreview, [this](FString) { UpdateVisualization(); GetToolManager()->PostInvalidation(); }); Settings->WatchProperty(Settings->SamplesPerPixel, [this](EBakeTextureSamplesPerPixel) { OpState |= EBakeOpState::Evaluate; }); Settings->WatchProperty(Settings->TextureSize, [this](EBakeTextureResolution) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties = NewObject(this); RenderCaptureProperties->RestoreProperties(this); AddToolPropertySource(RenderCaptureProperties); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->Resolution, [this](EBakeTextureResolution) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bBaseColorMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bNormalMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bMetallicMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bRoughnessMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bSpecularMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bPackedMRSMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->bEmissiveMap, [this](bool) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->CaptureFieldOfView, [this](float) { OpState |= EBakeOpState::Evaluate; }); RenderCaptureProperties->WatchProperty(RenderCaptureProperties->NearPlaneDist, [this](float) { OpState |= EBakeOpState::Evaluate; }); InputMeshSettings = NewObject(this); InputMeshSettings->RestoreProperties(this); AddToolPropertySource(InputMeshSettings); InputMeshSettings->TargetStaticMesh = GetStaticMeshTarget(Target); UpdateUVLayerNames(InputMeshSettings->TargetUVLayer, InputMeshSettings->TargetUVLayerNamesList, TargetMesh); InputMeshSettings->WatchProperty(InputMeshSettings->TargetUVLayer, [this](FString) { OpState |= EBakeOpState::Evaluate; }); { Settings->MapPreviewNamesList.Add(NormalTexParamName); Settings->MapPreviewNamesList.Add(BaseColorTexParamName); Settings->MapPreviewNamesList.Add(RoughnessTexParamName); Settings->MapPreviewNamesList.Add(MetallicTexParamName); Settings->MapPreviewNamesList.Add(SpecularTexParamName); Settings->MapPreviewNamesList.Add(EmissiveTexParamName); Settings->MapPreviewNamesList.Add(PackedMRSTexParamName); } ResultSettings = NewObject(this); ResultSettings->RestoreProperties(this); AddToolPropertySource(ResultSettings); SetToolPropertySourceEnabled(ResultSettings, true); TargetUVLayerToError.Reset(); // Used to implement SceneCapture cancellation ComputedRenderCaptureProperties = NewObject(this); // Hide the render capture meshes since this baker operates solely in world space which will occlude the preview of // the target mesh. for (int Idx = 1; Idx < NumTargets; ++Idx) { UE::ToolTarget::HideSourceObject(Targets[Idx]); } // Make sure we trigger SceneCapture computation in UpdateResult OpState |= EBakeOpState::Evaluate; ComputedRenderCaptureProperties->NearPlaneDist = 0.f; // Arbitrary invalid value SetToolDisplayName(LOCTEXT("ToolName", "Bake Render Capture")); GetToolManager()->DisplayMessage( LOCTEXT("OnStartTool", "Bake Render Capture. Select Bake Mesh (LowPoly) first, then select Detail Meshes (HiPoly) to bake. Assets will be created on Accept."), EToolMessageLevel::UserNotification); PostSetup(); } void UBakeRenderCaptureTool::Render(IToolsContextRenderAPI* RenderAPI) { Super::Render(RenderAPI); const float Brightness = VisualizationProps->Brightness; const FVector BrightnessColor(Brightness, Brightness, Brightness); PreviewMaterialRC->SetVectorParameterValue(TEXT("Brightness"), BrightnessColor); PreviewMaterialPackedRC->SetVectorParameterValue(TEXT("Brightness"), BrightnessColor); } void UBakeRenderCaptureTool::OnShutdown(EToolShutdownType ShutdownType) { TRACE_CPUPROFILER_EVENT_SCOPE(UBakeRenderCaptureTool::Shutdown); Super::OnShutdown(ShutdownType); Settings->SaveProperties(this); RenderCaptureProperties->SaveProperties(this); InputMeshSettings->SaveProperties(this); if (Compute) { Compute->Shutdown(); } // Restore visibility of source meshes const int NumTargets = Targets.Num(); for (int Idx = 1; Idx < NumTargets; ++Idx) { UE::ToolTarget::ShowSourceObject(Targets[Idx]); } if (ShutdownType == EToolShutdownType::Accept) { IStaticMeshBackedTarget* StaticMeshTarget = Cast(Targets[0]); UObject* SourceAsset = StaticMeshTarget ? StaticMeshTarget->GetStaticMesh() : nullptr; const UPrimitiveComponent* SourceComponent = UE::ToolTarget::GetTargetComponent(Targets[0]); CreateTextureAssetsRC(SourceComponent->GetWorld(), SourceAsset); } // Clear actors on shutdown so that their lifetime is not tied to the lifetime of the tool Actors.Empty(); } void UBakeRenderCaptureTool::CreateTextureAssetsRC(UWorld* SourceWorld, UObject* SourceAsset) { bool bCreatedAssetOK = true; const FString BaseName = UE::ToolTarget::GetTargetActor(Targets[0])->GetActorNameOrLabel(); auto CreateTextureAsset = [this, &bCreatedAssetOK, &SourceWorld, &SourceAsset](const FString& TexName, FTexture2DBuilder::ETextureType Type, TObjectPtr Tex) { // See :DeferredPopulateSourceData FTexture2DBuilder::CopyPlatformDataToSourceData(Tex, Type); // TODO The original implementation in ApproximateActors also did the following, see WriteTextureLambda in ApproximateActorsImpl.cpp //if (Type == FTexture2DBuilder::ETextureType::Roughness // || Type == FTexture2DBuilder::ETextureType::Metallic // || Type == FTexture2DBuilder::ETextureType::Specular) //{ // UE::AssetUtils::ConvertToSingleChannel(Texture); //} bCreatedAssetOK = bCreatedAssetOK && UE::Modeling::CreateTextureObject( GetToolManager(), FCreateTextureObjectParams{ 0, SourceWorld, SourceAsset, TexName, Tex }).IsOK(); }; if (RenderCaptureProperties->bBaseColorMap && ResultSettings->BaseColorMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *BaseColorTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::Color, ResultSettings->BaseColorMap); } if (RenderCaptureProperties->bNormalMap && ResultSettings->NormalMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *NormalTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::NormalMap, ResultSettings->NormalMap); } if (RenderCaptureProperties->bEmissiveMap && ResultSettings->EmissiveMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *EmissiveTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::EmissiveHDR, ResultSettings->EmissiveMap); } if (RenderCaptureProperties->bPackedMRSMap && ResultSettings->PackedMRSMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *PackedMRSTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::ColorLinear, ResultSettings->PackedMRSMap); } else { if (RenderCaptureProperties->bMetallicMap && ResultSettings->MetallicMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *MetallicTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::Metallic, ResultSettings->MetallicMap); } if (RenderCaptureProperties->bRoughnessMap && ResultSettings->RoughnessMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *RoughnessTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::Roughness, ResultSettings->RoughnessMap); } if (RenderCaptureProperties->bSpecularMap && ResultSettings->SpecularMap != nullptr) { const FString TexName = FString::Printf(TEXT("%s_%s"), *BaseName, *SpecularTexParamName); CreateTextureAsset(TexName, FTexture2DBuilder::ETextureType::Specular, ResultSettings->SpecularMap); } } ensure(bCreatedAssetOK); RecordAnalytics(); } bool UBakeRenderCaptureTool::CanAccept() const { if ((OpState & EBakeOpState::Invalid) == EBakeOpState::Invalid) { return false; } if (RenderCaptureProperties->bBaseColorMap && ResultSettings->BaseColorMap == nullptr) { return false; } if (RenderCaptureProperties->bNormalMap && ResultSettings->NormalMap == nullptr) { return false; } if (RenderCaptureProperties->bEmissiveMap && ResultSettings->EmissiveMap == nullptr) { return false; } if (RenderCaptureProperties->bPackedMRSMap) { if (ResultSettings->PackedMRSMap == nullptr) { return false; } } else { if (RenderCaptureProperties->bMetallicMap && ResultSettings->MetallicMap == nullptr) { return false; } if (RenderCaptureProperties->bRoughnessMap && ResultSettings->RoughnessMap == nullptr) { return false; } if (RenderCaptureProperties->bSpecularMap && ResultSettings->SpecularMap == nullptr) { return false; } } return true; } TUniquePtr> UBakeRenderCaptureTool::MakeNewOperator() { TUniquePtr Op = MakeUnique(); Op->BaseMesh = &TargetMesh; Op->BaseMeshTangents = TargetMeshTangents; Op->Options = FRenderCaptureSettings::ConstructOptions(*RenderCaptureProperties, *InputMeshSettings); Op->TextureImageSize = static_cast(Settings->TextureSize); Op->SamplesPerPixel = Settings->SamplesPerPixel; Op->SceneCapture = SceneCapture.Get(); return Op; } void UBakeRenderCaptureTool::OnMapsUpdatedRC(const TUniquePtr& NewResult) { // We do this to defer work I guess, it was like this in the original ApproximateActors implementation :DeferredPopulateSourceData constexpr bool bPopulateSourceData = false; TRACE_CPUPROFILER_EVENT_SCOPE(BakeRenderCaptureTool_Textures_BuildTextures); const int32 NumEval = NewResult->NumEvaluators(); for (int32 EvalIdx = 0; EvalIdx < NumEval; ++EvalIdx) { FMeshMapEvaluator* BaseEval = NewResult->GetEvaluator(EvalIdx); check(BaseEval->DataLayout().Num() == 1); switch (BaseEval->DataLayout()[0]) { case FMeshMapEvaluator::EComponents::Float4: { FRenderCaptureMapEvaluator* Eval = static_cast*>(BaseEval); TUniquePtr> ImageBuilder = MoveTemp(NewResult->GetBakeResults(EvalIdx)[0]); if (ensure(ImageBuilder.IsValid()) == false) return; switch (Eval->Channel) { case ERenderCaptureChannel::BaseColor: ResultSettings->BaseColorMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::Color, true, bPopulateSourceData); break; case ERenderCaptureChannel::Roughness: ResultSettings->RoughnessMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::Roughness, false, bPopulateSourceData); break; case ERenderCaptureChannel::Metallic: ResultSettings->MetallicMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::Metallic, false, bPopulateSourceData); break; case ERenderCaptureChannel::Specular: ResultSettings->SpecularMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::Specular, false, bPopulateSourceData); break; case ERenderCaptureChannel::Emissive: ResultSettings->EmissiveMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::EmissiveHDR, false, bPopulateSourceData); ResultSettings->EmissiveMap->CompressionSettings = TC_HDR_Compressed; break; case ERenderCaptureChannel::CombinedMRS: ResultSettings->PackedMRSMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::ColorLinear, false, bPopulateSourceData); break; default: ensure(false); return; } } break; // Float4 case FMeshMapEvaluator::EComponents::Float3: { FRenderCaptureMapEvaluator* Eval = static_cast*>(BaseEval); TUniquePtr> ImageBuilder = MoveTemp(NewResult->GetBakeResults(EvalIdx)[0]); if (ensure(ImageBuilder.IsValid()) == false) return; if (ensure(Eval->Channel == ERenderCaptureChannel::WorldNormal) == false) return; ResultSettings->NormalMap = FTexture2DBuilder::BuildTextureFromImage( *ImageBuilder, FTexture2DBuilder::ETextureType::NormalMap, false, bPopulateSourceData); } break; // Float3 default: ensure(false); return; } } GatherAnalytics(*NewResult); UpdateVisualization(); GetToolManager()->PostInvalidation(); } void UBakeRenderCaptureTool::InitializePreviewMaterials() { // EmptyColorMapWhite, EmptyColorMapBlack and EmptyNormalMap are defined in the base tool { FTexture2DBuilder Builder; Builder.Initialize(FTexture2DBuilder::ETextureType::EmissiveHDR, FImageDimensions(16, 16)); Builder.Commit(false); EmptyEmissiveMap = Builder.GetTexture2D(); } { FTexture2DBuilder Builder; Builder.Initialize(FTexture2DBuilder::ETextureType::ColorLinear, FImageDimensions(16, 16)); Builder.Clear(FColor(0,0,0)); Builder.Commit(false); EmptyPackedMRSMap = Builder.GetTexture2D(); } { FTexture2DBuilder Builder; Builder.Initialize(FTexture2DBuilder::ETextureType::Roughness, FImageDimensions(16, 16)); Builder.Commit(false); EmptyRoughnessMap = Builder.GetTexture2D(); } { FTexture2DBuilder Builder; Builder.Initialize(FTexture2DBuilder::ETextureType::Metallic, FImageDimensions(16, 16)); Builder.Commit(false); EmptyMetallicMap = Builder.GetTexture2D(); } { FTexture2DBuilder Builder; Builder.Initialize(FTexture2DBuilder::ETextureType::Specular, FImageDimensions(16, 16)); Builder.Commit(false); EmptySpecularMap = Builder.GetTexture2D(); } { UMaterial* Material = LoadObject(nullptr, TEXT("/MeshModelingToolsetExp/Materials/BakeRenderCapturePreviewMaterial")); check(Material); if (Material != nullptr) { PreviewMaterialRC = UMaterialInstanceDynamic::Create(Material, GetToolManager()); PreviewMaterialRC->SetTextureParameterValue(TEXT("BaseColor"), EmptyColorMapWhite); PreviewMaterialRC->SetTextureParameterValue(TEXT("Roughness"), EmptyRoughnessMap); PreviewMaterialRC->SetTextureParameterValue(TEXT("Metallic"), EmptyMetallicMap); PreviewMaterialRC->SetTextureParameterValue(TEXT("Specular"), EmptySpecularMap); PreviewMaterialRC->SetTextureParameterValue(TEXT("Emissive"), EmptyEmissiveMap); PreviewMaterialRC->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); } } { UMaterial* Material = LoadObject(nullptr, TEXT("/MeshModelingToolsetExp/Materials/FullMaterialBakePreviewMaterial_PackedMRS")); check(Material); if (Material != nullptr) { PreviewMaterialPackedRC = UMaterialInstanceDynamic::Create(Material, GetToolManager()); PreviewMaterialPackedRC->SetTextureParameterValue(TEXT("BaseColor"), EmptyColorMapWhite); PreviewMaterialPackedRC->SetTextureParameterValue(TEXT("PackedMRS"), EmptyPackedMRSMap); PreviewMaterialPackedRC->SetTextureParameterValue(TEXT("Emissive"), EmptyEmissiveMap); PreviewMaterialPackedRC->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); } } } void UBakeRenderCaptureTool::InvalidateComputeRC() { // Note: This implementation is identical to UBakeMeshAttributeMapsToolBase::InvalidateCompute but calls // OnMapsUpdatedRC rather than OnMapsUpdated if (!Compute) { // Initialize background compute Compute = MakeUnique>(); Compute->Setup(this); Compute->OnResultUpdated.AddLambda([this](const TUniquePtr& NewResult) { OnMapsUpdatedRC(NewResult); }); } Compute->InvalidateResult(); } void UBakeRenderCaptureTool::UpdateResult() { if (OpState == EBakeOpState::Clean) { // Evaluation already launched/complete. Note that the Compute background compute updates ResultSettings when // they are available by calling OnMapsUpdatedRC in its OnResultUpdated delegate. return; } // // create a set of spatially located render captures of the scene ("photo set"). // if (*RenderCaptureProperties != *ComputedRenderCaptureProperties) { for (int Idx = 1; Idx < Targets.Num(); ++Idx) { UE::ToolTarget::ShowSourceObject(Targets[Idx]); } // Do not allow user-cancellation on the call that occurs when the Render Capture Tool starts up const bool bAllowCancel = (bFirstEverSceneCapture == false); SceneCapture.Reset(); FRenderCaptureSettings::FOptions Options = FRenderCaptureSettings::ConstructOptions(*RenderCaptureProperties, *InputMeshSettings); SceneCapture = CapturePhotoSet(Actors, Options, bAllowCancel); for (int Idx = 1; Idx < Targets.Num(); ++Idx) { UE::ToolTarget::HideSourceObject(Targets[Idx]); } if (SceneCapture->Cancelled()) { // Restore the settings present before the change that invoked the scene capture recompute RenderCaptureProperties->Resolution = ComputedRenderCaptureProperties->Resolution; RenderCaptureProperties->bBaseColorMap = ComputedRenderCaptureProperties->bBaseColorMap; RenderCaptureProperties->bNormalMap = ComputedRenderCaptureProperties->bNormalMap; RenderCaptureProperties->bMetallicMap = ComputedRenderCaptureProperties->bMetallicMap; RenderCaptureProperties->bRoughnessMap = ComputedRenderCaptureProperties->bRoughnessMap; RenderCaptureProperties->bSpecularMap = ComputedRenderCaptureProperties->bSpecularMap; RenderCaptureProperties->bPackedMRSMap = ComputedRenderCaptureProperties->bPackedMRSMap; RenderCaptureProperties->bEmissiveMap = ComputedRenderCaptureProperties->bEmissiveMap; RenderCaptureProperties->CaptureFieldOfView = ComputedRenderCaptureProperties->CaptureFieldOfView; RenderCaptureProperties->NearPlaneDist = ComputedRenderCaptureProperties->NearPlaneDist; // Silently make the above updates so we don't overwrite the change to OpState below and call this function again RenderCaptureProperties->SilentUpdateWatched(); OpState = EBakeOpState::Clean; return; } // Cache Settings used to compute this SceneCapture so we can restore them if the user cancels a SceneCapture recompute ComputedRenderCaptureProperties->Resolution = RenderCaptureProperties->Resolution; ComputedRenderCaptureProperties->bBaseColorMap = RenderCaptureProperties->bBaseColorMap; ComputedRenderCaptureProperties->bNormalMap = RenderCaptureProperties->bNormalMap; ComputedRenderCaptureProperties->bMetallicMap = RenderCaptureProperties->bMetallicMap; ComputedRenderCaptureProperties->bRoughnessMap = RenderCaptureProperties->bRoughnessMap; ComputedRenderCaptureProperties->bSpecularMap = RenderCaptureProperties->bSpecularMap; ComputedRenderCaptureProperties->bPackedMRSMap = RenderCaptureProperties->bPackedMRSMap; ComputedRenderCaptureProperties->bEmissiveMap = RenderCaptureProperties->bEmissiveMap; ComputedRenderCaptureProperties->CaptureFieldOfView = RenderCaptureProperties->CaptureFieldOfView; ComputedRenderCaptureProperties->NearPlaneDist = RenderCaptureProperties->NearPlaneDist; bFirstEverSceneCapture = false; } FText ErrorMessage; // Empty message indicates no error { const int32 TargetUVLayer = InputMeshSettings->GetTargetUVLayerIndex(); if (FText* Message = TargetUVLayerToError.Find(TargetUVLayer); Message) { ErrorMessage = *Message; } else { const auto HasDegenerateUVs = [this] { FDynamicMeshUVOverlay* UVOverlay = TargetMesh.Attributes()->GetUVLayer(InputMeshSettings->GetTargetUVLayerIndex()); FAxisAlignedBox2f Bounds = FAxisAlignedBox2f::Empty(); for (const int Index : UVOverlay->ElementIndicesItr()) { FVector2f UV; UVOverlay->GetElement(Index, UV); Bounds.Contain(UV); } return Bounds.Min == Bounds.Max; }; if (TargetMesh.Attributes()->GetUVLayer(InputMeshSettings->GetTargetUVLayerIndex()) == nullptr) { ErrorMessage = LOCTEXT("TargetMeshMissingUVs", "The Target Mesh UV layer is missing"); } else if (HasDegenerateUVs()) { ErrorMessage = LOCTEXT("TargetMeshDegenerateUVs", "The Target Mesh UV layer is degenerate"); } else { ErrorMessage = FText(); // No error } TargetUVLayerToError.Add(TargetUVLayer, ErrorMessage); } // If there are no UV layer errors check for missing tangent space error if (ErrorMessage.IsEmpty() && RenderCaptureProperties->bNormalMap && ValidTargetMeshTangents() == false) { ErrorMessage = LOCTEXT("TargetMeshMissingTangentSpace", "The Target Mesh is missing a tangent space. Disable Normal Map capture to continue."); } } // Calling DisplayMessage with an empty string will clear existing messages GetToolManager()->DisplayMessage(ErrorMessage, EToolMessageLevel::UserWarning); InvalidateResults(); const bool bIsInvalid = (ErrorMessage.IsEmpty() == false); if (bIsInvalid) { const bool bWasValid = static_cast(OpState & EBakeOpState::Invalid) == false; if (bWasValid) { UpdateVisualization(); // Clear the preview mesh material inputs } OpState = EBakeOpState::Invalid; return; } InvalidateComputeRC(); OpState = EBakeOpState::Clean; } void UBakeRenderCaptureTool::UpdateVisualization() { if (Settings->MapPreview.IsEmpty()) { return; } if (ResultSettings->PackedMRSMap) { TObjectPtr Material = PreviewMaterialPackedRC; PreviewMesh->SetOverrideRenderMaterial(Material); if (VisualizationProps->bPreviewAsMaterial) { // We set all textures which were computed in the corresponding texture channels Material->SetTextureParameterValue(FName(BaseColorTexParamName), ResultSettings->BaseColorMap ? ResultSettings->BaseColorMap : EmptyColorMapWhite); Material->SetTextureParameterValue(FName(EmissiveTexParamName), ResultSettings->EmissiveMap ? ResultSettings->EmissiveMap : EmptyEmissiveMap); Material->SetTextureParameterValue(FName(NormalTexParamName), ResultSettings->NormalMap ? ResultSettings->NormalMap : EmptyNormalMap); Material->SetTextureParameterValue(FName(PackedMRSTexParamName), ResultSettings->PackedMRSMap); } else { // The BaseColor texture channel will be set according to the selected MapPreview TObjectPtr BaseColorMap = EmptyColorMapWhite; if (ResultSettings->BaseColorMap && Settings->MapPreview == BaseColorTexParamName) { BaseColorMap = ResultSettings->BaseColorMap; } else if (ResultSettings->EmissiveMap && Settings->MapPreview == EmissiveTexParamName) { BaseColorMap = ResultSettings->EmissiveMap; } else if (ResultSettings->NormalMap && Settings->MapPreview == NormalTexParamName) { BaseColorMap = ResultSettings->NormalMap; } else if (ResultSettings->PackedMRSMap && Settings->MapPreview == PackedMRSTexParamName) { BaseColorMap = ResultSettings->PackedMRSMap; } Material->SetTextureParameterValue(FName(BaseColorTexParamName), BaseColorMap); Material->SetTextureParameterValue(FName(EmissiveTexParamName), EmptyEmissiveMap); Material->SetTextureParameterValue(FName(NormalTexParamName), EmptyNormalMap); Material->SetTextureParameterValue(FName(PackedMRSTexParamName), EmptyPackedMRSMap); } Material->SetScalarParameterValue(TEXT("UVChannel"), InputMeshSettings->GetTargetUVLayerIndex()); } else { TObjectPtr Material = PreviewMaterialRC; PreviewMesh->SetOverrideRenderMaterial(Material); if (VisualizationProps->bPreviewAsMaterial) { // We set all textures which were computed in the corresponding texture channels Material->SetTextureParameterValue(FName(BaseColorTexParamName), ResultSettings->BaseColorMap ? ResultSettings->BaseColorMap : EmptyColorMapWhite); Material->SetTextureParameterValue(FName(RoughnessTexParamName), ResultSettings->RoughnessMap ? ResultSettings->RoughnessMap : EmptyRoughnessMap); Material->SetTextureParameterValue(FName(MetallicTexParamName), ResultSettings->MetallicMap ? ResultSettings->MetallicMap : EmptyMetallicMap); Material->SetTextureParameterValue(FName(SpecularTexParamName), ResultSettings->SpecularMap ? ResultSettings->SpecularMap : EmptySpecularMap); Material->SetTextureParameterValue(FName(EmissiveTexParamName), ResultSettings->EmissiveMap ? ResultSettings->EmissiveMap : EmptyEmissiveMap); Material->SetTextureParameterValue(FName(NormalTexParamName), ResultSettings->NormalMap ? ResultSettings->NormalMap : EmptyNormalMap); } else { // The BaseColor texture channel will be set according to the selected MapPreview TObjectPtr BaseColorMap = EmptyColorMapWhite; if (ResultSettings->BaseColorMap && Settings->MapPreview == BaseColorTexParamName) { BaseColorMap = ResultSettings->BaseColorMap; } else if (ResultSettings->RoughnessMap && Settings->MapPreview == RoughnessTexParamName) { BaseColorMap = ResultSettings->RoughnessMap; } else if (ResultSettings->MetallicMap && Settings->MapPreview == MetallicTexParamName) { BaseColorMap = ResultSettings->MetallicMap; } else if (ResultSettings->SpecularMap && Settings->MapPreview == SpecularTexParamName) { BaseColorMap = ResultSettings->SpecularMap; } else if (ResultSettings->EmissiveMap && Settings->MapPreview == EmissiveTexParamName) { BaseColorMap = ResultSettings->EmissiveMap; } else if (ResultSettings->NormalMap && Settings->MapPreview == NormalTexParamName) { BaseColorMap = ResultSettings->NormalMap; } Material->SetTextureParameterValue(TEXT("BaseColor"), BaseColorMap); Material->SetTextureParameterValue(TEXT("Roughness"), EmptyRoughnessMap); Material->SetTextureParameterValue(TEXT("Metallic"), EmptyMetallicMap); Material->SetTextureParameterValue(TEXT("Specular"), EmptySpecularMap); Material->SetTextureParameterValue(TEXT("Emissive"), EmptyEmissiveMap); Material->SetTextureParameterValue(TEXT("NormalMap"), EmptyNormalMap); } Material->SetScalarParameterValue(TEXT("UVChannel"), InputMeshSettings->GetTargetUVLayerIndex()); } } void UBakeRenderCaptureTool::InvalidateResults() { ResultSettings->BaseColorMap = nullptr; ResultSettings->RoughnessMap = nullptr; ResultSettings->MetallicMap = nullptr; ResultSettings->SpecularMap = nullptr; ResultSettings->PackedMRSMap = nullptr; ResultSettings->EmissiveMap = nullptr; ResultSettings->NormalMap = nullptr; } void UBakeRenderCaptureTool::RecordAnalytics() const { if (FEngineAnalytics::IsAvailable() == false) { return; } TArray Attributes; // General Attributes.Add(FAnalyticsEventAttribute(TEXT("Bake.Duration.Total.Seconds"), BakeAnalytics.TotalBakeDuration)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Bake.Duration.WriteToImage.Seconds"), BakeAnalytics.WriteToImageDuration)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Bake.Duration.WriteToGutter.Seconds"), BakeAnalytics.WriteToGutterDuration)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Bake.Stats.NumSamplePixels"), BakeAnalytics.NumSamplePixels)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Bake.Stats.NumGutterPixels"), BakeAnalytics.NumGutterPixels)); // Input mesh data Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.TargetMesh.NumTriangles"), BakeAnalytics.MeshSettings.NumTargetMeshTris)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.RenderCapture.NumMeshes"), BakeAnalytics.MeshSettings.NumDetailMesh)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.RenderCapture.NumTriangles"), BakeAnalytics.MeshSettings.NumDetailMeshTris)); // Bake settings Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.Image.Width"), static_cast(Settings->TextureSize))); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.Image.Height"), static_cast(Settings->TextureSize))); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.SamplesPerPixel"), static_cast(Settings->SamplesPerPixel))); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.TargetUVLayer"), InputMeshSettings->GetTargetUVLayerIndex())); // Render Capture settings Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.Image.Width"), static_cast(RenderCaptureProperties->Resolution))); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.Image.Height"), static_cast(RenderCaptureProperties->Resolution))); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.BaseColorMap.Enabled"), RenderCaptureProperties->bBaseColorMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.NormalMap.Enabled"), RenderCaptureProperties->bNormalMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.MetallicMap.Enabled"), RenderCaptureProperties->bMetallicMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.RoughnessMap.Enabled"), RenderCaptureProperties->bRoughnessMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.SpecularMap.Enabled"), RenderCaptureProperties->bSpecularMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.PackedMRSMap.Enabled"), RenderCaptureProperties->bPackedMRSMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.EmissiveMap.Enabled"), RenderCaptureProperties->bEmissiveMap)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.CaptureFieldOfView"), RenderCaptureProperties->CaptureFieldOfView)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.RenderCapture.NearPlaneDistance"), RenderCaptureProperties->NearPlaneDist)); FEngineAnalytics::GetProvider().RecordEvent(FString(TEXT("Editor.Usage.MeshModelingMode.")) + GetAnalyticsEventName(), Attributes); constexpr bool bDebugLogAnalytics = false; if constexpr (bDebugLogAnalytics) { for (const FAnalyticsEventAttribute& Attr : Attributes) { UE_LOG(LogGeometry, Log, TEXT("[%s] %s = %s"), *GetAnalyticsEventName(), *Attr.GetName(), *Attr.GetValue()); } } } void UBakeRenderCaptureTool::GatherAnalytics(const FMeshMapBaker& Result) { if (!FEngineAnalytics::IsAvailable()) { return; } BakeAnalytics.TotalBakeDuration = Result.BakeAnalytics.TotalBakeDuration; BakeAnalytics.WriteToImageDuration = Result.BakeAnalytics.WriteToImageDuration; BakeAnalytics.WriteToGutterDuration = Result.BakeAnalytics.WriteToGutterDuration; BakeAnalytics.NumSamplePixels = Result.BakeAnalytics.NumSamplePixels; BakeAnalytics.NumGutterPixels = Result.BakeAnalytics.NumGutterPixels; } void UBakeRenderCaptureTool::GatherAnalytics(FBakeAnalytics::FMeshSettings& Data) { if (FEngineAnalytics::IsAvailable() == false) { return; } Data.NumTargetMeshTris = TargetMesh.TriangleCount(); Data.NumDetailMesh = Actors.Num(); Data.NumDetailMeshTris = 0; for (AActor* Actor : Actors) { check(Actor != nullptr); TInlineComponentArray PrimitiveComponents; Actor->GetComponents(PrimitiveComponents); for (UPrimitiveComponent* PrimitiveComponent : PrimitiveComponents) { if (UStaticMeshComponent* StaticMeshComponent = Cast(PrimitiveComponent)) { if (StaticMeshComponent->GetStaticMesh() != nullptr) { // TODO We could also check GetNumNaniteTriangles here and use the maximum Data.NumDetailMeshTris += StaticMeshComponent->GetStaticMesh()->GetNumTriangles(0); } } } } } #undef LOCTEXT_NAMESPACE