// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraBakerRenderer.h" #include "NiagaraBakerSettings.h" #include "NiagaraBakerOutputRegistry.h" #include "NiagaraComponent.h" #include "NiagaraSystem.h" #include "NiagaraSystemInstanceController.h" #include "NiagaraComputeExecutionContext.h" #include "NiagaraGpuComputeDispatchInterface.h" #include "NiagaraBatchedElements.h" #include "NiagaraEditorCommon.h" #include "NiagaraDataInterfaceGrid2DCollection.h" #include "Components/SceneCaptureComponent2D.h" #include "Components/StaticMeshComponent.h" #include "Engine/TextureRenderTarget2D.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Modules/ModuleManager.h" #include "AdvancedPreviewScene.h" #include "BufferVisualizationData.h" #include "CanvasTypes.h" #include "CanvasItem.h" #include "EngineModule.h" #include "IImageWrapperModule.h" #include "IImageWrapper.h" #include "ImageWrapperHelper.h" #include "LegacyScreenPercentageDriver.h" #include "UObject/Package.h" #include "TextureResource.h" #include "SceneInterface.h" #include "NiagaraDataInterfaceGrid3DCollection.h" #include "NiagaraDataInterfaceRenderTargetVolume.h" #include "SparseVolumeTexture/SparseVolumeTexture.h" #include "Components/HeterogeneousVolumeComponent.h" DEFINE_LOG_CATEGORY(LogNiagaraBaker); ////////////////////////////////////////////////////////////////////////// namespace NiagaraBakerRendererPrivate { static void CreatePreviewScene(UNiagaraSystem* NiagaraSystem, TObjectPtr& OutComponent, TSharedPtr& OutPreviewScene) { check(NiagaraSystem); OutComponent = NewObject(GetTransientPackage(), NAME_None, RF_Transient); OutComponent->CastShadow = 1; OutComponent->bCastDynamicShadow = 1; OutComponent->SetAllowScalability(false); OutComponent->SetAsset(NiagaraSystem); OutComponent->SetForceSolo(true); OutComponent->SetAgeUpdateMode(ENiagaraAgeUpdateMode::DesiredAge); OutComponent->SetCanRenderWhileSeeking(true); OutComponent->SetMaxSimTime(0.0f); OutComponent->Activate(true); OutPreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues())); OutPreviewScene->SetFloorVisibility(false); OutPreviewScene->AddComponent(OutComponent, OutComponent->GetRelativeTransform()); } template static void CreatePreviewScene(TObjectPtr& OutComponent, TSharedPtr& OutPreviewScene) { OutComponent = NewObject(GetTransientPackage(), NAME_None, RF_Transient); OutPreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues())); OutPreviewScene->SetFloorVisibility(false); OutPreviewScene->AddComponent(OutComponent, OutComponent->GetRelativeTransform()); } template static void DestroyPreviewScene(TObjectPtr& InOutComponent, TSharedPtr& InOutPreviewScene) { if (InOutPreviewScene && InOutComponent) { InOutPreviewScene->RemoveComponent(InOutComponent); InOutPreviewScene = nullptr; } if (InOutComponent) { InOutComponent->DestroyComponent(); InOutComponent = nullptr; } } } ////////////////////////////////////////////////////////////////////////// const FString FNiagaraBakerOutputBindingHelper::STRING_SceneCaptureSource("SceneCaptureSource"); const FString FNiagaraBakerOutputBindingHelper::STRING_BufferVisualization("BufferVisualization"); const FString FNiagaraBakerOutputBindingHelper::STRING_EmitterDI("EmitterDI"); const FString FNiagaraBakerOutputBindingHelper::STRING_EmitterParticles("EmitterParticles"); FNiagaraBakerOutputBindingHelper::ERenderType FNiagaraBakerOutputBindingHelper::GetRenderType(FName BindingName, FName& OutName) { OutName = FName(); if (BindingName.IsNone()) { return ERenderType::SceneCapture; } FString SourceBindingString = BindingName.ToString(); TArray SplitNames; SourceBindingString.ParseIntoArray(SplitNames, TEXT(".")); if (!ensure(SplitNames.Num() > 0)) { return ERenderType::None; } // Scene Capture mode if (SplitNames[0] == STRING_SceneCaptureSource) { if (!ensure(SplitNames.Num() == 2)) { return ERenderType::None; } OutName = FName(SplitNames[1]); return ERenderType::SceneCapture; } // Buffer Visualization Mode if (SplitNames[0] == STRING_BufferVisualization) { if (!ensure(SplitNames.Num() == 2)) { return ERenderType::None; } OutName = FName(SplitNames[1]); return ERenderType::BufferVisualization; } // Emitter Data Interface if (SplitNames[0] == STRING_EmitterDI) { OutName = FName(*SourceBindingString.RightChop(SplitNames[0].Len() + 1)); return ERenderType::DataInterface; } // Emitter Data Interface if (SplitNames[0] == STRING_EmitterParticles) { OutName = FName(*SourceBindingString.RightChop(SplitNames[0].Len() + 1)); return ERenderType::Particle; } return ERenderType::None; } void FNiagaraBakerOutputBindingHelper::GetSceneCaptureBindings(TArray& OutBindings) { static UEnum* SceneCaptureOptions = StaticEnum(); for (int i = 0; i < SceneCaptureOptions->GetMaxEnumValue(); ++i) { FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef(); NewBinding.BindingName = FName(STRING_SceneCaptureSource + TEXT(".") + SceneCaptureOptions->GetNameStringByIndex(i)); NewBinding.MenuCategory = FText::FromString(STRING_SceneCaptureSource); NewBinding.MenuEntry = SceneCaptureOptions->GetDisplayNameTextByIndex(i); } } void FNiagaraBakerOutputBindingHelper::GetBufferVisualizationBindings(TArray& OutBindings) { // Gather all buffer visualization options struct FIterator { TArray& OutBindings; FIterator(TArray& InOutBindings) : OutBindings(InOutBindings) {} void ProcessValue(const FString& MaterialName, UMaterialInterface* Material, const FText& DisplayName) { FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef(); NewBinding.BindingName = FName(STRING_BufferVisualization + TEXT(".") + MaterialName); NewBinding.MenuCategory = FText::FromString(STRING_BufferVisualization); NewBinding.MenuEntry = DisplayName; } } Iterator(OutBindings); GetBufferVisualizationData().IterateOverAvailableMaterials(Iterator); } void FNiagaraBakerOutputBindingHelper::ForEachEmitterDataInterface(UNiagaraSystem* NiagaraSystem, FEmitterDIFunction Function) { check(NiagaraSystem); for (int32 EmitterIndex=0; EmitterIndex < NiagaraSystem->GetEmitterHandles().Num(); ++EmitterIndex) { const FNiagaraEmitterHandle& EmitterHandle = NiagaraSystem->GetEmitterHandle(EmitterIndex); FVersionedNiagaraEmitterData* EmitterData = EmitterHandle.GetInstance().GetEmitterData(); if (!EmitterHandle.IsValid() || !EmitterHandle.GetIsEnabled() || !EmitterData) { continue; } const FString EmitterName = EmitterHandle.GetName().ToString(); const FString EmitterPrefix = EmitterName + TEXT("."); EmitterData->ForEachScript( [&](UNiagaraScript* NiagaraScript) { if (const FNiagaraScriptExecutionParameterStore* SrcStore = NiagaraScript->GetExecutionReadyParameterStore(EmitterData->SimTarget)) { for (const FNiagaraVariableWithOffset& Variable : SrcStore->ReadParameterVariables()) { if (Variable.IsDataInterface() == false) { continue; } const FString VariableName = Variable.GetName().ToString(); if (!VariableName.StartsWith(EmitterPrefix)) { continue; } UNiagaraDataInterface* DataInterface = SrcStore->GetDataInterface(Variable.Offset); check(DataInterface); Function(EmitterName, VariableName.Mid(EmitterPrefix.Len()), DataInterface); } } } ); } } UNiagaraDataInterface* FNiagaraBakerOutputBindingHelper::GetDataInterface(UNiagaraComponent* NiagaraComponent, FName DataInterfaceName) { // Find data interface FNiagaraSystemInstanceControllerPtr SystemInstanceController = NiagaraComponent->GetSystemInstanceController(); if ( SystemInstanceController.IsValid() == false ) { return nullptr; } FNiagaraSystemInstance* SystemInstance = SystemInstanceController->GetSoloSystemInstance(); for (auto EmitterInstance : SystemInstance->GetEmitters()) { if ( FNiagaraComputeExecutionContext* ComputeContext = EmitterInstance->GetGPUContext() ) { for (const FNiagaraVariableWithOffset& Variable : ComputeContext->CombinedParamStore.ReadParameterVariables()) { if (Variable.IsDataInterface() && (Variable.GetName() == DataInterfaceName)) { return ComputeContext->CombinedParamStore.GetDataInterface(Variable.Offset); } } } } return nullptr; } void FNiagaraBakerOutputBindingHelper::GetDataInterfaceBindingsForCanvas(TArray& OutBindings, UNiagaraSystem* NiagaraSystem) { check(NiagaraSystem); ForEachEmitterDataInterface( NiagaraSystem, [&](const FString& EmitterName, const FString& VariableName, UNiagaraDataInterface* DataInterface) { if (DataInterface->CanRenderVariablesToCanvas()) { TArray RendererableVariables; DataInterface->GetCanvasVariables(RendererableVariables); for (const FNiagaraVariableBase& RendererableVariable : RendererableVariables) { const FString VariableString = VariableName + TEXT(".") + RendererableVariable.GetName().ToString(); FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef(); NewBinding.BindingName = FName(STRING_EmitterDI + TEXT(".") + EmitterName + TEXT(".") + VariableString); NewBinding.MenuCategory = FText::FromString(TEXT("DataInterface") + EmitterName); NewBinding.MenuEntry = FText::FromString(VariableString); } } } ); } void FNiagaraBakerOutputBindingHelper::GetParticleAttributeBindings(TArray& OutBindings, UNiagaraSystem* NiagaraSystem) { check(NiagaraSystem); const TArray>& AllEmitterCompiledData = NiagaraSystem->GetEmitterCompiledData(); for (int32 EmitterIndex = 0; EmitterIndex < NiagaraSystem->GetEmitterHandles().Num(); ++EmitterIndex) { const FNiagaraEmitterHandle& EmitterHandle = NiagaraSystem->GetEmitterHandle(EmitterIndex); FVersionedNiagaraEmitterData* EmitterData = EmitterHandle.GetInstance().GetEmitterData(); if (!EmitterHandle.IsValid() || !EmitterHandle.GetIsEnabled() || !EmitterData) { continue; } const FString EmitterName = EmitterHandle.GetName().ToString(); if (ensure(AllEmitterCompiledData.IsValidIndex(EmitterIndex))) { const FNiagaraDataSetCompiledData& ParticleDataSet = AllEmitterCompiledData[EmitterIndex]->DataSetCompiledData; for (int32 iVariable = 0; iVariable < ParticleDataSet.VariableLayouts.Num(); ++iVariable) { const FNiagaraVariableBase& Variable = ParticleDataSet.Variables[iVariable]; const FNiagaraVariableLayoutInfo& VariableLayout = ParticleDataSet.VariableLayouts[iVariable]; if (VariableLayout.GetNumFloatComponents() > 0) { const FString VariableString = Variable.GetName().ToString(); FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef(); NewBinding.BindingName = FName(STRING_EmitterParticles + TEXT(".") + EmitterName + TEXT(".") + VariableString); NewBinding.MenuCategory = FText::FromString(TEXT("ParticleAttribute ") + EmitterName); NewBinding.MenuEntry = FText::FromString(VariableString); } } } } } ////////////////////////////////////////////////////////////////////////// FNiagaraBakerRenderer::FNiagaraBakerRenderer(UNiagaraSystem* InNiagaraSystem) : NiagaraSystem(InNiagaraSystem) { } FNiagaraBakerRenderer::~FNiagaraBakerRenderer() { using namespace NiagaraBakerRendererPrivate; DestroyPreviewScene(PreviewComponent, AdvancedPreviewScene); DestroyPreviewScene(SimCachePreviewComponent, SimCacheAdvancedPreviewScene); DestroyPreviewScene(StaticMeshPreviewComponent, StaticMeshPreviewScene); DestroyPreviewScene(SVTPreviewComponent, SVTPreviewScene); } void FNiagaraBakerRenderer::SetAbsoluteTime(float AbsoluteTime, bool bShouldTickComponent) { UNiagaraBakerSettings* BakerSettings = GetBakerSettings(); if ( !ensure(BakerSettings) || !PreviewComponent ) { return; } if (!PreviewComponent->IsActive() && (AbsoluteTime < PreviewComponent->GetDesiredAge())) { PreviewComponent->ReinitializeSystem(); } PreviewComponent->SetSeekDelta(BakerSettings->GetSeekDelta()); PreviewComponent->SeekToDesiredAge(AbsoluteTime); UWorld* World = PreviewComponent->GetWorld(); if (ensure(World)) { World->TimeSeconds = AbsoluteTime; World->UnpausedTimeSeconds = AbsoluteTime; World->RealTimeSeconds = AbsoluteTime; World->DeltaRealTimeSeconds = BakerSettings->GetSeekDelta(); World->DeltaTimeSeconds = BakerSettings->GetSeekDelta(); World->Tick(ELevelTick::LEVELTICK_PauseTick, 0.0f); } if (bShouldTickComponent) { PreviewComponent->TickComponent(BakerSettings->GetSeekDelta(), ELevelTick::LEVELTICK_All, nullptr); // World should be guaranteed but let's be safe if ( World ) { // Send EOF updates before we flush our pending ticks to ensure everything is ready for Niagara World->SendAllEndOfFrameUpdates(); if (FNiagaraWorldManager* WorldManager = FNiagaraWorldManager::Get(World)) { WorldManager->FlushComputeAndDeferredQueues(false); } } } } void FNiagaraBakerRenderer::RenderSceneCapture(UTextureRenderTarget2D* RenderTarget, ESceneCaptureSource CaptureSource) const { RenderSceneCapture(RenderTarget, GetPreviewComponent(), CaptureSource); } void FNiagaraBakerRenderer::RenderSceneCapture(UTextureRenderTarget2D* RenderTarget, UPrimitiveComponent* BakedDataComponent, ESceneCaptureSource CaptureSource) const { UNiagaraBakerSettings* BakerSettings = GetBakerSettings(); if (!BakedDataComponent || !RenderTarget || !BakerSettings) { return; } const float WorldTime = GetWorldTime(); UWorld* World = BakedDataComponent->GetWorld(); FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), GetFeatureLevel()); Canvas.Clear(FLinearColor::Black); // Lazy create the component to avoid it always being present in every Niagara editor if (SceneCaptureComponent == nullptr) { SceneCaptureComponent = NewObject(GetTransientPackage(), NAME_None, RF_Transient); SceneCaptureComponent->bTickInEditor = false; SceneCaptureComponent->SetComponentTickEnabled(false); SceneCaptureComponent->SetVisibility(true); SceneCaptureComponent->bCaptureEveryFrame = false; SceneCaptureComponent->bCaptureOnMovement = false; } SceneCaptureComponent->RegisterComponentWithWorld(World); SceneCaptureComponent->TextureTarget = RenderTarget; SceneCaptureComponent->CaptureSource = CaptureSource; // Set view location const FNiagaraBakerCameraSettings& CurrentCamera = BakerSettings->GetCurrentCamera(); if (CurrentCamera.IsOrthographic()) { SceneCaptureComponent->ProjectionType = ECameraProjectionMode::Orthographic; SceneCaptureComponent->OrthoWidth = CurrentCamera.OrthoWidth; } else { SceneCaptureComponent->ProjectionType = ECameraProjectionMode::Perspective; SceneCaptureComponent->FOVAngle = CurrentCamera.FOV; } const FMatrix SceneCaptureMatrix = FMatrix(FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, 0, 1)); FMatrix ViewMatrix = SceneCaptureMatrix * BakerSettings->GetViewportMatrix().Inverse() * FRotationTranslationMatrix(BakerSettings->GetCameraRotation(), BakerSettings->GetCameraLocation()); SceneCaptureComponent->SetWorldLocationAndRotation(ViewMatrix.GetOrigin(), ViewMatrix.Rotator()); SceneCaptureComponent->bUseCustomProjectionMatrix = true; SceneCaptureComponent->CustomProjectionMatrix = BakerSettings->GetProjectionMatrix(); if (BakerSettings->bRenderComponentOnly) { const TArray>& AttachChildren = BakedDataComponent->GetAttachChildren(); SceneCaptureComponent->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList; SceneCaptureComponent->ShowOnlyComponents.Empty(1 + AttachChildren.Num()); SceneCaptureComponent->ShowOnlyComponents.Add(BakedDataComponent); for (TWeakObjectPtr WeakChildComponent : AttachChildren) { if (UPrimitiveComponent* ChildComponent = Cast(WeakChildComponent.Get())) { SceneCaptureComponent->ShowOnlyComponents.Add(ChildComponent); } } } else { SceneCaptureComponent->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_RenderScenePrimitives; } SceneCaptureComponent->CaptureScene(); SceneCaptureComponent->TextureTarget = nullptr; SceneCaptureComponent->UnregisterComponent(); // Alpha from a scene capture is 1- so we need to invert if (SceneCaptureComponent->CaptureSource == ESceneCaptureSource::SCS_SceneColorHDR) { FCanvasTileItem TileItem(FVector2D(0, 0), FVector2D(RenderTarget->GetSurfaceWidth(), RenderTarget->GetSurfaceHeight()), FLinearColor::White); TileItem.BlendMode = SE_BLEND_Opaque; TileItem.BatchedElementParameters = new FBatchedElementNiagaraInvertColorChannel(0); Canvas.DrawItem(TileItem); } Canvas.Flush_GameThread(); } void FNiagaraBakerRenderer::RenderBufferVisualization(UTextureRenderTarget2D* RenderTarget, FName BufferVisualizationMode) const { UNiagaraBakerSettings* BakerSettings = GetBakerSettings(); if (!RenderTarget || !BakerSettings) { return; } const FIntRect ViewRect = FIntRect(0, 0, RenderTarget->GetSurfaceWidth(), RenderTarget->GetSurfaceHeight()); const float WorldTime = GetWorldTime(); UWorld* World = GetWorld(); FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), GetFeatureLevel()); Canvas.Clear(FLinearColor::Black); // Create View Family FSceneViewFamilyContext ViewFamily( FSceneViewFamily::ConstructionValues(RenderTarget->GameThread_GetRenderTargetResource(), World->Scene, FEngineShowFlags(ESFIM_Game)) .SetTime(FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime())) ); ViewFamily.EngineShowFlags.SetScreenPercentage(false); //ViewFamily.EngineShowFlags.DisableAdvancedFeatures(); //ViewFamily.EngineShowFlags.MotionBlur = 0; //ViewFamily.EngineShowFlags.SetDistanceCulledPrimitives(true); // show distance culled objects //ViewFamily.EngineShowFlags.SetPostProcessing(false); if (BufferVisualizationMode.IsValid()) { ViewFamily.EngineShowFlags.SetPostProcessing(true); ViewFamily.EngineShowFlags.SetVisualizeBuffer(true); ViewFamily.EngineShowFlags.SetTonemapper(false); ViewFamily.EngineShowFlags.SetScreenPercentage(false); } FSceneViewInitOptions ViewInitOptions; ViewInitOptions.SetViewRectangle(ViewRect); ViewInitOptions.ViewFamily = &ViewFamily; ViewInitOptions.ViewOrigin = BakerSettings->GetCameraLocation(); ViewInitOptions.ViewRotationMatrix = BakerSettings->GetViewMatrix(); ViewInitOptions.ProjectionMatrix = BakerSettings->GetProjectionMatrix(); ViewInitOptions.BackgroundColor = FLinearColor::Black; if (BakerSettings->bRenderComponentOnly) { ViewInitOptions.ShowOnlyPrimitives.Emplace(); ViewInitOptions.ShowOnlyPrimitives->Add(GetPreviewComponent()->GetPrimitiveSceneId()); } FSceneView* NewView = new FSceneView(ViewInitOptions); NewView->CurrentBufferVisualizationMode = BufferVisualizationMode; ViewFamily.Views.Add(NewView); ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(ViewFamily, 1.0f)); GetRendererModule().BeginRenderingViewFamily(&Canvas, &ViewFamily); Canvas.Flush_GameThread(); } void FNiagaraBakerRenderer::RenderDataInterface(UTextureRenderTarget2D* RenderTarget, FName BindingName) const { const float WorldTime = GetWorldTime(); FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), GetFeatureLevel()); Canvas.Clear(FLinearColor::Black); ON_SCOPE_EXIT{ Canvas.Flush_GameThread(); }; // Gather data interface / attribute name FString SourceString = BindingName.ToString(); int32 DotIndex; if (!SourceString.FindLastChar('.', DotIndex)) { return; } const FName DataInterfaceName = FName(SourceString.LeftChop(SourceString.Len() - DotIndex)); const FName VariableName = FName(SourceString.RightChop(DotIndex + 1)); // Find data interface FNiagaraSystemInstanceControllerPtr SystemInstanceController = GetPreviewComponent()->GetSystemInstanceController(); if ( SystemInstanceController.IsValid() == false ) { return; } FNiagaraSystemInstance* SystemInstance = SystemInstanceController->GetSoloSystemInstance(); const FNiagaraSystemInstanceID SystemInstanceID = SystemInstance->GetId(); for (auto EmitterInstance : SystemInstance->GetEmitters()) { FNiagaraComputeExecutionContext* ExecContext = EmitterInstance->GetGPUContext(); if ( ExecContext == nullptr ) { continue; } for (const FNiagaraVariableWithOffset& Variable : ExecContext->CombinedParamStore.ReadParameterVariables()) { if (Variable.IsDataInterface()) { if (Variable.GetName() == DataInterfaceName) { if ( UNiagaraDataInterface* DataInterface = ExecContext->CombinedParamStore.GetDataInterface(Variable.Offset) ) { const FIntRect ViewRect(0, 0, RenderTarget->GetSurfaceWidth(), RenderTarget->GetSurfaceHeight()); DataInterface->RenderVariableToCanvas(SystemInstanceID, VariableName, &Canvas, ViewRect); return; } } } } } } void FNiagaraBakerRenderer::RenderParticleAttribute(UTextureRenderTarget2D* RenderTarget, FName BindingName) const { const float WorldTime = GetWorldTime(); FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), GetFeatureLevel()); Canvas.Clear(FLinearColor::Black); ON_SCOPE_EXIT { Canvas.Flush_GameThread(); }; FString SourceString = BindingName.ToString(); int32 DotIndex; if ( !SourceString.FindChar('.', DotIndex) ) { return; } const FString EmitterName = SourceString.LeftChop(SourceString.Len() - DotIndex); const FName AttributeName = FName(SourceString.RightChop(DotIndex + 1)); FNiagaraSystemInstanceControllerPtr SystemInstanceController = GetPreviewComponent()->GetSystemInstanceController(); if (!ensure(SystemInstanceController.IsValid())) { return; } FNiagaraSystemInstance* SystemInstance = SystemInstanceController->GetSoloSystemInstance(); if ( !ensure(SystemInstance) ) { return; } for ( const auto& EmitterInstance : SystemInstance->GetEmitters() ) { const UNiagaraEmitter* NiagaraEmitter = EmitterInstance->GetEmitter(); if ( !NiagaraEmitter || (NiagaraEmitter->GetUniqueEmitterName() != EmitterName) ) { continue; } if (EmitterInstance->GetGPUContext() != nullptr) { return; } const FNiagaraDataSet& ParticleDataSet = EmitterInstance->GetParticleData(); const FNiagaraDataBuffer* ParticleDataBuffer = ParticleDataSet.GetCurrentData(); FNiagaraDataSetReaderInt32 UniqueIDAccessor = FNiagaraDataSetAccessor::CreateReader(ParticleDataSet, FName("UniqueID")); if ( !ParticleDataBuffer || !UniqueIDAccessor.IsValid() ) { return; } const int32 VariableIndex = ParticleDataSet.GetCompiledData().Variables.IndexOfByPredicate([&AttributeName](const FNiagaraVariable& Variable) { return Variable.GetName() == AttributeName; }); if (VariableIndex == INDEX_NONE) { return; } const FNiagaraVariableLayoutInfo& VariableInfo = ParticleDataSet.GetCompiledData().VariableLayouts[VariableIndex]; const float* FloatChannels[4]; FloatChannels[0] = reinterpret_cast(ParticleDataBuffer->GetComponentPtrFloat(VariableInfo.GetFloatComponentStart())); FloatChannels[1] = VariableInfo.GetNumFloatComponents() > 1 ? reinterpret_cast(ParticleDataBuffer->GetComponentPtrFloat(VariableInfo.GetFloatComponentStart() + 1)) : nullptr; FloatChannels[2] = VariableInfo.GetNumFloatComponents() > 2 ? reinterpret_cast(ParticleDataBuffer->GetComponentPtrFloat(VariableInfo.GetFloatComponentStart() + 2)) : nullptr; FloatChannels[3] = VariableInfo.GetNumFloatComponents() > 3 ? reinterpret_cast(ParticleDataBuffer->GetComponentPtrFloat(VariableInfo.GetFloatComponentStart() + 3)) : nullptr; const FIntPoint RenderTargetSize(RenderTarget->GetSurfaceWidth(), RenderTarget->GetSurfaceHeight()); const int32 ParticleBufferStore = RenderTargetSize.X * RenderTargetSize.Y; for ( uint32 i=0; i < ParticleDataBuffer->GetNumInstances(); ++i ) { const int32 UniqueID = UniqueIDAccessor[i]; if (UniqueID >= ParticleBufferStore) { continue; } FLinearColor OutputColor; OutputColor.R = FloatChannels[0] ? FloatChannels[0][i] : 0.0f; OutputColor.G = FloatChannels[1] ? FloatChannels[1][i] : 0.0f; OutputColor.B = FloatChannels[2] ? FloatChannels[2][i] : 0.0f; OutputColor.A = FloatChannels[3] ? FloatChannels[3][i] : 0.0f; const int32 TexelX = UniqueID % RenderTargetSize.X; const int32 TexelY = UniqueID / RenderTargetSize.X; Canvas.DrawTile(TexelX, TexelY, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, OutputColor); } // We are done break; } } void FNiagaraBakerRenderer::RenderSimCache(UTextureRenderTarget2D* RenderTarget, UNiagaraSimCache* SimCache) const { using namespace NiagaraBakerRendererPrivate; UNiagaraBakerSettings* BakerSettings = GetBakerSettings(); if (!SimCache) { return; } if (SimCachePreviewComponent == nullptr) { CreatePreviewScene(NiagaraSystem, SimCachePreviewComponent, SimCacheAdvancedPreviewScene); } const float SeekDelta = BakerSettings->GetSeekDelta(); SimCachePreviewComponent->SetSimCache(SimCache); SimCachePreviewComponent->SetSeekDelta(SeekDelta); SimCachePreviewComponent->SeekToDesiredAge(GetWorldTime()); SimCachePreviewComponent->TickComponent(SeekDelta, ELevelTick::LEVELTICK_All, nullptr); SimCachePreviewComponent->MarkRenderDynamicDataDirty(); UWorld* World = SimCachePreviewComponent->GetWorld(); World->SendAllEndOfFrameUpdates(); RenderSceneCapture(RenderTarget, SimCachePreviewComponent, ESceneCaptureSource::SCS_SceneColorHDR); SimCachePreviewComponent->SetSimCache(nullptr); } void FNiagaraBakerRenderer::RenderSparseVolumeTexture(UTextureRenderTarget2D* RenderTarget, const FNiagaraBakerOutputFrameIndices Indices, UAnimatedSparseVolumeTexture* SVT) const { UNiagaraBakerSettings* BakerSettings = GetBakerSettings(); if (!SVT) { return; } if (SVTPreviewComponent == nullptr) { SVTPreviewComponent = NewObject(GetTransientPackage(), NAME_None, RF_Transient); // create HV component and wire all the things UMaterialInterface* MaterialInterface = LoadObject(nullptr, TEXT("/Engine/EngineMaterials/SparseVolumeMaterial")); UMaterial *Mat = MaterialInterface->GetMaterial(); // #todo(dmp): we had to duplicate the material itself because we cannot make a mid and send that to HV // HV internally makes a MID from whatever is bound, and the MID of a MID workflow appears broken UMaterial* DuplicateMat = DuplicateObject(Mat, SVTPreviewComponent); DuplicateMat->ClearFlags(RF_Standalone); FGuid ExprGuid; DuplicateMat->SetStaticComponentMaskParameterValueEditorOnly("Temperature Mask", false, true, false, false, ExprGuid); FGuid SwitchGuid; DuplicateMat->SetStaticSwitchParameterValueEditorOnly("Temperature (Attributes B)", false, SwitchGuid); DuplicateMat->SetSparseVolumeTextureParameterValueEditorOnly("SparseVolumeTexture", SVT); SVTPreviewComponent->OverrideMaterials.Add(DuplicateMat); SVTPreviewComponent->bIssueBlockingRequests = true; SVTPreviewComponent->PostLoad(); SVTPreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues())); SVTPreviewScene->SetFloorVisibility(false); SVTPreviewScene->AddComponent(SVTPreviewComponent, SVTPreviewComponent->GetRelativeTransform()); } TArray ParameterInfo; TArray ParameterIds; SVTPreviewComponent->OverrideMaterials[0]->GetAllSparseVolumeTextureParameterInfo(ParameterInfo, ParameterIds); USparseVolumeTexture* OldSVT; SVTPreviewComponent->OverrideMaterials[0]->GetSparseVolumeTextureParameterValue(ParameterInfo[0], OldSVT); if (OldSVT != SVT) { SVTPreviewComponent->OverrideMaterials[0]->GetMaterial()->SetSparseVolumeTextureParameterValueEditorOnly("SparseVolumeTexture", SVT); } int32 NumFrames = SVT->GetNumFrames(); if (NumFrames == 0) { return; } // do mod if we are previewing the looped result and the timeline doesn't line up with the baked number of frames for the looped // sim. This will at least loop it, but there could be a frame pop when the timeline resets SVTPreviewComponent->SetFrame(Indices.FrameIndexA % SVT->GetNumFrames()); // #todo(dmp): apply world scale and pivot to HV actor const float SeekDelta = BakerSettings->GetSeekDelta(); SVTPreviewComponent->TickComponent(SeekDelta, ELevelTick::LEVELTICK_All, nullptr); SVTPreviewComponent->MarkRenderDynamicDataDirty(); UWorld* World = SVTPreviewComponent->GetWorld(); World->SendAllEndOfFrameUpdates(); RenderSceneCapture(RenderTarget, SVTPreviewComponent, ESceneCaptureSource::SCS_SceneColorHDR); } void FNiagaraBakerRenderer::RenderStaticMesh(UTextureRenderTarget2D* RenderTarget, UStaticMesh* StaticMesh) const { using namespace NiagaraBakerRendererPrivate; UNiagaraBakerSettings* BakerSettings = GetBakerSettings(); if (!StaticMesh) { return; } if (StaticMeshPreviewComponent == nullptr) { CreatePreviewScene(StaticMeshPreviewComponent, StaticMeshPreviewScene); } StaticMeshPreviewComponent->SetStaticMesh(StaticMesh); UWorld* World = StaticMeshPreviewComponent->GetWorld(); World->SendAllEndOfFrameUpdates(); RenderSceneCapture(RenderTarget, StaticMeshPreviewComponent, ESceneCaptureSource::SCS_SceneColorHDR); StaticMeshPreviewComponent->SetStaticMesh(nullptr); } UWorld* FNiagaraBakerRenderer::GetWorld() const { return GetPreviewComponent()->GetWorld(); } float FNiagaraBakerRenderer::GetWorldTime() const { return GetPreviewComponent()->GetDesiredAge(); } ERHIFeatureLevel::Type FNiagaraBakerRenderer::GetFeatureLevel() const { return GetPreviewComponent()->GetWorld()->Scene->GetFeatureLevel(); } UNiagaraComponent* FNiagaraBakerRenderer::GetPreviewComponent() const { if (PreviewComponent == nullptr) { using namespace NiagaraBakerRendererPrivate; CreatePreviewScene(NiagaraSystem, PreviewComponent, AdvancedPreviewScene); } return PreviewComponent; } UNiagaraSystem* FNiagaraBakerRenderer::GetNiagaraSystem() const { return NiagaraSystem; } void FNiagaraBakerRenderer::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(NiagaraSystem); Collector.AddReferencedObject(PreviewComponent); Collector.AddReferencedObject(SceneCaptureComponent); Collector.AddReferencedObject(SimCachePreviewComponent); Collector.AddReferencedObject(SVTPreviewComponent); } FNiagaraBakerOutputRenderer* FNiagaraBakerRenderer::GetOutputRenderer(UClass* Class) { return FNiagaraBakerOutputRegistry::Get().GetRendererForClass(Class); } bool FNiagaraBakerRenderer::ExportImage(FStringView FilePath, FIntPoint ImageSize, TArrayView ImageData) { const FString FileExtension = FPaths::GetExtension(FilePath.GetData(), true); const EImageFormat ImageFormat = ImageWrapperHelper::GetImageFormat(FileExtension); if (ImageFormat == EImageFormat::Invalid) { return false; } IImageWrapperModule & ImageWrapperModule = FModuleManager::LoadModuleChecked("ImageWrapper"); TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(ImageFormat); if ( ImageWrapper.IsValid() == false ) { return false; } if ( ImageFormat == EImageFormat::EXR || ImageFormat == EImageFormat::HDR ) { TArray TempImageData; TempImageData.Reserve(ImageData.Num()); for (const FFloat16Color& HalfColor : ImageData) { TempImageData.Emplace(HalfColor.GetFloats()); } if (ImageWrapper->SetRaw(TempImageData.GetData(), TempImageData.Num() * TempImageData.GetTypeSize(), ImageSize.X, ImageSize.Y, ERGBFormat::RGBAF, 32) == false) { return false; } } else { TArray TempImageData; TempImageData.Reserve(ImageData.Num()); for (const FFloat16Color& HalfColor : ImageData) { TempImageData.Add(HalfColor.GetFloats().ToFColor(true)); } if (ImageWrapper->SetRaw(TempImageData.GetData(), TempImageData.Num() * TempImageData.GetTypeSize(), ImageSize.X, ImageSize.Y, ERGBFormat::BGRA, 8) == false) { return false; } } const TArray64 TempData = ImageWrapper->GetCompressed(); return FFileHelper::SaveArrayToFile(TempData, FilePath.GetData()); } bool FNiagaraBakerRenderer::ExportVolume(FStringView FilePath, FIntVector ImageSize, TArrayView ImageData) { const FString FileExtension = FPaths::GetExtension(FilePath.GetData(), true); if (FileExtension == TEXT(".vdb")) { UE_LOG(LogNiagaraEditor, Warning, TEXT("Exporting vdb grids from the Niagara Baker is no longer supported.")); return false; } else { return ExportImage(FilePath, FIntPoint(ImageSize.X, ImageSize.Y * ImageSize.Z), ImageData); } } bool FVolumeDataInterfaceHelper::Initialize(const TArray& InputDataInterfacePath, UNiagaraComponent* InNiagaraComponent) { NiagaraComponent = InNiagaraComponent; DataInterfacePath = InputDataInterfacePath; if (DataInterfacePath.Num() < 2) { return false; } const FName DataInterfaceName(DataInterfacePath[0] + "." + DataInterfacePath[1]); UNiagaraDataInterface* DataInterface = FNiagaraBakerOutputBindingHelper::GetDataInterface(NiagaraComponent, DataInterfaceName); if (DataInterface == nullptr) { return false; } // Guaranteed since we got a data interface SystemInstance = NiagaraComponent->GetSystemInstanceController()->GetSoloSystemInstance(); // Render Target Volume if (DataInterface->IsA()) { VolumeRenderTargetDataInterface = CastChecked(DataInterface); VolumeRenderTargetProxy = static_cast(VolumeRenderTargetDataInterface->GetProxy()); VolumeRenderTargetInstanceData_GameThread = static_cast(SystemInstance->FindDataInterfaceInstanceData(VolumeRenderTargetDataInterface)); if (VolumeRenderTargetInstanceData_GameThread == nullptr) { return false; } } // Grid 3D else if (DataInterface->IsA()) { Grid3DDataInterface = CastChecked(DataInterface); Grid3DProxy = static_cast(Grid3DDataInterface->GetProxy()); Grid3DInstanceData_GameThread = static_cast(SystemInstance->FindDataInterfaceInstanceData(Grid3DDataInterface)); if (Grid3DInstanceData_GameThread == nullptr) { return false; } if (DataInterfacePath.Num() != 3) { // Perhaps a path to pull all attributes, i.e. whole texture? return false; } Grid3DAttributeName = FName(DataInterfacePath[2]); Grid3DVariableIndex = Grid3DInstanceData_GameThread->Vars.IndexOfByPredicate([&](const FNiagaraVariableBase& VariableBase) { return VariableBase.GetName() == Grid3DAttributeName; }); if (Grid3DVariableIndex == INDEX_NONE) { return false; } Grid3DAttributeStart = Grid3DInstanceData_GameThread->Offsets[Grid3DVariableIndex]; Grid3DAttributeChannels = Grid3DInstanceData_GameThread->Vars[Grid3DVariableIndex].GetType().GetSize() / sizeof(float); Grid3DTextureSize.X = Grid3DInstanceData_GameThread->NumCells.X * Grid3DInstanceData_GameThread->NumTiles.X; Grid3DTextureSize.Y = Grid3DInstanceData_GameThread->NumCells.Y * Grid3DInstanceData_GameThread->NumTiles.Y; Grid3DTextureSize.Z = Grid3DInstanceData_GameThread->NumCells.Z * Grid3DInstanceData_GameThread->NumTiles.Z; } // Unsupported type else { return false; } return true; }