Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/LocalFogVolumeRendering.cpp
2025-05-18 13:04:45 +08:00

1131 lines
60 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LocalFogVolumeRendering.h"
#include "ScenePrivate.h"
#include "RendererUtils.h"
#include "ScreenPass.h"
#include "LocalFogVolumeSceneProxy.h"
#include "MobileBasePassRendering.h"
#include "PixelShaderUtils.h"
// The runtime ON/OFF toggle
static TAutoConsoleVariable<int32> CVarLocalFogVolume(
TEXT("r.LocalFogVolume"), 1,
TEXT("Project settings enabling the rendering of Local Fog Volumes."),
ECVF_RenderThreadSafe);
// The project setting (disable runtime and shader code)
static TAutoConsoleVariable<int32> CVarSupportLocalFogVolumes(
TEXT("r.SupportLocalFogVolumes"),
1,
TEXT("Enables local fog volume rendering and shader code."),
ECVF_ReadOnly | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeRenderDuringHeightFogPass(
TEXT("r.LocalFogVolume.RenderDuringHeightFogPass"), 0,
TEXT("LocalFogVolume are going to be rendered during the height fog pass, skipping the tiled rendering pass specific to them. Only work on the non mobile path as an experiment."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeRenderIntoVolumetricFog(
TEXT("r.LocalFogVolume.RenderIntoVolumetricFog"), 1,
TEXT("Enables the voxelization of local fog volumes into the volumetric fog rendering system. Otherwise, local fog volumes will remain isolated."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarLocalFogVolumeMaxDensityIntoVolumetricFog(
TEXT("r.LocalFogVolume.MaxDensityIntoVolumetricFog"), 0.01f,
TEXT("LocalFogVolume height fog mode can become exponentially dense in the bottom part. VolumetricFog temporal reprojection then can leak du to high density. Clamping density is a way to get that visual artefact under control."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeApplyOnTranslucent(
TEXT("r.LocalFogVolume.ApplyOnTranslucent"), 0,
TEXT("Project settings enabling the sampling of local fog volumes on translucent elements."),
ECVF_ReadOnly | ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeTilePixelSize(
TEXT("r.LocalFogVolume.TilePixelSize"), 128,
TEXT("Tile size on screen in pixel at which we cull the local fog volumes."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeTileMaxInstanceCount(
TEXT("r.LocalFogVolume.TileMaxInstanceCount"), 32,
TEXT("Maximum number of local fog volumes to account for per view (and per tile or consistency)."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeTileCullingUseAsync(
TEXT("r.LocalFogVolume.TileCullingUseAsync"), 1,
TEXT("Enables running the culling process on the async compute pipe."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeTileDebug(
TEXT("r.LocalFogVolume.TileDebug"), 0,
TEXT("Debug the tiled rendering data complexity. 1: show per tile LFV count as color ; 2: same as one but also show the effect of pixel discard/clipping."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<float> CVarLocalFogVolumeGlobalStartDistance(
TEXT("r.LocalFogVolume.GlobalStartDistance"), 2000.0f,
TEXT("The start distance in centimeter from which local fog volumes starts to appear."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeUseHZB(
TEXT("r.LocalFogVolume.UseHZB"), 1,
TEXT("Enables the use of the HZB to cull local fog volumes away.\n"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarLocalFogVolumeHalfResolution(
TEXT("r.LocalFogVolume.HalfResolution"), 0,
TEXT("Enables half resolution rendering of local fog volumes with an upsampling to full resolution later. Only works for the mobile path for now.\n"),
ECVF_RenderThreadSafe);
// Example of tile setup
// - 1920x1080 => 15x9 tiles
// - Allowing max 32 volumes at once => culling list buffer = 15 * 9 * 32 * 1 byte = 4320 bytes = 4.3KB
static uint32 GetLocalFogVolumeTilePixelSize()
{
return FMath::Max(8u, FMath::Min(512u, (uint32)CVarLocalFogVolumeTilePixelSize.GetValueOnRenderThread()));
}
static bool GetLocalFogVolumeTileCullingUseAsync()
{
return GSupportsEfficientAsyncCompute && CVarLocalFogVolumeTileCullingUseAsync.GetValueOnRenderThread() > 0;
}
static float GetLocalFogVolumeMaxDensityIntoVolumetricFog()
{
return FMath::Max(0.0f, CVarLocalFogVolumeMaxDensityIntoVolumetricFog.GetValueOnRenderThread());
}
static uint32 GetLocalFogVolumeTileMaxInstanceCount()
{
// We do not allow more than 256 instances since culled indices might be stored a u8 values.
return FMath::Max(1u, FMath::Min(256u, (uint32)CVarLocalFogVolumeTileMaxInstanceCount.GetValueOnRenderThread()));
}
static int32 GetLocalFogVolumeTileDebugMode(const FEngineShowFlags& EngineShowFlags)
{
return EngineShowFlags.VisualizeLocalFogVolumes ? 2 : FMath::Clamp(CVarLocalFogVolumeTileDebug.GetValueOnRenderThread(), 0, 2);
}
bool ProjectSupportsLocalFogVolumes()
{
return CVarSupportLocalFogVolumes.GetValueOnRenderThread() > 0;
}
bool ShouldRenderLocalFogVolume(const FScene* Scene, const FSceneViewFamily& SceneViewFamily)
{
const FEngineShowFlags& EngineShowFlags = SceneViewFamily.EngineShowFlags;
if (Scene && Scene->HasAnyLocalFogVolume() && EngineShowFlags.Fog && !SceneViewFamily.UseDebugViewPS())
{
return ProjectSupportsLocalFogVolumes() && (CVarLocalFogVolume.GetValueOnRenderThread() > 0);
}
return false;
}
bool ShouldRenderLocalFogVolumeDuringHeightFogPass(const FScene* Scene, const FSceneViewFamily& SceneViewFamily)
{
if (ShouldRenderLocalFogVolume(Scene, SceneViewFamily))
{
return CVarLocalFogVolumeRenderDuringHeightFogPass.GetValueOnRenderThread() > 0;
}
return false;
}
bool ShouldRenderLocalFogVolumeInVolumetricFog(const FScene* Scene, const FSceneViewFamily& SceneViewFamily, bool bShouldRenderVolumetricFog)
{
if (ShouldRenderLocalFogVolume(Scene, SceneViewFamily) && bShouldRenderVolumetricFog)
{
return CVarLocalFogVolumeRenderIntoVolumetricFog.GetValueOnRenderThread() > 0;
}
return false;
}
bool ShouldRenderLocalFogVolumeVisualizationPass(const FScene* Scene, const FSceneViewFamily& SceneViewFamily)
{
if (ShouldRenderLocalFogVolume(Scene, SceneViewFamily))
{
const FEngineShowFlags& EngineShowFlags = SceneViewFamily.EngineShowFlags;
return GetLocalFogVolumeTileDebugMode(EngineShowFlags) > 0;
}
return false;
}
float GetLocalFogVolumeGlobalStartDistance()
{
return FMath::Max(10.0f, CVarLocalFogVolumeGlobalStartDistance.GetValueOnRenderThread());
}
bool IsLocalFogVolumeHalfResolution()
{
return CVarLocalFogVolumeHalfResolution.GetValueOnRenderThread() > 0;
}
DECLARE_GPU_STAT(LocalFogVolumeVolumes);
static const uint32 SizeOfUintVec4 = sizeof(FUintVector4);
static const uint32 UintVec4CountInLocalFogVolumeGPUInstanceData = sizeof(FLocalFogVolumeGPUInstanceData) / SizeOfUintVec4;
void SetDummyLocalFogVolumeUniformParametersStruct(FRDGBuilder& GraphBuilder, FLocalFogVolumeUniformParameters& UniformParametersStruct)
{
UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTileDataTextureResolution = FUintVector2(1, 1);
UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeInstanceCount = 0;
UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTilePixelSize = GetLocalFogVolumeTilePixelSize();
UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeMaxDensityIntoVolumetricFog = GetLocalFogVolumeMaxDensityIntoVolumetricFog();
UniformParametersStruct.LocalFogVolumeCommon.ShouldRenderLocalFogVolumeInVolumetricFog = 0;
UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor = FVector3f::Zero();
UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightDirection = FVector3f::Zero();
UniformParametersStruct.LocalFogVolumeCommon.GlobalStartDistance = 0.0f;
UniformParametersStruct.LocalFogVolumeCommon.HalfResTextureSizeAndInvSize = FVector4f(1.0f, 1.0f, 1.0f, 1.0f);
UniformParametersStruct.LocalFogVolumeTileDataTexture = GraphBuilder.CreateSRV(GSystemTextures.GetZeroUIntArrayDummy(GraphBuilder));
static FLocalFogVolumeGPUInstanceData DummyData; // Static data so ERDGInitialDataFlags::NoCopy can be used.
FRDGBufferRef GPUInstanceDataBuffer = CreateVertexBuffer(GraphBuilder, TEXT("DUMMYLocalFogVolumeGPUInstanceDataBuffer"),
FRDGBufferDesc::CreateBufferDesc(SizeOfUintVec4, UintVec4CountInLocalFogVolumeGPUInstanceData), &DummyData, sizeof(FLocalFogVolumeGPUInstanceData) * 1, ERDGInitialDataFlags::NoCopy);
UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeInstances = GraphBuilder.CreateSRV(GPUInstanceDataBuffer, PF_A32B32G32R32F);
}
void SetDummyLocalFogVolumeForView(FRDGBuilder& GraphBuilder, FViewInfo& View)
{
// The size of the structure must be a multiple of FUintVector4.
static_assert(sizeof(FLocalFogVolumeGPUInstanceData) == UintVec4CountInLocalFogVolumeGPUInstanceData * SizeOfUintVec4);
View.LocalFogVolumeViewData.GPUInstanceCount = 0;
static FLocalFogVolumeGPUInstanceData DummyData; // Static data so ERDGInitialDataFlags::NoCopy can be used.
View.LocalFogVolumeViewData.GPUInstanceDataBuffer = CreateVertexBuffer(GraphBuilder, TEXT("DUMMYLocalFogVolumeGPUInstanceDataBuffer"),
FRDGBufferDesc::CreateBufferDesc(SizeOfUintVec4, UintVec4CountInLocalFogVolumeGPUInstanceData), &DummyData, sizeof(FLocalFogVolumeGPUInstanceData) * 1, ERDGInitialDataFlags::NoCopy);
View.LocalFogVolumeViewData.GPUInstanceDataBufferSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.GPUInstanceDataBuffer, PF_A32B32G32R32F);
static FVector4f DummyCullingData(EForceInit::ForceInitToZero);
View.LocalFogVolumeViewData.GPUInstanceCullingDataBuffer = CreateVertexBuffer(GraphBuilder, TEXT("DUMMYLocalFogVolumeGPUInstanceCullingDataBuffer"),
FRDGBufferDesc::CreateBufferDesc(sizeof(FVector4f), 1), &DummyCullingData, sizeof(FVector4f) * 1, ERDGInitialDataFlags::NoCopy);
View.LocalFogVolumeViewData.GPUInstanceCullingDataBufferSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.GPUInstanceCullingDataBuffer, PF_A32B32G32R32F);
View.LocalFogVolumeViewData.TileDataTextureArray = GSystemTextures.GetZeroUIntArrayDummy(GraphBuilder);
View.LocalFogVolumeViewData.TileDataTextureArraySRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.TileDataTextureArray);
View.LocalFogVolumeViewData.TileDataTextureArrayUAV = nullptr; // Should never be written by culling passes if there are no instances.
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTileDataTextureResolution = FUintVector2(1, 1);
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeInstanceCount = View.LocalFogVolumeViewData.GPUInstanceCount;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTilePixelSize = GetLocalFogVolumeTilePixelSize();
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeMaxDensityIntoVolumetricFog = GetLocalFogVolumeMaxDensityIntoVolumetricFog();
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.ShouldRenderLocalFogVolumeInVolumetricFog = 0;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeInstances = View.LocalFogVolumeViewData.GPUInstanceDataBufferSRV;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor = FVector3f::Zero();
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightDirection = FVector3f::Zero();
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.GlobalStartDistance = 0.0f;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.HalfResTextureSizeAndInvSize = FVector4f(1.0f, 1.0f, 1.0f, 1.0f);
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeTileDataTexture = View.LocalFogVolumeViewData.TileDataTextureArraySRV;
View.LocalFogVolumeViewData.UniformBuffer = GraphBuilder.CreateUniformBuffer(&View.LocalFogVolumeViewData.UniformParametersStruct);
// This buffer must remain a basic vertex buffer for mobile to be able to read it from vertex shader
View.LocalFogVolumeViewData.GPUTileDataBuffer = CreateVertexBuffer(
GraphBuilder, TEXT("LocalFogVolume.GPUTileDataBuffer"), FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1 * 1 * sizeof(uint32)), nullptr, 0);
View.LocalFogVolumeViewData.GPUTileDataBufferSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.GPUTileDataBuffer, PF_R32_UINT);
View.LocalFogVolumeViewData.GPUTileDataBufferUAV = nullptr; // Should never be written by culling passes if there are no instances.
View.LocalFogVolumeViewData.GPUTileDrawIndirectBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc<FRHIDrawIndirectParameters>(), TEXT("LocalFogVolume.DispatchIndirectBuffer"));
View.LocalFogVolumeViewData.GPUTileDrawIndirectBufferUAV= nullptr;
View.LocalFogVolumeViewData.bUseHalfResLocalFogVolume = false;
View.LocalFogVolumeViewData.HalfResLocalFogVolumeView = GSystemTextures.GetBlackAlphaOneDummy(GraphBuilder);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeViewSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.HalfResLocalFogVolumeView);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth = GSystemTextures.GetBlackDummy(GraphBuilder);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepthSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth);
}
void SetDummyLocalFogVolumeForViews(FRDGBuilder& GraphBuilder, TArray<FViewInfo>& Views)
{
for (FViewInfo& View : Views)
{
SetDummyLocalFogVolumeForView(GraphBuilder, View);
}
}
void SetDummyLocalFogVolumeForViews(FRDGBuilder& GraphBuilder, TArray<FViewInfo*>& Views)
{
for (FViewInfo* View : Views)
{
SetDummyLocalFogVolumeForView(GraphBuilder, *View);
}
}
/*=============================================================================
FScene functions
=============================================================================*/
void FScene::AddLocalFogVolume(class FLocalFogVolumeSceneProxy* FogProxy)
{
check(FogProxy);
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FAddLocalFogVolumeCommand)(
[Scene, FogProxy](FRHICommandListImmediate& RHICmdList)
{
check(!Scene->LocalFogVolumes.Contains(FogProxy));
Scene->LocalFogVolumes.Push(FogProxy);
} );
}
void FScene::RemoveLocalFogVolume(class FLocalFogVolumeSceneProxy* FogProxy)
{
check(FogProxy);
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FRemoveLocalFogVolumeCommand)(
[Scene, FogProxy](FRHICommandListImmediate& RHICmdList)
{
Scene->LocalFogVolumes.RemoveSingle(FogProxy);
} );
}
bool FScene::HasAnyLocalFogVolume() const
{
return LocalFogVolumes.Num() > 0;
}
/*=============================================================================
Local height fog tiled culling
=============================================================================*/
class FLocalFogVolumeTiledCullingCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FLocalFogVolumeTiledCullingCS);
SHADER_USE_PARAMETER_STRUCT(FLocalFogVolumeTiledCullingCS, FGlobalShader);
class FUseHZB : SHADER_PERMUTATION_BOOL("USE_HZB");
using FPermutationDomain = TShaderPermutationDomain<FUseHZB>;
public:
const static uint32 GroupSize = 8;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_STRUCT(FLocalFogVolumeCommonParameters, LFV)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2DArray<uint>, LocalFogVolumeTileDataTextureUAV)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, LocalFogVolumeCullingDataBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, TileDataBufferUAV)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, TileDrawIndirectBufferUAV)
SHADER_PARAMETER_STRUCT_INCLUDE(FHZBParameters, HZBParameters)
SHADER_PARAMETER(FVector4f, LeftPlane)
SHADER_PARAMETER(FVector4f, RightPlane)
SHADER_PARAMETER(FVector4f, TopPlane)
SHADER_PARAMETER(FVector4f, BottomPlane)
SHADER_PARAMETER(FVector4f, NearPlane)
SHADER_PARAMETER(FVector2f, ViewToTileSpaceRatio)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GroupSize);
}
};
IMPLEMENT_GLOBAL_SHADER(FLocalFogVolumeTiledCullingCS, "/Engine/Private/LocalFogVolumes/LocalFogVolumeTiledCulling.usf", "LocalFogVolumeTiledCullingCS", SF_Compute);
static void LocalFogVolumeViewTiledCullingPass(FViewInfo& View, FRDGBuilder& GraphBuilder)
{
check(View.LocalFogVolumeViewData.GPUInstanceCount > 0);
FIntVector TileDataTextureSize = View.LocalFogVolumeViewData.TileDataTextureArray->Desc.GetSize();
FLocalFogVolumeTiledCullingCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FLocalFogVolumeTiledCullingCS::FParameters>();
PassParameters->View = View.ViewUniformBuffer;
PassParameters->LFV = View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon;
PassParameters->LocalFogVolumeCullingDataBuffer = View.LocalFogVolumeViewData.GPUInstanceCullingDataBufferSRV;
PassParameters->LocalFogVolumeTileDataTextureUAV = View.LocalFogVolumeViewData.TileDataTextureArrayUAV;
PassParameters->TileDataBufferUAV = View.LocalFogVolumeViewData.GPUTileDataBufferUAV;
PassParameters->TileDrawIndirectBufferUAV = View.LocalFogVolumeViewData.GPUTileDrawIndirectBufferUAV;
PassParameters->HZBParameters = GetHZBParameters(GraphBuilder, View, EHZBType::FurthestHZB);
auto ConvertPlanToVector4f = [&](FVector4f& OutVec4f, auto& Plane, bool bFlipPlane)
{
OutVec4f.X = Plane.X;
OutVec4f.Y = Plane.Y;
OutVec4f.Z = Plane.Z;
OutVec4f.W = Plane.W;
if (bFlipPlane)
{
// We swap some of the planes normal so that they are lerpable while avoiding potential null normal and precision issue at the middle of the frustum.
OutVec4f.X *= -1.0f;
OutVec4f.Y *= -1.0f;
OutVec4f.Z *= -1.0f;
OutVec4f.W *= -1.0f;
}
};
// Using world space plane for now. LFV_TODO: do computation in view space.
if (View.ViewFrustum.Planes.Num() >= 4)
{
// We use the view frustum witch matches the rendering frustum even when in stereo mode (not monoscopic).
ConvertPlanToVector4f(PassParameters->LeftPlane, View.ViewFrustum.Planes[0], false);
ConvertPlanToVector4f(PassParameters->RightPlane, View.ViewFrustum.Planes[1], true);
ConvertPlanToVector4f(PassParameters->TopPlane, View.ViewFrustum.Planes[2], true);
ConvertPlanToVector4f(PassParameters->BottomPlane, View.ViewFrustum.Planes[3], false);
}
else
{
// Disable culling and make each volume visible.
PassParameters->LeftPlane = FVector4f::Zero();
PassParameters->RightPlane = FVector4f::Zero();
PassParameters->TopPlane = FVector4f::Zero();
PassParameters->BottomPlane = FVector4f::Zero();
}
ConvertPlanToVector4f(PassParameters->NearPlane, View.NearClippingPlane, false);
float TileCoveredResolutionX = View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTilePixelSize * View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTileDataTextureResolution.X;
float TileCoveredResolutionY = View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTilePixelSize * View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTileDataTextureResolution.Y;
PassParameters->ViewToTileSpaceRatio = FVector2f(TileCoveredResolutionX * View.CachedViewUniformShaderParameters->ViewSizeAndInvSize.Z, TileCoveredResolutionY * View.CachedViewUniformShaderParameters->ViewSizeAndInvSize.W);
ERDGPassFlags PassFlag = GetLocalFogVolumeTileCullingUseAsync() ? ERDGPassFlags::AsyncCompute : ERDGPassFlags::Compute;
TileDataTextureSize.Z = 1;
const FIntVector NumGroups = FIntVector::DivideAndRoundUp(TileDataTextureSize, FLocalFogVolumeTiledCullingCS::GroupSize);
FLocalFogVolumeTiledCullingCS::FPermutationDomain PermutationVector;
PermutationVector.Set<FLocalFogVolumeTiledCullingCS::FUseHZB>(IsHZBValid(View, EHZBType::FurthestHZB) && CVarLocalFogVolumeUseHZB.GetValueOnRenderThread());
TShaderMapRef<FLocalFogVolumeTiledCullingCS> ComputeShader(View.ShaderMap, PermutationVector);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("LocalFogVolume.TiledCulling"), PassFlag, ComputeShader, PassParameters, NumGroups);
}
/*=============================================================================
Local height fog rendering common function
=============================================================================*/
// This is view data because the transform is expressed in translated world space
void GetLocalFogVolumeViewSortingData(const FScene* Scene, const FViewInfo& View, FRDGBuilder& GraphBuilder, FLocalFogVolumeSortingData& Out)
{
check(Scene->LocalFogVolumes.Num() > 0); // We should not get there if there is not any local fog volume.
// No culling as of today
Out.LocalFogVolumeInstanceCount = Scene->LocalFogVolumes.Num();
Out.LocalFogVolumeInstanceCountFinal = 0;
Out.LocalFogVolumeGPUInstanceData = (FLocalFogVolumeGPUInstanceData*)GraphBuilder.Alloc(sizeof(FLocalFogVolumeGPUInstanceData) * Out.LocalFogVolumeInstanceCount, 16);
Out.LocalFogVolumeCenterPos = (FVector*)GraphBuilder.Alloc(sizeof(FVector) * Out.LocalFogVolumeInstanceCount, 16);
Out.LocalFogVolumeSortKeys.SetNumUninitialized(Out.LocalFogVolumeInstanceCount);
for (FLocalFogVolumeSceneProxy* LHF : Scene->LocalFogVolumes)
{
if (LHF->RadialFogExtinction <= 0.0f && LHF->HeightFogExtinction <= 0.0f)
{
continue; // this volume will never be visible
}
FLocalFogVolumeGPUInstanceData* LocalFogVolumeGPUInstanceDataIt = &Out.LocalFogVolumeGPUInstanceData[Out.LocalFogVolumeInstanceCountFinal];
// Falloff needs to be made safe in order to avoid artifact when the camera is looking toward the horizon at the level of the offset.
const float SafeFalloffThreshold = 1.0f;
const float FalloffScaleUI = 0.01f;
const float SafeFallOff = FMath::Max(LHF->HeightFogFalloff, SafeFalloffThreshold) * FalloffScaleUI;
FTransform RotationScaleMatrix = LHF->FogTransform;
RotationScaleMatrix.SetTranslation(FVector::ZeroVector);
FMatrix44f InvTransform = FMatrix44f(RotationScaleMatrix.ToMatrixWithScale().Inverse());
FVector3f XVec(InvTransform.M[0][0], InvTransform.M[0][1], InvTransform.M[0][2]);
FVector3f YVec(InvTransform.M[1][0], InvTransform.M[1][1], InvTransform.M[1][2]);
FVector3f TranslatedWorldPosition(View.ViewMatrices.GetPreViewTranslation() + LHF->FogTransform.GetTranslation());
// Normalization requires small tolerance for large volumes.
const float NormalizeTolerance = 1.e-32;
XVec.Normalize(NormalizeTolerance);
YVec.Normalize(NormalizeTolerance);
auto AsUint32 = [](float X)
{
union { float F; uint32 U; } FU = { X };
return FU.U;
};
auto AsFloat111110 = [](float x, float y, float z)
{
FVector2DHalf HalfXY(x, y);
FVector2DHalf HalfZ0(z, 0.0f);
uint32 r = (uint32(HalfXY.X.Encoded) << 17) & 0xFFE00000;
uint32 g = (uint32(HalfXY.Y.Encoded) << 6) & 0x001FFC00;
uint32 b = (uint32(HalfZ0.X.Encoded) >> 5) & 0x000003FF;
return r | g | b;
};
auto AsUNorm8888 = [](float x, float y, float z, float w)
{
uint32 r = (uint32(FMath::Clamp(x * 255.0f, 0.0f, 255.0f)) & 0xFFu);
uint32 g = (uint32(FMath::Clamp(y * 255.0f, 0.0f, 255.0f)) & 0xFFu) << 8u;
uint32 b = (uint32(FMath::Clamp(z * 255.0f, 0.0f, 255.0f)) & 0xFFu) << 16u;
uint32 a = (uint32(FMath::Clamp(w * 255.0f, 0.0f, 255.0f)) & 0xFFu) << 24u;
return r | g | b | a;
};
// Translation and scale at fp32 for stability. Further optimization: we could use fp16 if translated/view space position would be sent.
LocalFogVolumeGPUInstanceDataIt->Data0[0] = AsUint32(TranslatedWorldPosition.X);
LocalFogVolumeGPUInstanceDataIt->Data0[1] = AsUint32(TranslatedWorldPosition.Y);
LocalFogVolumeGPUInstanceDataIt->Data0[2] = AsUint32(TranslatedWorldPosition.Z);
LocalFogVolumeGPUInstanceDataIt->Data0[3] = AsUint32(LHF->FogUniformScale);
// Store X and Y from the rotation matrix and recover Z in the shader.
LocalFogVolumeGPUInstanceDataIt->Data1[0] = FVector2DHalf(XVec.X, XVec.Y).AsUInt32();
LocalFogVolumeGPUInstanceDataIt->Data1[1] = FVector2DHalf(XVec.Z, YVec.X).AsUInt32();
LocalFogVolumeGPUInstanceDataIt->Data1[2] = FVector2DHalf(YVec.Y, YVec.Z).AsUInt32();
LocalFogVolumeGPUInstanceDataIt->Data1[3] = 0; // FREE
// All the remaining data are packed as small as possible w.r.t. their range of value.
LocalFogVolumeGPUInstanceDataIt->Data2[0] = AsFloat111110( LHF->RadialFogExtinction, LHF->HeightFogExtinction, SafeFallOff);
LocalFogVolumeGPUInstanceDataIt->Data2[1] = AsFloat111110( LHF->FogEmissive.R, LHF->FogEmissive.G, LHF->FogEmissive.B);
LocalFogVolumeGPUInstanceDataIt->Data2[2] = AsUNorm8888( LHF->FogAlbedo.R, LHF->FogAlbedo.G, LHF->FogAlbedo.B, LHF->FogPhaseG);
LocalFogVolumeGPUInstanceDataIt->Data2[3] = AsUint32( LHF->HeightFogOffset);
// Register the sorting data
Out.LocalFogVolumeCenterPos[Out.LocalFogVolumeInstanceCountFinal] = LHF->FogTransform.GetTranslation();
FLocalFogVolumeSortKey* LocalFogVolumeSortKeysIt= &Out.LocalFogVolumeSortKeys[Out.LocalFogVolumeInstanceCountFinal];
LocalFogVolumeSortKeysIt->FogVolume.Index = Out.LocalFogVolumeInstanceCountFinal;
LocalFogVolumeSortKeysIt->FogVolume.Distance = 0; // Filled up right before sorting according to a view
LocalFogVolumeSortKeysIt->FogVolume.Priority = LHF->FogSortPriority;
Out.LocalFogVolumeInstanceCountFinal++;
}
// Shrink the array to only what is needed in order for the sort to correctly work on only what is needed.
Out.LocalFogVolumeSortKeys.SetNum(Out.LocalFogVolumeInstanceCountFinal, EAllowShrinking::No);
}
void CreateViewLocalFogVolumeBufferSRV(const FScene* Scene, FViewInfo& View, FRDGBuilder& GraphBuilder, FLocalFogVolumeSortingData& SortingData, bool bShouldRenderLocalFogVolumeInVolumetricFog, bool bUseHalfResLocalFogVolume)
{
if (SortingData.LocalFogVolumeInstanceCountFinal == 0)
{
SetDummyLocalFogVolumeForView(GraphBuilder, View);
return;
}
// 1. Sort all the volumes
const FVector ViewOrigin = View.ViewMatrices.GetViewOrigin();
for (uint32 i = 0; i < SortingData.LocalFogVolumeInstanceCountFinal; ++i)
{
FVector FogCenterPos = SortingData.LocalFogVolumeCenterPos[SortingData.LocalFogVolumeSortKeys[i].FogVolume.Index]; // Recovered form the original array via index because the sorting of the previous view might have changed the order.
float DistancetoView = float((FogCenterPos - ViewOrigin).Size());
SortingData.LocalFogVolumeSortKeys[i].FogVolume.Distance = *reinterpret_cast<uint32*>(&DistancetoView);
}
SortingData.LocalFogVolumeSortKeys.Sort();
// We limit the instance count to the maximum of instance we can have per tile
const uint32 LocalFogVolumeTileMaxInstanceCount = GetLocalFogVolumeTileMaxInstanceCount();
const uint32 DiscardedOffset = (uint32)FMath::Max(0, int32(SortingData.LocalFogVolumeInstanceCountFinal) - int32(LocalFogVolumeTileMaxInstanceCount));
SortingData.LocalFogVolumeInstanceCountFinal = FMath::Min(SortingData.LocalFogVolumeInstanceCountFinal, LocalFogVolumeTileMaxInstanceCount);
// 2. Create the buffer containing all the fog volume data instance sorted according to their key for the current view.
FLocalFogVolumeGPUInstanceData* LocalFogVolumeGPUSortedInstanceData = (FLocalFogVolumeGPUInstanceData*)GraphBuilder.Alloc(sizeof(FLocalFogVolumeGPUInstanceData) * SortingData.LocalFogVolumeInstanceCountFinal, 16);
FVector4f* LocalFogVolumeGPUSortedInstanceCullingData = (FVector4f*)GraphBuilder.Alloc(sizeof(FVector4f) * SortingData.LocalFogVolumeInstanceCountFinal, 16);
for (uint32 i = 0; i < SortingData.LocalFogVolumeInstanceCountFinal; i++)
{
FLocalFogVolumeSortKey LFVKey = SortingData.LocalFogVolumeSortKeys[i + DiscardedOffset];
// We could also have an indirection buffer on GPU but choosing to go with the sorting + copy on CPU since it is expected to not have many local height fog volumes.
LocalFogVolumeGPUSortedInstanceData[i] = SortingData.LocalFogVolumeGPUInstanceData[LFVKey.FogVolume.Index];
FVector& LFVPosition = SortingData.LocalFogVolumeCenterPos[LFVKey.FogVolume.Index];
LocalFogVolumeGPUSortedInstanceCullingData[i] = FVector4f(LFVPosition.X, LFVPosition.Y, LFVPosition.Z, LocalFogVolumeGPUSortedInstanceData[i].GetUniformScale());
}
// 3. Allocate buffer and initialize with sorted data to upload to GPU
const uint32 AllLocalFogVolumeInstanceBytesFinal = sizeof(FLocalFogVolumeGPUInstanceData) * SortingData.LocalFogVolumeInstanceCountFinal;
View.LocalFogVolumeViewData.GPUInstanceCount = SortingData.LocalFogVolumeInstanceCountFinal;
View.LocalFogVolumeViewData.GPUInstanceDataBuffer = CreateVertexBuffer(
GraphBuilder, TEXT("LocalFogVolume.GPUInstanceDataBuffer"),
FRDGBufferDesc::CreateBufferDesc(SizeOfUintVec4, SortingData.LocalFogVolumeInstanceCountFinal * UintVec4CountInLocalFogVolumeGPUInstanceData),
LocalFogVolumeGPUSortedInstanceData, AllLocalFogVolumeInstanceBytesFinal, ERDGInitialDataFlags::NoCopy);
View.LocalFogVolumeViewData.GPUInstanceDataBufferSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.GPUInstanceDataBuffer, PF_A32B32G32R32F);
View.LocalFogVolumeViewData.GPUInstanceCullingDataBuffer = CreateVertexBuffer(
GraphBuilder, TEXT("LocalFogVolume.GPUInstanceCullingDataBuffer"),
FRDGBufferDesc::CreateBufferDesc(sizeof(FVector4f), SortingData.LocalFogVolumeInstanceCountFinal * sizeof(FVector4f)),
LocalFogVolumeGPUSortedInstanceCullingData, SortingData.LocalFogVolumeInstanceCountFinal * sizeof(FVector4f), ERDGInitialDataFlags::NoCopy);
View.LocalFogVolumeViewData.GPUInstanceCullingDataBufferSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.GPUInstanceCullingDataBuffer, PF_A32B32G32R32F); // LFV_TODO use byte buffer to leverage scalar pipe in the culling compute shader.
// Create the texture that will contain the tiled culled result: count in the first slice and indices in the remaining slices
const uint32 LocalFogVolumeTilePixelSize = GetLocalFogVolumeTilePixelSize();
const FIntPoint TileDataTextureResolutionUnsafe = FIntPoint::DivideAndRoundUp(View.ViewRect.Size(), FIntPoint(LocalFogVolumeTilePixelSize, LocalFogVolumeTilePixelSize));
const FIntPoint TileDataTextureResolution = FIntPoint(FMath::Max(1, TileDataTextureResolutionUnsafe.X), FMath::Max(1, TileDataTextureResolutionUnsafe.Y));
const uint32 TileDataTextureSliceCount = LocalFogVolumeTileMaxInstanceCount + 1; // +1 because the first slice is the culled instance count
EPixelFormat TileDataFormat = PF_R8_UINT;
if(!UE::PixelFormat::HasCapabilities(TileDataFormat, EPixelFormatCapabilities::UAV))
{
// Some mobile platforms do not support UAV onto R8. A 32bit format is required.
TileDataFormat = PF_R8G8B8A8_UINT;
check(UE::PixelFormat::HasCapabilities(TileDataFormat, EPixelFormatCapabilities::UAV));
}
FRDGTextureDesc Texture2DArrayDesc(FRDGTextureDesc::Create2DArray(TileDataTextureResolution, TileDataFormat, FClearValueBinding(EClearBinding::ENoneBound), TexCreate_ShaderResource | TexCreate_UAV | TexCreate_ReduceMemoryWithTilingMode | TexCreate_3DTiling, TileDataTextureSliceCount));
// LFV TODO, consider FFastVramConfig onto Texture2DArrayDesc
View.LocalFogVolumeViewData.TileDataTextureArray = GraphBuilder.CreateTexture(Texture2DArrayDesc, TEXT("LocalFogVolume.CullingDataTexture"));
View.LocalFogVolumeViewData.TileDataTextureArraySRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(View.LocalFogVolumeViewData.TileDataTextureArray));
View.LocalFogVolumeViewData.TileDataTextureArrayUAV = GraphBuilder.CreateUAV(FRDGTextureUAVDesc(View.LocalFogVolumeViewData.TileDataTextureArray));
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTileDataTextureResolution = FUintVector2(TileDataTextureResolution.X, TileDataTextureResolution.Y);
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeInstanceCount = View.LocalFogVolumeViewData.GPUInstanceCount;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTilePixelSize = LocalFogVolumeTilePixelSize;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeMaxDensityIntoVolumetricFog = GetLocalFogVolumeMaxDensityIntoVolumetricFog();
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.ShouldRenderLocalFogVolumeInVolumetricFog = bShouldRenderLocalFogVolumeInVolumetricFog ? 1 : 0;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeInstances = View.LocalFogVolumeViewData.GPUInstanceDataBufferSRV;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.GlobalStartDistance = GetLocalFogVolumeGlobalStartDistance();
const FLightSceneProxy* ApproximatePerPixelTransmittanceProxy = nullptr; // by default ignore
if (IsMobilePlatform(View.GetShaderPlatform()))
{
// On mobile there is a separate FMobileDirectionalLightShaderParameters UB which holds all directional light data.
// See SetupMobileDirectionalLightUniformParameters
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor = FVector3f::Zero();
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightDirection = FVector3f::Zero();
for (uint32 ChannelIdx = 0; ChannelIdx < UE_ARRAY_COUNT(Scene->MobileDirectionalLights); ChannelIdx++)
{
FLightSceneInfo* Light = Scene->MobileDirectionalLights[ChannelIdx];
if (Light != nullptr)
{
ApproximatePerPixelTransmittanceProxy = Light->Proxy;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor = FVector3f( Light->Proxy->GetSunIlluminanceAccountingForSkyAtmospherePerPixelTransmittance());
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightDirection = FVector3f(-Light->Proxy->GetDirection());
break;
}
}
}
else if (View.ForwardLightingResources.SelectedForwardDirectionalLightProxy)
{
const FLightSceneProxy* SelectedForwardDirectionalLightProxy = View.ForwardLightingResources.SelectedForwardDirectionalLightProxy;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor = FVector3f( SelectedForwardDirectionalLightProxy->GetSunIlluminanceAccountingForSkyAtmospherePerPixelTransmittance());
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightDirection = FVector3f(-SelectedForwardDirectionalLightProxy->GetDirection());
ApproximatePerPixelTransmittanceProxy = SelectedForwardDirectionalLightProxy;
}
else
{
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor = FVector3f(View.CachedViewUniformShaderParameters->DirectionalLightColor);
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightDirection = View.CachedViewUniformShaderParameters->DirectionalLightDirection;
}
if (ApproximatePerPixelTransmittanceProxy && ApproximatePerPixelTransmittanceProxy->GetUsePerPixelAtmosphereTransmittance())
{
// When using PerPixelTransmittance, transmittance is evaluated per pixel by sampling the transmittance texture. It gives better gradient on large scale objects such as mountains.
// However, to skip doing that texture sampling in Local Fog Splatting shader, we use the simple planet top ground transmittance as a simplification.
// That will work for most of the cases for most of the map/terrain at the top of the virtual planet.
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.DirectionalLightColor *= FVector3f(ApproximatePerPixelTransmittanceProxy->GetAtmosphereTransmittanceTowardSun());
}
// This buffer must remain a basic vertex buffer for mobile to be able to read it from vertex shader
View.LocalFogVolumeViewData.GPUTileDataBuffer = CreateVertexBuffer(
GraphBuilder, TEXT("LocalFogVolume.GPUTileDataBuffer"),
FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), TileDataTextureResolution.X * TileDataTextureResolution.Y * sizeof(uint32)), nullptr, 0);
View.LocalFogVolumeViewData.GPUTileDataBufferSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.GPUTileDataBuffer, PF_R32_UINT);
View.LocalFogVolumeViewData.GPUTileDataBufferUAV = GraphBuilder.CreateUAV(View.LocalFogVolumeViewData.GPUTileDataBuffer, PF_R32_UINT);
View.LocalFogVolumeViewData.GPUTileDrawIndirectBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc<FRHIDrawIndirectParameters>(), TEXT("LocalFogVolume.DispatchIndirectBuffer"));
View.LocalFogVolumeViewData.GPUTileDrawIndirectBufferUAV = GraphBuilder.CreateUAV(View.LocalFogVolumeViewData.GPUTileDrawIndirectBuffer, PF_R32_UINT);
AddClearUAVPass(GraphBuilder, View.LocalFogVolumeViewData.GPUTileDrawIndirectBufferUAV, 0, GetLocalFogVolumeTileCullingUseAsync() ? ERDGPassFlags::AsyncCompute : ERDGPassFlags::Compute);
View.LocalFogVolumeViewData.bUseHalfResLocalFogVolume = bUseHalfResLocalFogVolume;
if (View.LocalFogVolumeViewData.bUseHalfResLocalFogVolume)
{
FIntRect ViewRectAtOrigin = View.ViewRect;
ViewRectAtOrigin.Max -= ViewRectAtOrigin.Min;
ViewRectAtOrigin.Min -= ViewRectAtOrigin.Min;
FIntRect HalfResRect = GetDownscaledViewport(FScreenPassTextureViewport(ViewRectAtOrigin), FIntPoint(2, 2)).Rect;
View.LocalFogVolumeViewData.HalfResResolution = HalfResRect.Max - HalfResRect.Min;
const FIntPoint& HalfResSize = View.LocalFogVolumeViewData.HalfResResolution;
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.HalfResTextureSizeAndInvSize = FVector4f(HalfResSize.X, HalfResSize.Y, 1.0f/float(HalfResSize.X), 1.0f/float(HalfResSize.Y));
FRDGTextureDesc Texture2DHalfResLFVDesc(FRDGTextureDesc::Create2D(View.LocalFogVolumeViewData.HalfResResolution, PF_FloatRGBA, FClearValueBinding(EClearBinding::ENoneBound), TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_ReduceMemoryWithTilingMode | TexCreate_NoFastClear));
View.LocalFogVolumeViewData.HalfResLocalFogVolumeView = GraphBuilder.CreateTexture(Texture2DHalfResLFVDesc, TEXT("LocalFogVolume.HalfResLocalFogVolumeView"));
View.LocalFogVolumeViewData.HalfResLocalFogVolumeViewSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(View.LocalFogVolumeViewData.HalfResLocalFogVolumeView));
Texture2DHalfResLFVDesc.Format = PF_R16F;
View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth = GraphBuilder.CreateTexture(Texture2DHalfResLFVDesc, TEXT("LocalFogVolume.HalfResLocalFogVolumeDepth"));
View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepthSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::Create(View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth));
}
else
{
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.HalfResTextureSizeAndInvSize = FVector4f(1.0f, 1.0f, 1.0f, 1.0f);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeView = GSystemTextures.GetBlackAlphaOneDummy(GraphBuilder);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeViewSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.HalfResLocalFogVolumeView);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth = GSystemTextures.GetBlackDummy(GraphBuilder);
View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepthSRV = GraphBuilder.CreateSRV(View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth);
}
View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeTileDataTexture = View.LocalFogVolumeViewData.TileDataTextureArraySRV;
View.LocalFogVolumeViewData.UniformBuffer = GraphBuilder.CreateUniformBuffer(&View.LocalFogVolumeViewData.UniformParametersStruct);
}
void InitLocalFogVolumesForViews(
const FScene* Scene,
TArray<FViewInfo>& Views,
const FSceneViewFamily& SceneViewFamily,
FRDGBuilder& GraphBuilder,
bool bShouldRenderVolumetricFog,
bool bUseHalfResLocalFogVolume)
{
const uint32 LocalFogVolumeInstanceCount = Scene->LocalFogVolumes.Num();
const bool bShouldRenderLocalFogVolume = ShouldRenderLocalFogVolume(Scene, SceneViewFamily);
if (LocalFogVolumeInstanceCount > 0 && bShouldRenderLocalFogVolume)
{
RDG_EVENT_SCOPE_STAT(GraphBuilder, LocalFogVolumeVolumes, "LocalFogVolumeVolumes");
RDG_GPU_STAT_SCOPE(GraphBuilder, LocalFogVolumeVolumes);
for (FViewInfo& View : Views)
{
FLocalFogVolumeSortingData SortingData;
GetLocalFogVolumeViewSortingData(Scene, View, GraphBuilder, SortingData);
CreateViewLocalFogVolumeBufferSRV(Scene, View, GraphBuilder, SortingData, ShouldRenderLocalFogVolumeInVolumetricFog(Scene, SceneViewFamily, bShouldRenderVolumetricFog), bUseHalfResLocalFogVolume);
if (View.LocalFogVolumeViewData.GPUInstanceCount > 0)
{
LocalFogVolumeViewTiledCullingPass(View, GraphBuilder);
}
}
}
else
{
SetDummyLocalFogVolumeForViews(GraphBuilder, Views);
}
}
/*=============================================================================
Local height fog rendering - non mobile
=============================================================================*/
class FLocalFogVolumeTiledRenderVS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FLocalFogVolumeTiledRenderVS);
SHADER_USE_PARAMETER_STRUCT(FLocalFogVolumeTiledRenderVS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_STRUCT(FLocalFogVolumeUniformParameters, LFV)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, TileDataBuffer)
SHADER_PARAMETER(float, StartDepthZ)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
if (IsMobilePlatform(Parameters.Platform))
{
return false;
}
return true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("LFV_TILED_VS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FLocalFogVolumeTiledRenderVS, "/Engine/Private/LocalFogVolumes/LocalFogVolumeSplat.usf", "LocalFogVolumeTiledVS", SF_Vertex);
class FLocalFogVolumeTiledRenderPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FLocalFogVolumeTiledRenderPS);
SHADER_USE_PARAMETER_STRUCT(FLocalFogVolumeTiledRenderPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_STRUCT(FLocalFogVolumeUniformParameters, LFV)
SHADER_PARAMETER(int32, LocalFogVolumeTileDebug)
END_SHADER_PARAMETER_STRUCT()
class FVisualizationPass : SHADER_PERMUTATION_BOOL("LFV_VISUALIZATION_PS");
using FPermutationDomain = TShaderPermutationDomain<FVisualizationPass>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
if (IsMobilePlatform(Parameters.Platform))
{
return false;
}
return true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("LFV_TILED_PS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FLocalFogVolumeTiledRenderPS, "/Engine/Private/LocalFogVolumes/LocalFogVolumeSplat.usf", "LocalFogVolumeTiledPS", SF_Pixel);
BEGIN_SHADER_PARAMETER_STRUCT(FLocalFogVolumeTiledPassParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FLocalFogVolumeTiledRenderVS::FParameters, VS)
SHADER_PARAMETER_STRUCT_INCLUDE(FLocalFogVolumeTiledRenderPS::FParameters, PS)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneTextureUniformParameters, SceneTextures)
RDG_BUFFER_ACCESS(TileDrawIndirectBuffer, ERHIAccess::IndirectArgs)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
struct FDepthBoundSetup
{
bool bEnabled = false;
float FogClipDeviceZ = 0.0f;
float MinDeviceZ = 0.0f;
float MaxDeviceZ = 1.0f;
};
static FDepthBoundSetup GetDepthBoundSetup(float FogStartDistance, FMatrix ViewProjectionMatrix, FMatrix ViewInvProjectionMatrix)
{
FDepthBoundSetup DepthBoundSetup;
// Here we compute the nearest z value the fog can start
// to skip shader execution on pixels that are closer.
// This means with a bigger distance specified more pixels are
// are culled and don't need to be rendered. This is faster if
// there is opaque content nearer than the computed z.
// This optimization is achieved using depth bound tests.
// Mobile platforms typically does not support that feature
// but typically renders the world using forward shading
// with height fog evaluated as part of the material vertex or pixel shader.
FVector ViewSpaceCorner = ViewInvProjectionMatrix.TransformFVector4(FVector4(1, 1, 1, 1));
float Ratio = ViewSpaceCorner.Z / ViewSpaceCorner.Size();
FVector ViewSpaceStartFogPoint(0.0f, 0.0f, FogStartDistance * Ratio);
FVector4f ClipSpaceMaxDistance = (FVector4f)ViewProjectionMatrix.TransformPosition(ViewSpaceStartFogPoint); // LWC_TODO: precision loss
float FogClipSpaceZ = ClipSpaceMaxDistance.Z / ClipSpaceMaxDistance.W;
DepthBoundSetup.FogClipDeviceZ = FMath::Clamp(FogClipSpaceZ, 0.f, 1.f);
DepthBoundSetup.bEnabled = GSupportsDepthBoundsTest;
if (bool(ERHIZBuffer::IsInverted))
{
DepthBoundSetup.MinDeviceZ = 0.0f;
DepthBoundSetup.MaxDeviceZ = DepthBoundSetup.FogClipDeviceZ;
}
else
{
DepthBoundSetup.MinDeviceZ = DepthBoundSetup.FogClipDeviceZ;
DepthBoundSetup.MaxDeviceZ = 1.0f;
}
return DepthBoundSetup;
}
void RenderLocalFogVolume(
const FScene* Scene,
TArray<FViewInfo>& Views,
const FSceneViewFamily& SceneViewFamily,
FRDGBuilder& GraphBuilder,
const FMinimalSceneTextures& SceneTextures,
FRDGTextureRef LightShaftOcclusionTexture,
bool bVisualizationPass)
{
uint32 LocalFogVolumeInstanceCount = Scene->LocalFogVolumes.Num();
if (LocalFogVolumeInstanceCount > 0 && ShouldRenderLocalFogVolume(Scene, SceneViewFamily))
{
RDG_EVENT_SCOPE_STAT(GraphBuilder, LocalFogVolumeVolumes, "%s", bVisualizationPass ? TEXT("LocalFogVolume.Visualization") : TEXT("LocalFogVolume.Render"));
RDG_GPU_STAT_SCOPE(GraphBuilder, LocalFogVolumeVolumes);
FRDGTextureRef SceneColorTexture = SceneTextures.Color.Resolve;
for (FViewInfo& View : Views)
{
if (View.LocalFogVolumeViewData.GPUInstanceCount == 0)
{
continue;
}
const FIntRect ViewRect = View.ViewRect;
const FMatrix ViewProjectionMatrix = View.ViewMatrices.GetProjectionMatrix();
const FMatrix ViewInvProjectionMatrix = View.ViewMatrices.GetInvProjectionMatrix();
FDepthBoundSetup DepthBoundSetup = GetDepthBoundSetup(GetLocalFogVolumeGlobalStartDistance(), ViewProjectionMatrix, ViewInvProjectionMatrix);
FLocalFogVolumeTiledPassParameters* PassParameters = GraphBuilder.AllocParameters<FLocalFogVolumeTiledPassParameters>();
PassParameters->VS.View = GetShaderBinding(View.ViewUniformBuffer);
PassParameters->VS.LFV = View.LocalFogVolumeViewData.UniformParametersStruct;
PassParameters->VS.TileDataBuffer = View.LocalFogVolumeViewData.GPUTileDataBufferSRV;
PassParameters->VS.StartDepthZ = DepthBoundSetup.FogClipDeviceZ;
PassParameters->PS.View = GetShaderBinding(View.ViewUniformBuffer);
PassParameters->PS.LFV = View.LocalFogVolumeViewData.UniformParametersStruct;
PassParameters->PS.LocalFogVolumeTileDebug = GetLocalFogVolumeTileDebugMode(SceneViewFamily.EngineShowFlags);
PassParameters->SceneTextures = SceneTextures.UniformBuffer;
PassParameters->TileDrawIndirectBuffer = View.LocalFogVolumeViewData.GPUTileDrawIndirectBuffer;
PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneColorTexture, ERenderTargetLoadAction::ENoAction);
PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite);
FLocalFogVolumeTiledRenderVS::FPermutationDomain VSPermutationVector;
auto VertexShader = View.ShaderMap->GetShader< FLocalFogVolumeTiledRenderVS >(VSPermutationVector);
FLocalFogVolumeTiledRenderPS::FPermutationDomain PsPermutationVector;
PsPermutationVector.Set<FLocalFogVolumeTiledRenderPS::FVisualizationPass>(bVisualizationPass);
auto PixelShader = View.ShaderMap->GetShader< FLocalFogVolumeTiledRenderPS >(PsPermutationVector);
ClearUnusedGraphResources(VertexShader, &PassParameters->VS);
ClearUnusedGraphResources(PixelShader, &PassParameters->PS);
FUintVector2& LocalFogVolumeTileDataTextureResolution = View.LocalFogVolumeViewData.UniformParametersStruct.LocalFogVolumeCommon.LocalFogVolumeTileDataTextureResolution;
GraphBuilder.AddPass(
RDG_EVENT_NAME("LocalFogVolume.Tiled (%u x %u)", LocalFogVolumeTileDataTextureResolution.X, LocalFogVolumeTileDataTextureResolution.Y),
PassParameters,
ERDGPassFlags::Raster,
[VertexShader, PixelShader, PassParameters, ViewRect, DepthBoundSetup](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f);
// Render back faces only since camera may intersect
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_SourceAlpha, BO_Add, BF_Zero, BF_SourceAlpha>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList; // LFV_TODO check if rects are supported and use them if so
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.bDepthBounds = DepthBoundSetup.bEnabled;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
RHICmdList.SetDepthBounds(DepthBoundSetup.MinDeviceZ, DepthBoundSetup.MaxDeviceZ);
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->VS);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS);
RHICmdList.SetStreamSource(0, nullptr, 0);
RHICmdList.DrawPrimitiveIndirect(PassParameters->TileDrawIndirectBuffer->GetIndirectRHICallBuffer(), 0);
});
}
}
}
void RenderLocalFogVolumeVisualization(
const FScene* Scene,
TArray<FViewInfo>& Views,
const FSceneViewFamily& SceneViewFamily,
FRDGBuilder& GraphBuilder,
const FMinimalSceneTextures& SceneTextures)
{
#if !UE_BUILD_SHIPPING
const bool bVisualizationPass = true;
FRDGTexture* LightShaftOcclusionTexture = GSystemTextures.GetBlackDummy(GraphBuilder);
RenderLocalFogVolume(Scene, Views, SceneViewFamily, GraphBuilder, SceneTextures, LightShaftOcclusionTexture, bVisualizationPass);
#endif
}
/*=============================================================================
Local height fog rendering - mobile
=============================================================================*/
class FMobileLocalFogVolumeTiledRenderVS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMobileLocalFogVolumeTiledRenderVS);
SHADER_USE_PARAMETER_STRUCT(FMobileLocalFogVolumeTiledRenderVS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
SHADER_PARAMETER_STRUCT(FLocalFogVolumeUniformParameters, LFV)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, TileDataBuffer)
RDG_BUFFER_ACCESS(TileDrawIndirectBuffer, ERHIAccess::IndirectArgs)
SHADER_PARAMETER(float, StartDepthZ)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
if (!IsMobilePlatform(Parameters.Platform))
{
return false;
}
return true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
const bool bMobileForceDepthRead = MobileUsesFullDepthPrepass(Parameters.Platform);
OutEnvironment.SetDefine(TEXT("LFV_TILED_VS"), 1);
OutEnvironment.SetDefine(TEXT("IS_MOBILE_DEPTHREAD_SUBPASS"), bMobileForceDepthRead ? 0u : 1u);
}
};
IMPLEMENT_GLOBAL_SHADER(FMobileLocalFogVolumeTiledRenderVS, "/Engine/Private/LocalFogVolumes/LocalFogVolumeSplat.usf", "LocalFogVolumeTiledVS", SF_Vertex);
class FMobileLocalFogVolumeTiledRenderPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMobileLocalFogVolumeTiledRenderPS);
SHADER_USE_PARAMETER_STRUCT(FMobileLocalFogVolumeTiledRenderPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMobileBasePassUniformParameters, MobileBasePass)
SHADER_PARAMETER_STRUCT(FLocalFogVolumeUniformParameters, LFV)
SHADER_PARAMETER(int32, LocalFogVolumeTileDebug)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
if (!IsMobilePlatform(Parameters.Platform))
{
return false;
}
return true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
const bool bMobileForceDepthRead = MobileUsesFullDepthPrepass(Parameters.Platform);
OutEnvironment.SetDefine(TEXT("LFV_TILED_PS"), 1);
OutEnvironment.SetDefine(TEXT("IS_MOBILE_DEPTHREAD_SUBPASS"), bMobileForceDepthRead ? 0u : 1u);
}
};
IMPLEMENT_GLOBAL_SHADER(FMobileLocalFogVolumeTiledRenderPS, "/Engine/Private/LocalFogVolumes/LocalFogVolumeSplat.usf", "LocalFogVolumeTiledPS", SF_Pixel);
class FMobileLocalFogVolumeTiledRenderHalfResPS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FMobileLocalFogVolumeTiledRenderHalfResPS);
SHADER_USE_PARAMETER_STRUCT(FMobileLocalFogVolumeTiledRenderHalfResPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMobileBasePassUniformParameters, MobileBasePass)
//SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMobileSceneTextureUniformParameters, SceneTextures)
SHADER_PARAMETER_STRUCT(FLocalFogVolumeUniformParameters, LFV)
SHADER_PARAMETER(int32, LocalFogVolumeTileDebug)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
if (!IsMobilePlatform(Parameters.Platform))
{
return false;
}
return true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
const bool bMobileForceDepthRead = MobileUsesFullDepthPrepass(Parameters.Platform);
OutEnvironment.SetDefine(TEXT("LFV_TILED_PS"), 1);
OutEnvironment.SetDefine(TEXT("LFV_HALFRES_PS"), 1);
OutEnvironment.SetDefine(TEXT("IS_MOBILE_DEPTHREAD_SUBPASS"), bMobileForceDepthRead ? 0u : 1u);
}
};
IMPLEMENT_GLOBAL_SHADER(FMobileLocalFogVolumeTiledRenderHalfResPS, "/Engine/Private/LocalFogVolumes/LocalFogVolumeSplat.usf", "LocalFogVolumeTiledPS", SF_Pixel);
void RenderLocalFogVolumeMobile(
FRHICommandList& RHICmdList,
const FViewInfo& View)
{
if (View.LocalFogVolumeViewData.GPUInstanceCount == 0)
{
return;
}
SCOPED_DRAW_EVENT(RHICmdList, LocalFogVolumeVolumes);
FMobileLocalFogVolumeTiledRenderVS::FPermutationDomain VSPermutationVector;
auto VertexShader = View.ShaderMap->GetShader< FMobileLocalFogVolumeTiledRenderVS >(VSPermutationVector);
FMobileLocalFogVolumeTiledRenderPS::FPermutationDomain PsPermutationVector;
auto PixelShader = View.ShaderMap->GetShader< FMobileLocalFogVolumeTiledRenderPS >(PsPermutationVector);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
const FIntRect ViewRect = View.ViewRect;
RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f);
// Render back faces only since camera may intersect
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_SourceAlpha, BO_Add, BF_Zero, BF_SourceAlpha,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero,
CW_NONE, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList; // LFV_TODO check if rects are supported and use them if so
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
FDepthBoundSetup DepthBoundSetup = GetDepthBoundSetup(GetLocalFogVolumeGlobalStartDistance(), View.ViewMatrices.GetProjectionMatrix(), View.ViewMatrices.GetInvProjectionMatrix());
GraphicsPSOInit.bDepthBounds = DepthBoundSetup.bEnabled;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
RHICmdList.SetDepthBounds(DepthBoundSetup.MinDeviceZ, DepthBoundSetup.MaxDeviceZ);
FMobileLocalFogVolumeTiledRenderVS::FParameters VSParameters;
VSParameters.View = View.GetShaderParameters();
VSParameters.LFV = View.LocalFogVolumeViewData.UniformParametersStruct;
VSParameters.TileDataBuffer = View.LocalFogVolumeViewData.GPUTileDataBufferSRV;
VSParameters.TileDrawIndirectBuffer = View.LocalFogVolumeViewData.GPUTileDrawIndirectBuffer;
VSParameters.StartDepthZ = DepthBoundSetup.FogClipDeviceZ;
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VSParameters);
FMobileLocalFogVolumeTiledRenderPS::FParameters PSParameters;
PSParameters.View = View.GetShaderParameters();
PSParameters.LFV = View.LocalFogVolumeViewData.UniformParametersStruct;
PSParameters.LocalFogVolumeTileDebug = GetLocalFogVolumeTileDebugMode(View.Family->EngineShowFlags);
// PSParameters.MobileBasePass filled up by the RDG pass parameters.
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PSParameters);
RHICmdList.SetStreamSource(0, nullptr, 0);
RHICmdList.DrawPrimitiveIndirect(View.LocalFogVolumeViewData.GPUTileDrawIndirectBuffer->GetIndirectRHICallBuffer(), 0);
}
void RenderLocalFogVolumeHalfResMobile(
FRDGBuilder& GraphBuilder,
const FViewInfo& View)
{
if (View.LocalFogVolumeViewData.GPUInstanceCount == 0)
{
return;
}
FMobileLocalFogVolumeTiledRenderHalfResPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FMobileLocalFogVolumeTiledRenderHalfResPS::FParameters>();
// When rendering the half resolution LFV texture, we are right after the depth prepass so it should not be bound (the texture is going to be generated now).
// That is why we notify that we are still in the prepass stage.
const EMobileBasePass BasePass = EMobileBasePass::DepthPrePass;
PassParameters->View = View.GetShaderParameters();
PassParameters->LFV = View.LocalFogVolumeViewData.UniformParametersStruct;
PassParameters->LocalFogVolumeTileDebug = GetLocalFogVolumeTileDebugMode(View.Family->EngineShowFlags);
PassParameters->MobileBasePass = CreateMobileBasePassUniformBuffer(GraphBuilder, View, BasePass, EMobileSceneTextureSetupMode::SceneDepth);
PassParameters->RenderTargets[0] = FRenderTargetBinding(View.LocalFogVolumeViewData.HalfResLocalFogVolumeView, ERenderTargetLoadAction::ENoAction);
PassParameters->RenderTargets[1] = FRenderTargetBinding(View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepth, ERenderTargetLoadAction::ENoAction);
FMobileLocalFogVolumeTiledRenderHalfResPS::FPermutationDomain PsPermutationVector;
auto PixelShader = View.ShaderMap->GetShader< FMobileLocalFogVolumeTiledRenderHalfResPS >(PsPermutationVector);
ClearUnusedGraphResources(PixelShader, PassParameters);
const FIntPoint HalfResolution = View.LocalFogVolumeViewData.HalfResResolution;
FPixelShaderUtils::AddFullscreenPass<FMobileLocalFogVolumeTiledRenderHalfResPS>(
GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("LocalFogVolume.HalfRes"), PixelShader, PassParameters,
FIntRect(0, 0, HalfResolution.X, HalfResolution.Y));
}