// 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 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 CVarSupportLocalFogVolumes( TEXT("r.SupportLocalFogVolumes"), 1, TEXT("Enables local fog volume rendering and shader code."), ECVF_ReadOnly | ECVF_RenderThreadSafe); static TAutoConsoleVariable 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 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 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 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 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 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 CVarLocalFogVolumeTileCullingUseAsync( TEXT("r.LocalFogVolume.TileCullingUseAsync"), 1, TEXT("Enables running the culling process on the async compute pipe."), ECVF_RenderThreadSafe); static TAutoConsoleVariable 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 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 CVarLocalFogVolumeUseHZB( TEXT("r.LocalFogVolume.UseHZB"), 1, TEXT("Enables the use of the HZB to cull local fog volumes away.\n"), ECVF_RenderThreadSafe); static TAutoConsoleVariable 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(), 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& Views) { for (FViewInfo& View : Views) { SetDummyLocalFogVolumeForView(GraphBuilder, View); } } void SetDummyLocalFogVolumeForViews(FRDGBuilder& GraphBuilder, TArray& 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; 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, LocalFogVolumeTileDataTextureUAV) SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, LocalFogVolumeCullingDataBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, TileDataBufferUAV) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, 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(); 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(IsHZBValid(View, EHZBType::FurthestHZB) && CVarLocalFogVolumeUseHZB.GetValueOnRenderThread()); TShaderMapRef 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(&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(), 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& 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, 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; 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& 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(); 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(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::GetRHI(); GraphicsPSOInit.BlendState = TStaticBlendState::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& 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, 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::GetRHI(); GraphicsPSOInit.BlendState = TStaticBlendState::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(); // 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( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("LocalFogVolume.HalfRes"), PixelShader, PassParameters, FIntRect(0, 0, HalfResolution.X, HalfResolution.Y)); }