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

762 lines
35 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#define USE_HAIR_COMPLEX_TRANSMITTANCE 1
#include "../Common.ush"
#include "LumenMaterial.ush"
#include "../DeferredShadingCommon.ush"
#include "LumenScreenProbeCommon.ush"
#include "LumenScreenSpaceBentNormal.ush"
#include "LumenReflectionsCombine.ush"
#include "LumenFloatQuantization.ush"
#include "LumenPosition.ush"
#include "../StochasticLighting/StochasticLightingCommon.ush"
#include "../StochasticLighting/StochasticLightingUpsample.ush"
// Float R11G11B10 max
#define INVALID_LIGHTING float3(65024.0f, 65024.0f, 64512.0f)
#if SHORT_RANGE_AO_MODE == 2
// Denoise direction and AO separately for higher quality
typedef uint TShortRangeAOPacked;
typedef float4 TShortRangeAO;
float4 InitShortRangeAO(FLumenMaterialData Material)
{
return float4(Material.WorldNormal, 1.0f);
}
uint PackSharedShortRangeAO(float4 BentNormalAndAO)
{
float3 BentNormal = normalize(BentNormalAndAO.xyz) * BentNormalAndAO.w;
return PackRGB111110(BentNormal * 0.5f + 0.5f);
}
float4 UnpackSharedShortRangeAO(uint PackedBentNormalAndAO)
{
float3 BentNormal = UnpackRGB111110(PackedBentNormalAndAO) * 2.0f - 1.0f;
return float4(normalize(BentNormal.xyz), length(BentNormal.xyz));
}
#else
typedef UNORM float TShortRangeAOPacked;
typedef float TShortRangeAO;
float InitShortRangeAO(FLumenMaterialData Material)
{
return 1.0f;
}
float PackSharedShortRangeAO(float AmbientOcclusion)
{
return AmbientOcclusion;
}
float UnpackSharedShortRangeAO(float AmbientOcclusion)
{
return AmbientOcclusion;
}
#endif
RWTexture2DArray<float3> RWNewHistoryDiffuseIndirect;
RWTexture2DArray<float3> RWNewHistoryBackfaceDiffuseIndirect;
RWTexture2DArray<float3> RWNewHistoryRoughSpecularIndirect;
RWTexture2DArray<UNORM float> RWNewHistoryFastUpdateMode_NumFramesAccumulated;
RWTexture2DArray<TShortRangeAOPacked> RWNewHistoryShortRangeAO;
Texture2DArray DiffuseIndirect;
Texture2DArray LightIsMoving;
Texture2DArray BackfaceDiffuseIndirect;
Texture2DArray RoughSpecularIndirect;
Texture2DArray<TShortRangeAOPacked> ShortRangeAOTexture;
Texture2DArray DiffuseIndirectHistory;
Texture2DArray BackfaceDiffuseIndirectHistory;
Texture2DArray RoughSpecularIndirectHistory;
Texture2DArray<TShortRangeAOPacked> ShortRangeAOHistory;
Texture2DArray HistoryFastUpdateMode_NumFramesAccumulated;
Texture2D DiffuseIndirectDepthHistory;
Texture2D DiffuseIndirectNormalHistory;
float HistoryDistanceThreshold;
float HistoryDistanceThresholdForFoliage;
float4 HistoryScreenPositionScaleBias;
float4 HistoryUVToScreenPositionScaleBias;
float4 HistoryUVMinMax;
uint4 HistoryViewportMinMax;
float4 HistoryBufferSizeAndInvSize;
float ShortRangeAOTemporalNeighborhoodClampScale;
uint2 IntegrateViewMin;
uint2 IntegrateViewSize;
uint2 ShortRangeAOViewMin;
uint2 ShortRangeAOViewSize;
float2 DownsampledBufferInvSize;
float PrevSceneColorPreExposureCorrection;
float InvFractionOfLightingMovingForFastUpdateMode;
float MaxFastUpdateModeAmount;
float MaxFramesAccumulated;
float HistoryNormalCosThreshold;
uint bIsSubstrateTileHistoryValid;
static const int2 kOffsets3x3[8] =
{
int2(-1, -1),
int2( 0, -1),
int2( 1, -1),
int2(-1, 0),
int2( 1, 0),
int2(-1, 1),
int2( 0, 1),
int2( 1, 1),
};
bool IsNeighborPixelValid(uint2 InNeighborScreenCoord, uint InClosureIndex, bool bDefaultValue)
{
// With Substrate, we can't ensure the neighbor indirect diffuse/rough lighting values have been computed for closure index > 0.
// This is because we only clear lighting data for pixel within a tile, not pixels of neighboring tiles (In FScreenProbeTileClassificationMarkCS).
// The following test ensures (only used for closer index > 0), ensure we don't fetch invalid/uninitialized lighting data.
// Ideally this should be computed ahead of time and directly stored into LightingTexture.w to avoid this extra fetch.
bool bIsValid = bDefaultValue;
#if SUBTRATE_GBUFFER_FORMAT==1 && SUBSTRATE_MATERIAL_CLOSURE_COUNT > 1
if (InClosureIndex > 0)
{
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(InNeighborScreenCoord.xy, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel);
const FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture);
bIsValid = InClosureIndex < SubstratePixelHeader.ClosureCount;
}
#endif
return bIsValid;
}
float3 GetFilteredNeighborhoodLighting(
Texture2DArray LightingTexture,
uint2 ScreenCoord,
uint2 MinScreenCoord,
uint2 MaxScreenCoord,
uint InClosureIndex,
out bool bLightingIsValid)
{
float3 FilteredLighting = 0;
float TotalWeight = 0;
for (uint NeighborId = 0; NeighborId < 8; NeighborId++)
{
const int2 SampleOffset = kOffsets3x3[NeighborId];
const uint3 NeighborScreenCoord = uint3(clamp(int2(ScreenCoord) + SampleOffset, int2(MinScreenCoord), int2(MaxScreenCoord)), InClosureIndex);
const float4 Lighting = LightingTexture[NeighborScreenCoord];
const bool bSampleLightingIsValid = IsNeighborPixelValid(NeighborScreenCoord.xy, InClosureIndex, Lighting.w > 0.0f /*DefaultValue*/);
if (bSampleLightingIsValid)
{
FilteredLighting += Lighting.xyz;
TotalWeight++;
}
}
bLightingIsValid = TotalWeight > 0;
return FilteredLighting /= max(TotalWeight, 1.0f);
}
float3 ClampHistory(
Texture2DArray LightingTexture,
uint2 ScreenCoord,
uint2 MinScreenCoord,
uint2 MaxScreenCoord,
float3 NewLighting,
float3 HistoryLighting,
uint InClosureIndex)
{
float3 NeighborMin = NewLighting;
float3 NeighborMax = NewLighting;
UNROLL
for (uint NeighborId = 0; NeighborId < 8; NeighborId++)
{
const int2 SampleOffset = kOffsets3x3[NeighborId];
const uint3 NeighborScreenCoord = uint3(clamp(int2(ScreenCoord) + SampleOffset, int2(MinScreenCoord), int2(MaxScreenCoord)), InClosureIndex);
const bool bSampleLightingIsValid = IsNeighborPixelValid(NeighborScreenCoord.xy, InClosureIndex, true /*DefaultValue*/);
if (bSampleLightingIsValid)
{
const float3 Lighting = LightingTexture[NeighborScreenCoord].xyz;
NeighborMin = min(NeighborMin, Lighting.xyz);
NeighborMax = max(NeighborMax, Lighting.xyz);
}
}
HistoryLighting = clamp(HistoryLighting, NeighborMin, NeighborMax);
return HistoryLighting;
}
struct Bilinear
{
float2 Origin;
float2 Weights;
};
Bilinear GetBilinearFilter(float2 UV, float2 TextureSize)
{
Bilinear Result;
Result.Origin = floor(UV * TextureSize - .5f);
Result.Weights = frac(UV * TextureSize - .5f);
return Result;
}
float4 GetBilinearCustomWeights(Bilinear F, float4 CustomWeights)
{
float4 Weights;
Weights.x = (1.0f - F.Weights.x) * (1.0f - F.Weights.y);
Weights.y = F.Weights.x * (1.0f - F.Weights.y);
Weights.z = (1.0f - F.Weights.x) * F.Weights.y;
Weights.w = F.Weights.x * F.Weights.y;
return Weights * CustomWeights;
}
float4 WeightedAverage(float4 V00, float4 V10, float4 V01, float4 V11, float4 Weights)
{
float4 Result = V00 * Weights.x + V10 * Weights.y + V01 * Weights.z + V11 * Weights.w;
return Result / max(dot(Weights, 1), .00001f);
}
float3 WeightedAverage(float3 V00, float3 V10, float3 V01, float3 V11, float4 Weights)
{
float3 Result = V00 * Weights.x + V10 * Weights.y + V01 * Weights.z + V11 * Weights.w;
return Result / max(dot(Weights, 1), .00001f);
}
float WeightedAverage(float4 V, float4 Weights)
{
return dot(V, Weights) / max(dot(Weights, 1), .00001f);
}
struct FGatherUV
{
float2 UV00;
float2 UV10;
float2 UV11;
float2 UV01;
};
FGatherUV GetGatherUV(Bilinear In, float2 InTexelSize)
{
FGatherUV Out;
Out.UV00 = (In.Origin + .5f) * InTexelSize;
Out.UV10 = Out.UV00 + float2(InTexelSize.x, 0);
Out.UV01 = Out.UV00 + float2(0, InTexelSize.y);
Out.UV11 = Out.UV00 + InTexelSize;
return Out;
}
float3 GetHistoryNormal(float2 InUV)
{
return UnpackNormalAndShadingInfo(Texture2DSampleLevel(DiffuseIndirectNormalHistory, GlobalPointClampedSampler, InUV, 0)).Normal;
}
float2 DownsampledCoordToScreenUV(uint2 DownsampledCoord, uint DownsampleFactor)
{
uint2 ScreenCoord = DownsampledCoord * DownsampleFactor + GetDownsampledCoordJitter(DownsampledCoord, DownsampleFactor);
float2 ScreenUV = (ScreenCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
return ScreenUV;
}
#define SHARED_TILE_BORDER 1
#define SHARED_TILE_SIZE (THREADGROUP_SIZE / SHORT_RANGE_AO_DOWNSAMPLE_FACTOR + 2 * SHARED_TILE_BORDER)
#if SHORT_RANGE_AO_MODE
struct FShortRangeAONeighborhood
{
TShortRangeAO Center;
TShortRangeAO Extent;
};
groupshared TShortRangeAOPacked SharedShortRangeAO[SHARED_TILE_SIZE][SHARED_TILE_SIZE];
// Compute local neighborhood statistics
FShortRangeAONeighborhood GetShortRangeAONeighborhood(uint2 SharedCoord, TShortRangeAO CenterShortRangeAO)
{
TShortRangeAO AOSum = CenterShortRangeAO;
TShortRangeAO AOSqSum = Pow2(CenterShortRangeAO);
float WeightSum = 1.0f;
const int KernelSize = 1;
for (int NeigborOffsetY = -KernelSize; NeigborOffsetY <= KernelSize; ++NeigborOffsetY)
{
for (int NeigborOffsetX = -KernelSize; NeigborOffsetX <= KernelSize; ++NeigborOffsetX)
{
const bool bCenter = NeigborOffsetX == 0 && NeigborOffsetY == 0;
const int2 NeigborSharedCoord = int2(SharedCoord.x + NeigborOffsetX, SharedCoord.y + NeigborOffsetY);
if (!bCenter && all(NeigborSharedCoord >= 0) && all(NeigborSharedCoord < SHARED_TILE_SIZE))
{
TShortRangeAO NeigborAO = UnpackSharedShortRangeAO(SharedShortRangeAO[NeigborSharedCoord.x][NeigborSharedCoord.y]);
AOSum += NeigborAO;
AOSqSum += Pow2(NeigborAO);
WeightSum += 1.0f;
}
}
}
TShortRangeAO M1 = AOSum / WeightSum;
TShortRangeAO M2 = AOSqSum / WeightSum;
TShortRangeAO Variance = max(M2 - Pow2(M1), 0.0f);
TShortRangeAO StdDev = sqrt(Variance);
FShortRangeAONeighborhood Neighborhood;
Neighborhood.Center = M1;
Neighborhood.Extent = ShortRangeAOTemporalNeighborhoodClampScale * StdDev;
return Neighborhood;
}
#endif
Texture2D<float> DownsampledSceneDepth;
Texture2D<UNORM float3> DownsampledSceneWorldNormal;
float GetNormalWeight(float3 SceneWorldNormal, uint2 DownsampledScreenCoord)
{
float3 SampleWorldNormal = normalize(DecodeNormal(DownsampledSceneWorldNormal[DownsampledScreenCoord]));
float AngleBetweenNormals = acosFast(saturate(dot(SampleWorldNormal, SceneWorldNormal)));
float NormalWeight = 1.0f - saturate(AngleBetweenNormals);
return Pow2(NormalWeight);
}
// Pack into 8 bits. NumFramesAccumulated in [3:0] and FastUpdateModeAmount in [7:4]
float PackFastUpdateModeAmountAndNumFramesAccumulated(float FastUpdateModeAmount, float NumFramesAccumulated)
{
// Use ceil so that only 0 is mapped to 0. That is, only not moving is encoded as not moving
float EncodedFastUpdateModeAmount = ceil(FastUpdateModeAmount * 15.0f);
// Clamp max to 15 because we only have 4 bits. This means that any value >= 15 will be mapped to MaxFramesAccumulated
float LocalMaxFramesAccumulated = min(MaxFramesAccumulated, 15.0f);
float EncodedNumFramesAccumulated = round(saturate(NumFramesAccumulated / LocalMaxFramesAccumulated) * 15.0f);
return (EncodedFastUpdateModeAmount * 16.0f + EncodedNumFramesAccumulated) / 255.0f;
}
void UnpackFastUpdateModeAmountAndNumFramesAccumulated(float4 Packed, out float4 OutFastUpdateModeAmount, out float4 OutNumFramesAccumulated)
{
Packed = round(Packed * 255.0f);
float4 EncodedFastUpdateModeAmount = floor(Packed / 16.0f);
float4 EncodedNumFramesAccumulated = Packed - EncodedFastUpdateModeAmount * 16.0f;
OutFastUpdateModeAmount = EncodedFastUpdateModeAmount / 15.0f;
OutNumFramesAccumulated = select(EncodedNumFramesAccumulated == 15.0f, MaxFramesAccumulated, EncodedNumFramesAccumulated / 15.0f * min(MaxFramesAccumulated, 15.0f));
}
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void ScreenProbeTemporalReprojectionCS(
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupId : SV_GroupID,
uint2 GroupThreadId : SV_GroupThreadID)
{
// SUBSTRATE_TODO: Reenable overflow tile once history tracking is correct
#if 0
const FLumenMaterialCoord ScreenCoord = GetLumenMaterialCoord(DispatchThreadId, GroupId, GroupThreadId);
#else
FLumenMaterialCoord ScreenCoord = GetLumenMaterialCoord(DispatchThreadId.xy, DispatchThreadId.z);
ScreenCoord.SvPosition += View.ViewRectMinAndSize.xy;
ScreenCoord.SvPositionFlatten.xy += View.ViewRectMinAndSize.xy;
#endif
uint3 IntegrateCoord = uint3(ScreenCoord.SvPositionFlatten.xy / INTEGRATE_DOWNSAMPLE_FACTOR, ScreenCoord.SvPositionFlatten.z);
uint3 ShortRangeAOCoord = uint3(ScreenCoord.SvPositionFlatten.xy / SHORT_RANGE_AO_DOWNSAMPLE_FACTOR, ScreenCoord.SvPositionFlatten.z);
uint2 ShortRangeAOSharedCoord = SHARED_TILE_BORDER + GroupThreadId.xy / SHORT_RANGE_AO_DOWNSAMPLE_FACTOR;
#if SHORT_RANGE_AO_MODE
// Load ShortRangeAO into groupshared for fast neighborhood clamp
for (uint SharedCoordY = GroupThreadId.y; SharedCoordY < SHARED_TILE_SIZE; SharedCoordY += THREADGROUP_SIZE)
{
for (uint SharedCoordX = GroupThreadId.x; SharedCoordX < SHARED_TILE_SIZE; SharedCoordX += THREADGROUP_SIZE)
{
int2 LoadCoord = ShortRangeAOViewMin + GroupId.xy * (THREADGROUP_SIZE / SHORT_RANGE_AO_DOWNSAMPLE_FACTOR) + int2(SharedCoordX, SharedCoordY) - SHARED_TILE_BORDER;
LoadCoord = clamp(LoadCoord, (int2)ShortRangeAOViewMin, (int2)ShortRangeAOViewMin + (int2)ShortRangeAOViewSize - 1);
SharedShortRangeAO[SharedCoordX][SharedCoordY] = ShortRangeAOTexture[int3(LoadCoord, ScreenCoord.ClosureIndex)];
}
}
GroupMemoryBarrierWithGroupSync();
#endif
const FLumenMaterialData Material = ReadMaterialData(ScreenCoord, MaxRoughnessToTrace);
if (!IsValid(Material))
{
// #lumen_todo: Fixup VisibilityWeights = 1 on hole filling to skip writing all those channels
RWNewHistoryDiffuseIndirect[ScreenCoord.SvPositionFlatten] = 0;
RWNewHistoryRoughSpecularIndirect[ScreenCoord.SvPositionFlatten] = 0;
#if SUPPORT_BACKFACE_DIFFUSE
RWNewHistoryBackfaceDiffuseIndirect[ScreenCoord.SvPositionFlatten] = 0;
#endif
RWNewHistoryFastUpdateMode_NumFramesAccumulated[ScreenCoord.SvPositionFlatten] = 0;
#if SHORT_RANGE_AO_MODE
RWNewHistoryShortRangeAO[ScreenCoord.SvPositionFlatten] = PackSharedShortRangeAO(InitShortRangeAO(Material));
#endif
return;
}
const float2 ScreenUV = (ScreenCoord.SvPosition + 0.5f) * View.BufferSizeAndInvSize.zw;
const float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
const float DeviceZ = SceneDepthTexture[ScreenCoord.SvPosition].x;
const float SceneDepth = ConvertFromDeviceZ(DeviceZ);
const float3 HistoryScreenPosition = GetHistoryScreenPosition(ScreenPosition, ScreenUV, DeviceZ);
float2 HistoryScreenUV = HistoryScreenPosition.xy * HistoryScreenPositionScaleBias.xy + HistoryScreenPositionScaleBias.wz;
const bool bHistoryWasOnscreen = all(HistoryScreenUV < HistoryUVMinMax.zw) && all(HistoryScreenUV > HistoryUVMinMax.xy);
// Avoid reading NaNs outside the valid viewport, just setting the weight to 0 is not enough
HistoryScreenUV = clamp(HistoryScreenUV, HistoryUVMinMax.xy, HistoryUVMinMax.zw);
const Bilinear BilinearFilterAtHistoryScreenUV = GetBilinearFilter(HistoryScreenUV, HistoryBufferSizeAndInvSize.xy);
float2 HistoryGatherUV = (BilinearFilterAtHistoryScreenUV.Origin + 1.0f) * HistoryBufferSizeAndInvSize.zw;
// History depth doesn't have overflow, and has the dimension as the view unlike other history data
const float2 HistoryDepthGatherUV = (BilinearFilterAtHistoryScreenUV.Origin + 1.0f) * HistoryBufferSizeAndInvSize.zw;
const float4 HistoryDepthDeviceZ = DiffuseIndirectDepthHistory.GatherRed(GlobalPointClampedSampler, HistoryDepthGatherUV).wzxy;
const float4 HistorySceneDepth = float4(ConvertFromDeviceZ(HistoryDepthDeviceZ.x), ConvertFromDeviceZ(HistoryDepthDeviceZ.y), ConvertFromDeviceZ(HistoryDepthDeviceZ.z), ConvertFromDeviceZ(HistoryDepthDeviceZ.w));
const float3 TranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(ScreenPosition, SceneDepth), SceneDepth, 1), View.ScreenToTranslatedWorld).xyz;
const float RandomScalar = BlueNoiseScalar(ScreenCoord.SvPositionFlatten.xy, ScreenProbeGatherStateFrameIndex);
#if INTEGRATE_DOWNSAMPLE_FACTOR == 2 || SHORT_RANGE_AO_DOWNSAMPLE_FACTOR == 2
{
#define MAX_DOWNSAMPLE_FACTOR max(INTEGRATE_DOWNSAMPLE_FACTOR, SHORT_RANGE_AO_DOWNSAMPLE_FACTOR)
// Prepare weights for upsampling
float4 UpsampleWeights = 0.0f;
{
int2 DownsampledCoord00 = floor(ScreenUV * View.BufferSizeAndInvSize.xy / MAX_DOWNSAMPLE_FACTOR - 0.5f);
float2 DownsampledGatherUV = (DownsampledCoord00 + 1.0f) * DownsampledBufferInvSize;
float4 CornerDepths = DownsampledSceneDepth.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV).wzxy;
int2 ScreenCoordOffset = ScreenCoord.SvPositionFlatten.xy - DownsampledCoord00 * 2;
int2 SampleOffset00 = GetDownsampledCoordJitter(DownsampledCoord00 + uint2(0, 0), MAX_DOWNSAMPLE_FACTOR) + uint2(0, 0) * 2 - ScreenCoordOffset;
int2 SampleOffset10 = GetDownsampledCoordJitter(DownsampledCoord00 + uint2(1, 0), MAX_DOWNSAMPLE_FACTOR) + uint2(1, 0) * 2 - ScreenCoordOffset;
int2 SampleOffset01 = GetDownsampledCoordJitter(DownsampledCoord00 + uint2(0, 1), MAX_DOWNSAMPLE_FACTOR) + uint2(0, 1) * 2 - ScreenCoordOffset;
int2 SampleOffset11 = GetDownsampledCoordJitter(DownsampledCoord00 + uint2(1, 1), MAX_DOWNSAMPLE_FACTOR) + uint2(1, 1) * 2 - ScreenCoordOffset;
// Triangle filter weights between pixel and 4 samples
UpsampleWeights.x = (2.0f - abs(SampleOffset00.x)) * (2.0f - abs(SampleOffset00.y));
UpsampleWeights.y = (2.0f - abs(SampleOffset10.x)) * (2.0f - abs(SampleOffset10.y));
UpsampleWeights.z = (2.0f - abs(SampleOffset01.x)) * (2.0f - abs(SampleOffset01.y));
UpsampleWeights.w = (2.0f - abs(SampleOffset11.x)) * (2.0f - abs(SampleOffset11.y));
float4 DepthWeights = 0.0f;
{
float4 ScenePlane = float4(Material.WorldNormal, dot(TranslatedWorldPosition, Material.WorldNormal));
float3 Position00 = GetTranslatedWorldPositionFromScreenUV(DownsampledCoordToScreenUV(DownsampledCoord00 + uint2(0, 0), MAX_DOWNSAMPLE_FACTOR), CornerDepths.x);
float3 Position10 = GetTranslatedWorldPositionFromScreenUV(DownsampledCoordToScreenUV(DownsampledCoord00 + uint2(1, 0), MAX_DOWNSAMPLE_FACTOR), CornerDepths.y);
float3 Position01 = GetTranslatedWorldPositionFromScreenUV(DownsampledCoordToScreenUV(DownsampledCoord00 + uint2(0, 1), MAX_DOWNSAMPLE_FACTOR), CornerDepths.z);
float3 Position11 = GetTranslatedWorldPositionFromScreenUV(DownsampledCoordToScreenUV(DownsampledCoord00 + uint2(1, 1), MAX_DOWNSAMPLE_FACTOR), CornerDepths.w);
float4 PlaneDistances;
PlaneDistances.x = abs(dot(float4(Position00, -1), ScenePlane));
PlaneDistances.y = abs(dot(float4(Position10, -1), ScenePlane));
PlaneDistances.z = abs(dot(float4(Position01, -1), ScenePlane));
PlaneDistances.w = abs(dot(float4(Position11, -1), ScenePlane));
float4 RelativeDepthDifference = PlaneDistances / Material.SceneDepth;
DepthWeights = select(CornerDepths > 0.0f, exp2(-10000.0f * (RelativeDepthDifference * RelativeDepthDifference)), 0.0f);
}
UpsampleWeights *= DepthWeights;
float4 NormalWeights = 1.0f;
{
NormalWeights.x = GetNormalWeight(Material.WorldNormal, DownsampledCoord00 + uint2(0, 0));
NormalWeights.y = GetNormalWeight(Material.WorldNormal, DownsampledCoord00 + uint2(1, 0));
NormalWeights.z = GetNormalWeight(Material.WorldNormal, DownsampledCoord00 + uint2(0, 1));
NormalWeights.w = GetNormalWeight(Material.WorldNormal, DownsampledCoord00 + uint2(1, 1));
}
UpsampleWeights *= NormalWeights;
}
const uint2 StochasticBilinearOffset = GetStochasticBilinearOffset(RandomScalar, UpsampleWeights);
#if INTEGRATE_DOWNSAMPLE_FACTOR != 1
{
IntegrateCoord.xy = (int2(ScreenCoord.SvPositionFlatten.xy) - 1) / INTEGRATE_DOWNSAMPLE_FACTOR + StochasticBilinearOffset;
IntegrateCoord.xy = min(IntegrateCoord.xy, IntegrateViewMin + IntegrateViewSize - 1);
}
#endif
#if SHORT_RANGE_AO_DOWNSAMPLE_FACTOR != 1
{
ShortRangeAOCoord.xy = (int2(ScreenCoord.SvPositionFlatten.xy) - 1) / SHORT_RANGE_AO_DOWNSAMPLE_FACTOR + StochasticBilinearOffset;
ShortRangeAOCoord.xy = min(ShortRangeAOCoord.xy, ShortRangeAOViewMin + ShortRangeAOViewSize - 1);
ShortRangeAOSharedCoord = (SHARED_TILE_BORDER * SHORT_RANGE_AO_DOWNSAMPLE_FACTOR + int2(GroupThreadId.xy) - 1) / SHORT_RANGE_AO_DOWNSAMPLE_FACTOR + StochasticBilinearOffset;
}
#endif
}
#endif
float DisocclusionDistanceThreshold = Material.bHasBackfaceDiffuse ? HistoryDistanceThresholdForFoliage : HistoryDistanceThreshold;
DisocclusionDistanceThreshold *= lerp(.5f, 1.5f, RandomScalar);
const float PrevSceneDepth = ConvertFromDeviceZ(HistoryScreenPosition.z);
FGatherUV HistoryGather = GetGatherUV(BilinearFilterAtHistoryScreenUV, HistoryBufferSizeAndInvSize.zw);
#if SUBTRATE_GBUFFER_FORMAT==1
// When Substrate is enabled: use top layer normal instead of the BSDF normal, since it is used for occlusion rejection, and needs to be stable during reprojection.
const float3 WorldNormal = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(ScreenCoord.SvPosition, 0))).WorldNormal;
#else
const float3 WorldNormal = Material.WorldNormal;
#endif
#define PLANE_DISOCCLUSION_WEIGHTS 0
#if PLANE_DISOCCLUSION_WEIGHTS
float3 PrevTranslatedPrevWorldPosition = mul(float4(GetScreenPositionForProjectionType(HistoryScreenPosition.xy,PrevSceneDepth), PrevSceneDepth, 1), View.PrevScreenToTranslatedWorld).xyz;
float4 PrevTranslatedPrevScenePlane = float4(WorldNormal, dot(PrevTranslatedPrevWorldPosition, WorldNormal));
float4 PlaneDistance;
{
float2 HistoryScreenPosition00 = HistoryGatherUV00 * HistoryUVToScreenPositionScaleBias.xy + HistoryUVToScreenPositionScaleBias.zw;
float3 PrevTranslatedHistoryWorldPosition00 = mul(float4(GetScreenPositionForProjectionType(HistoryScreenPosition00, HistorySceneDepth.x), HistorySceneDepth.x, 1), View.PrevScreenToTranslatedWorld).xyz;
PlaneDistance.x = abs(dot(float4(PrevTranslatedHistoryWorldPosition00, -1), PrevTranslatedPrevScenePlane));
float2 HistoryScreenPosition10 = HistoryGatherUV10.x * HistoryUVToScreenPositionScaleBias.xy + HistoryUVToScreenPositionScaleBias.zw;
float3 PrevTranslatedHistoryWorldPosition10 = mul(float4(GetScreenPositionForProjectionType(HistoryScreenPosition10, HistorySceneDepth.y), HistorySceneDepth.y, 1), View.PrevScreenToTranslatedWorld).xyz;
PlaneDistance.y = abs(dot(float4(PrevTranslatedHistoryWorldPosition10, -1), PrevTranslatedPrevScenePlane));
float2 HistoryScreenPosition01 = HistoryGatherUV01 * HistoryUVToScreenPositionScaleBias.xy + HistoryUVToScreenPositionScaleBias.zw;
float3 PrevTranslatedHistoryWorldPosition01 = mul(float4(GetScreenPositionForProjectionType(HistoryScreenPosition01, HistorySceneDepth.z), HistorySceneDepth.z, 1), View.PrevScreenToTranslatedWorld).xyz;
PlaneDistance.z = abs(dot(float4(PrevTranslatedHistoryWorldPosition01, -1), PrevTranslatedPrevScenePlane));
float2 HistoryScreenPosition11 = HistoryGatherUV11 * HistoryUVToScreenPositionScaleBias.xy + HistoryUVToScreenPositionScaleBias.zw;
float3 PrevTranslatedHistoryWorldPosition11 = mul(float4(GetScreenPositionForProjectionType(HistoryScreenPosition11.xy, HistorySceneDepth.w), HistorySceneDepth.w, 1), View.PrevScreenToTranslatedWorld).xyz;
PlaneDistance.w = abs(dot(float4(PrevTranslatedHistoryWorldPosition11, -1), PrevTranslatedPrevScenePlane));
}
float4 OcclusionWeights = PlaneDistance >= PrevSceneDepth * DisocclusionDistanceThreshold ? 1 : 0;
#else
#define EXPAND_HISTORY_DISTANCE_THRESHOLD_FOR_JITTER 1
#if EXPAND_HISTORY_DISTANCE_THRESHOLD_FOR_JITTER
const float3 V = normalize(-TranslatedWorldPosition);
// Raise the threshold at grazing angles to compensate for TAA jitter causing a depth mismatch dependent on the angle
// This also introduces some ghosting around characters, needs a better solution
DisocclusionDistanceThreshold /= clamp(saturate(dot(V, WorldNormal)), .1f, 1.0f);
#endif
const float4 DistanceToHistoryValue = abs(HistorySceneDepth - PrevSceneDepth);
float4 OcclusionWeights = select(DistanceToHistoryValue >= PrevSceneDepth * DisocclusionDistanceThreshold, 1.0, 0.0);
#endif
const float4 OriginalOcclusionWeights = OcclusionWeights;
#if HISTORY_REJECT_BASED_ON_NORMAL
const float3 HistoryNormal00 = GetHistoryNormal(HistoryGather.UV00);
const float3 HistoryNormal10 = GetHistoryNormal(HistoryGather.UV10);
const float3 HistoryNormal01 = GetHistoryNormal(HistoryGather.UV01);
const float3 HistoryNormal11 = GetHistoryNormal(HistoryGather.UV11);
const float4 HistoryNormalWeights = select(float4(
dot(HistoryNormal00, WorldNormal),
dot(HistoryNormal10, WorldNormal),
dot(HistoryNormal01, WorldNormal),
dot(HistoryNormal11, WorldNormal)) < HistoryNormalCosThreshold, 1.0f, 0.0f);
OcclusionWeights = saturate(HistoryNormalWeights + OcclusionWeights);
#endif
// Reject based on the foliage material flag (bHasBackfaceDiffuse)
{
const float4 PackedW = DiffuseIndirectNormalHistory.GatherAlpha(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
const bool4 bHasBackfaceDiffuse = bool4(
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.x)).bHasBackfaceDiffuse,
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.y)).bHasBackfaceDiffuse,
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.z)).bHasBackfaceDiffuse,
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.w)).bHasBackfaceDiffuse);
OcclusionWeights = saturate(OcclusionWeights + select(Material.bHasBackfaceDiffuse.xxxx == bHasBackfaceDiffuse, 0.0f, 1.0f));
}
//@todo - calculate for each texel in the footprint to avoid tossing history around screen edges
float4 VisibilityWeights = saturate((bHistoryWasOnscreen ? 1.0f : 0.0f) - OcclusionWeights);
#if SHORT_RANGE_AO_MODE
TShortRangeAO NewShortRangeAO = UnpackSharedShortRangeAO(SharedShortRangeAO[ShortRangeAOSharedCoord.x][ShortRangeAOSharedCoord.y]);
#endif
float4 NewDiffuseLighting = float4(DiffuseIndirect[IntegrateCoord].xyz, LightIsMoving[IntegrateCoord].x);
float3 NewRoughSpecularLighting = RoughSpecularIndirect[IntegrateCoord].xyz;
bool bLightingIsValid = NewDiffuseLighting.w > 0.0f;
float LightingIsMoving = abs(NewDiffuseLighting.w);
float4 FinalWeights = GetBilinearCustomWeights(BilinearFilterAtHistoryScreenUV, VisibilityWeights);
float NewNumFramesAccumulated = 0.0f;
#if SHORT_RANGE_AO_MODE
TShortRangeAO OutShortRangeAO = NewShortRangeAO;
#endif
float3 OutDiffuseIndirect = NewDiffuseLighting.xyz;
float3 OutRoughSpecularIndirect = NewRoughSpecularLighting;
float3 OutBackfaceDiffuseIndirect = 0.0f;
float OutFastUpdateModeAmount = 0.0f;
#if VALID_HISTORY
{
TShortRangeAO HistoryShortRangeAO;
float3 HistoryDiffuseIndirect;
float3 HistoryRoughSpecularIndirect;
// Diffuse
{
const float4 R4 = DiffuseIndirectHistory.GatherRed(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float4 G4 = DiffuseIndirectHistory.GatherGreen(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float4 B4 = DiffuseIndirectHistory.GatherBlue(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float3 HistoryDiffuseIndirect00 = float3(R4.x, G4.x, B4.x);
const float3 HistoryDiffuseIndirect10 = float3(R4.y, G4.y, B4.y);
const float3 HistoryDiffuseIndirect01 = float3(R4.z, G4.z, B4.z);
const float3 HistoryDiffuseIndirect11 = float3(R4.w, G4.w, B4.w);
// TODO: clear unused tiles at closures > 0 to INVALID_LIGHTING
if (ScreenCoord.ClosureIndex > 0)
{
float4 ValidWeights = 0;
ValidWeights.x = all(HistoryDiffuseIndirect00 == INVALID_LIGHTING) ? 0.f : 1.f;
ValidWeights.y = all(HistoryDiffuseIndirect10 == INVALID_LIGHTING) ? 0.f : 1.f;
ValidWeights.z = all(HistoryDiffuseIndirect01 == INVALID_LIGHTING) ? 0.f : 1.f;
ValidWeights.w = all(HistoryDiffuseIndirect11 == INVALID_LIGHTING) ? 0.f : 1.f;
FinalWeights *= ValidWeights;
}
HistoryDiffuseIndirect = WeightedAverage(HistoryDiffuseIndirect00, HistoryDiffuseIndirect10, HistoryDiffuseIndirect01, HistoryDiffuseIndirect11, FinalWeights) * PrevSceneColorPreExposureCorrection;
}
// Rough specular
{
const float4 R4 = RoughSpecularIndirectHistory.GatherRed(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float4 G4 = RoughSpecularIndirectHistory.GatherGreen(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float4 B4 = RoughSpecularIndirectHistory.GatherBlue(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float3 HistoryRoughSpecularIndirect00 = float3(R4.x, G4.x, B4.x);
const float3 HistoryRoughSpecularIndirect10 = float3(R4.y, G4.y, B4.y);
const float3 HistoryRoughSpecularIndirect01 = float3(R4.z, G4.z, B4.z);
const float3 HistoryRoughSpecularIndirect11 = float3(R4.w, G4.w, B4.w);
HistoryRoughSpecularIndirect = WeightedAverage(HistoryRoughSpecularIndirect00, HistoryRoughSpecularIndirect10, HistoryRoughSpecularIndirect01, HistoryRoughSpecularIndirect11, FinalWeights) * PrevSceneColorPreExposureCorrection;
}
#if SHORT_RANGE_AO_MODE == 2
uint4 HistoryShortRangeAOGather4 = ShortRangeAOHistory.GatherRed(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const TShortRangeAO HistoryShortRangeAO00 = UnpackSharedShortRangeAO(HistoryShortRangeAOGather4.x);
const TShortRangeAO HistoryShortRangeAO10 = UnpackSharedShortRangeAO(HistoryShortRangeAOGather4.y);
const TShortRangeAO HistoryShortRangeAO01 = UnpackSharedShortRangeAO(HistoryShortRangeAOGather4.z);
const TShortRangeAO HistoryShortRangeAO11 = UnpackSharedShortRangeAO(HistoryShortRangeAOGather4.w);
HistoryShortRangeAO = WeightedAverage(HistoryShortRangeAO00, HistoryShortRangeAO10, HistoryShortRangeAO01, HistoryShortRangeAO11, FinalWeights);
#else
float4 HistoryShortRangeAOGather4 = ShortRangeAOHistory.GatherRed(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
HistoryShortRangeAO = WeightedAverage(HistoryShortRangeAOGather4, FinalWeights);
#endif
float4 Packed4 = HistoryFastUpdateMode_NumFramesAccumulated.GatherRed(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
float4 HistoryNumFramesAccumulated4;
float4 HistoryFastUpdateModeAmount4;
UnpackFastUpdateModeAmountAndNumFramesAccumulated(Packed4, HistoryFastUpdateModeAmount4, HistoryNumFramesAccumulated4);
const float NumFramesAccumulated = min(WeightedAverage(HistoryNumFramesAccumulated4 + 1.0f, FinalWeights), MaxFramesAccumulated);
float FastUpdateModeAmount = saturate(LightingIsMoving * InvFractionOfLightingMovingForFastUpdateMode);
FastUpdateModeAmount = saturate(min((FastUpdateModeAmount - .2f) / .8f, MaxFastUpdateModeAmount));
{
OutFastUpdateModeAmount = FastUpdateModeAmount;
float FastUpdateModeHistoryValue = WeightedAverage(HistoryFastUpdateModeAmount4, FinalWeights);
FastUpdateModeHistoryValue = min(FastUpdateModeHistoryValue, MaxFastUpdateModeAmount);
// Stabilizes the calculated value, and speeds up lighting change propagation around where the moving object was last frame
FastUpdateModeAmount = max(FastUpdateModeAmount, FastUpdateModeHistoryValue);
}
NewNumFramesAccumulated = min(NumFramesAccumulated, (1.0f - FastUpdateModeAmount) * MaxFramesAccumulated);
NewNumFramesAccumulated = bHistoryWasOnscreen ? NewNumFramesAccumulated : 0;
#if FAST_UPDATE_MODE_NEIGHBORHOOD_CLAMP
if (FastUpdateModeAmount > 0.0f)
{
const uint2 MinScreenCoord = IntegrateViewMin;
const uint2 MaxScreenCoord = IntegrateViewMin + IntegrateViewSize - 1;
HistoryDiffuseIndirect = ClampHistory(DiffuseIndirect, IntegrateCoord.xy, MinScreenCoord, MaxScreenCoord, NewDiffuseLighting.xyz, HistoryDiffuseIndirect, ScreenCoord.ClosureIndex);
HistoryRoughSpecularIndirect = ClampHistory(RoughSpecularIndirect, IntegrateCoord.xy, MinScreenCoord, MaxScreenCoord, NewRoughSpecularLighting, HistoryRoughSpecularIndirect, ScreenCoord.ClosureIndex);
}
#endif
#if SHORT_RANGE_AO_MODE
if (ShortRangeAOTemporalNeighborhoodClampScale > 0.0f)
{
// Rectify history
FShortRangeAONeighborhood ShortRangeAONeighborhood = GetShortRangeAONeighborhood(ShortRangeAOSharedCoord, NewShortRangeAO);
TShortRangeAO ClampedHistoryShortRangeAO = clamp(HistoryShortRangeAO, ShortRangeAONeighborhood.Center - ShortRangeAONeighborhood.Extent, ShortRangeAONeighborhood.Center + ShortRangeAONeighborhood.Extent);
HistoryShortRangeAO = ClampedHistoryShortRangeAO;
}
#endif
float Alpha = 1.0f / (1.0f + NewNumFramesAccumulated);
// If current sample is invalid we need to instead to heavily rely on history
if (!bLightingIsValid && NewNumFramesAccumulated >= 1)
{
Alpha = 1.0f / (1.0f + 4.0f * NewNumFramesAccumulated);
}
OutDiffuseIndirect = lerp(HistoryDiffuseIndirect, NewDiffuseLighting.xyz, Alpha);
OutRoughSpecularIndirect = lerp(HistoryRoughSpecularIndirect, NewRoughSpecularLighting, Alpha);
#if SHORT_RANGE_AO_MODE
OutShortRangeAO = lerp(HistoryShortRangeAO, NewShortRangeAO, Alpha);
#endif
#if SUPPORT_BACKFACE_DIFFUSE
if (Material.bHasBackfaceDiffuse)
{
const float4 R4 = BackfaceDiffuseIndirectHistory.GatherRed(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float4 G4 = BackfaceDiffuseIndirectHistory.GatherGreen(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float4 B4 = BackfaceDiffuseIndirectHistory.GatherBlue(GlobalPointClampedSampler, float3(HistoryGatherUV, ScreenCoord.ClosureIndex)).wzxy;
const float3 HistoryDiffuseIndirect00 = float3(R4.x, G4.x, B4.x);
const float3 HistoryDiffuseIndirect10 = float3(R4.y, G4.y, B4.y);
const float3 HistoryDiffuseIndirect01 = float3(R4.z, G4.z, B4.z);
const float3 HistoryDiffuseIndirect11 = float3(R4.w, G4.w, B4.w);
const float3 HistoryBackfaceDiffuseIndirect = WeightedAverage(HistoryDiffuseIndirect00, HistoryDiffuseIndirect10, HistoryDiffuseIndirect01, HistoryDiffuseIndirect11, FinalWeights) * PrevSceneColorPreExposureCorrection;
const float3 NewBackfaceDiffuseLighting = BackfaceDiffuseIndirect[IntegrateCoord].xyz;
OutBackfaceDiffuseIndirect = lerp(HistoryBackfaceDiffuseIndirect, NewBackfaceDiffuseLighting, Alpha);
OutBackfaceDiffuseIndirect = MakeFinite(OutBackfaceDiffuseIndirect);
}
#endif
}
#endif
// Debug visualizations
#if VALID_HISTORY
//OutRoughSpecularIndirect.xyz = dot(VisibilityWeights, 1) > 0.0f ? float3(0, 1, 0) : float3(1, 0, 0);
#endif
//OutRoughSpecularIndirect.xyz = OutDiffuseIndirect.xyz = FastUpdateModeAmount;
//if (!bLightingIsValid) { OutRoughSpecularIndirect.xyz = OutDiffuseIndirect.xyz = float3(1, 0, 0); }
//OutRoughSpecularIndirect.xyz = OutDiffuseIndirect.xyz = bHistoryWasOnscreen ? saturate(HistoryNormalWeights.xyz - OriginalOcclusionWeights.xyz) : 0.0f;
OutDiffuseIndirect.rgb = -min(-OutDiffuseIndirect.rgb, 0.0f);
OutRoughSpecularIndirect.rgb = -min(-OutRoughSpecularIndirect.rgb, 0.0f);
RWNewHistoryDiffuseIndirect[ScreenCoord.SvPositionFlatten] = QuantizeForFloatRenderTarget(OutDiffuseIndirect, RandomScalar);
RWNewHistoryRoughSpecularIndirect[ScreenCoord.SvPositionFlatten] = QuantizeForFloatRenderTarget(OutRoughSpecularIndirect, RandomScalar);
RWNewHistoryFastUpdateMode_NumFramesAccumulated[ScreenCoord.SvPositionFlatten] = PackFastUpdateModeAmountAndNumFramesAccumulated(OutFastUpdateModeAmount, NewNumFramesAccumulated);
#if SHORT_RANGE_AO_MODE
RWNewHistoryShortRangeAO[ScreenCoord.SvPositionFlatten] = PackSharedShortRangeAO(OutShortRangeAO);
#endif
#if SUPPORT_BACKFACE_DIFFUSE
RWNewHistoryBackfaceDiffuseIndirect[ScreenCoord.SvPositionFlatten] = QuantizeForFloatRenderTarget(OutBackfaceDiffuseIndirect, RandomScalar);
#endif
}