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

387 lines
16 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
DistanceFieldLightingPost.usf
=============================================================================*/
#include "Common.ush"
#include "DeferredShadingCommon.ush"
#include "DistanceFieldLightingShared.ush"
#include "DistanceFieldAOShared.ush"
#include "ScreenPass.ush"
Texture2D BentNormalHistoryTexture;
SamplerState BentNormalHistorySampler;
Texture2D IrradianceHistoryTexture;
SamplerState IrradianceHistorySampler;
float HistoryWeight;
float HistoryDistanceThreshold;
float UseHistoryFilter;
float4 HistoryScreenPositionScaleBias;
float4 HistoryUVMinMax;
Texture2D VelocityTexture;
SamplerState VelocityTextureSampler;
float ComputeHistoryWeightBasedOnPosition(float2 ScreenPosition, float SceneDepth, float2 OldScreenPosition, float HistoryCameraDepth)
{
float3 TranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(ScreenPosition, SceneDepth), SceneDepth, 1), View.ScreenToTranslatedWorld).xyz;
float3 PrevPositionTranslatedWorld = mul(float4(GetScreenPositionForProjectionType(OldScreenPosition, HistoryCameraDepth), HistoryCameraDepth, 1), View.PrevScreenToTranslatedWorld).xyz;
float DistanceToHistoryValue = length(PrevPositionTranslatedWorld - TranslatedWorldPosition);
float RelativeHistoryDistanceThreshold = HistoryDistanceThreshold / 1000.0f;
return DistanceToHistoryValue / SceneDepth > RelativeHistoryDistanceThreshold ? 0.0f : 1.0f;
}
float2 DistanceFieldGBufferTexelSize;
float2 DistanceFieldGBufferJitterOffset;
float4 BentNormalBufferAndTexelSize;
FScreenTransform UVToScreenPos;
float4 GetNormalWeights(float2 Corner00UV, float2 LowResTexelSize, float3 WorldNormal)
{
float4 NormalWeights;
{
float3 SampleWorldNormal;
float Unused;
GetDownsampledGBuffer(Corner00UV, SampleWorldNormal, Unused);
NormalWeights.x = dot(SampleWorldNormal, WorldNormal);
}
{
float3 SampleWorldNormal;
float Unused;
GetDownsampledGBuffer(Corner00UV + float2(LowResTexelSize.x, 0), SampleWorldNormal, Unused);
NormalWeights.y = dot(SampleWorldNormal, WorldNormal);
}
{
float3 SampleWorldNormal;
float Unused;
GetDownsampledGBuffer(Corner00UV + float2(0, LowResTexelSize.y), SampleWorldNormal, Unused);
NormalWeights.z = dot(SampleWorldNormal, WorldNormal);
}
{
float3 SampleWorldNormal;
float Unused;
GetDownsampledGBuffer(Corner00UV + LowResTexelSize, SampleWorldNormal, Unused);
NormalWeights.w = dot(SampleWorldNormal, WorldNormal);
}
return max(NormalWeights, .0001f);
}
float ComputeSampleWeightBasedOnPosition(float4 ReferencePlane, float2 SampleScreenUV, float SampleDepth)
{
float2 SampleScreenPosition = ApplyScreenTransform(SampleScreenUV, UVToScreenPos);
float3 SampleTranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(SampleScreenPosition, SampleDepth), SampleDepth, 1), PrimaryView.ScreenToTranslatedWorld).xyz;
float PlaneDistance = dot(ReferencePlane, float4(SampleTranslatedWorldPosition, 1));
float Epsilon = .0001f;
float RelativeDistance = 1000 * abs(PlaneDistance) / SampleDepth;
return min(10.0f / (RelativeDistance + Epsilon), 1);
}
void GeometryAwareUpsample(float4 UVAndScreenPos, out float4 OutBentNormal)
{
float3 WorldNormal;
float SceneDepth;
GetDownsampledGBuffer(UVAndScreenPos.xy, WorldNormal, SceneDepth);
float3 TranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(UVAndScreenPos.zw, SceneDepth), SceneDepth, 1), PrimaryView.ScreenToTranslatedWorld).xyz;
float4 ReferencePlane = float4(WorldNormal, -dot(TranslatedWorldPosition, WorldNormal));
float2 LowResBufferSize = BentNormalBufferAndTexelSize.xy;
float2 LowResTexelSize = BentNormalBufferAndTexelSize.zw;
float2 Corner00UV = floor((UVAndScreenPos.xy - DistanceFieldGBufferJitterOffset) * LowResBufferSize) * LowResTexelSize;
float2 BilinearWeights = (UVAndScreenPos.xy - Corner00UV - DistanceFieldGBufferJitterOffset) * LowResBufferSize;
float2 LowResCorner00UV = Corner00UV + .5f * LowResTexelSize;
float4 TextureValues00 = Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, LowResCorner00UV, 0);
float4 TextureValues10 = Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, LowResCorner00UV + float2(LowResTexelSize.x, 0), 0);
float4 TextureValues01 = Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, LowResCorner00UV + float2(0, LowResTexelSize.y), 0);
float4 TextureValues11 = Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, LowResCorner00UV + LowResTexelSize, 0);
float4 CornerWeights = float4(
(1 - BilinearWeights.y) * (1 - BilinearWeights.x),
(1 - BilinearWeights.y) * BilinearWeights.x,
BilinearWeights.y * (1 - BilinearWeights.x),
BilinearWeights.y * BilinearWeights.x);
float4 CornerDepths = float4(TextureValues00.w, TextureValues10.w, TextureValues01.w, TextureValues11.w);
float4 PositionWeights;
PositionWeights.x = ComputeSampleWeightBasedOnPosition(ReferencePlane, LowResCorner00UV, CornerDepths.x);
PositionWeights.y = ComputeSampleWeightBasedOnPosition(ReferencePlane, LowResCorner00UV + float2(LowResTexelSize.x, 0), CornerDepths.y);
PositionWeights.z = ComputeSampleWeightBasedOnPosition(ReferencePlane, LowResCorner00UV + float2(0, LowResTexelSize.y), CornerDepths.z);
PositionWeights.w = ComputeSampleWeightBasedOnPosition(ReferencePlane, LowResCorner00UV + LowResTexelSize, CornerDepths.w);
//float4 DepthWeights = max(exp2(-abs(CornerDepths - SceneDepth.xxxx) * .01f), .001f);
float Epsilon = .0001f;
//float4 DepthWeights = min(10.0f / (abs(CornerDepths - SceneDepth.xxxx) + Epsilon), 1);
float2 FullResCorner00UV = Corner00UV + DistanceFieldGBufferJitterOffset + 0.5f * DistanceFieldGBufferTexelSize;
float4 NormalWeights = GetNormalWeights(FullResCorner00UV, LowResTexelSize, WorldNormal);
float4 FinalWeights = CornerWeights * PositionWeights * NormalWeights;
float InvSafeWeight = 1.0f / max(dot(FinalWeights, 1), .00001f);
float3 AverageBentNormal =
(FinalWeights.x * TextureValues00.xyz
+ FinalWeights.y * TextureValues10.xyz
+ FinalWeights.z * TextureValues01.xyz
+ FinalWeights.w * TextureValues11.xyz)
* InvSafeWeight;
OutBentNormal = float4(AverageBentNormal, SceneDepth);
float BentNormalLength = length(OutBentNormal.rgb);
float3 NormalizedBentNormal = OutBentNormal.rgb / max(BentNormalLength, .0001f);
OutBentNormal.rgb = NormalizedBentNormal * BentNormalLength;
//OutBentNormal = float4(WorldNormal, SceneDepth);
}
void GeometryAwareUpsamplePS(
in float4 UVAndScreenPos : TEXCOORD0
, out float4 OutBentNormal : SV_Target0
)
{
GeometryAwareUpsample(UVAndScreenPos, OutBentNormal);
}
/** Reproject the occlusion history. */
void UpdateHistoryDepthRejectionPS(
in float4 UVAndScreenPos : TEXCOORD0
,out float4 OutBentNormal : SV_Target0
)
{
float4 NewValue;
GeometryAwareUpsample(UVAndScreenPos, NewValue);
float SceneDepth = NewValue.w;
float4 ThisClip = float4( UVAndScreenPos.zw, ConvertToDeviceZ(SceneDepth), 1 );
float4 PrevClip = mul( ThisClip, View.ClipToPrevClip );
float2 PrevScreen = PrevClip.xy / PrevClip.w;
float2 ScreenVelocity = UVAndScreenPos.zw - PrevScreen;
// The scene velocity texture is rendered as a texture atlas for all views,
// so first convert the vertex shader's UV coordinates to the size of the view rect and offset into the atlas
// before converting back to the correct UV coordinates.
float2 FullResTexel = (UVAndScreenPos.xy * View.ViewSizeAndInvSize.xy + View.ViewRectMin.xy - .5f) * View.BufferSizeAndInvSize.zw;
float4 EncodedVelocity = Texture2DSampleLevel(VelocityTexture, VelocityTextureSampler, FullResTexel, 0);
if (EncodedVelocity.x > 0.0)
{
ScreenVelocity = DecodeVelocityFromTexture(EncodedVelocity).xy;
}
float PixelSpeed = 0.5 * length(ScreenVelocity);
float2 OldScreenPosition = (UVAndScreenPos.zw - ScreenVelocity);
float2 OldDistanceFieldUVs = OldScreenPosition * HistoryScreenPositionScaleBias.xy + HistoryScreenPositionScaleBias.wz;
float EffectiveHistoryWeight = HistoryWeight;
FLATTEN
if (any(OldDistanceFieldUVs > HistoryUVMinMax.zw) || any(OldDistanceFieldUVs < HistoryUVMinMax.xy))
{
EffectiveHistoryWeight = 0;
}
// It's not enough just to set EffectiveHistoryWeight to 0, as sampling from invalid history region may result in a NaN
OldDistanceFieldUVs = clamp(OldDistanceFieldUVs, HistoryUVMinMax.xy, HistoryUVMinMax.zw);
// Manual resample disabled as it doesn't affect the artifacts that cause most ghosting
#define MANUAL_HISTORY_RESAMPLE 0
#if MANUAL_HISTORY_RESAMPLE
float2 HistoryBufferSize = floor(View.BufferSizeAndInvSize.xy / DOWNSAMPLE_FACTOR);
float2 HistoryTexelSize = View.BufferSizeAndInvSize.zw * DOWNSAMPLE_FACTOR;
float2 OldDistanceFieldUVsCorner00 = HistoryTexelSize * (floor(OldDistanceFieldUVs * HistoryBufferSize - .5f) + .5f);
float2 OldDistanceFieldUVsCorner01 = OldDistanceFieldUVsCorner00 + float2(0, HistoryTexelSize.y);
float2 OldDistanceFieldUVsCorner11 = OldDistanceFieldUVsCorner00 + HistoryTexelSize;
float2 OldDistanceFieldUVsCorner10 = OldDistanceFieldUVsCorner00 + float2(HistoryTexelSize.x, 0);
float2 LerpWeights = (OldDistanceFieldUVs - OldDistanceFieldUVsCorner00) * HistoryBufferSize;
float4 Corner00Value = Texture2DSampleLevel(BentNormalHistoryTexture, BentNormalHistorySampler, OldDistanceFieldUVsCorner00, 0);
float4 Corner01Value = Texture2DSampleLevel(BentNormalHistoryTexture, BentNormalHistorySampler, OldDistanceFieldUVsCorner01, 0);
float4 Corner11Value = Texture2DSampleLevel(BentNormalHistoryTexture, BentNormalHistorySampler, OldDistanceFieldUVsCorner11, 0);
float4 Corner10Value = Texture2DSampleLevel(BentNormalHistoryTexture, BentNormalHistorySampler, OldDistanceFieldUVsCorner10, 0);
#define COMPUTE_SEPARATE_POSITION_WEIGHTS 1
#if COMPUTE_SEPARATE_POSITION_WEIGHTS
float PositionWeight00 = ComputeHistoryWeightBasedOnPosition(UVAndScreenPos.zw, SceneDepth, OldScreenPosition, Corner00Value.w);
float PositionWeight01 = ComputeHistoryWeightBasedOnPosition(UVAndScreenPos.zw, SceneDepth, OldScreenPosition, Corner01Value.w);
float PositionWeight11 = ComputeHistoryWeightBasedOnPosition(UVAndScreenPos.zw, SceneDepth, OldScreenPosition, Corner11Value.w);
float PositionWeight10 = ComputeHistoryWeightBasedOnPosition(UVAndScreenPos.zw, SceneDepth, OldScreenPosition, Corner10Value.w);
float BilinearWeight00 = (1 - LerpWeights.y) * (1 - LerpWeights.x);
float BilinearWeight01 = (LerpWeights.y) * (1 - LerpWeights.x);
float BilinearWeight11 = (LerpWeights.y) * (LerpWeights.x);
float BilinearWeight10 = (1 - LerpWeights.y) * (LerpWeights.x);
float3 HistoryValue = (BilinearWeight00 * lerp(NewValue.xyz, Corner00Value.xyz, PositionWeight00)
+ BilinearWeight01 * lerp(NewValue.xyz, Corner01Value.xyz, PositionWeight01)
+ BilinearWeight11 * lerp(NewValue.xyz, Corner11Value.xyz, PositionWeight11)
+ BilinearWeight10 * lerp(NewValue.xyz, Corner10Value.xyz, PositionWeight10));
OutBentNormal.rgb = lerp(NewValue.rgb, HistoryValue.rgb, EffectiveHistoryWeight);
EffectiveHistoryWeight *= (PositionWeight00 > 0 || PositionWeight01 > 0 || PositionWeight11 > 0 || PositionWeight10 > 0) ? 1.0f : 0.0f;
#define BENT_NORMAL_LENGTH_FIXUP 0
#if BENT_NORMAL_LENGTH_FIXUP
float BentNormalLength = length(HistoryValue.xyz);
float LengthVerticalLerp0 = lerp(length(Corner00Value.xyz), length(Corner01Value.xyz), LerpWeights.y);
float LengthVerticalLerp1 = lerp(length(Corner10Value.xyz), length(Corner11Value.xyz), LerpWeights.y);
float AverageLength = lerp(LengthVerticalLerp0, LengthVerticalLerp1, LerpWeights.x);
if (BentNormalLength < AverageLength && BentNormalLength > 0)
{
// Fixup normal shortening due to weighted average of vectors
HistoryValue.xyz = HistoryValue.xyz / BentNormalLength * AverageLength;
}
#endif
#else
// Manually implement bilinear filtering with a single position weight, for testing
float4 VerticalLerp0 = lerp(Corner00Value, Corner01Value, LerpWeights.y);
float4 VerticalLerp1 = lerp(Corner10Value, Corner11Value, LerpWeights.y);
float4 HistoryValue = lerp(VerticalLerp0, VerticalLerp1, LerpWeights.x);
float PositionWeight = ComputeHistoryWeightBasedOnPosition(UVAndScreenPos.zw, SceneDepth, OldScreenPosition, HistoryValue.w);
EffectiveHistoryWeight *= PositionWeight;
OutBentNormal.rgb = lerp(NewValue.rgb, HistoryValue.rgb, EffectiveHistoryWeight);
#endif
#else
// Fast path that uses hardware interpolation
// This fails to reject based on depth consistently because the depth has been filtered
float4 HistoryValue = Texture2DSampleLevel(BentNormalHistoryTexture, BentNormalHistorySampler, OldDistanceFieldUVs, 0);
float PositionWeight = ComputeHistoryWeightBasedOnPosition(UVAndScreenPos.zw, SceneDepth, OldScreenPosition, HistoryValue.w);
EffectiveHistoryWeight *= PositionWeight;
OutBentNormal.rgb = lerp(NewValue.rgb, HistoryValue.rgb, EffectiveHistoryWeight);
#endif
OutBentNormal.rgb = MakeFinite(OutBentNormal.rgb);
OutBentNormal.a = SceneDepth;
FLATTEN
if (UseHistoryFilter > 0)
{
// Sign bit of alpha stores whether the history was rejected or not, to be read by the history filter pass
OutBentNormal.a *= EffectiveHistoryWeight > 0 ? 1 : -1;
}
}
#define HALF_HISTORY_FILL_KERNEL_SIZE 2
float2 BentNormalAOTexelSize;
float2 MaxSampleBufferUV;
/** Seeds newly rejected history values (which are sources of temporal instability) with the results of a spatial search from stable history values */
void FilterHistoryPS(
in float4 UVAndScreenPos : TEXCOORD0
,out float4 OutBentNormal : SV_Target0
)
{
float2 BufferUV = UVAndScreenPos.xy;
#if MANUALLY_CLAMP_UV
BufferUV = min(BufferUV, MaxSampleBufferUV);
#endif
float4 HistoryValue = Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, BufferUV, 0);
// Only do the spatial search for pixels who discarded their history value
if (HistoryValue.w < 0)
{
float SceneDepth = abs(HistoryValue.w);
float4 Accumulation = 0;
for (float y = -HALF_HISTORY_FILL_KERNEL_SIZE; y <= HALF_HISTORY_FILL_KERNEL_SIZE; y++)
{
for (float x = -HALF_HISTORY_FILL_KERNEL_SIZE; x <= HALF_HISTORY_FILL_KERNEL_SIZE; x++)
{
float2 SampleBufferUV = UVAndScreenPos.xy + BentNormalAOTexelSize * float2(x, y);
#if MANUALLY_CLAMP_UV
// Distance field AO was computed at 0,0 regardless of viewrect min.
SampleBufferUV = min(SampleBufferUV, MaxSampleBufferUV);
#endif
float4 TextureValue = Texture2DSampleLevel(BentNormalAOTexture, BentNormalAOSampler, SampleBufferUV, 0);
float SampleSceneDepth = abs(TextureValue.w);
float ValidMask = TextureValue.w > 0;
// Weight by depth to avoid pulling in values of a foreground object
// This is a careful tradeoff between ghosting behind panning foreground objects and successful spatial searches to reduce flickering
float DepthWeight = exp2(-1000 * abs(SceneDepth - SampleSceneDepth) / SceneDepth);
float2 Weight2D = exp2(-abs(float2(x, y) * 10.0f / HALF_HISTORY_FILL_KERNEL_SIZE));
float ScreenSpaceSpatialWeight = max(Weight2D.x, Weight2D.y);
float Weight = ValidMask * ScreenSpaceSpatialWeight * DepthWeight;
Accumulation.rgb += TextureValue.rgb * Weight;
Accumulation.a += Weight;
}
}
// Only change the history value if the spatial search turned up something applicable
if (Accumulation.a > 0.01f)
{
float InvWeight = 1.0f / Accumulation.a;
// Construct the history value as if the spatial search result was the previous history,
// And the AO we just computed this frame was the new value
HistoryValue.xyz = lerp(HistoryValue.xyz, Accumulation.xyz * InvWeight, HistoryWeight);
}
}
OutBentNormal = HistoryValue;
// Remove sign bit so future reprojection interpolation isn't affected
OutBentNormal.w = abs(OutBentNormal.w);
}
float MinIndirectDiffuseOcclusion;
/** Upsamples the AO results to full resolution using a bilateral filter. */
void AOUpsamplePS(
in float4 UVAndScreenPos : TEXCOORD0
,out float4 OutSceneColor : SV_Target0
)
{
#if SHADING_PATH_MOBILE
FGBufferData GBuffer = MobileFetchAndDecodeGBuffer(UVAndScreenPos.xy, UVAndScreenPos.zw);
#else
FGBufferData GBuffer = GetGBufferData(UVAndScreenPos.xy);
#endif
// The rectangle is drawn according to GBuffer rect,
// while the bent normal is always offset at (0, 0)
const float2 BentNormalUV = UVAndScreenPos.xy - View.ViewRectMin.xy * View.BufferSizeAndInvSize.zw;
float3 BentNormal = UpsampleDFAO(BentNormalUV, GBuffer.Depth, GBuffer.WorldNormal);
#if MODULATE_SCENE_COLOR
float Visibility = lerp(length(BentNormal), 1.0f, MinIndirectDiffuseOcclusion);
OutSceneColor = Visibility;
#else
OutSceneColor = float4(length(BentNormal).xxx, 1.0f);
#endif
}