// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= MobileTranslucentRendering.cpp: translucent rendering implementation. =============================================================================*/ #include "CoreMinimal.h" #include "DataDrivenShaderPlatformInfo.h" #include "HAL/IConsoleManager.h" #include "RHI.h" #include "Shader.h" #include "GlobalShader.h" #include "RenderUtils.h" #include "SceneRendering.h" #include "ScenePrivate.h" #include "FogRendering.h" #include "PostProcess/SceneRenderTargets.h" #include "ProfilingDebugging/RealtimeGPUProfiler.h" #include "MobileBasePassRendering.h" #include "SkyAtmosphereRendering.h" #include "LocalFogVolumeRendering.h" DECLARE_GPU_STAT(MobileFog); static TAutoConsoleVariable CVarPixelFogQuality( TEXT("r.Mobile.PixelFogQuality"), 1, TEXT("Exponentional height fog rendering quality.\n") TEXT("0 - basic per-pixel fog") TEXT("1 - all per-pixel fog features (second fog, directional inscattering, aerial perspective)"), ECVF_Scalability | ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarPixelFogDepthTest( TEXT("r.Mobile.PixelFogDepthTest"), 1, TEXT("Whether to use depth and stencil tests for fog rendering"), ECVF_RenderThreadSafe); class FMobileFogVS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FMobileFogVS); SHADER_USE_PARAMETER_STRUCT(FMobileFogVS, FGlobalShader); static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsMobilePlatform(Parameters.Platform); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER(float, StartDepthZ) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FMobileFogVS, "/Engine/Private/MobileFog.usf", "MobileFogVS", SF_Vertex); class FMobileFogPS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FMobileFogPS); SHADER_USE_PARAMETER_STRUCT(FMobileFogPS, FGlobalShader); class FSupportHeightFog : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_HEIGHT_FOG"); class FSupportFogStartDistance : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_FOG_START_DISTANCE"); class FSupportFogInScatteringTexture : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_FOG_INSCATTERING_TEXTURE"); class FSupportFogSecondTerm : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_FOG_SECOND_TERM"); class FSupportFogDirectionalLightInScattering : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_FOG_DIRECTIONAL_LIGHT_INSCATTERING"); class FSupportAerialPerspective : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_AERIAL_PERSPECTIVE"); class FSupportVolumetricFog : SHADER_PERMUTATION_BOOL("PERMUTATION_SUPPORT_VOLUMETRIC_FOG"); class FSupportLocalFogVolume : SHADER_PERMUTATION_INT("PERMUTATION_SUPPORT_LOCAL_FOG_VOLUME", 3); // 0 disabled, 1 sample LFVs, 2 compose halfres LFV texture using FPermutationDomain = TShaderPermutationDomain< FSupportHeightFog, FSupportFogStartDistance, FSupportFogInScatteringTexture, FSupportFogSecondTerm, FSupportFogDirectionalLightInScattering, FSupportAerialPerspective, FSupportVolumetricFog, FSupportLocalFogVolume >; static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector) { if (PermutationVector.Get() == false) { PermutationVector.Set(false); PermutationVector.Set(false); PermutationVector.Set(false); PermutationVector.Set(false); } return PermutationVector; } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { if (!IsMobilePlatform(Parameters.Platform)) { return false; } FPermutationDomain PermutationVector(Parameters.PermutationId); if (RemapPermutation(PermutationVector) != PermutationVector) { 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("IS_MOBILE_DEPTHREAD_SUBPASS"), bMobileForceDepthRead ? 0u : 1u); } BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER_STRUCT(FLocalFogVolumeUniformParameters, LFV) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, HalfResLocalFogVolumeViewSRV) SHADER_PARAMETER_SAMPLER(SamplerState, HalfResLocalFogVolumeViewSRVSampler) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, HalfResLocalFogVolumeDepthSRV) SHADER_PARAMETER_SAMPLER(SamplerState, HalfResLocalFogVolumeDepthSRVSampler) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_SHADER_TYPE(, FMobileFogPS, TEXT("/Engine/Private/MobileFog.usf"), TEXT("MobileFogPS"), SF_Pixel); void FMobileSceneRenderer::RenderFog(FRHICommandList& RHICmdList, const FViewInfo& View) { RHI_BREADCRUMB_EVENT_STAT(RHICmdList, MobileFog, "MobileFog"); SCOPED_GPU_STAT(RHICmdList, MobileFog); // RenderFog has some extra logic to skip the rendering of fog. So we account for that inside this function using a lambda. bool bFogHasComposedLocalFogVolumes = false; auto RenderLocalFogVolumeMobileLambda = [&]() { if (!bFogHasComposedLocalFogVolumes) { RenderLocalFogVolumeMobile(RHICmdList, View); } }; static const auto* CVarDisableVertexFog = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.DisableVertexFog")); if (CVarDisableVertexFog && CVarDisableVertexFog->GetValueOnRenderThread() == 0) { // Project uses only vertex fogging return; } const int32 PixelFogQuality = CVarPixelFogQuality.GetValueOnRenderThread(); const bool bUseHeightFog = Scene->ExponentialFogs.Num() > 0 && ShouldRenderFog(*View.Family); const bool bUseAerialPerspective = PixelFogQuality > 0 && ShouldRenderSkyAtmosphere(Scene, View.Family->EngineShowFlags); if (!bUseAerialPerspective && !bUseHeightFog) { RenderLocalFogVolumeMobileLambda(); return; } const bool bUseDepthTest = CVarPixelFogDepthTest.GetValueOnRenderThread() != 0; SCOPED_DRAW_EVENT(RHICmdList, Fog); RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f); FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.RasterizerState = TStaticRasterizerState::GetRHI(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; if (bUseDepthTest) { GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState< false, CF_DepthNearOrEqual, true, CF_NotEqual, SO_Keep, SO_Keep, SO_Keep, false, CF_Always, SO_Keep, SO_Keep, SO_Keep, STENCIL_MOBILE_SKY_MASK, 0x00>::GetRHI(); } else { GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); } GraphicsPSOInit.BlendState = TStaticBlendState::GetRHI(); TShaderMapRef VertexShader(View.ShaderMap); const bool bUseFogStartDistance = View.ExponentialFogParameters.W > 0; const bool bUseFogInscatteringColorCubemap = View.FogInscatteringColorCubemap != nullptr; const bool bUseFogSecondTerm = PixelFogQuality > 0 && (View.ExponentialFogParameters2.X > 0); const bool bUseFogDirectionalInscatering = PixelFogQuality > 0 && !bUseFogInscatteringColorCubemap && (View.DirectionalInscatteringColor.GetLuminance() > 0 || View.bUseDirectionalInscattering); const bool bShouldRenderVolumetricFog = ShouldRenderVolumetricFog(); bFogHasComposedLocalFogVolumes = bShouldRenderVolumetricFog && ShouldRenderLocalFogVolume(Scene, *View.Family) && ShouldRenderLocalFogVolumeInVolumetricFog(Scene, *View.Family, bShouldRenderVolumetricFog); const bool bComposeHalfResLFV = View.LocalFogVolumeViewData.bUseHalfResLocalFogVolume; bFogHasComposedLocalFogVolumes |= bComposeHalfResLFV; FMobileFogPS::FPermutationDomain PsPermutationVector; PsPermutationVector.Set(bUseHeightFog); PsPermutationVector.Set(bUseFogStartDistance); PsPermutationVector.Set(bUseFogInscatteringColorCubemap); PsPermutationVector.Set(bUseFogSecondTerm); PsPermutationVector.Set(bUseAerialPerspective); PsPermutationVector.Set(bUseFogDirectionalInscatering); PsPermutationVector.Set(bShouldRenderVolumetricFog); PsPermutationVector.Set(bFogHasComposedLocalFogVolumes ? (bComposeHalfResLFV ? 2 : 1) : 0); TShaderMapRef PixelShader(View.ShaderMap, PsPermutationVector); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GTileVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); if (bUseDepthTest) { SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, STENCIL_MOBILE_SKY_MASK); } else { SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); } // Use height fog start distance by default and fallback to AP distance float FogStartDistance = GetFogDefaultStartDistance(); { if (bUseHeightFog) { FogStartDistance = GetViewFogCommonStartDistance(View, bShouldRenderVolumetricFog, bFogHasComposedLocalFogVolumes); } if (bUseAerialPerspective) { FSkyAtmosphereRenderSceneInfo& SkyInfo = *Scene->GetSkyAtmosphereSceneInfo(); const FSkyAtmosphereSceneProxy& SkyAtmosphereSceneProxy = SkyInfo.GetSkyAtmosphereSceneProxy(); const float AerialPerspectiveStartDepthInCm = GetValidAerialPerspectiveStartDepthInCm(View, SkyAtmosphereSceneProxy); FogStartDistance = bUseHeightFog ? FMath::Min(FogStartDistance, AerialPerspectiveStartDepthInCm) : AerialPerspectiveStartDepthInCm; } } float StartDepthZ = 0.1; if (bUseDepthTest && FogStartDistance > 0.f) { const FMatrix ProjectionMatrix = View.ViewMatrices.GetProjectionMatrix(); float HalfHorizontalFOV = FMath::Atan(1.0f / ProjectionMatrix.M[0][0]); float HalfVerticalFOV = FMath::Atan(1.0f / ProjectionMatrix.M[1][1]); float StartDepthViewCm = FMath::Cos(FMath::Max(HalfHorizontalFOV, HalfVerticalFOV)) * FogStartDistance; StartDepthViewCm = FMath::Max(StartDepthViewCm, View.NearClippingDistance); // In any case, we need to limit the distance to frustum near plane to not be clipped away. const FVector4 Projected = ProjectionMatrix.TransformFVector4(FVector4(0.0f, 0.0f, StartDepthViewCm, 1.0f)); StartDepthZ = (float)(Projected.Z / Projected.W); // LWC_TODO: precision loss } FMobileFogVS::FParameters VSParameters; VSParameters.View = View.GetShaderParameters(); VSParameters.StartDepthZ = StartDepthZ; SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VSParameters); FMobileFogPS::FParameters PSParameters; PSParameters.View = View.GetShaderParameters(); PSParameters.LFV = View.LocalFogVolumeViewData.UniformParametersStruct; PSParameters.HalfResLocalFogVolumeViewSRV = View.LocalFogVolumeViewData.HalfResLocalFogVolumeViewSRV; PSParameters.HalfResLocalFogVolumeDepthSRV = View.LocalFogVolumeViewData.HalfResLocalFogVolumeDepthSRV; PSParameters.HalfResLocalFogVolumeViewSRVSampler = TStaticSamplerState::GetRHI(); PSParameters.HalfResLocalFogVolumeDepthSRVSampler = TStaticSamplerState::GetRHI(); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PSParameters); // Draw a quad covering the view. RHICmdList.SetStreamSource(0, GScreenSpaceVertexBuffer.VertexBufferRHI, 0); RHICmdList.DrawIndexedPrimitive(GTwoTrianglesIndexBuffer.IndexBufferRHI, 0, 0, 4, 0, 2, View.GetStereoPassInstanceFactor()); RenderLocalFogVolumeMobileLambda(); }