Files
UnrealEngine/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraBakerRendererOutputVolumeTexture.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

593 lines
25 KiB
C++

// 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<FNiagaraBakerOutputBinding> FNiagaraBakerRendererOutputVolumeTexture::GetRendererBindings(UNiagaraBakerOutput* InBakerOutput) const
{
TArray<FNiagaraBakerOutputBinding> OutBindings;
if (UNiagaraSystem* NiagaraSystem = InBakerOutput->GetTypedOuter<UNiagaraSystem>())
{
FNiagaraBakerOutputBindingHelper::ForEachEmitterDataInterface(
NiagaraSystem,
[&](const FString& EmitterName, const FString& VariableName, UNiagaraDataInterface* DataInterface)
{
if ( UNiagaraDataInterfaceGrid3DCollection* Grid3D = Cast<UNiagaraDataInterfaceGrid3DCollection>(DataInterface) )
{
TArray<FNiagaraVariableBase> GridVariables;
TArray<uint32> 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<UNiagaraDataInterfaceRenderTargetVolume>(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<FString>& OutErrorString) const
{
UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputVolumeTexture>(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<FString> 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<FVector3f, TInlineAllocator<4>> 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<FVector3f, TInlineAllocator<4>> 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<int32>(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<SF_Bilinear>::GetRHI();
}
}
}
);
Canvas.DrawItem(TileItem);
}
}
}
}
FIntPoint FNiagaraBakerRendererOutputVolumeTexture::GetGeneratedSize(UNiagaraBakerOutput* InBakerOutput, FIntPoint InAvailableSize) const
{
UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputVolumeTexture>(InBakerOutput);
UNiagaraBakerSettings* BakerSettings = OutputVolumeTexture->GetTypedOuter<UNiagaraBakerSettings>();
FIntPoint RequiredSize = FIntPoint::ZeroValue;
if ( OutputVolumeTexture->bGenerateAtlas )
{
if ( UVolumeTexture* AtlasVolumeTexture = OutputVolumeTexture->GetAsset<UVolumeTexture>(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<FString>& OutErrorString) const
{
static FString AtlasNotFoundError(TEXT("Atlas texture not found.\nPlease bake an atlas to see the result."));
UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputVolumeTexture>(InBakerOutput);
UNiagaraBakerSettings* BakerGeneratedSettings = OutputVolumeTexture->GetTypedOuter<UNiagaraBakerSettings>();
if (OutputVolumeTexture->bGenerateAtlas == false || !BakerGeneratedSettings)
{
OutErrorString = AtlasNotFoundError;
return;
}
UVolumeTexture* AtlasVolumeTexture = OutputVolumeTexture->GetAsset<UVolumeTexture>(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<SF_Bilinear>::GetRHI();
//}
}
);
Canvas.DrawItem(TileItem);
}
}
}
Canvas.Flush_GameThread();
}
bool FNiagaraBakerRendererOutputVolumeTexture::BeginBake(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput)
{
UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputVolumeTexture>(InBakerOutput);
// @todo:
BakedFrameRange = FIntVector2(TNumericLimits<int32>::Lowest(), TNumericLimits<int32>::Lowest());
return OutputVolumeTexture->bGenerateAtlas || OutputVolumeTexture->bGenerateFrames || OutputVolumeTexture->bExportFrames;
}
void FNiagaraBakerRendererOutputVolumeTexture::BakeFrame(FNiagaraBakerFeedbackContext& FeedbackContext, UNiagaraBakerOutput* InBakerOutput, int FrameIndex, const FNiagaraBakerRenderer& BakerRenderer)
{
UNiagaraBakerOutputVolumeTexture* OutputVolumeTexture = CastChecked<UNiagaraBakerOutputVolumeTexture>(InBakerOutput);
FVolumeDataInterfaceHelper DataInterface;
TArray<FString> DataInterfacePath;
OutputVolumeTexture->SourceBinding.SourceName.ToString().ParseIntoArray(DataInterfacePath, TEXT("."));
if ( DataInterface.Initialize(DataInterfacePath, BakerRenderer.GetPreviewComponent()) == false )
{
return;
}
FIntVector TextureSize = FIntVector::ZeroValue;
TArray<FFloat16Color> 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<FFloat16Color> 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<UNiagaraBakerSettings>();
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<UVolumeTexture, UVolumeTextureFactory>(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<int32>::Lowest() && BakedFrameRange.Y == TNumericLimits<int32>::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<UNiagaraBakerOutputVolumeTexture>(InBakerOutput);
if (BakeAtlasTextureData.Num() > 0)
{
const FString AssetFullName = OutputVolumeTexture->GetAssetPath(OutputVolumeTexture->AtlasAssetPathFormat, 0);
if (UVolumeTexture* OutputTexture = UNiagaraBakerOutput::GetOrCreateAsset<UVolumeTexture, UVolumeTextureFactory>(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<UVolumeCache, UVolumeCacheFactory>(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);
}
}
}
}