Files
UnrealEngine/Engine/Shaders/Private/PostProcessDOF.usf
2025-05-18 13:04:45 +08:00

274 lines
8.6 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessDOF.usf: PostProcessing Depth of Field
=============================================================================*/
#define FAR_BLUR FAR_BLUR_COUNT >= 1
#define NEAR_BLUR NEAR_BLUR_COUNT >= 1
#define DOF_VIGNETTE NEAR_BLUR_COUNT == 2
#define MRT_COUNT FAR_BLUR_COUNT + (NEAR_BLUR_COUNT > 0 ? 1 : 0)
#include "Common.ush"
#include "DepthOfFieldCommon.ush"
Texture2D SceneColorTexture;
SamplerState SceneColorSampler;
Texture2D DofFarBlurTexture;
SamplerState DofFarBlurSampler;
Texture2D DofNearBlurTexture;
SamplerState DofNearBlurSampler;
Texture2D SunShaftAndDofTexture;
SamplerState SunShaftAndDofSampler;
float4 BufferSizeAndInvSize;
// todo move to central place
float ComputeDOFNearFocalMask(float SceneDepth)
{
float NearFocalPlane = View.DepthOfFieldFocalDistance;
return saturate((NearFocalPlane - SceneDepth) / View.DepthOfFieldNearTransitionRegion);
}
// todo move to central place
float ComputeDOFFarFocalMask(float SceneDepth)
{
float FarFocalPlane = View.DepthOfFieldFocalDistance + View.DepthOfFieldFocalRegion;
return saturate((SceneDepth - FarFocalPlane) / View.DepthOfFieldFarTransitionRegion);
}
// Calculate focal masks from Mobile's PP CoC.
float2 DecodeDOFFocalMask(float SceneDepth)
{
float2 Ret;
SceneDepth = min(1.0, SceneDepth);
if (SceneDepth > 0.5)
{
Ret.x = (SceneDepth - 0.5) * 2.0;
Ret.y = 0;
}
else
{
Ret.y = 1.0 - (SceneDepth * 2.0);
Ret.x = 0;
}
return Ret * View.DepthOfFieldScale;
}
// @return .x:far, .y:near
float2 ComputeDOFFocalMask(float SceneDepth, float SkyWithoutHorizonMask)
{
float2 Ret = float2(ComputeDOFFarFocalMask(SceneDepth), ComputeDOFNearFocalMask(SceneDepth));
float SkyFocusDistance = DepthOfFieldParams.x;
// The skybox should not be faded out, expect in the horizon, this can be optimized
if(SceneDepth > SkyFocusDistance)
{
Ret.x = lerp(Ret.x, 0, SkyWithoutHorizonMask);
}
Ret = DecodeDOFFocalMask(SceneDepth);
return Ret;
}
float4 ReadFullResAndDepth(float2 UV, in out float Depth)
{
float4 Color = Texture2DSampleLevel(SceneColorTexture, SceneColorSampler, UV, 0);
Color.a = Texture2DSampleLevel(SunShaftAndDofTexture, SunShaftAndDofSampler, UV, 0).r;
Depth = Color.a;
Color.a = 1.0;
return Color;
}
// pixel shader entry point
void SetupPS(
noperspective float4 unused : TEXCOORD0,
float4 SvPosition : SV_POSITION
, out float4 OutColor0 : SV_Target0
#if MRT_COUNT > 1
, out float4 OutColor1 : SV_Target1
#endif
)
{
float DoFToScreenScale = 4;
ResolvedView = ResolveView();
// Manually compute UV and ScreenSpacePos from SvPosition to avoid south east shiting of the pixel in bottom right corner
// caused by the DestRect.Size() + 1 viewport size done for odd view rect resolution.
float2 ViewportUV = min(SvPositionToViewportUV(SvPosition * DoFToScreenScale), 1 - View.ViewSizeAndInvSize.zw * 0.5);
float2 UV = ViewportUVToBufferUV(ViewportUV) - ((0.5f - View.TemporalAAParams.zw) * BufferSizeAndInvSize.zw);
float2 ScreenSpacePos = DoFToScreenScale * (float2(ViewportUV.x, 1 - ViewportUV.y) - 0.5);
float2 Offset = 0.5f * BufferSizeAndInvSize.zw;
float MaskDistance = View.DepthOfFieldFocalDistance + View.DepthOfFieldFocalRegion * 0.5f;
// Mobile path gathers Depth later in ReadFullResAndDepth
float4 DepthQuad = float4(0,0,0,0);
float4 FarColor = 0;
float4 NearColor = 0;
float2 Mask;
float4 Sample;
// for each sample of the full res input image
// we compute the mask (front of back layer)
// and put into MRT0 or MRT1
// can be optimized, needed to not blur the skybox
float3 ScreenVector = normalize(mul(float3(ScreenSpacePos, 1), DFToFloat3x3(PrimaryView.ScreenToWorld)).xyz);
float SkyWithoutHorizonMask = saturate(ScreenVector.z * 3.0f);
// 0:see though..1:Near blurred
float VignetteMask = 0;
#if DOF_VIGNETTE
{
float DepthOfFieldVignetteMul = DepthOfFieldParams.y;
float DepthOfFieldVignetteAdd = DepthOfFieldParams.z;
// todo: we could optimize that multiplication away
float InvAspectRatio = View.ViewSizeAndInvSize.y * View.ViewSizeAndInvSize.z;
float CenterDist = length(ScreenSpacePos * float2(1, InvAspectRatio));
// We prepare the constants on CPU side and use MAD (Multiply and Add) as this is faster
VignetteMask = saturate(CenterDist * DepthOfFieldVignetteMul + DepthOfFieldVignetteAdd);
}
#endif
Sample = ReadFullResAndDepth(UV + Offset * float2(-1, 1), DepthQuad.x);
Mask = ComputeDOFFocalMask(DepthQuad.x, SkyWithoutHorizonMask);
FarColor += Sample * Mask.x;
NearColor += Sample * lerp(Mask.y, 1, VignetteMask);
Sample = ReadFullResAndDepth(UV + Offset * float2(1, 1), DepthQuad.y);
Mask = ComputeDOFFocalMask(DepthQuad.y, SkyWithoutHorizonMask);
FarColor += Sample * Mask.x;
NearColor += Sample * lerp(Mask.y, 1, VignetteMask);
Sample = ReadFullResAndDepth(UV + Offset * float2(1, -1), DepthQuad.z);
Mask = ComputeDOFFocalMask(DepthQuad.z, SkyWithoutHorizonMask);
FarColor += Sample * Mask.x;
NearColor += Sample * lerp(Mask.y, 1, VignetteMask);
Sample = ReadFullResAndDepth(UV + Offset * float2(-1, -1), DepthQuad.w);
Mask = ComputeDOFFocalMask(DepthQuad.w, SkyWithoutHorizonMask);
FarColor += Sample * Mask.x;
NearColor += Sample * lerp(Mask.y, 1, VignetteMask);
// we average 4 samples
FarColor /= 4;
NearColor /= 4;
#if MRT_COUNT > 1
OutColor0 = FarColor;
OutColor1 = NearColor;
#else
#if NEAR_BLUR
OutColor0 = NearColor;
#else
OutColor0 = FarColor;
#endif
#endif
}
float4 DepthOfFieldUVLimit;
// pixel shader to combine the full res scene and the blurred images behind and in front of the the focal plane
void MainRecombinePS(
noperspective float4 unused : TEXCOORD0,
float4 SvPosition : SV_POSITION,
out float4 OutColor : SV_Target0
)
{
// SceneColor in full res
float2 PixelPosCenter = SvPosition.xy;
float2 FullResUV = PixelPosCenter * BufferSizeAndInvSize.zw;
// DOF in half res
float2 ViewportUV = SvPositionToBufferUV(SvPosition);
// Clamp UV to avoid pulling bad data.
ViewportUV.x = clamp(ViewportUV.x, DepthOfFieldUVLimit.x, DepthOfFieldUVLimit.z);
ViewportUV.y = clamp(ViewportUV.y, DepthOfFieldUVLimit.y, DepthOfFieldUVLimit.w);
float4 SceneColorAndDepth = Texture2DSample(SceneColorTexture, SceneColorSampler, FullResUV);
SceneColorAndDepth.a = Texture2DSample(SunShaftAndDofTexture, SunShaftAndDofSampler, FullResUV).r;
float3 UnfocusedSceneColor = SceneColorAndDepth.rgb;
// I'm presuming all that matters here is the W==0 bit to mask out this value
// TODO: Should check that compiler is doing a good job of removing the usages of this
// from the rest of the code. It has no reason not to be able to do so...
float4 DOFAccumLayer1 = float4(0,0,0,0);
float4 DOFAccumLayer3 = float4(0,0,0,0);
#if FAR_BLUR
DOFAccumLayer1 = Texture2DSample(DofFarBlurTexture, DofFarBlurSampler, ViewportUV);
#endif
#if NEAR_BLUR
DOFAccumLayer3 = Texture2DSample(DofNearBlurTexture, DofNearBlurSampler, ViewportUV);
#endif
float Layer1Mask = DOFAccumLayer1.a;
float Layer2Mask = 1.0f - ComputeDOFFarFocalMask(SceneColorAndDepth.a);
// float Layer2Mask = 1.0f - DOFAccumLayer1.a;
float Layer3Mask = DOFAccumLayer3.a;
float PerPixelNearMask = ComputeDOFNearFocalMask(SceneColorAndDepth.a);
float2 DoFMasks = DecodeDOFFocalMask(SceneColorAndDepth.a);
Layer2Mask = 1.0-DoFMasks.x;
// 3 layers
float Div0Bias = 0.0001f;
// RGB color, A how much the full resolution showes through
float3 LayerMerger = 0;
// Layer 1: half res background
LayerMerger = (UnfocusedSceneColor * Div0Bias + DOFAccumLayer1.rgb) / (DOFAccumLayer1.a + Div0Bias);
// Needed to cope with the skybox not being blurred, the tweak value
// avoids having a discontinuity between blurry far objects and the skybox
// and is choosen to not produce too much blobby looking out of focus rendering.
float Blend = DOFAccumLayer1.a;
// Magic function to transform alpha into smooth blend function against in-focus skybox.
Blend = sqrt(Blend);
Blend = sqrt(Blend);
Blend = Blend * Blend * (3.0 - 2.0 * Blend);
LayerMerger = lerp(UnfocusedSceneColor, LayerMerger, Blend);
// Layer 2: then we add the focused scene to fill the empty areas
float Smash = 0.25;
Layer2Mask = saturate((Layer2Mask - (1.0 - Smash)) * rcp(Smash));
Layer2Mask *= Layer2Mask;
// LayerMerger = lerp(LayerMerger, SceneColorAndDepth.rgb, Layer2Mask * (1 - PerPixelNearMask));
LayerMerger = lerp(LayerMerger, SceneColorAndDepth.rgb, Layer2Mask);
float3 FrontLayer = (UnfocusedSceneColor * Div0Bias + DOFAccumLayer3.rgb) / (DOFAccumLayer3.a + Div0Bias);
// Layer 3: on top of that blend the front half res layer
LayerMerger = lerp(LayerMerger, FrontLayer, saturate(Layer3Mask * 5));
OutColor.rgb = LayerMerger;
OutColor.a = 0;
}