// Copyright Epic Games, Inc. All Rights Reserved. #include "NiagaraBakerRendererOutputVolumeTexture.h" #include "NiagaraBakerOutputVolumeTexture.h" #include "NiagaraBakerSettings.h" #include "NiagaraComponent.h" #include "NiagaraSystemInstance.h" #include "NiagaraSystemInstanceController.h" #include "NiagaraComputeExecutionContext.h" #include "NiagaraBatchedElements.h" #include "NiagaraDataInterfaceGrid3DCollection.h" #include "NiagaraDataInterfaceRenderTargetVolume.h" #include "Engine/Canvas.h" #include "Engine/TextureRenderTarget2D.h" #include "Engine/VolumeTexture.h" #include "HAL/PlatformFileManager.h" #include "Misc/PathViews.h" #include "Modules/ModuleManager.h" #include "Factories/VolumeTextureFactory.h" #include "TextureResource.h" #include "UObject/UObjectGlobals.h" #include "VolumeCache.h" #include "VolumeCacheFactory.h" TArray FNiagaraBakerRendererOutputVolumeTexture::GetRendererBindings(UNiagaraBakerOutput* InBakerOutput) const { TArray OutBindings; if (UNiagaraSystem* NiagaraSystem = InBakerOutput->GetTypedOuter()) { FNiagaraBakerOutputBindingHelper::ForEachEmitterDataInterface( NiagaraSystem, [&](const FString& EmitterName, const FString& VariableName, UNiagaraDataInterface* DataInterface) { if ( UNiagaraDataInterfaceGrid3DCollection* Grid3D = Cast(DataInterface) ) { TArray GridVariables; TArray GridVariableOffsets; int32 NumAttribChannelsFound; Grid3D->FindAttributes(GridVariables, GridVariableOffsets, NumAttribChannelsFound); for ( const FNiagaraVariableBase& GridVariable : GridVariables) { const FString GridVariableString = GridVariable.GetName().ToString(); FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef(); NewBinding.BindingName = FName(EmitterName + "." + VariableName + "." + GridVariableString); NewBinding.MenuCategory = FText::FromString(EmitterName + " " + TEXT("Grid3DCollection")); NewBinding.MenuEntry = FText::FromString(VariableName + "." + GridVariableString); } } else if ( UNiagaraDataInterfaceRenderTargetVolume* VolumeRT = Cast(DataInterface) ) { FNiagaraBakerOutputBinding& NewBinding = OutBindings.AddDefaulted_GetRef(); NewBinding.BindingName = FName(EmitterName + "." + VariableName); NewBinding.MenuCategory = FText::FromString(EmitterName + " " + TEXT("VolumeRenderTarget")); NewBinding.MenuEntry = FText::FromString(VariableName); } } ); } return OutBindings; } FIntPoint FNiagaraBakerRendererOutputVolumeTexture::GetPreviewSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const { return InAvailableSize; } void FNiagaraBakerRendererOutputVolumeTexture::RenderPreview(UNiagaraBakerOutput* InBakerOutput, const FNiagaraBakerRenderer& BakerRenderer, UTextureRenderTarget2D* RenderTarget, TOptional& OutErrorString) const { UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked(InBakerOutput); const float WorldTime = BakerRenderer.GetWorldTime(); FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), BakerRenderer.GetFeatureLevel()); Canvas.Clear(FLinearColor::Black); ON_SCOPE_EXIT{ Canvas.Flush_GameThread(); }; FVolumeDataInterfaceHelper DataInterface; TArray DataInterfacePath; OutputVolumeTexture->SourceBinding.SourceName.ToString().ParseIntoArray(DataInterfacePath, TEXT(".")); if (DataInterface.Initialize(DataInterfacePath, BakerRenderer.GetPreviewComponent()) == false) { OutErrorString = TEXT("Could not find data to preview from.\nPlease ensure binding is set to a valid source."); return; } // Volume Render Target if ( DataInterface.VolumeRenderTargetDataInterface != nullptr ) { const int32 NumSlices = DataInterface.VolumeRenderTargetInstanceData_GameThread->Size.Z; const int32 SlicePerAxis = FMath::CeilToInt(FMath::Sqrt((float)NumSlices)); const int32 TileWidth = RenderTarget->GetSurfaceWidth() / SlicePerAxis; const int32 TileHeight = RenderTarget->GetSurfaceHeight() / SlicePerAxis; if (TileWidth <= 0 && TileHeight <= 0) { return; } const int32 AttributeChannels[] = {0, 1, 2, 3}; const FIntVector TextureSize = DataInterface.VolumeRenderTargetInstanceData_GameThread->Size; const FVector2f TileUVOffset = FVector2f(0.5f / float(TextureSize.X), 0.5f / float(TextureSize.Y)); const FVector2f TileUVSize = FVector2f(float(TextureSize.X - 1) / float(TextureSize.X), float(TextureSize.Y - 1) / float(TextureSize.Y)); for (int32 Slice=0; Slice < NumSlices; ++Slice) { const float SliceW = (float(Slice) + 0.5f) / float(NumSlices - 1); const FVector3f AttributeUVs[] = { FVector3f(TileUVOffset.X, TileUVOffset.Y, SliceW), FVector3f(TileUVOffset.X, TileUVOffset.Y, SliceW), FVector3f(TileUVOffset.X, TileUVOffset.Y, SliceW), FVector3f(TileUVOffset.X, TileUVOffset.Y, SliceW), }; const int32 TileX = (Slice % SlicePerAxis) * TileWidth; const int32 TileY = (Slice / SlicePerAxis) * TileHeight; FCanvasTileItem TileItem(FVector2D(TileX, TileY), GWhiteTexture, FVector2D(TileWidth, TileHeight), FVector2D(0.0, 1.0f), FVector2D(1.0, 0.0f), FLinearColor::White); TileItem.BlendMode = SE_BLEND_Opaque; TileItem.BatchedElementParameters = new FBatchedElementNiagaraVolumeAttribute( FVector2f(1.0f, 1.0f), MakeArrayView(AttributeUVs), MakeArrayView(AttributeChannels), [Proxy_RT=DataInterface.VolumeRenderTargetProxy, SystemInstanceID_RT= DataInterface.SystemInstance->GetId()](FRHITexture*& OutTexture, FRHISamplerState*& OutSamplerState) { if ( const FRenderTargetVolumeRWInstanceData_RenderThread* InstanceData_RT = Proxy_RT->SystemInstancesToProxyData_RT.Find(SystemInstanceID_RT) ) { OutTexture = InstanceData_RT->RenderTarget->GetRHI(); OutSamplerState = InstanceData_RT->SamplerStateRHI; } } ); Canvas.DrawItem(TileItem); } } // Grid Collection else if ( DataInterface.Grid3DDataInterface != nullptr ) { const FVector2f TileUVSize = FVector2f( float(DataInterface.Grid3DInstanceData_GameThread->NumCells.X - 1) / float(DataInterface.Grid3DTextureSize.X), float(DataInterface.Grid3DInstanceData_GameThread->NumCells.Y - 1) / float(DataInterface.Grid3DTextureSize.Y) ); TArray> AttributeUVs; for ( int32 i=0; i < DataInterface.Grid3DAttributeChannels; ++i ) { const int32 AttributeIndex = DataInterface.Grid3DAttributeStart + i; const FIntVector AttributeTileIndex( AttributeIndex % DataInterface.Grid3DInstanceData_GameThread->NumTiles.X, (AttributeIndex / DataInterface.Grid3DInstanceData_GameThread->NumTiles.X) % DataInterface.Grid3DInstanceData_GameThread->NumTiles.Y, AttributeIndex / (DataInterface.Grid3DInstanceData_GameThread->NumTiles.X * DataInterface.Grid3DInstanceData_GameThread->NumTiles.Y) ); FVector3f& AttributeUV = AttributeUVs.AddDefaulted_GetRef(); AttributeUV.X = (float(AttributeTileIndex.X * DataInterface.Grid3DInstanceData_GameThread->NumCells.X) + 0.5f) / DataInterface.Grid3DTextureSize.X; AttributeUV.Y = (float(AttributeTileIndex.Y * DataInterface.Grid3DInstanceData_GameThread->NumCells.Y) + 0.5f) / DataInterface.Grid3DTextureSize.Y; AttributeUV.Z = (float(AttributeTileIndex.Z * DataInterface.Grid3DInstanceData_GameThread->NumCells.Z) + 0.5f) / DataInterface.Grid3DTextureSize.Z; } const int32 NumSlices = DataInterface.Grid3DInstanceData_GameThread->NumCells.Z; const int32 SlicePerAxis = FMath::CeilToInt(FMath::Sqrt((float)NumSlices)); const int32 TileWidth = RenderTarget->GetSurfaceWidth() / SlicePerAxis; const int32 TileHeight = RenderTarget->GetSurfaceHeight() / SlicePerAxis; if (TileWidth > 0 && TileHeight > 0) { for (int32 Slice=0; Slice < NumSlices; ++Slice) { const int32 TileX = (Slice % SlicePerAxis) * TileWidth; const int32 TileY = (Slice / SlicePerAxis) * TileHeight; TArray> SliceAttributeUVs; for ( FVector3f SliceUV : AttributeUVs ) { SliceUV.Z += float(Slice) / float(DataInterface.Grid3DTextureSize.Z); SliceAttributeUVs.Add(SliceUV); } FCanvasTileItem TileItem(FVector2D(TileX, TileY), GWhiteTexture, FVector2D(TileWidth, TileHeight), FVector2D(0.0, 1.0f), FVector2D(1.0, 0.0f), FLinearColor::White); TileItem.BlendMode = SE_BLEND_Opaque; TileItem.BatchedElementParameters = new FBatchedElementNiagaraVolumeAttribute( TileUVSize, MakeArrayView(SliceAttributeUVs), MakeArrayView(nullptr, 0), [RT_Proxy=DataInterface.Grid3DProxy, RT_SystemInstanceID=DataInterface.SystemInstance->GetId()](FRHITexture*& OutTexture, FRHISamplerState*& OutSamplerState) { if ( const FGrid3DCollectionRWInstanceData_RenderThread* RT_InstanceData = RT_Proxy->SystemInstancesToProxyData_RT.Find(RT_SystemInstanceID) ) { if ( RT_InstanceData->CurrentData != nullptr ) { OutTexture = RT_InstanceData->CurrentData->GetPooledTexture()->GetRHI(); OutSamplerState = TStaticSamplerState::GetRHI(); } } } ); Canvas.DrawItem(TileItem); } } } } FIntPoint FNiagaraBakerRendererOutputVolumeTexture::GetGeneratedSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const { UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked(InBakerOutput); UNiagaraBakerSettings* BakerSettings = OutputVolumeTexture->GetTypedOuter(); FIntPoint RequiredSize = FIntPoint::ZeroValue; if ( OutputVolumeTexture->bGenerateAtlas ) { if ( UVolumeTexture* AtlasVolumeTexture = OutputVolumeTexture->GetAsset(OutputVolumeTexture->AtlasAssetPathFormat, 0) ) { const FIntPoint FrameSize( AtlasVolumeTexture->GetSizeX() / BakerSettings->FramesPerDimension.X, AtlasVolumeTexture->GetSizeY() / BakerSettings->FramesPerDimension.Y ); const float Ratio = FMath::Min(float(InAvailableSize.X) / float(FrameSize.X), float(InAvailableSize.Y) / float(FrameSize.Y)); RequiredSize.X = FMath::RoundToInt(FrameSize.X * Ratio); RequiredSize.Y = FMath::RoundToInt(FrameSize.Y * Ratio); } } return RequiredSize; } void FNiagaraBakerRendererOutputVolumeTexture::RenderGenerated(UNiagaraBakerOutput* InBakerOutput, const FNiagaraBakerRenderer& BakerRenderer, UTextureRenderTarget2D* RenderTarget, TOptional& OutErrorString) const { static FString AtlasNotFoundError(TEXT("Atlas texture not found.\nPlease bake an atlas to see the result.")); UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked(InBakerOutput); UNiagaraBakerSettings* BakerGeneratedSettings = OutputVolumeTexture->GetTypedOuter(); if (OutputVolumeTexture->bGenerateAtlas == false || !BakerGeneratedSettings) { OutErrorString = AtlasNotFoundError; return; } UVolumeTexture* AtlasVolumeTexture = OutputVolumeTexture->GetAsset(OutputVolumeTexture->AtlasAssetPathFormat, 0); if (AtlasVolumeTexture == nullptr) { OutErrorString = AtlasNotFoundError; return; } const float WorldTime = BakerRenderer.GetWorldTime(); FCanvas Canvas(RenderTarget->GameThread_GetRenderTargetResource(), nullptr, FGameTime::CreateUndilated(WorldTime, FApp::GetDeltaTime()), BakerRenderer.GetFeatureLevel()); Canvas.Clear(FLinearColor::Black); const FIntVector AtlasTextureSize(AtlasVolumeTexture->GetSizeX(), AtlasVolumeTexture->GetSizeY(), AtlasVolumeTexture->GetSizeZ()); if (AtlasTextureSize.X > 0 && AtlasTextureSize.Y > 0 && AtlasTextureSize.Z > 0) { const FNiagaraBakerOutputFrameIndices FrameIndices = BakerGeneratedSettings->GetOutputFrameIndices(OutputVolumeTexture, WorldTime); const FIntPoint TileIndex(FrameIndices.FrameIndexA % BakerGeneratedSettings->FramesPerDimension.X, FrameIndices.FrameIndexA / BakerGeneratedSettings->FramesPerDimension.X); const FIntPoint TileSize(AtlasVolumeTexture->GetSizeX() / BakerGeneratedSettings->FramesPerDimension.X, AtlasVolumeTexture->GetSizeY() / BakerGeneratedSettings->FramesPerDimension.Y); const FVector2f TileUV((float(TileSize.X * TileIndex.X) + 0.5f) / float(AtlasTextureSize.X), (float(TileSize.Y * TileIndex.Y) + 0.5f) / float(AtlasTextureSize.Y)); const FVector2f TileUVSize(float(TileSize.X - 1) / float(AtlasTextureSize.X), float(TileSize.Y - 1) / float(AtlasTextureSize.Y)); const int32 AttributeChannels[] = { 0, 1, 2, 3 }; const int32 NumSlices = AtlasTextureSize.Z; const int32 SlicePerAxis = FMath::CeilToInt(FMath::Sqrt((float)NumSlices)); const int32 TileWidth = RenderTarget->GetSurfaceWidth() / SlicePerAxis; const int32 TileHeight = RenderTarget->GetSurfaceHeight() / SlicePerAxis; if (TileWidth > 0 && TileHeight > 0) { for (int32 Slice=0; Slice < NumSlices; ++Slice) { const int32 TileX = (Slice % SlicePerAxis) * TileWidth; const int32 TileY = (Slice / SlicePerAxis) * TileHeight; FVector3f SliceAttributeUVs[4]; for (int i=0; i < UE_ARRAY_COUNT(SliceAttributeUVs); ++i) { SliceAttributeUVs[i].X = TileUV.X; SliceAttributeUVs[i].Y = TileUV.Y; SliceAttributeUVs[i].Z = (float(Slice) + 0.5f) / float(AtlasTextureSize.Z); } FCanvasTileItem TileItem(FVector2D(TileX, TileY), GWhiteTexture, FVector2D(TileWidth, TileHeight), FVector2D(0.0, 1.0f), FVector2D(1.0, 0.0f), FLinearColor::White); TileItem.BlendMode = SE_BLEND_Opaque; TileItem.BatchedElementParameters = new FBatchedElementNiagaraVolumeAttribute( TileUVSize, MakeArrayView(SliceAttributeUVs), MakeArrayView(AttributeChannels), [Resource_RT=AtlasVolumeTexture->GetResource()](FRHITexture*& OutTexture, FRHISamplerState*& OutSamplerState) { if (Resource_RT) { OutTexture = Resource_RT->GetTexture3DRHI(); OutSamplerState = Resource_RT->SamplerStateRHI; } //else //{ // OutTexture = &GBlackVolumeTexture; // OutSamplerState = TStaticSamplerState::GetRHI(); //} } ); Canvas.DrawItem(TileItem); } } } Canvas.Flush_GameThread(); } bool FNiagaraBakerRendererOutputVolumeTexture::BeginBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput) { UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked(InBakerOutput); // @todo: BakedFrameRange = FIntVector2(TNumericLimits::Lowest(), TNumericLimits::Lowest()); return OutputVolumeTexture->bGenerateAtlas || OutputVolumeTexture->bGenerateFrames || OutputVolumeTexture->bExportFrames; } void FNiagaraBakerRendererOutputVolumeTexture::BakeFrame(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput, int FrameIndex, const FNiagaraBakerRenderer& BakerRenderer) { UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked(InBakerOutput); FVolumeDataInterfaceHelper DataInterface; TArray DataInterfacePath; OutputVolumeTexture->SourceBinding.SourceName.ToString().ParseIntoArray(DataInterfacePath, TEXT(".")); if ( DataInterface.Initialize(DataInterfacePath, BakerRenderer.GetPreviewComponent()) == false ) { return; } FIntVector TextureSize = FIntVector::ZeroValue; TArray TextureData; // Are we a volume render target? if ( DataInterface.VolumeRenderTargetDataInterface ) { ENQUEUE_RENDER_COMMAND(CopyVolumeRenderTarget) ( [Proxy_RT=DataInterface.VolumeRenderTargetProxy, SystemInstanceId_RT=DataInterface.SystemInstance->GetId(), OutTextureSize_RT=&TextureSize, OutTextureData_RT=&TextureData](FRHICommandListImmediate& RHICmdList) { if ( const FRenderTargetVolumeRWInstanceData_RenderThread* InstanceData_RT = Proxy_RT->SystemInstancesToProxyData_RT.Find(SystemInstanceId_RT) ) { *OutTextureSize_RT = InstanceData_RT->Size; RHICmdList.Read3DSurfaceFloatData( InstanceData_RT->RenderTarget->GetRHI(), FIntRect(0, 0, InstanceData_RT->Size.X, InstanceData_RT->Size.Y), FIntPoint(0, InstanceData_RT->Size.Z), *OutTextureData_RT ); } } ); FlushRenderingCommands(); } // Are we a grid collection? else if ( DataInterface.Grid3DDataInterface ) { ENQUEUE_RENDER_COMMAND(CopyVolumeRenderTarget) ( [Proxy_RT=DataInterface.Grid3DProxy, SystemInstanceId_RT=DataInterface.SystemInstance->GetId(), OutTextureSize_RT=&TextureSize, OutTextureData_RT=&TextureData](FRHICommandListImmediate& RHICmdList) { if ( const FGrid3DCollectionRWInstanceData_RenderThread* InstanceData_RT = Proxy_RT->SystemInstancesToProxyData_RT.Find(SystemInstanceId_RT) ) { if (InstanceData_RT->CurrentData) { FIntVector TextureSize; TextureSize.X = InstanceData_RT->NumCells.X * InstanceData_RT->NumTiles.X; TextureSize.Y = InstanceData_RT->NumCells.Y * InstanceData_RT->NumTiles.Y; TextureSize.Z = InstanceData_RT->NumCells.Z * InstanceData_RT->NumTiles.Z; *OutTextureSize_RT = TextureSize; RHICmdList.Read3DSurfaceFloatData( InstanceData_RT->CurrentData->GetPooledTexture()->GetRHI(), FIntRect(0, 0, TextureSize.X, TextureSize.Y), FIntPoint(0, TextureSize.Z), *OutTextureData_RT ); } } } ); FlushRenderingCommands(); if ( TextureSize == DataInterface.Grid3DTextureSize ) { TArray TileTextureData; const FIntVector TileTextureSize = DataInterface.Grid3DInstanceData_GameThread->NumCells; TileTextureData.AddUninitialized(TileTextureSize.X * TileTextureSize.Y * TileTextureSize.Z); FFloat16 Channels[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; int32 AttributeOffset[4] = { 0, 0, 0, 0 }; for (int32 c=0; c < DataInterface.Grid3DAttributeChannels; ++c) { const int32 AttributeIndex = DataInterface.Grid3DAttributeStart + c; const FIntVector AttributeTileIndex( AttributeIndex % DataInterface.Grid3DInstanceData_GameThread->NumTiles.X, (AttributeIndex / DataInterface.Grid3DInstanceData_GameThread->NumTiles.X) % DataInterface.Grid3DInstanceData_GameThread->NumTiles.Y, AttributeIndex / (DataInterface.Grid3DInstanceData_GameThread->NumTiles.X * DataInterface.Grid3DInstanceData_GameThread->NumTiles.Y) ); AttributeOffset[c] = AttributeTileIndex.X * TileTextureSize.X; AttributeOffset[c] += AttributeTileIndex.Y * TextureSize.X; AttributeOffset[c] += AttributeTileIndex.Z * TextureSize.X * TextureSize.Y; } int32 OutTexel = 0; for ( int32 z=0; z < TileTextureSize.Z; ++z ) { const int32 SliceOffset = z * TextureSize.X * TextureSize.Y; for ( int32 y=0; y < TileTextureSize.Y; ++y ) { const int32 SliceRowOffset = SliceOffset + (y * TextureSize.X); for ( int32 x=0; x < TileTextureSize.X; ++x ) { const int32 Offset = SliceRowOffset + x; for ( int32 c=0; c < DataInterface.Grid3DAttributeChannels; ++c ) { Channels[c] = TextureData[AttributeOffset[c] + Offset].R; } TileTextureData[OutTexel].R = Channels[0]; TileTextureData[OutTexel].G = Channels[1]; TileTextureData[OutTexel].B = Channels[2]; TileTextureData[OutTexel].A = Channels[3]; ++OutTexel; } } } TextureSize = TileTextureSize; TextureData = TileTextureData; } else { TextureData.Empty(); } } // Process the volume data if ( TextureData.Num() > 0 ) { // Create atlas if (OutputVolumeTexture->bGenerateAtlas) { UNiagaraBakerSettings* BakerSettings = OutputVolumeTexture->GetTypedOuter(); check(BakerSettings); if (BakeAtlasTextureData.Num() == 0) { BakeAtlasFrameSize = TextureSize; BakeAtlasTextureSize = FIntVector( TextureSize.X * BakerSettings->FramesPerDimension.X, TextureSize.Y * BakerSettings->FramesPerDimension.Y, TextureSize.Z ); BakeAtlasTextureData.AddDefaulted(BakeAtlasTextureSize.X * BakeAtlasTextureSize.Y * BakeAtlasTextureSize.Z); } for ( int32 z=0; z < BakeAtlasFrameSize.Z; ++z ) { for ( int32 y=0; y < BakeAtlasFrameSize.Y; ++y ) { const int32 TileX = FrameIndex % BakerSettings->FramesPerDimension.X; const int32 TileY = FrameIndex / BakerSettings->FramesPerDimension.X; const int32 DstOffset = (BakeAtlasTextureSize.X * BakeAtlasTextureSize.Y * z) + (BakeAtlasFrameSize.Y * BakeAtlasTextureSize.X * TileY) + (BakeAtlasFrameSize.X * TileX) + (BakeAtlasTextureSize.X * y); const int32 SrcOffset = (TextureSize.X * TextureSize.Y * z) + (TextureSize.X * y); FMemory::Memcpy(BakeAtlasTextureData.GetData() + DstOffset, TextureData.GetData() + SrcOffset, BakeAtlasFrameSize.X * sizeof(FFloat16Color)); } } } // Create asset per frame if (OutputVolumeTexture->bGenerateFrames) { const FString AssetFullName = OutputVolumeTexture->GetAssetPath(OutputVolumeTexture->FramesAssetPathFormat, FrameIndex); if (UVolumeTexture* OutputTexture = UNiagaraBakerOutput::GetOrCreateAsset(AssetFullName)) { OutputTexture->PreEditChange(nullptr); OutputTexture->Source.Init(TextureSize.X, TextureSize.Y, TextureSize.Z, 1, TSF_RGBA16F, (const uint8*)(TextureData.GetData())); OutputTexture->PowerOfTwoMode = ETexturePowerOfTwoSetting::None; OutputTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; OutputTexture->PostEditChange(); } } // Export each frame if (OutputVolumeTexture->bExportFrames) { BakedFrameSize = TextureSize; if (BakedFrameRange.X == TNumericLimits::Lowest() && BakedFrameRange.Y == TNumericLimits::Lowest()) { BakedFrameRange.X = FrameIndex; BakedFrameRange.Y = FrameIndex; } else { BakedFrameRange.X = FMath::Min(BakedFrameRange.X, FrameIndex); BakedFrameRange.Y = FMath::Max(BakedFrameRange.Y, FrameIndex); } const FString FilePath = OutputVolumeTexture->GetExportPath(OutputVolumeTexture->FramesExportPathFormat, FrameIndex); const FString FileDirectory = FString(FPathViews::GetPath(FilePath)); IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); if (!PlatformFile.DirectoryExists(*FileDirectory)) { if (!PlatformFile.CreateDirectoryTree(*FileDirectory)) { UE_LOG(LogNiagaraBaker, Warning, TEXT("Cannot Create Directory : %s"), *FileDirectory); return; } } FNiagaraBakerRenderer::ExportVolume(FilePath, TextureSize, TextureData); } } } void FNiagaraBakerRendererOutputVolumeTexture::EndBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput) { UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked(InBakerOutput); if (BakeAtlasTextureData.Num() > 0) { const FString AssetFullName = OutputVolumeTexture->GetAssetPath(OutputVolumeTexture->AtlasAssetPathFormat, 0); if (UVolumeTexture* OutputTexture = UNiagaraBakerOutput::GetOrCreateAsset(AssetFullName)) { OutputTexture->PreEditChange(nullptr); OutputTexture->Source.Init(BakeAtlasTextureSize.X, BakeAtlasTextureSize.Y, BakeAtlasTextureSize.Z, 1, TSF_RGBA16F, (const uint8*)(BakeAtlasTextureData.GetData())); OutputTexture->PowerOfTwoMode = ETexturePowerOfTwoSetting::None; OutputTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; OutputTexture->UpdateResource(); OutputTexture->PostEditChange(); OutputTexture->MarkPackageDirty(); } BakeAtlasFrameSize = FIntVector::ZeroValue; BakeAtlasTextureSize = FIntVector::ZeroValue; BakeAtlasTextureData.Empty(); } // @todo: move this to the right place if (OutputVolumeTexture->bExportFrames) { const FString AssetFullName = OutputVolumeTexture->GetAssetPath(OutputVolumeTexture->AtlasAssetPathFormat, 0); if (UVolumeCache* OutputCache = UNiagaraBakerOutput::GetOrCreateAsset(AssetFullName)) { FString FullPath = OutputVolumeTexture->GetExportPath(OutputVolumeTexture->FramesExportPathFormat, 0); FullPath = FString(FPathViews::GetPath(FullPath)); FString FullFileName = FString(FPathViews::GetCleanFilename(OutputVolumeTexture->FramesExportPathFormat)); FullPath = FullPath + "/" + FullFileName; OutputCache->FilePath = FullPath; OutputCache->FrameRangeStart = BakedFrameRange.X; OutputCache->FrameRangeEnd = BakedFrameRange.Y; OutputCache->Resolution = BakedFrameSize; OutputCache->CacheType = EVolumeCacheType::OpenVDB; OutputCache->Modify(); OutputCache->PostEditChange(); if (BakedFrameRange.Y - BakedFrameRange.X > 0) { if (BakedFrameSize.X + BakedFrameSize.Y + BakedFrameSize.Z > 0) { OutputCache->InitData(); // @todo: could be cool to have an option where the whole cache is read into memory after writing it so you can preview results super quick // bool LoadedCache = OutputCache->LoadRange(); // if (!LoadedCache) // { // UE_LOG(LogNiagaraBaker, Warning, TEXT("Could not load cache into memory: %s"), *FullPath); // } } else { UE_LOG(LogNiagaraBaker, Warning, TEXT("Output cache has no voxels : %s"), *FullPath); } } else { UE_LOG(LogNiagaraBaker, Warning, TEXT("No frames written : %s"), *FullPath); } } } }