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

440 lines
20 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
// When loading SSS checkerboard pixel, do not adjust DiffuseColor/SpecularColor to preserve specular and diffuse lighting values for each pixel
#define ALLOW_SSS_MATERIAL_OVERRIDE 0
// When loading Substrate material, needs to support all types of pixels (Simple/Single/Complex/Special)
// as the temporal pass does not have tile types
#define SUBSTRATE_FASTPATH 0
#define SUBSTRATE_SINGLEPATH 0
#define SUBSTRATE_COMPLEXSPECIALPATH 1
#include "../Common.ush"
#include "MegaLightsShading.ush"
#include "../Lumen/LumenReflectionDenoiserCommon.ush"
#include "../StochasticLighting/StochasticLightingCommon.ush"
// Improves performance on certain platforms
#if !DEBUG_MODE && VALID_HISTORY
MAX_OCCUPANCY
#endif
Texture2D ResolvedDiffuseLighting;
Texture2D ResolvedSpecularLighting;
Texture2D<UNORM float> ShadingConfidenceTexture;
Texture2D MegaLightsDepthHistory;
Texture2D<float4> MegaLightsNormalAndShading;
Texture2D DiffuseLightingAndSecondMomentHistoryTexture;
Texture2D SpecularLightingAndSecondMomentHistoryTexture;
Texture2D<UNORM float> NumFramesAccumulatedHistoryTexture;
float4 HistoryScreenPositionScaleBias;
float4 HistoryUVMinMax;
float4 HistoryGatherUVMinMax;
float4 HistoryBufferSizeAndInvSize;
float PrevSceneColorPreExposureCorrection;
float MinFramesAccumulatedForHistoryMiss;
float MinFramesAccumulatedForHighConfidence;
float TemporalMaxFramesAccumulated;
float TemporalNeighborhoodClampScale;
RWTexture2D<float4> RWDiffuseLightingAndSecondMoment;
RWTexture2D<float4> RWSpecularLightingAndSecondMoment;
RWTexture2D<UNORM float> RWNumFramesAccumulated;
struct FNeighborhood
{
float3 Center;
float3 Extent;
};
#define SHARED_TILE_BORDER 2
#define SHARED_TILE_SIZE (8 + 2 * SHARED_TILE_BORDER)
groupshared uint3 SharedResolvedLightingYCoCg[SHARED_TILE_SIZE][SHARED_TILE_SIZE];
uint3 PackDiffuseAndSpecular(float3 Diffuse, float3 Specular)
{
uint3 V;
V.x = (f32tof16(Diffuse.x) << 16) | f32tof16(Diffuse.y);
V.y = (f32tof16(Diffuse.z) << 16) | f32tof16(Specular.x);
V.z = (f32tof16(Specular.y) << 16) | f32tof16(Specular.z);
return V;
}
half3 UnpackDiffuse(uint3 V)
{
half3 Diffuse;
Diffuse.x = f16tof32(V.x >> 16);
Diffuse.y = f16tof32(V.x & 0xFFFF);
Diffuse.z = f16tof32(V.y >> 16);
return Diffuse;
}
float3 UnpackSpecular(uint3 V)
{
half3 Specular;
Specular.x = f16tof32(V.y & 0xFFFF);
Specular.y = f16tof32(V.z >> 16);
Specular.z = f16tof32(V.z & 0xFFFF);
return Specular;
}
// Compute local neighborhood statistics
void GetNeighborhood(uint2 LocalCoord, float3 DiffuseCenterSample, float3 SpecularCenterSample, inout FNeighborhood DiffuseNeighborhood, inout FNeighborhood SpecularNeighborhood, inout FShaderPrintContext DebugContext)
{
DiffuseCenterSample = LumenRGBToYCoCg(DiffuseCenterSample);
SpecularCenterSample = LumenRGBToYCoCg(SpecularCenterSample);
half DiffuseWeightSum = 1.0f;
half3 DiffuseSampleSum = DiffuseCenterSample;
half3 DiffuseSampleSqSum = Pow2(DiffuseCenterSample);
half SpecularWeightSum = 1.0f;
half3 SpecularSampleSum = SpecularCenterSample;
half3 SpecularSampleSqSum = Pow2(SpecularCenterSample);
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)
{
uint3 PackedLighting = SharedResolvedLightingYCoCg[SHARED_TILE_BORDER + LocalCoord.x + NeigborOffsetX][SHARED_TILE_BORDER + LocalCoord.y + NeigborOffsetY];
half3 DiffuseNeigbor = UnpackDiffuse(PackedLighting);
half3 SpecularNeigbor = UnpackSpecular(PackedLighting);
if (IsLightingValid(DiffuseNeigbor))
{
DiffuseSampleSum += DiffuseNeigbor;
DiffuseSampleSqSum += Pow2(DiffuseNeigbor);
DiffuseWeightSum += 1.0f;
}
if (IsLightingValid(SpecularNeigbor))
{
SpecularSampleSum += SpecularNeigbor;
SpecularSampleSqSum += Pow2(SpecularNeigbor);
SpecularWeightSum += 1.0f;
}
}
}
}
float3 M1 = DiffuseSampleSum / DiffuseWeightSum;
float3 M2 = DiffuseSampleSqSum / DiffuseWeightSum;
float3 Variance = max(M2 - Pow2(M1), 0.0f);
float3 StdDev = sqrt(Variance);
DiffuseNeighborhood = (FNeighborhood)0;
DiffuseNeighborhood.Center = M1;
DiffuseNeighborhood.Extent = TemporalNeighborhoodClampScale * StdDev;
M1 = SpecularSampleSum / SpecularWeightSum;
M2 = SpecularSampleSqSum / SpecularWeightSum;
Variance = max(M2 - Pow2(M1), 0.0f);
StdDev = sqrt(Variance);
SpecularNeighborhood = (FNeighborhood)0;
SpecularNeighborhood.Center = M1;
SpecularNeighborhood.Extent = TemporalNeighborhoodClampScale * StdDev;
}
float3 ClampLuminance(float3 Lighting, float LuminanceClamp)
{
if (Luminance(Lighting) > LuminanceClamp)
{
Lighting *= LuminanceClamp / Luminance(Lighting);
}
return Lighting;
}
float ShadingConfidenceWeight(float ShadingConfidence)
{
// #ml_todo: expose as CVars
float Edge0 = 0.75f;
float Edge1 = 0.25f;
return saturate((ShadingConfidence - Edge0) / (Edge1 - Edge0));
}
/**
* Temporal accumulation of shaded light samples
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void DenoiserTemporalCS(
uint3 GroupId : SV_GroupID,
uint3 GroupThreadId : SV_GroupThreadID,
uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint2 ScreenCoord = DispatchThreadId.xy + View.ViewRectMinAndSize.xy;
FShaderPrintContext DebugContext = InitDebugContext(ScreenCoord, /*bDownsampled*/ false, float2(0.55, 0.55));
// Load resolved diffuse and specular neighborhood into shared memory
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 ResolvedCoord = GroupId.xy * THREADGROUP_SIZE + View.ViewRectMinAndSize.xy + int2(SharedCoordX, SharedCoordY) - SHARED_TILE_BORDER;
float3 ResolvedDiffuseYCoCg = INVALID_LIGHTING;
float3 ResolvedSpecularYCoCg = 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(ResolvedCoord.xy >= View.ViewRectMinAndSize.xy) && all(ResolvedCoord.xy < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
float3 DiffuseSample = ResolvedDiffuseLighting[ResolvedCoord].xyz;
float3 SpecularSample = ResolvedSpecularLighting[ResolvedCoord].xyz;
if (IsLightingValid(DiffuseSample))
{
ResolvedDiffuseYCoCg = LumenRGBToYCoCg(DiffuseSample);
}
if (IsLightingValid(SpecularSample))
{
ResolvedSpecularYCoCg = LumenRGBToYCoCg(SpecularSample);
}
}
SharedResolvedLightingYCoCg[SharedCoordX][SharedCoordY] = PackDiffuseAndSpecular(ResolvedDiffuseYCoCg, ResolvedSpecularYCoCg);
}
}
GroupMemoryBarrierWithGroupSync();
if (all(ScreenCoord < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
float2 ScreenUV = (ScreenCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
FMegaLightsMaterial Material = LoadMaterial(ScreenUV, ScreenCoord);
const float DeviceZ = SceneDepthTexture[ScreenCoord].x;
Material.SetDepth(ConvertFromDeviceZ(DeviceZ));
uint3 PackedLighting = SharedResolvedLightingYCoCg[GroupThreadId.x + SHARED_TILE_BORDER][GroupThreadId.y + SHARED_TILE_BORDER];
float3 DiffuseLighting = UnpackDiffuse(PackedLighting);
float3 SpecularLighting = UnpackSpecular(PackedLighting);
float DiffuseSecondMoment = 0.0f;
float SpecularSecondMoment = 0.0f;
float NumFramesAccumulated = 0.0f;
float ShadingConfidence = ShadingConfidenceTexture[ScreenCoord];
if (DebugContext.bIsActive)
{
Print(DebugContext, TEXT("TemporalAccumulation"), FontTitle);
Newline(DebugContext);
Print(DebugContext, TEXT("ScreenCoord : "));
Print(DebugContext, ScreenCoord.x, FontValue);
Print(DebugContext, ScreenCoord.y, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("CenterDiffuse : "));
Print(DebugContext, DiffuseLighting, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("CenterSpecular : "));
Print(DebugContext, SpecularLighting, FontValue);
}
if (IsLightingValid(DiffuseLighting))
{
if (IsLightingValid(DiffuseLighting))
{
DiffuseLighting = LumenYCoCgToRGB(DiffuseLighting);
DiffuseSecondMoment = Pow2(Luminance(DiffuseLighting));
}
if (IsLightingValid(SpecularLighting))
{
SpecularLighting = LumenYCoCgToRGB(SpecularLighting);
SpecularSecondMoment = Pow2(Luminance(SpecularLighting));
}
#if VALID_HISTORY
{
float2 ScreenPosition = (ScreenUV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
float3 HistoryScreenPosition = GetHistoryScreenPosition(ScreenPosition, ScreenUV, DeviceZ);
float2 HistoryScreenUV = HistoryScreenPosition.xy * HistoryScreenPositionScaleBias.xy + HistoryScreenPositionScaleBias.wz;
bool bHistoryWasOnScreen = all(HistoryScreenUV >= HistoryUVMinMax.xy) && all(HistoryScreenUV <= HistoryUVMinMax.zw);
if (bHistoryWasOnScreen)
{
HistoryScreenUV = clamp(HistoryScreenUV, HistoryGatherUVMinMax.xy, HistoryGatherUVMinMax.zw);
uint2 HistoryScreenCoord = floor(HistoryScreenUV * HistoryBufferSizeAndInvSize.xy - 0.5f);
float2 HistoryBilinearWeights = frac(HistoryScreenUV * HistoryBufferSizeAndInvSize.xy - 0.5f);
float2 HistoryGatherUV = (HistoryScreenCoord + 1.0f) * HistoryBufferSizeAndInvSize.zw;
float4 HistorySampleSceneDepth4 = MegaLightsDepthHistory.GatherRed(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
HistorySampleSceneDepth4.x = ConvertFromDeviceZ(HistorySampleSceneDepth4.x);
HistorySampleSceneDepth4.y = ConvertFromDeviceZ(HistorySampleSceneDepth4.y);
HistorySampleSceneDepth4.z = ConvertFromDeviceZ(HistorySampleSceneDepth4.z);
HistorySampleSceneDepth4.w = ConvertFromDeviceZ(HistorySampleSceneDepth4.w);
// #ml_todo: plane weighting
float ReprojectedSceneDepth = ConvertFromDeviceZ(HistoryScreenPosition.z);
float DisocclusionDistanceThreshold = 0.03f;
const float4 DistanceToHistoryValue = abs(HistorySampleSceneDepth4 - ReprojectedSceneDepth);
float4 DepthWeights = select(DistanceToHistoryValue >= ReprojectedSceneDepth * DisocclusionDistanceThreshold, 0.0f, 1.0f);
float4 HistoryWeights = float4(
(1 - HistoryBilinearWeights.y) * (1 - HistoryBilinearWeights.x),
(1 - HistoryBilinearWeights.y) * HistoryBilinearWeights.x,
HistoryBilinearWeights.y * (1 - HistoryBilinearWeights.x),
HistoryBilinearWeights.y * HistoryBilinearWeights.x);
HistoryWeights *= DepthWeights;
// If shading info history is available, used it to only fetch compatible pixels.
// For now only hair pixel are filtered out as their shading model is too different from the others
{
const float4 PackedW = MegaLightsNormalAndShading.GatherAlpha(GlobalPointClampedSampler, HistoryGatherUV).wzxy;
const bool4 bIsHair4 = bool4(
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.x)).bIsHair,
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.y)).bIsHair,
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.z)).bIsHair,
UnpackNormalAndShadingInfo(float4(0,0,0,PackedW.w)).bIsHair);
const float4 ShadingInfoWeights = select(Material.bIsHair.xxxx == bIsHair4, 1.f, 0.f);
HistoryWeights *= ShadingInfoWeights;
}
if (DebugContext.bIsActive)
{
Newline(DebugContext);
Print(DebugContext, TEXT("DepthWeights : "));
Print(DebugContext, DepthWeights, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("HistoryWeights : "));
Print(DebugContext, HistoryWeights, FontValue);
}
// Use history only if we found at least one valid sample
if (any(HistoryWeights > 0.01f))
{
float4 DiffuseLightingAndSecondMomentHistory = 0.0f;
DiffuseLightingAndSecondMomentHistory.x = dot(DiffuseLightingAndSecondMomentHistoryTexture.GatherRed(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
DiffuseLightingAndSecondMomentHistory.y = dot(DiffuseLightingAndSecondMomentHistoryTexture.GatherGreen(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
DiffuseLightingAndSecondMomentHistory.z = dot(DiffuseLightingAndSecondMomentHistoryTexture.GatherBlue(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
DiffuseLightingAndSecondMomentHistory.w = dot(DiffuseLightingAndSecondMomentHistoryTexture.GatherAlpha(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
DiffuseLightingAndSecondMomentHistory = DiffuseLightingAndSecondMomentHistory / dot(HistoryWeights, 1.0f);
float4 SpecularLightingAndSecondMomentHistory = 0.0f;
SpecularLightingAndSecondMomentHistory.x = dot(SpecularLightingAndSecondMomentHistoryTexture.GatherRed(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
SpecularLightingAndSecondMomentHistory.y = dot(SpecularLightingAndSecondMomentHistoryTexture.GatherGreen(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
SpecularLightingAndSecondMomentHistory.z = dot(SpecularLightingAndSecondMomentHistoryTexture.GatherBlue(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
SpecularLightingAndSecondMomentHistory.w = dot(SpecularLightingAndSecondMomentHistoryTexture.GatherAlpha(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights);
SpecularLightingAndSecondMomentHistory = SpecularLightingAndSecondMomentHistory / dot(HistoryWeights, 1.0f);
// Correct history for current frame exposure
float3 HistoryDiffuseLighting = DiffuseLightingAndSecondMomentHistory.xyz * PrevSceneColorPreExposureCorrection;
float HistoryDiffuseSecondMoment = DiffuseLightingAndSecondMomentHistory.w * Pow2(PrevSceneColorPreExposureCorrection);
float3 HistorySpecularLighting = SpecularLightingAndSecondMomentHistory.xyz * PrevSceneColorPreExposureCorrection;
float HistorySpecularSecondMoment = SpecularLightingAndSecondMomentHistory.w * Pow2(PrevSceneColorPreExposureCorrection);
FNeighborhood DiffuseNeighborhood;
FNeighborhood SpecularNeighborhood;
GetNeighborhood(GroupThreadId.xy, DiffuseLighting, SpecularLighting, DiffuseNeighborhood, SpecularNeighborhood, DebugContext);
// Clamp diffuse history
float DiffuseConfidence = 1.0f;
float3 ClampedHistoryDiffuseLighting = HistoryDiffuseLighting;
{
HistoryDiffuseLighting = LumenRGBToYCoCg(HistoryDiffuseLighting);
ClampedHistoryDiffuseLighting = clamp(HistoryDiffuseLighting, DiffuseNeighborhood.Center - DiffuseNeighborhood.Extent, DiffuseNeighborhood.Center + DiffuseNeighborhood.Extent);
// Speedup accumulation if history is far away from the current neighborhood
float NormalizedDistanceToNeighborhood = length(abs(ClampedHistoryDiffuseLighting - HistoryDiffuseLighting) / max(DiffuseNeighborhood.Extent, 0.1f));
DiffuseConfidence = saturate(1.0f - NormalizedDistanceToNeighborhood);
ClampedHistoryDiffuseLighting = LumenYCoCgToRGB(ClampedHistoryDiffuseLighting);
}
// Clamp specular history
float SpecularConfidence = 1.0f;
float3 ClampedHistorySpecularLighting = HistorySpecularLighting;
{
HistorySpecularLighting = LumenRGBToYCoCg(HistorySpecularLighting);
ClampedHistorySpecularLighting = clamp(HistorySpecularLighting, SpecularNeighborhood.Center - SpecularNeighborhood.Extent, SpecularNeighborhood.Center + SpecularNeighborhood.Extent);
// Speedup accumulation if history is far away from the current neighborhood
float NormalizedDistanceToNeighborhood = length(abs(ClampedHistorySpecularLighting - HistorySpecularLighting) / max(SpecularNeighborhood.Extent, 0.1f));
SpecularConfidence = saturate(1.0f - NormalizedDistanceToNeighborhood);
ClampedHistorySpecularLighting = LumenYCoCgToRGB(ClampedHistorySpecularLighting);
}
// #ml_todo: clamp also the second moment
float ClampedHistoryDiffuseSecondMoment = max(HistoryDiffuseSecondMoment, 0.0f);
float ClampedHistorySpecularSecondMoment = max(HistorySpecularSecondMoment, 0.0f);
// Reproject and rescale normalized NumFramesAccumulated
float NumFramesAccumulatedHistory = 0.0f;
NumFramesAccumulatedHistory = UnpackNumFramesAccumulated(dot(NumFramesAccumulatedHistoryTexture.GatherRed(GlobalPointClampedSampler, HistoryGatherUV).wzxy, HistoryWeights) / dot(HistoryWeights, 1.0f));
float MaxConfidenceFrames = lerp(MinFramesAccumulatedForHighConfidence, TemporalMaxFramesAccumulated, ShadingConfidenceWeight(ShadingConfidence));
float MaxDiffuseFrames = min(lerp(MinFramesAccumulatedForHistoryMiss, TemporalMaxFramesAccumulated, DiffuseConfidence), MaxConfidenceFrames);
float MaxSpecularFrames = min(lerp(MinFramesAccumulatedForHistoryMiss, TemporalMaxFramesAccumulated, SpecularConfidence), MaxConfidenceFrames);
// Advance the frame counter
float DiffuseNumFramesAccumulated = min(NumFramesAccumulatedHistory + 1.0f, MaxDiffuseFrames);
float SpecularNumFramesAccumulated = min(NumFramesAccumulatedHistory + 1.0f, MaxSpecularFrames);
NumFramesAccumulated = lerp(DiffuseNumFramesAccumulated, SpecularNumFramesAccumulated, 0.5f);
// Blend history with new samples
float DiffuseAlpha = 1.0f / DiffuseNumFramesAccumulated;
DiffuseLighting = lerp(ClampedHistoryDiffuseLighting, DiffuseLighting, DiffuseAlpha);
DiffuseSecondMoment = lerp(ClampedHistoryDiffuseSecondMoment, DiffuseSecondMoment, DiffuseAlpha);
float SpecularAlpha = 1.0f / SpecularNumFramesAccumulated;
SpecularLighting = lerp(ClampedHistorySpecularLighting, SpecularLighting, SpecularAlpha);
SpecularSecondMoment = lerp(ClampedHistorySpecularSecondMoment, SpecularSecondMoment, SpecularAlpha);
DiffuseLighting = MakeFinite(DiffuseLighting);
SpecularLighting = MakeFinite(SpecularLighting);
if (DebugContext.bIsActive)
{
float DiffuseVariance = max(DiffuseSecondMoment - Pow2(Luminance(DiffuseLighting)), 0.0f);
float SpecularVariance = max(SpecularSecondMoment - Pow2(Luminance(SpecularLighting)), 0.0f);
Newline(DebugContext);
Print(DebugContext, TEXT("NumFramesAccumulated: "));
Print(DebugContext, NumFramesAccumulated, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("DiffuseVariance : "));
Print(DebugContext, DiffuseVariance, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("SpecularVariance : "));
Print(DebugContext, SpecularVariance, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("Diffuse : "));
Print(DebugContext, DiffuseLighting, FontValue);
Print(DebugContext, Luminance(DiffuseLighting), FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("Specular : "));
Print(DebugContext, SpecularLighting, FontValue);
Print(DebugContext, Luminance(SpecularLighting), FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("Confidence : "));
Print(DebugContext, DiffuseConfidence, FontValue);
Print(DebugContext, SpecularConfidence, FontValue);
}
}
}
}
#endif
}
RWDiffuseLightingAndSecondMoment[ScreenCoord] = float4(DiffuseLighting, DiffuseSecondMoment);
RWSpecularLightingAndSecondMoment[ScreenCoord] = float4(SpecularLighting, SpecularSecondMoment);
RWNumFramesAccumulated[ScreenCoord] = PackNumFramesAccumulated(NumFramesAccumulated);
}
}