// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraBakerRendererOutputStaticMesh.h" #include "NiagaraBakerOutputStaticMesh.h" #include "NiagaraBakerSettings.h" #include "NiagaraComponent.h" #include "NiagaraGpuComputeDispatchInterface.h" #include "NiagaraRendererReadback.h" #include "NiagaraSystemInstance.h" #include "Engine/StaticMesh.h" #include "Engine/TextureRenderTarget2D.h" #include "MeshDescription.h" #include "MeshAttributes.h" #include "StaticMeshAttributes.h" #include "StaticMeshOperations.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(NiagaraBakerRendererOutputStaticMesh) UNiagaraBakerStaticMeshFactoryNew::UNiagaraBakerStaticMeshFactoryNew() { SupportedClass = UStaticMesh::StaticClass(); } UObject* UNiagaraBakerStaticMeshFactoryNew::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { return NewObject(InParent, InClass, InName, Flags); } TArray FNiagaraBakerRendererOutputStaticMesh::GetRendererBindings(UNiagaraBakerOutput* InBakerOutput) const { return TArray(); } FIntPoint FNiagaraBakerRendererOutputStaticMesh::GetPreviewSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const { return InAvailableSize; } void FNiagaraBakerRendererOutputStaticMesh::RenderPreview(UNiagaraBakerOutput* InBakerOutput, const FNiagaraBakerRenderer& BakerRenderer, UTextureRenderTarget2D* RenderTarget, TOptional& OutErrorString) const { BakerRenderer.RenderSceneCapture(RenderTarget, ESceneCaptureSource::SCS_SceneColorHDR); } FIntPoint FNiagaraBakerRendererOutputStaticMesh::GetGeneratedSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const { return InAvailableSize; } void FNiagaraBakerRendererOutputStaticMesh::RenderGenerated(UNiagaraBakerOutput* InBakerOutput, const FNiagaraBakerRenderer& BakerRenderer, UTextureRenderTarget2D* RenderTarget, TOptional& OutErrorString) const { static FString StaticMeshNotFoundError(TEXT("StaticMesh asset not found.\nPlease bake to see the result.")); UNiagaraBakerOutputStaticMesh* BakerOutput = CastChecked(InBakerOutput); UNiagaraBakerSettings* BakerGeneratedSettings = BakerOutput->GetTypedOuter(); const float WorldTime = BakerRenderer.GetWorldTime(); const FNiagaraBakerOutputFrameIndices FrameIndices = BakerGeneratedSettings->GetOutputFrameIndices(BakerOutput, WorldTime); UStaticMesh* StaticMesh = BakerOutput->GetAsset(BakerOutput->FramesAssetPathFormat, FrameIndices.FrameIndexA); if (StaticMesh == nullptr) { OutErrorString = StaticMeshNotFoundError; return; } BakerRenderer.RenderStaticMesh(RenderTarget, StaticMesh); } bool FNiagaraBakerRendererOutputStaticMesh::BeginBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput) { #if WITH_NIAGARA_RENDERER_READBACK BakeRenderTarget = NewObject(); BakeRenderTarget->AddToRoot(); BakeRenderTarget->ClearColor = FLinearColor::Transparent; BakeRenderTarget->TargetGamma = 1.0f; BakeRenderTarget->InitCustomFormat(128, 128, PF_FloatRGBA, false); return true; #else FeedbackContext.Errors.Add(TEXT("Niagara Renderer Readback not enabled, failed to bake")); return false; #endif } void FNiagaraBakerRendererOutputStaticMesh::BakeFrame(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput, int FrameIndex, const FNiagaraBakerRenderer& BakerRenderer) { UNiagaraBakerOutputStaticMesh* BakerOutput = CastChecked(InBakerOutput); UNiagaraComponent* PreviewComponent = BakerRenderer.GetPreviewComponent(); if (!PreviewComponent || PreviewComponent->IsComplete()) { return; } #if WITH_NIAGARA_RENDERER_READBACK NiagaraRendererReadback::EnqueueReadback( PreviewComponent, [BakerOutput, FrameIndex](const FNiagaraRendererReadbackResult& ReadbackResult) { // Failed or no data if (ReadbackResult.NumVertices == 0) { return; } // Create asset const FString AssetFullName = BakerOutput->GetAssetPath(BakerOutput->FramesAssetPathFormat, FrameIndex); UStaticMesh* StaticMesh = UNiagaraBakerOutput::GetOrCreateAsset(AssetFullName); if (StaticMesh == nullptr) { return; } ConvertReadbackResultsToStaticMesh(ReadbackResult, StaticMesh); }, BakerOutput->ExportParameters ); BakerRenderer.RenderSceneCapture(BakeRenderTarget, ESceneCaptureSource::SCS_SceneColorHDR); #endif } void FNiagaraBakerRendererOutputStaticMesh::EndBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput) { if (BakeRenderTarget) { BakeRenderTarget->RemoveFromRoot(); BakeRenderTarget->MarkAsGarbage(); BakeRenderTarget = nullptr; } } bool FNiagaraBakerRendererOutputStaticMesh::ConvertReadbackResultsToStaticMesh(const FNiagaraRendererReadbackResult& ReadbackResult, UStaticMesh* StaticMesh) { #if WITH_NIAGARA_RENDERER_READBACK // Failed or no data if (ReadbackResult.NumVertices == 0 || StaticMesh == nullptr) { return false; } const uint32 NumTexCoords = ReadbackResult.VertexTexCoordNum; const uint32 NumTriangles = ReadbackResult.NumVertices / 3; // Create Mesh Description FMeshDescription MeshDescription; TVertexAttributesRef VertexPositions = MeshDescription.GetVertexPositions(); TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription.VertexInstanceAttributes().RegisterAttribute(MeshAttribute::VertexInstance::Normal, 1, FVector3f::ZeroVector, EMeshAttributeFlags::Mandatory); TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription.VertexInstanceAttributes().RegisterAttribute(MeshAttribute::VertexInstance::Tangent, 1, FVector3f::ZeroVector, EMeshAttributeFlags::Mandatory); TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription.VertexInstanceAttributes().RegisterAttribute(MeshAttribute::VertexInstance::BinormalSign, 1, 1.0f, EMeshAttributeFlags::Mandatory); TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription.VertexInstanceAttributes().RegisterAttribute(MeshAttribute::VertexInstance::Color, 1, FVector4f(1.0f, 1.0f, 1.0f, 1.0f), EMeshAttributeFlags::Lerpable | EMeshAttributeFlags::Mandatory); TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription.VertexInstanceAttributes().RegisterAttribute(MeshAttribute::VertexInstance::TextureCoordinate, NumTexCoords, FVector2f::ZeroVector, EMeshAttributeFlags::Lerpable | EMeshAttributeFlags::Mandatory); MeshDescription.EdgeAttributes().RegisterAttribute(MeshAttribute::Edge::IsHard, 1, false, EMeshAttributeFlags::Mandatory); TPolygonGroupAttributesRef PolygonGroupSlotNames = MeshDescription.PolygonGroupAttributes().RegisterAttribute(MeshAttribute::PolygonGroup::ImportedMaterialSlotName, 1, NAME_None, EMeshAttributeFlags::Mandatory); //The unique key to match the mesh material slot // Reserve space MeshDescription.ReserveNewVertices(ReadbackResult.NumVertices); MeshDescription.ReserveNewVertexInstances(NumTriangles); MeshDescription.ReserveNewEdges(NumTriangles); MeshDescription.ReserveNewPolygons(NumTriangles); MeshDescription.ReserveNewPolygonGroups(ReadbackResult.Sections.Num()); // Build vertices TArray VertexInstances; VertexInstances.Reserve(ReadbackResult.NumVertices); for (uint32 iVertex=0; iVertex < ReadbackResult.NumVertices; ++iVertex) { const FVertexID VertexID = MeshDescription.CreateVertex(); check(VertexID.GetValue() == iVertex); VertexPositions[VertexID] = ReadbackResult.HasPosition() ? ReadbackResult.GetPosition(iVertex) : FVector3f::ZeroVector; FVertexInstanceID VertexInstanceID = MeshDescription.CreateVertexInstance(VertexID); VertexInstances.Add(VertexInstanceID); const FVector3f TangentX = ReadbackResult.HasTangentBasis() ? ReadbackResult.GetTangentX(iVertex) : FVector3f::XAxisVector; const FVector3f TangentY = ReadbackResult.HasTangentBasis() ? ReadbackResult.GetTangentY(iVertex) : FVector3f::YAxisVector; const FVector3f TangentZ = ReadbackResult.HasTangentBasis() ? ReadbackResult.GetTangentZ(iVertex) : FVector3f::ZAxisVector; const float TangentSign = FVector3f::DotProduct(FVector3f::CrossProduct(TangentX, TangentZ), TangentY) < 0.0f ? -1.0f : 1.0f; VertexInstanceNormals.Set(VertexInstanceID, TangentX); VertexInstanceTangents.Set(VertexInstanceID, TangentZ); VertexInstanceBinormalSigns.Set(VertexInstanceID, TangentSign); VertexInstanceColors.Set(VertexInstanceID, ReadbackResult.HasColor() ? ReadbackResult.GetColor(iVertex) : FLinearColor::White); for (uint32 iTexCoord=0; iTexCoord < NumTexCoords; ++iTexCoord) { VertexInstanceUVs.Set(VertexInstanceID, iTexCoord, ReadbackResult.GetTexCoord(iVertex, iTexCoord)); } } // Build sections / triangles TArray StaticMaterials; for (int32 iSection=0; iSection < ReadbackResult.Sections.Num(); ++iSection) { const FNiagaraRendererReadbackResult::FSection& Section = ReadbackResult.Sections[iSection]; const FName MaterialSlotName(*FString::Printf(TEXT("Section%d"), iSection)); const FPolygonGroupID PolyGroupId = MeshDescription.CreatePolygonGroup(); PolygonGroupSlotNames[PolyGroupId] = MaterialSlotName; FStaticMaterial& StaticMaterial = StaticMaterials.AddDefaulted_GetRef(); StaticMaterial.MaterialInterface = Section.WeakMaterialInterface.Get(); StaticMaterial.MaterialSlotName = MaterialSlotName; StaticMaterial.ImportedMaterialSlotName = MaterialSlotName; for (uint32 iTriangle=0; iTriangle < Section.NumTriangles; ++iTriangle) { const uint32 BaseIndex = (Section.FirstTriangle + iTriangle) * 3; const FPolygonID PolygonID = MeshDescription.CreatePolygon(PolyGroupId, { VertexInstances[BaseIndex + 0], VertexInstances[BaseIndex + 1], VertexInstances[BaseIndex + 2] }); MeshDescription.ComputePolygonTriangulation(PolygonID); } } StaticMesh->SetNumSourceModels(1); { FMeshBuildSettings& MeshBuildSettings = StaticMesh->GetSourceModel(0).BuildSettings; MeshBuildSettings.bRecomputeNormals = false; MeshBuildSettings.bRecomputeTangents = false; } StaticMesh->SetStaticMaterials(StaticMaterials); // Build Mesh UStaticMesh::FBuildMeshDescriptionsParams Params; Params.bFastBuild = WITH_EDITOR ? false : true; Params.bUseHashAsGuid = true; Params.bMarkPackageDirty = false; Params.bCommitMeshDescription = true; Params.bAllowCpuAccess = false; StaticMesh->BuildFromMeshDescriptions({ &MeshDescription }, Params); return true; #endif }