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

278 lines
14 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
// 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"
// Spatial denoise is the last pass writing to the SceneColor buffer. We allow SSS checkerboard pixel to adjust
// DiffuseColor/SpecularColor to write specular & diffuse lighting in checkerboard pattern
#define ALLOW_SSS_MATERIAL_OVERRIDE 1
Texture2D<float4> DiffuseLightingAndSecondMomentTexture;
Texture2D<float4> SpecularLightingAndSecondMomentTexture;
Texture2D<UNORM float> NumFramesAccumulatedTexture;
Texture2D<UNORM float> ShadingConfidenceTexture;
RWTexture2D<float4> RWSceneColor;
float SpatialFilterDepthWeightScale;
float SpatialFilterKernelRadius;
uint SpatialFilterNumSamples;
float SpatialFilterMaxDisocclusionFrames;
float TemporalMaxFramesAccumulated;
float3 TonemapLighting(float3 Lighting, float DisocclusionFactor)
{
// Run heavy tonemapping for DisocclusionFactor in order to suppress fireflies in new areas
return Lighting / (1.0f + DisocclusionFactor * Luminance(Lighting));
}
float3 InverseTonemapLighting(float3 TonemappedLighting, float DisocclusionFactor)
{
return TonemappedLighting / (1.0f - DisocclusionFactor * Luminance(TonemappedLighting));
}
/**
* Run a spatial filter in order to filter out noise based on the temporal variance.
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void DenoiserSpatialCS(
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.8));
#if INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS
if (all(ScreenCoord < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
float2 ScreenUV = (ScreenCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
float4 CenterDiffuseLightingAndSecondMoment = DiffuseLightingAndSecondMomentTexture[ScreenCoord];
if (IsLightingValid(CenterDiffuseLightingAndSecondMoment.xyz))
{
// For hair, we don't apply proper spatial filtering at the moment.
FMegaLightsMaterial Material = LoadMaterial(ScreenUV, ScreenCoord);
if (Material.bIsValid)
{
float ShadingConfidence = ShadingConfidenceTexture[ScreenCoord];
float CenterDiffuseStdDev = sqrt(max(CenterDiffuseLightingAndSecondMoment.w - Pow2(Luminance(CenterDiffuseLightingAndSecondMoment.xyz)), 0.0f));
// Boost spatial filter until we accumulate enough frames to be able to rely on temporal variance
float NumFramesAccumulated = UnpackNumFramesAccumulated(NumFramesAccumulatedTexture[ScreenCoord]);
float DisocclusionFactor = 0.0f;
if (TemporalMaxFramesAccumulated > 1 && ShadingConfidence <= 0.25f && SpatialFilterMaxDisocclusionFrames > 0.0f)
{
DisocclusionFactor = 1.0f - saturate((NumFramesAccumulated - 1.0f) / (SpatialFilterMaxDisocclusionFrames - 1.0f));
CenterDiffuseStdDev = lerp(CenterDiffuseStdDev, 1.0f, DisocclusionFactor);
}
float3 DiffuseLightingSum = TonemapLighting(CenterDiffuseLightingAndSecondMoment.xyz, DisocclusionFactor);
float DiffuseLightingWeightSum = 1.0f;
uint2 RandomSeed = Rand3DPCG16(int3(ScreenCoord, MegaLightsStateFrameIndex)).xy;
float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, Material.Depth);
float4 ScenePlane = float4(Material.WorldNormalForPositionBias, dot(TranslatedWorldPosition, Material.WorldNormalForPositionBias));
// Final pass outputs composites irradiance and outputs it to scene color
float3 DiffuseLighting = InverseTonemapLighting(DiffuseLightingSum / DiffuseLightingWeightSum, DisocclusionFactor);
float3 SpecularLighting = 0.f;
// Do not modulate/demodulate hair lighting ModulateLighting() as does not work with hair shading (demodulation is done in ShadeLightSamplesCS())
//ModulateLighting(Material, TranslatedWorldPosition, DiffuseLighting, SpecularLighting);
// Composite diffuse and specular into scene color
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
LightAccumulator_AddSplit(LightAccumulator, DiffuseLighting, SpecularLighting, /*ScatterableLight*/ DiffuseLighting, /*CommonMultiplier*/ 1.0f, Material.bNeedsSeparateSubsurfaceLightAccumulation);
float4 AccumulatedSceneColor = LightAccumulator_GetResult(LightAccumulator);
const uint TotalNodeCount = HairStrands.HairSampleCount[0];
const uint2 Resolution = GetHairSampleResolution(TotalNodeCount);
const FNodeDesc NodeDesc = DecodeNodeDesc(HairStrands.HairSampleOffset.Load(uint3(ScreenCoord, 0)));
LOOP
for (uint SampleIt = 0; SampleIt < NodeDesc.Count; SampleIt++)
{
const uint LinearCoord = NodeDesc.Offset + SampleIt;
const uint2 OutCoord = GetHairSampleCoord(LinearCoord, Resolution);
const FHairSample Sample = UnpackHairSample(HairStrands.HairSampleData[LinearCoord]);
const float SampleCoverage = saturate(From8bitCoverage(Sample.Coverage8bit));
RWSceneColor[OutCoord] = RWSceneColor[OutCoord] + AccumulatedSceneColor * SampleCoverage;
}
}
}
}
#elif INPUT_TYPE == INPUT_TYPE_GBUFFER
if (all(ScreenCoord < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
float2 ScreenUV = (ScreenCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
float4 CenterDiffuseLightingAndSecondMoment = DiffuseLightingAndSecondMomentTexture[ScreenCoord];
float4 CenterSpecularLightingAndSecondMoment = SpecularLightingAndSecondMomentTexture[ScreenCoord];
if (IsLightingValid(CenterDiffuseLightingAndSecondMoment.xyz))
{
FMegaLightsMaterial Material = LoadMaterial(ScreenUV, ScreenCoord);
float ShadingConfidence = ShadingConfidenceTexture[ScreenCoord];
float CenterDiffuseStdDev = sqrt(max(CenterDiffuseLightingAndSecondMoment.w - Pow2(Luminance(CenterDiffuseLightingAndSecondMoment.xyz)), 0.0f));
float CenterSpecularStdDev = sqrt(max(CenterSpecularLightingAndSecondMoment.w - Pow2(Luminance(CenterSpecularLightingAndSecondMoment.xyz)), 0.0f));
// Boost spatial filter until we accumulate enough frames to be able to rely on temporal variance
float NumFramesAccumulated = UnpackNumFramesAccumulated(NumFramesAccumulatedTexture[ScreenCoord]);
float DisocclusionFactor = 0.0f;
if (TemporalMaxFramesAccumulated > 1 && ShadingConfidence <= 0.25f && SpatialFilterMaxDisocclusionFrames > 0.0f)
{
DisocclusionFactor = 1.0f - saturate((NumFramesAccumulated - 1.0f) / (SpatialFilterMaxDisocclusionFrames - 1.0f));
CenterDiffuseStdDev = lerp(CenterDiffuseStdDev, 1.0f, DisocclusionFactor);
}
float3 DiffuseLightingSum = TonemapLighting(CenterDiffuseLightingAndSecondMoment.xyz, DisocclusionFactor);
float3 SpecularLightingSum = TonemapLighting(CenterSpecularLightingAndSecondMoment.xyz, DisocclusionFactor);
float DiffuseLightingWeightSum = 1.0f;
float SpecularLightingWeightSum = 1.0f;
uint2 RandomSeed = Rand3DPCG16(int3(ScreenCoord, MegaLightsStateFrameIndex)).xy;
float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, Material.Depth);
float4 ScenePlane = float4(Material.WorldNormalForPositionBias, dot(TranslatedWorldPosition, Material.WorldNormalForPositionBias));
bool bFilterDiffuse = false;
bool bFilterSpecular = false;
// #ml_todo: expose as CVars
if (ShadingConfidence <= 0.2f || DisocclusionFactor > 0.01f)
{
// Don't run any filtering if relative error isn't big enough. TSR can clean it up without adding extra blur.
bFilterDiffuse = CenterDiffuseStdDev / max(Luminance(CenterDiffuseLightingAndSecondMoment.xyz), 0.1f) > 0.5f;
bFilterSpecular = CenterSpecularStdDev / max(Luminance(CenterSpecularLightingAndSecondMoment.xyz), 0.1f) > 0.5f;
}
#if SPATIAL_FILTER
if (Material.bAllowSpatialFilter && SpatialFilterKernelRadius > 0.0f && (bFilterDiffuse || bFilterSpecular))
{
for (uint NeighborIndex = 0; NeighborIndex < SpatialFilterNumSamples; ++NeighborIndex)
{
float2 NeighborOffsetInRect = Hammersley16(NeighborIndex, SpatialFilterNumSamples, RandomSeed);
float2 NeighborOffset = UniformSampleDiskConcentric(NeighborOffsetInRect) * SpatialFilterKernelRadius;
int2 NeighborCoord = (int2)(ScreenCoord + NeighborOffset);
if (all(and(NeighborCoord >= View.ViewRectMinAndSize.xy, NeighborCoord < (View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))))
{
// Depth weight
float2 NeighborScreenUV = (NeighborCoord + 0.5f) * View.BufferSizeAndInvSize.zw;
const FMegaLightsMaterial NeighborMaterial = LoadMaterial(NeighborScreenUV, NeighborCoord);
const float3 NeighborTranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(NeighborScreenUV, NeighborMaterial.Depth);
float PlaneDistance = abs(dot(float4(NeighborTranslatedWorldPosition, -1), ScenePlane));
float RelativeDepthDifference = PlaneDistance / Material.Depth;
float DepthWeight = exp2(-SpatialFilterDepthWeightScale * (RelativeDepthDifference * RelativeDepthDifference));
// #sdl_todo: separate weight for specular accounting for roughness and expose as CVars
// Normal weight
float AngleBetweenNormals = acosFast(saturate(dot(ScenePlane.xyz, NeighborMaterial.WorldNormalForPositionBias)));
float NormalWeight = 1.0f - saturate(AngleBetweenNormals);
// Diffuse
if (bFilterDiffuse)
{
float4 NeighborDiffuseLightingAndSecondMoment = DiffuseLightingAndSecondMomentTexture[NeighborCoord];
if (IsLightingValid(NeighborDiffuseLightingAndSecondMoment.xyz))
{
float LuminanceDelta = abs(Luminance(CenterDiffuseLightingAndSecondMoment.xyz) - Luminance(NeighborDiffuseLightingAndSecondMoment.xyz));
float LuminanceWeight = exp2(-(LuminanceDelta / max(CenterDiffuseStdDev, 0.001f)) * saturate(1.0f - DisocclusionFactor));
const float DiffuseNeighborWeight = DepthWeight * NormalWeight * LuminanceWeight;
DiffuseLightingSum += TonemapLighting(NeighborDiffuseLightingAndSecondMoment.xyz, DisocclusionFactor) * DiffuseNeighborWeight;
DiffuseLightingWeightSum += DiffuseNeighborWeight;
}
}
// Specular
if (bFilterSpecular)
{
float4 NeighborSpecularLightingAndSecondMoment = SpecularLightingAndSecondMomentTexture[NeighborCoord];
if (IsLightingValid(NeighborSpecularLightingAndSecondMoment.xyz))
{
float LuminanceDelta = abs(Luminance(CenterSpecularLightingAndSecondMoment.xyz) - Luminance(NeighborSpecularLightingAndSecondMoment.xyz));
float LuminanceWeight = exp2(-(LuminanceDelta / max(CenterSpecularStdDev, 0.001f)) * saturate(1.0f - DisocclusionFactor));
const float SpecularNeighborWeight = DepthWeight * NormalWeight * LuminanceWeight;
SpecularLightingSum += TonemapLighting(NeighborSpecularLightingAndSecondMoment.xyz, DisocclusionFactor) * SpecularNeighborWeight;
SpecularLightingWeightSum += SpecularNeighborWeight;
}
}
}
}
}
#endif
// Final pass outputs composites irradiance and outputs it to scene color
float3 DiffuseLighting = InverseTonemapLighting(DiffuseLightingSum / DiffuseLightingWeightSum, DisocclusionFactor);
float3 SpecularLighting = InverseTonemapLighting(SpecularLightingSum / SpecularLightingWeightSum, DisocclusionFactor);
ModulateLighting(Material, TranslatedWorldPosition, DiffuseLighting, SpecularLighting);
// Composite diffuse and specular into scene color
FLightAccumulator LightAccumulator = (FLightAccumulator)0;
LightAccumulator_AddSplit(LightAccumulator, DiffuseLighting, SpecularLighting, /*ScatterableLight*/ DiffuseLighting, /*CommonMultiplier*/ 1.0f, Material.bNeedsSeparateSubsurfaceLightAccumulation);
float4 AccumulatedSceneColor = LightAccumulator_GetResult(LightAccumulator);
if (DebugContext.bIsActive)
{
Print(DebugContext, TEXT("Spatial pass"), FontTitle);
Newline(DebugContext);
Print(DebugContext, TEXT("AccumulatedSceneColor : "));
Print(DebugContext, AccumulatedSceneColor, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("CenterSpecLit+2nd Mmt : "));
Print(DebugContext, CenterSpecularLightingAndSecondMoment, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("CenterDiffuseStdDev : "));
Print(DebugContext, CenterDiffuseStdDev, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("CenterSpecularStdDev : "));
Print(DebugContext, CenterSpecularStdDev, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("NumFramesAccumulated : "));
Print(DebugContext, NumFramesAccumulated, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("DisocclusionFactor : "));
Print(DebugContext, DisocclusionFactor, FontValue);
Newline(DebugContext);
Print(DebugContext, TEXT("ShadingConfidence : "));
Print(DebugContext, ShadingConfidence, Select(ShadingConfidence > 0.75f, FontYellow, Select(ShadingConfidence > 0.25f, FontWhite, FontGrey)));
Newline(DebugContext);
Print(DebugContext, TEXT("Filter : "));
Print(DebugContext, bFilterDiffuse ? 1u : 0u, FontValue);
Print(DebugContext, bFilterSpecular ? 1u : 0u, FontValue);
}
// Visualize where spatial filter gets applied
/*
if (bFilterDiffuse || bFilterSpecular)
{
float4 DebugColor = 0;
if (bFilterDiffuse)
DebugColor.x = 1.0f;
if (bFilterSpecular)
DebugColor.y = 1.0f;
AccumulatedSceneColor = DebugColor;
}*/
RWSceneColor[ScreenCoord] = RWSceneColor[ScreenCoord] + AccumulatedSceneColor;
}
}
#endif
}