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

543 lines
22 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "../SceneTextureParameters.ush"
#include "LumenReflectionCommon.ush"
#include "LumenReflectionDenoiserCommon.ush"
#if PERMUTATION_DEBUG
#include "../ShaderPrint.ush"
#endif
Texture2D VelocityTexture;
Texture2D SceneDepthHistory;
Texture2D SceneNormalHistory;
Texture2DArray ResolvedSpecularLighting;
Texture2DArray ResolvedReflectionsDepth;
Texture2DArray SpecularHistoryTexture;
Texture2DArray<UNORM float> NumFramesAccumulatedHistoryTexture;
float4 HistoryScreenPositionScaleBias;
float4 HistoryUVMinMax;
float4 HistoryGatherUVMinMax;
float4 HistoryBufferSizeAndInvSize;
float PrevSceneColorPreExposureCorrection;
float TemporalMaxFramesAccumulated;
float TemporalNeighborhoodClampScale;
float InvSubstrateMaxClosureCount;
float HistoryDistanceThreshold;
RWTexture2DArray<float4> RWSpecularAndSecondMoment;
RWTexture2DArray<UNORM float> RWNumFramesAccumulated;
struct FDebug
{
#if PERMUTATION_DEBUG
bool bActive;
FShaderPrintContext Context;
#endif
};
struct FNeighborhood
{
float3 Center;
float3 Extent;
};
#define SHARED_TILE_BORDER 2
#define SHARED_TILE_SIZE (8 + 2 * SHARED_TILE_BORDER)
groupshared float3 SharedResolvedSpecularYCoCg[SHARED_TILE_SIZE][SHARED_TILE_SIZE];
// Compute local neighborhood statistics
FNeighborhood GetNeighborhood(uint2 LocalCoord, float3 CenterSample, float CenterSampleSceneDepth, inout FDebug Debug)
{
CenterSample = LumenRGBToYCoCg(CenterSample);
float WeightSum = 1.0f;
float3 SampleSum = CenterSample;
float3 SampleSqSum = Pow2(CenterSample);
const int KernelSize = 2;
for (int NeigborOffsetY = -KernelSize; NeigborOffsetY <= KernelSize; ++NeigborOffsetY)
{
for (int NeigborOffsetX = -KernelSize; NeigborOffsetX <= KernelSize; ++NeigborOffsetX)
{
const bool bCenter = NeigborOffsetX == 0 && NeigborOffsetY == 0;
const bool bCorner = abs(NeigborOffsetX) == KernelSize && abs(NeigborOffsetY) == KernelSize;
// Skip center as it's already accounted for
// Also optimize it a bit by skipping corners
if (!bCenter && !bCorner)
{
float3 NeigborSample = SharedResolvedSpecularYCoCg[SHARED_TILE_BORDER + LocalCoord.x + NeigborOffsetX][SHARED_TILE_BORDER + LocalCoord.y + NeigborOffsetY];
if (IsLightingValid(NeigborSample))
{
SampleSum += NeigborSample;
SampleSqSum += Pow2(NeigborSample);
WeightSum += 1.0f;
}
}
}
}
float3 M1 = SampleSum / WeightSum;
float3 M2 = SampleSqSum / WeightSum;
float3 Variance = max(M2 - Pow2(M1), 0.0f);
float3 StdDev = sqrt(Variance);
FNeighborhood Neighborhood = (FNeighborhood)0;
Neighborhood.Center = M1;
Neighborhood.Extent = TemporalNeighborhoodClampScale * StdDev;
return Neighborhood;
}
struct FLightingHistory
{
bool bValid;
float2 ScreenUV;
float3 GatherUV;
float4 GatherWeights;
float3 Specular;
float SpecularSecondMoment;
};
float DepthPlaneWeight(float3 HistoryTranslatedWorldPosition, float3 SceneWorldNormal, float3 TranslatedWorldPosition, float DisocclusionDistanceThreshold)
{
float PlaneDistance = abs(dot(TranslatedWorldPosition - HistoryTranslatedWorldPosition, SceneWorldNormal));
return PlaneDistance > DisocclusionDistanceThreshold ? 0.0f : 1.0f;
}
FLightingHistory GetLightingHistory(float2 ScreenPosition, uint ClosureIndex, float DeviceZ, float ReflectionHitDeviceZ, float4 EncodedVelocity, bool bFromReflectHit, float3 TranslatedWorldPosition, float3 SceneWorldNormal, float SceneRoughness, float Noise, inout FDebug Debug)
{
FLightingHistory History;
float3 HistoryScreenPosition;
if (bFromReflectHit)
{
HistoryScreenPosition = GetHistoryScreenPosition(ScreenPosition, DeviceZ, ReflectionHitDeviceZ, EncodedVelocity);
}
else
{
HistoryScreenPosition = GetHistoryScreenPosition(ScreenPosition, DeviceZ, DeviceZ, EncodedVelocity);
}
float2 HistoryScreenUV = HistoryScreenPosition.xy * HistoryScreenPositionScaleBias.xy + HistoryScreenPositionScaleBias.wz;
History.bValid = all(HistoryScreenUV >= HistoryUVMinMax.xy) && all(HistoryScreenUV <= HistoryUVMinMax.zw);
History.Specular = 0.0f;
History.SpecularSecondMoment = 0.0f;
History.ScreenUV = HistoryScreenUV;
History.GatherUV = 0.0f;
History.GatherWeights = 0.0f;
if (History.bValid)
{
HistoryScreenUV = clamp(HistoryScreenUV, HistoryGatherUVMinMax.xy, HistoryGatherUVMinMax.zw);
uint2 HistoryScreenCoord = floor(HistoryScreenUV * HistoryBufferSizeAndInvSize.xy - 0.5f);
float2 HistoryBilinearWeights = frac(HistoryScreenUV * HistoryBufferSizeAndInvSize.xy - 0.5f);
float3 HistoryGatherUV;
HistoryGatherUV.xy = (HistoryScreenCoord + 1.0f) * HistoryBufferSizeAndInvSize.zw;
HistoryGatherUV.z = (ClosureIndex + 0.5f) * InvSubstrateMaxClosureCount;
float4 HistoryWeights = float4(
(1 - HistoryBilinearWeights.y) * (1 - HistoryBilinearWeights.x),
(1 - HistoryBilinearWeights.y) * HistoryBilinearWeights.x,
HistoryBilinearWeights.y * (1 - HistoryBilinearWeights.x),
HistoryBilinearWeights.y * HistoryBilinearWeights.x);
float4 HistorySampleSceneDepth4 = SceneDepthHistory.GatherRed(GlobalPointClampedSampler, HistoryGatherUV.xy).wzxy;
HistorySampleSceneDepth4.x = ConvertFromDeviceZ(HistorySampleSceneDepth4.x);
HistorySampleSceneDepth4.y = ConvertFromDeviceZ(HistorySampleSceneDepth4.y);
HistorySampleSceneDepth4.z = ConvertFromDeviceZ(HistorySampleSceneDepth4.z);
HistorySampleSceneDepth4.w = ConvertFromDeviceZ(HistorySampleSceneDepth4.w);
float ReprojectedSceneDepth = ConvertFromDeviceZ(HistoryScreenPosition.z);
float DisocclusionDistanceThreshold = HistoryDistanceThreshold * lerp(0.5f, 1.5f, Noise);
if (bFromReflectHit)
{
// Useful in theory, but couldn't find a spot where it would reduce leaking
#define REFLECT_HIT_DEPTH_TEST 0
#if REFLECT_HIT_DEPTH_TEST
float3 HistoryTranslatedWorldPosition00 = GetPrevTranslatedWorldPosition(HistoryScreenPosition.xy, HistorySampleSceneDepth4.x);
float3 HistoryTranslatedWorldPosition10 = GetPrevTranslatedWorldPosition(HistoryScreenPosition.xy, HistorySampleSceneDepth4.y);
float3 HistoryTranslatedWorldPosition01 = GetPrevTranslatedWorldPosition(HistoryScreenPosition.xy, HistorySampleSceneDepth4.z);
float3 HistoryTranslatedWorldPosition11 = GetPrevTranslatedWorldPosition(HistoryScreenPosition.xy, HistorySampleSceneDepth4.w);
float4 DepthWeights;
DepthWeights.x = DepthPlaneWeight(HistoryTranslatedWorldPosition00, SceneWorldNormal, TranslatedWorldPosition, DisocclusionDistanceThreshold);
DepthWeights.y = DepthPlaneWeight(HistoryTranslatedWorldPosition10, SceneWorldNormal, TranslatedWorldPosition, DisocclusionDistanceThreshold);
DepthWeights.z = DepthPlaneWeight(HistoryTranslatedWorldPosition01, SceneWorldNormal, TranslatedWorldPosition, DisocclusionDistanceThreshold);
DepthWeights.w = DepthPlaneWeight(HistoryTranslatedWorldPosition11, SceneWorldNormal, TranslatedWorldPosition, DisocclusionDistanceThreshold);
HistoryWeights *= DepthWeights;
#if PERMUTATION_DEBUG
if (Debug.bActive)
{
Newline(Debug.Context);
Print(Debug.Context, TEXT("DisocclusionDistanceThreshold: "));
Print(Debug.Context, DisocclusionDistanceThreshold);
Newline(Debug.Context);
Print(Debug.Context, TEXT("DepthWeights: "));
Print(Debug.Context, DepthWeights);
}
#endif
#endif
}
else if (!bFromReflectHit)
{
// #lumen_todo: Use plane weighting instead
#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, SceneWorldNormal)), 0.1f, 1.0f);
}
#endif
const float4 DistanceToHistoryValue = abs(HistorySampleSceneDepth4 - ReprojectedSceneDepth);
float4 DepthWeights = select(DistanceToHistoryValue >= ReprojectedSceneDepth * DisocclusionDistanceThreshold, 0.0f, 1.0f);
HistoryWeights *= DepthWeights;
}
History.bValid = any(HistoryWeights > 0.01f);
if (History.bValid)
{
float4 R4 = SpecularHistoryTexture.GatherRed(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
float4 G4 = SpecularHistoryTexture.GatherGreen(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
float4 B4 = SpecularHistoryTexture.GatherBlue(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
float4 A4 = SpecularHistoryTexture.GatherAlpha(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
float4 SpecularHistory00 = RGBAToDenoiserSpace(float4(R4.x, G4.x, B4.x, A4.x));
float4 SpecularHistory10 = RGBAToDenoiserSpace(float4(R4.y, G4.y, B4.y, A4.y));
float4 SpecularHistory01 = RGBAToDenoiserSpace(float4(R4.z, G4.z, B4.z, A4.z));
float4 SpecularHistory11 = RGBAToDenoiserSpace(float4(R4.w, G4.w, B4.w, A4.w));
HistoryWeights.x = IsLightingValid(SpecularHistory00.xyz) ? HistoryWeights.x : 0.0f;
HistoryWeights.y = IsLightingValid(SpecularHistory10.xyz) ? HistoryWeights.y : 0.0f;
HistoryWeights.z = IsLightingValid(SpecularHistory01.xyz) ? HistoryWeights.z : 0.0f;
HistoryWeights.w = IsLightingValid(SpecularHistory11.xyz) ? HistoryWeights.w : 0.0f;
History.bValid = any(HistoryWeights > 0.01f);
if (History.bValid)
{
float4 SpecularAndSecondMomentHistory = 0.0f;
SpecularAndSecondMomentHistory += SpecularHistory00 * HistoryWeights.x;
SpecularAndSecondMomentHistory += SpecularHistory10 * HistoryWeights.y;
SpecularAndSecondMomentHistory += SpecularHistory01 * HistoryWeights.z;
SpecularAndSecondMomentHistory += SpecularHistory11 * HistoryWeights.w;
SpecularAndSecondMomentHistory = SpecularAndSecondMomentHistory / dot(HistoryWeights, 1.0f);
// Correct specular and its second moment for the current frame exposure
SpecularAndSecondMomentHistory.xyz *= PrevSceneColorPreExposureCorrection;
SpecularAndSecondMomentHistory.w *= Pow2(PrevSceneColorPreExposureCorrection);
History.GatherUV = HistoryGatherUV;
History.GatherWeights = HistoryWeights;
History.Specular = SpecularAndSecondMomentHistory.xyz;
History.SpecularSecondMoment = SpecularAndSecondMomentHistory.w;
}
}
}
return History;
}
uint ClosureIndex;
[numthreads(REFLECTION_THREADGROUP_SIZE_2D, REFLECTION_THREADGROUP_SIZE_2D, 1)]
void LumenReflectionDenoiserTemporalCS(
uint GroupId : SV_GroupID,
uint2 GroupThreadId : SV_GroupThreadID)
{
FReflectionTileData TileData;
const uint2 ReflectionScreenCoord = GetReflectionResolveScreenCoord(GroupId, GroupThreadId, TileData);
const FLumenMaterialCoord Coord = GetLumenMaterialCoord_Reflection(ReflectionScreenCoord, TileData, ClosureIndex);
FDebug Debug;
#if PERMUTATION_DEBUG
int2 DebugScreenCoord = View.CursorPosition.x >= 0 ? View.CursorPosition * View.ViewResolutionFraction : -1;
Debug.bActive = all(Coord.SvPosition == DebugScreenCoord);
Debug.Context = InitShaderPrintContext(true, float2(0.6, 0.05));
#endif
// Load resolved specular neighborhood into shared memory
for (uint SharedCoordY = GroupThreadId.y; SharedCoordY < SHARED_TILE_SIZE; SharedCoordY += REFLECTION_THREADGROUP_SIZE_2D)
{
for (uint SharedCoordX = GroupThreadId.x; SharedCoordX < SHARED_TILE_SIZE; SharedCoordX += REFLECTION_THREADGROUP_SIZE_2D)
{
int3 ResolvedSpecularCoord;
ResolvedSpecularCoord.xy = TileData.Coord * REFLECTION_THREADGROUP_SIZE_2D + View.ViewRectMinAndSize.xy + int2(SharedCoordX, SharedCoordY) - SHARED_TILE_BORDER;
ResolvedSpecularCoord.z = Coord.SvPositionFlatten.z;
float3 SpecularLightingYCoCg = INVALID_LIGHTING;
// Skip corners as they will be also skiped in GetNeighborhood()
const bool bCorner = (SharedCoordX == 0 || SharedCoordX + 1 == SHARED_TILE_SIZE) && (SharedCoordY == 0 || SharedCoordY + 1 == SHARED_TILE_SIZE);
if (!bCorner && all(ResolvedSpecularCoord.xy >= View.ViewRectMinAndSize.xy) && all(ResolvedSpecularCoord.xy < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
float3 Sample = RGBToDenoiserSpace(ResolvedSpecularLighting[ResolvedSpecularCoord].xyz);
if (IsLightingValid(Sample))
{
SpecularLightingYCoCg = LumenRGBToYCoCg(Sample);
}
}
SharedResolvedSpecularYCoCg[SharedCoordX][SharedCoordY] = SpecularLightingYCoCg;
}
}
GroupMemoryBarrierWithGroupSync();
if (all(Coord.SvPosition < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
float2 ScreenUV = (Coord.SvPosition + 0.5f) * View.BufferSizeAndInvSize.zw;
const FLumenMaterialData Material = ReadMaterialData(Coord.SvPosition, false/*bAllowStochaticMaterial*/);
const float DeviceZ = SceneDepthTexture[Coord.SvPosition].x;
const float SceneDepth = ConvertFromDeviceZ(DeviceZ);
float3 SpecularLighting = SharedResolvedSpecularYCoCg[GroupThreadId.x + SHARED_TILE_BORDER][GroupThreadId.y + SHARED_TILE_BORDER];
float SpecularSecondMoment = 0.0f;
if (IsLightingValid(SpecularLighting))
{
SpecularLighting = LumenYCoCgToRGB(SpecularLighting);
SpecularSecondMoment = Pow2(Luminance(SpecularLighting));
}
float NumFramesAccumulated = 0.0f;
float MaxFramesAccumulated = TemporalMaxFramesAccumulated;
#if PERMUTATION_VALID_HISTORY
if (IsLightingValid(SpecularLighting))
{
float2 ScreenPosition = (ScreenUV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
float4 EncodedVelocity = VelocityTexture[Coord.SvPosition];
float ReflectionHitDistance = ResolvedReflectionsDepth[Coord.SvPositionFlatten].x;
#if RAY_TRACED_TRANSLUCENCY_LIGHTING
// Primary ray HitT is already included
float ReflectionHitDeviceZ = ConvertToDeviceZ(ReflectionHitDistance);
#else
float ReflectionHitDeviceZ = ConvertToDeviceZ(SceneDepth + ReflectionHitDistance);
#endif
float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth);
const float Noise = InterleavedGradientNoise(ReflectionScreenCoord.xy, ReflectionsStateFrameIndexMod8);
#if PERMUTATION_DEBUG
if (Debug.bActive)
{
Print(Debug.Context, TEXT("TemporalAccumulation"));
Newline(Debug.Context);
PrintLineN(Debug.Context, ClosureIndex);
Print(Debug.Context, TEXT("ScreenCoord: "));
Print(Debug.Context, Coord.SvPosition);
Newline(Debug.Context);
Print(Debug.Context, TEXT("TranslatedWorldPosition: "));
Print(Debug.Context, TranslatedWorldPosition);
Newline(Debug.Context);
Print(Debug.Context, TEXT("ReflectionHitDistance: "));
Print(Debug.Context, ReflectionHitDistance);
Newline(Debug.Context);
Print(Debug.Context, TEXT("Material.Roughness: "));
Print(Debug.Context, Material.Roughness);
}
#endif
bool bPreferredHistoryFailed = false;
FLightingHistory LightingHistory;
LightingHistory.bValid = false;
// Select preferred history based on the GGX lobe dominant direction
float SpecularDominantDirFactor = GetSpecularDominantDirFactor(Material.TopLayerRoughness);
// Ray traced translucency currently always tries to reproject reflection hits. Material.TopLayerRoughness
// is from the opaque gbuffer. It seems wrong to drive reprojection for translucency using opaque roughness
#if !RAY_TRACED_TRANSLUCENCY_LIGHTING
if (SpecularDominantDirFactor >= 0.5f)
#endif
{
// Lighting history based on the reprojected ray hit
LightingHistory = GetLightingHistory(ScreenPosition, Coord.ClosureIndex, DeviceZ, ReflectionHitDeviceZ, EncodedVelocity, /*bFromReflectHit*/ true, TranslatedWorldPosition, Material.WorldNormal, Material.TopLayerRoughness, Noise, Debug);
if (!LightingHistory.bValid)
{
bPreferredHistoryFailed = true;
}
}
if (!LightingHistory.bValid)
{
// Lighting history based on the reprojected surface point
LightingHistory = GetLightingHistory(ScreenPosition, Coord.ClosureIndex, DeviceZ, DeviceZ, EncodedVelocity, /*bFromReflectHit*/ false, TranslatedWorldPosition, Material.WorldNormal, Material.TopLayerRoughness, Noise, Debug);
}
#if PERMUTATION_DEBUG
if (Debug.bActive)
{
Newline(Debug.Context);
Print(Debug.Context, TEXT("SpecularDominantDirFactor: "));
Print(Debug.Context, SpecularDominantDirFactor);
}
#endif
// Speedup accumulation for full screen mirror reflections, where we can rely mostly on TSR to cleanup noise
if (all(ReflectionDownsampleFactorXY == 1))
{
MaxFramesAccumulated = lerp(2, MaxFramesAccumulated, saturate(Material.TopLayerRoughness / 0.05f));
}
if (LightingHistory.bValid)
{
// Reproject and rescale normalized NumFramesAccumulated
float NumFramesAccumulatedHistory = 0.0f;
NumFramesAccumulatedHistory = UnpackNumFramesAccumulated(
dot(NumFramesAccumulatedHistoryTexture.GatherRed(GlobalPointClampedSampler, LightingHistory.GatherUV), LightingHistory.GatherWeights));
NumFramesAccumulated = NumFramesAccumulatedHistory / dot(LightingHistory.GatherWeights, 1.0f);
float Confidence = 1.0f;
// Speedup accumulation based on the mismatch between current and reprojected lobe
// #lumen_todo: figure out how to detect lobe mismatches without adding noise
#define LOBE_CONFIDENCE 0
#if LOBE_CONFIDENCE
{
FNormalAndRoughnessHistory NormalAndRoughnessHistory = UnpackNormalAndRoughnessHistory(
Texture2DSampleLevel(SceneNormalAndRoughnessHistory, GlobalBilinearClampedSampler, LightingHistory.ScreenUV, 0));
float AngleBetweenLobes = acosFast(min(dot(Material.WorldNormal, NormalAndRoughnessHistory.Normal) + 1.0f / 255.0f, 1.0f));
float LobeHalfAngleA = GetSpecularLobeHalfAngle(Material.TopLayerRoughness) + 0.001f;
float LobeHalfAngleB = GetSpecularLobeHalfAngle(NormalAndRoughnessHistory.Roughness) + 0.001f;
float A1 = 0.0f - LobeHalfAngleA;
float A2 = 0.0f + LobeHalfAngleA;
float B1 = AngleBetweenLobes - LobeHalfAngleB;
float B2 = AngleBetweenLobes + LobeHalfAngleB;
float LobeOverlap = max(min(A2, B2) - max(A1, B1), 0.0f);
float LobeTotal = 2.0f * (LobeHalfAngleA + LobeHalfAngleB) - LobeOverlap;
Confidence *= saturate((LobeOverlap + 0.0001f) / (LobeTotal + 0.0001f));
#if PERMUTATION_DEBUG
if (Debug.bActive)
{
Newline(Debug.Context);
Print(Debug.Context, TEXT("LobeHalfAngleDelta: "));
Print(Debug.Context, abs(LobeHalfAngleA - LobeHalfAngleB));
Newline(Debug.Context);
Print(Debug.Context, TEXT("AngleBetweenLobes: "));
Print(Debug.Context, AngleBetweenLobes);
Newline(Debug.Context);
Print(Debug.Context, TEXT("LobeOverlap: "));
Print(Debug.Context, LobeOverlap);
Newline(Debug.Context);
Print(Debug.Context, TEXT("LobeTotal: "));
Print(Debug.Context, LobeTotal);
Newline(Debug.Context);
Print(Debug.Context, TEXT("LobeConfidence: "));
Print(Debug.Context, Confidence);
}
#endif
}
#endif
bool bReconstructedSample = false;
if (ReflectionDownsampleFactorXY.x > 1)
{
uint2 TracingPixelSize = uint2(2, ReflectionDownsampleFactorXY.y > 1 ? 2 : 1);
if (any(Coord.SvPosition % TracingPixelSize != GetScreenTileJitter(Coord.SvPosition / TracingPixelSize)))
{
bReconstructedSample = true;
}
}
// Clamp history to the current neighborhood in YCoCg space
{
float3 HistoryLighting = LumenRGBToYCoCg(LightingHistory.Specular);
FNeighborhood SpecularNeighborhood = GetNeighborhood(GroupThreadId, SpecularLighting, SceneDepth, Debug);
// Neighborhood without a center sample isn't very precise, so increase it a bit
if (bReconstructedSample)
{
SpecularNeighborhood.Extent *= 2.0f;
}
float3 ClampedHistoryLighting = clamp(HistoryLighting, SpecularNeighborhood.Center - SpecularNeighborhood.Extent, SpecularNeighborhood.Center + SpecularNeighborhood.Extent);
// Speedup accumulation if history is far away from the current neighborhood
float NormalizedDistanceToNeighborhood = length(abs(ClampedHistoryLighting - HistoryLighting) / max(SpecularNeighborhood.Extent, 0.1f));
Confidence *= saturate(1.0f - NormalizedDistanceToNeighborhood);
// #lumen_todo: figure out how to clamp second moment without breaking temporal variance accumulation
LightingHistory.Specular = LumenYCoCgToRGB(ClampedHistoryLighting);
}
// Speedup accumulation if preferred history isn't available
if (bPreferredHistoryFailed)
{
Confidence = 0.0f;
}
// Blend a bit of history even when we have low confidence in order to smoothen transitions
Confidence = 0.75f * Confidence + 0.25f;
// Advance the frame counter
NumFramesAccumulated = min(NumFramesAccumulated * Confidence + 1.0f, MaxFramesAccumulated);
// Blend history with new samples
// Samples which weren't traced this frame and come from spatial interpolation need to rely more on history
// This way over N frames things converge into a properly upscaled result
float Alpha = 1.0f / (NumFramesAccumulated + (bReconstructedSample ? 4.0f : 0.0f));
SpecularLighting = lerp(LightingHistory.Specular, SpecularLighting, Alpha);
SpecularSecondMoment = lerp(LightingHistory.SpecularSecondMoment, SpecularSecondMoment, Alpha);
#if PERMUTATION_DEBUG
if (Debug.bActive)
{
Newline(Debug.Context);
Print(Debug.Context, TEXT("Confidence: "));
Print(Debug.Context, Confidence);
}
#endif
}
}
#endif
SpecularLighting = max(MakeFinite(SpecularLighting), 0.0f);
SpecularSecondMoment = max(MakeFinite(SpecularSecondMoment), 0.0f);
#if PERMUTATION_DEBUG
if (Debug.bActive)
{
Newline(Debug.Context);
Print(Debug.Context, TEXT("NumFramesAccumulated: "));
Print(Debug.Context, NumFramesAccumulated);
Newline(Debug.Context);
Print(Debug.Context, TEXT("Specular Second Moment: "));
Print(Debug.Context, SpecularSecondMoment);
Newline(Debug.Context);
Print(Debug.Context, TEXT("Specular Lighting: "));
Print(Debug.Context, SpecularLighting);
Newline(Debug.Context);
Print(Debug.Context, TEXT("Luminance: "));
Print(Debug.Context, Luminance(SpecularLighting));
}
#endif
RWSpecularAndSecondMoment[Coord.SvPositionFlatten] = float4(DenoiserSpaceToRGB(SpecularLighting), SpecularSecondMoment);
RWNumFramesAccumulated[Coord.SvPositionFlatten] = PackNumFramesAccumulated(NumFramesAccumulated);
}
}