387 lines
16 KiB
HLSL
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
|
|
} |