// 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 RWNewHistoryDiffuseIndirect; RWTexture2DArray RWNewHistoryBackfaceDiffuseIndirect; RWTexture2DArray RWNewHistoryRoughSpecularIndirect; RWTexture2DArray RWNewHistoryFastUpdateMode_NumFramesAccumulated; RWTexture2DArray RWNewHistoryShortRangeAO; Texture2DArray DiffuseIndirect; Texture2DArray LightIsMoving; Texture2DArray BackfaceDiffuseIndirect; Texture2DArray RoughSpecularIndirect; Texture2DArray ShortRangeAOTexture; Texture2DArray DiffuseIndirectHistory; Texture2DArray BackfaceDiffuseIndirectHistory; Texture2DArray RoughSpecularIndirectHistory; Texture2DArray 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 DownsampledSceneDepth; Texture2D 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 }