631 lines
25 KiB
HLSL
631 lines
25 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "../Common.ush"
|
|
#include "../SceneTextureParameters.ush"
|
|
#include "LumenReflectionCommon.ush"
|
|
#include "LumenReflectionDenoiserCommon.ush"
|
|
#include "../StochasticLighting/StochasticLightingUpsample.ush"
|
|
#if DEBUG_MODE
|
|
#include "../ShaderPrint.ush"
|
|
#endif
|
|
|
|
RWTexture2DArray<float3> RWSpecularIndirect;
|
|
RWTexture2DArray<float> RWSpecularIndirectDepth;
|
|
|
|
RWTexture2D<float3> RWBackgroundVisibility;
|
|
Texture2DArray<float3> TraceBackgroundVisibility;
|
|
|
|
float2 ReflectionTracingBufferInvSize;
|
|
Texture2DArray<uint> ResolveTileUsed;
|
|
|
|
Texture2D<float> DownsampledClosureIndex;
|
|
|
|
uint ClosureIndex;
|
|
uint NumSpatialReconstructionSamples;
|
|
float SpatialReconstructionKernelRadius;
|
|
float SpatialReconstructionRoughnessScale;
|
|
float SpatialReconstructionMinWeight;
|
|
float InvSubstrateMaxClosureCount;
|
|
|
|
bool IsValidDownsampledCoord(uint2 DownsampledScreenCoord)
|
|
{
|
|
return all(DownsampledScreenCoord - ReflectionTracingViewMin < ReflectionTracingViewSize);
|
|
}
|
|
|
|
struct FReflectionLighting
|
|
{
|
|
float3 Lighting;
|
|
float WeightSum;
|
|
float MinRayHitT;
|
|
};
|
|
|
|
FReflectionLighting InitReflectionLighting(float3 InitLighting=0.f)
|
|
{
|
|
FReflectionLighting Out;
|
|
Out.Lighting = InitLighting;
|
|
Out.WeightSum = 0.0f;
|
|
Out.MinRayHitT = FLOAT_32_MAX;
|
|
return Out;
|
|
}
|
|
|
|
// Weight reflection rays by BRDF(Ray) / RayPDF
|
|
float GetSampleWeight(float3 RayDir, float RayPDF, const float3x3 TangentBasis, float3 V, bool bHasAnisotropy, float a2, float2 ax_ay)
|
|
{
|
|
float3 H = mul(TangentBasis, normalize(V + RayDir));
|
|
|
|
float SampleNDF = 1.0f;
|
|
if (bHasAnisotropy)
|
|
{
|
|
SampleNDF = D_GGXaniso(ax_ay.x, ax_ay.y, H.z, H.x, H.y);
|
|
}
|
|
else
|
|
{
|
|
SampleNDF = D_GGX(a2, H.z);
|
|
}
|
|
|
|
return SampleNDF / RayPDF;
|
|
}
|
|
|
|
[numthreads(REFLECTION_THREADGROUP_SIZE_2D, REFLECTION_THREADGROUP_SIZE_2D, 1)]
|
|
void LumenReflectionResolveCS(
|
|
uint GroupId : SV_GroupID,
|
|
uint2 GroupThreadId : SV_GroupThreadID)
|
|
{
|
|
const int2 DownsampleFactor = int2(DOWNSAMPLE_FACTOR_X, DOWNSAMPLE_FACTOR_Y);
|
|
|
|
FReflectionTileData TileData;
|
|
uint2 ScreenCoord = GetReflectionResolveScreenCoord(GroupId, GroupThreadId, TileData);
|
|
const FLumenMaterialCoord Coord = GetLumenMaterialCoord_Reflection(ScreenCoord, TileData, ClosureIndex);
|
|
|
|
#if DEBUG_MODE
|
|
int2 DebugScreenCoord = View.CursorPosition.x >= 0 ? View.CursorPosition * View.ViewResolutionFraction : -1;
|
|
bool bDebug = all(Coord.SvPosition == DebugScreenCoord);
|
|
FShaderPrintContext Context = InitShaderPrintContext(true, float2(0.2, 0.05));
|
|
#endif
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Print(Context, TEXT("ReflectionsResolve"));
|
|
Newline(Context);
|
|
Print(Context, TEXT("ScreenCoord: "));
|
|
Newline(Context);
|
|
Print(Context, Coord.SvPosition);
|
|
Newline(Context);
|
|
PrintLineN(Context, ClosureIndex);
|
|
}
|
|
#endif
|
|
|
|
FLumenMaterialData Material = ApplySmoothBias(ReadMaterialData(Coord, MaxRoughnessToTrace, false/*bAllowStochaticMaterial*/), true /*bTopLayerRoughness*/);
|
|
#if !USE_ANISOTROPY
|
|
Material.Anisotropy = 0.0f;
|
|
#endif
|
|
bool bMatchSampledMaterialClosureIndex = false;
|
|
|
|
// Mark invalid values with INVALID_LIGHTING for history accumulation
|
|
FReflectionLighting Center = InitReflectionLighting(INVALID_LIGHTING);
|
|
FReflectionLighting CenterFallback = InitReflectionLighting();
|
|
float3 BackgroundVisibility = 1.0f;
|
|
|
|
if (NeedRayTracedReflections(Material.TopLayerRoughness, Material))
|
|
{
|
|
uint2 ReflectionScreenCoord = ScreenCoord;
|
|
uint3 ReflectionScreenCoord_Flatten = uint3(ReflectionScreenCoord, Coord.ClosureIndex);
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Substrate.bStochasticLighting)
|
|
{
|
|
ReflectionScreenCoord_Flatten.z = 0; // Set the closure index to 0 as with stochastic lighting, the traced material has a single closure
|
|
}
|
|
#endif
|
|
Center.Lighting = 0.0f;
|
|
|
|
// Center sample
|
|
#if DOWNSAMPLE_FACTOR_X == 1 && DOWNSAMPLE_FACTOR_Y == 1
|
|
{
|
|
Center.Lighting = RGBToDenoiserSpace(TraceRadiance[ReflectionScreenCoord_Flatten]);
|
|
Center.WeightSum = 1.0f;
|
|
|
|
bool bHit;
|
|
Center.MinRayHitT = DecodeRayDistance(TraceHit[ReflectionScreenCoord_Flatten].x, bHit);
|
|
|
|
BackgroundVisibility = TraceBackgroundVisibility[ReflectionScreenCoord_Flatten];
|
|
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Substrate.bStochasticLighting)
|
|
{
|
|
bMatchSampledMaterialClosureIndex = ClosureIndex == SubstrateUnpackClosureIndex(DownsampledClosureIndex[Coord.SvPosition]);
|
|
}
|
|
#endif
|
|
}
|
|
#else
|
|
{
|
|
int2 SampleOffsets[4];
|
|
uint2 DownsampledScreenCoord00;
|
|
float3 DownsampledGatherUV;
|
|
float4 UpsampleWeights;
|
|
float4 UpsampleDepth4;
|
|
|
|
#if DOWNSAMPLE_FACTOR_Y == 1
|
|
uint2 ClampedScreenCoord = max(ScreenCoord, ReflectionTracingViewMin * DownsampleFactor);
|
|
|
|
SampleOffsets[0] = int2(0, 0);
|
|
SampleOffsets[1] = int2(select(ClampedScreenCoord.x % DownsampleFactor.x == 0, -1, 1), 0);
|
|
SampleOffsets[2] = int2(0, -1);
|
|
SampleOffsets[3] = int2(0, 1);
|
|
|
|
DownsampledScreenCoord00 = ClampedScreenCoord / DownsampleFactor;
|
|
DownsampledGatherUV.xy = DownsampledScreenCoord00 * ReflectionTracingBufferInvSize;
|
|
DownsampledGatherUV.z = (Coord.ClosureIndex + 0.5f) * InvSubstrateMaxClosureCount;
|
|
|
|
// We trace 1 sample out of 2x1. One can be copied directly. Other one need be reconstructed using bilinear filtering.
|
|
// [o x] or [x o] is one downsampled pixel, x is traced, and o is reconstructed. + is the pixel processed by this shader thread.
|
|
// There are three scenarios:
|
|
// 1) We just take the traced value if + coincide with x;
|
|
// 2) [x o][x o][x o]
|
|
// [o x][+ x][o x]
|
|
// [x o][x o][x o]
|
|
// We pick the left, up, down, and center samples if + is the first pixel in the 2x1;
|
|
// 3) [o x][o x][o x]
|
|
// [x o][x +][x o]
|
|
// [o x][o x][o x]
|
|
// We pick the right, up, down, and center samples if + is the second pixel in the 2x1.
|
|
int2 ScreenCoordOffset = ClampedScreenCoord - DownsampledScreenCoord00 * DownsampleFactor;
|
|
int2 TracedPixelOffset = GetScreenTileJitter(DownsampledScreenCoord00);
|
|
bool bReconstructed = ScreenCoordOffset.x != TracedPixelOffset.x;
|
|
|
|
UpsampleWeights = select(bReconstructed, float4(0.25f, 0.25f, 0.25f, 0.25f), float4(1.0f, 0.0f, 0.0f, 0.0f));
|
|
|
|
float4 Depth4 = DownsampledDepth.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV);
|
|
UpsampleDepth4.xyz = Depth4.yxz;
|
|
|
|
Depth4 = DownsampledDepth.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV + float3(ReflectionTracingBufferInvSize, 0.0f));
|
|
UpsampleDepth4.w = Depth4.x;
|
|
UpsampleDepth4.y = select(SampleOffsets[1].x > 0, Depth4.z, UpsampleDepth4.y);
|
|
#else
|
|
SampleOffsets[0] = int2(0, 0);
|
|
SampleOffsets[1] = int2(1, 0);
|
|
SampleOffsets[2] = int2(0, 1);
|
|
SampleOffsets[3] = int2(1, 1);
|
|
|
|
DownsampledScreenCoord00 = (max(ScreenCoord, View.ViewRectMinAndSize.xy + 1) - 1) / DownsampleFactor;
|
|
DownsampledGatherUV.xy = (DownsampledScreenCoord00 + 1.0f) * ReflectionTracingBufferInvSize;
|
|
DownsampledGatherUV.z = (Coord.ClosureIndex + 0.5f) * InvSubstrateMaxClosureCount;
|
|
|
|
// We trace 1 sample out of 2x2. One can be copied directly. Other three need be reconstructed using bilinear filtering.
|
|
int2 ScreenCoordOffset = max(ScreenCoord, View.ViewRectMinAndSize.xy + 1) - DownsampledScreenCoord00 * DownsampleFactor;
|
|
int2 ToSample00 = GetScreenTileJitter(DownsampledScreenCoord00 + SampleOffsets[0]) + SampleOffsets[0] * DownsampleFactor - ScreenCoordOffset;
|
|
int2 ToSample10 = GetScreenTileJitter(DownsampledScreenCoord00 + SampleOffsets[1]) + SampleOffsets[1] * DownsampleFactor - ScreenCoordOffset;
|
|
int2 ToSample01 = GetScreenTileJitter(DownsampledScreenCoord00 + SampleOffsets[2]) + SampleOffsets[2] * DownsampleFactor - ScreenCoordOffset;
|
|
int2 ToSample11 = GetScreenTileJitter(DownsampledScreenCoord00 + SampleOffsets[3]) + SampleOffsets[3] * DownsampleFactor - ScreenCoordOffset;
|
|
|
|
UpsampleWeights.x = (2.0f - abs(ToSample00.x)) * (2.0f - abs(ToSample00.y));
|
|
UpsampleWeights.y = (2.0f - abs(ToSample10.x)) * (2.0f - abs(ToSample10.y));
|
|
UpsampleWeights.z = (2.0f - abs(ToSample01.x)) * (2.0f - abs(ToSample01.y));
|
|
UpsampleWeights.w = (2.0f - abs(ToSample11.x)) * (2.0f - abs(ToSample11.y));
|
|
|
|
UpsampleDepth4 = DownsampledDepth.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV).wzxy;
|
|
#endif
|
|
|
|
float4 DepthWeights = select(UpsampleDepth4 > 0.0f, 1.0f, 0.0f);
|
|
UpsampleWeights *= DepthWeights;
|
|
|
|
// Skip out of view samples
|
|
UpsampleWeights.x = IsValidDownsampledCoord(DownsampledScreenCoord00 + SampleOffsets[0]) ? UpsampleWeights.x : 0.0f;
|
|
UpsampleWeights.y = IsValidDownsampledCoord(DownsampledScreenCoord00 + SampleOffsets[1]) ? UpsampleWeights.y : 0.0f;
|
|
UpsampleWeights.z = IsValidDownsampledCoord(DownsampledScreenCoord00 + SampleOffsets[2]) ? UpsampleWeights.z : 0.0f;
|
|
UpsampleWeights.w = IsValidDownsampledCoord(DownsampledScreenCoord00 + SampleOffsets[3]) ? UpsampleWeights.w : 0.0f;
|
|
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Substrate.bStochasticLighting)
|
|
{
|
|
float4 EncodedClosureIndex4;
|
|
#if DOWNSAMPLE_FACTOR_Y == 1
|
|
float4 Encoded4 = DownsampledClosureIndex.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV.xy);
|
|
EncodedClosureIndex4.xyz = Encoded4.yxz;
|
|
|
|
Encoded4 = DownsampledClosureIndex.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV.xy + ReflectionTracingBufferInvSize);
|
|
EncodedClosureIndex4.w = Encoded4.x;
|
|
EncodedClosureIndex4.y = select(SampleOffsets[1].x > 0, Encoded4.z, EncodedClosureIndex4.y);
|
|
#else
|
|
EncodedClosureIndex4 = DownsampledClosureIndex.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV.xy).wzxy;
|
|
#endif
|
|
uint4 ClosureWeight = SubstrateUnpackClosureIndex(EncodedClosureIndex4);
|
|
|
|
float4 ClosureMatchWeights;
|
|
ClosureMatchWeights.x = ClosureWeight.x == ClosureIndex ? 1.f : 0.f;
|
|
ClosureMatchWeights.y = ClosureWeight.y == ClosureIndex ? 1.f : 0.f;
|
|
ClosureMatchWeights.z = ClosureWeight.z == ClosureIndex ? 1.f : 0.f;
|
|
ClosureMatchWeights.w = ClosureWeight.w == ClosureIndex ? 1.f : 0.f;
|
|
|
|
bMatchSampledMaterialClosureIndex = any(ClosureMatchWeights * UpsampleWeights > 0);
|
|
if (bMatchSampledMaterialClosureIndex)
|
|
{
|
|
UpsampleWeights *= ClosureMatchWeights;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#define STOCHASTIC_UPSAMPLE 0
|
|
#if STOCHASTIC_UPSAMPLE
|
|
if (any(UpsampleWeights > 0.01f))
|
|
{
|
|
const float RandomScalar = BlueNoiseScalar(Coord.SvPositionFlatten.xy, ReflectionsStateFrameIndex);
|
|
const int2 StochasticBilinearOffset = GetStochasticBilinearOffset(RandomScalar, UpsampleWeights, SampleOffsets);
|
|
|
|
ReflectionScreenCoord_Flatten.xy = DownsampledScreenCoord00 + StochasticBilinearOffset;
|
|
|
|
Center.Lighting = RGBToDenoiserSpace(TraceRadiance[ReflectionScreenCoord_Flatten]);
|
|
Center.WeightSum = 1.0f;
|
|
|
|
bool bHit;
|
|
Center.MinRayHitT = DecodeRayDistance(TraceHit[ReflectionScreenCoord_Flatten].x, bHit);
|
|
|
|
BackgroundVisibility = TraceBackgroundVisibility[ReflectionScreenCoord_Flatten];
|
|
}
|
|
#else
|
|
if (any(UpsampleWeights > 0.01f))
|
|
{
|
|
float Epsilon = 0.001f;
|
|
UpsampleWeights /= max(dot(UpsampleWeights, 1), Epsilon);
|
|
|
|
#if DOWNSAMPLE_FACTOR_Y == 1
|
|
float3 BaseSampleUV = DownsampledGatherUV;
|
|
BaseSampleUV.xy = BaseSampleUV.xy + 0.5f * ReflectionTracingBufferInvSize;
|
|
|
|
float3 SampleRadiance[4];
|
|
for (uint SampleIndex = 0; SampleIndex < 4; ++SampleIndex)
|
|
{
|
|
if (UpsampleWeights[SampleIndex] > 0.0f)
|
|
{
|
|
float3 SampleUV = BaseSampleUV + float3(SampleOffsets[SampleIndex] * ReflectionTracingBufferInvSize, 0.0f);
|
|
SampleRadiance[SampleIndex] = TraceRadiance.SampleLevel(GlobalPointClampedSampler, SampleUV, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
SampleRadiance[SampleIndex] = 0.0f;
|
|
}
|
|
}
|
|
|
|
float3 TraceRadiance0 = RGBToDenoiserSpace(SampleRadiance[0]);
|
|
float3 TraceRadiance1 = RGBToDenoiserSpace(SampleRadiance[1]);
|
|
float3 TraceRadiance2 = RGBToDenoiserSpace(SampleRadiance[2]);
|
|
float3 TraceRadiance3 = RGBToDenoiserSpace(SampleRadiance[3]);
|
|
#else
|
|
// Multiplying zero weight is not enough as radiance outside view rect can be NaN
|
|
float4 TraceRadiance4R = select(UpsampleWeights > 0.0f, TraceRadiance.GatherRed(GlobalPointClampedSampler, DownsampledGatherUV).wzxy, 0.0f);
|
|
float4 TraceRadiance4G = select(UpsampleWeights > 0.0f, TraceRadiance.GatherGreen(GlobalPointClampedSampler, DownsampledGatherUV).wzxy, 0.0f);
|
|
float4 TraceRadiance4B = select(UpsampleWeights > 0.0f, TraceRadiance.GatherBlue(GlobalPointClampedSampler, DownsampledGatherUV).wzxy, 0.0f);
|
|
|
|
float3 TraceRadiance0 = RGBToDenoiserSpace(float3(TraceRadiance4R.x, TraceRadiance4G.x, TraceRadiance4B.x));
|
|
float3 TraceRadiance1 = RGBToDenoiserSpace(float3(TraceRadiance4R.y, TraceRadiance4G.y, TraceRadiance4B.y));
|
|
float3 TraceRadiance2 = RGBToDenoiserSpace(float3(TraceRadiance4R.z, TraceRadiance4G.z, TraceRadiance4B.z));
|
|
float3 TraceRadiance3 = RGBToDenoiserSpace(float3(TraceRadiance4R.w, TraceRadiance4G.w, TraceRadiance4B.w));
|
|
#endif
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Print(Context, TEXT("UpsampleWeights: "));
|
|
Print(Context, UpsampleWeights);
|
|
Newline(Context);
|
|
}
|
|
#endif
|
|
|
|
Center.Lighting = TraceRadiance0 * UpsampleWeights.x
|
|
+ TraceRadiance1 * UpsampleWeights.y
|
|
+ TraceRadiance2 * UpsampleWeights.z
|
|
+ TraceRadiance3 * UpsampleWeights.w;
|
|
|
|
const float MaxUpsampleWeight = max(max(max(UpsampleWeights.x, UpsampleWeights.y), UpsampleWeights.z), UpsampleWeights.w);
|
|
|
|
// Select closest ReflectionScreenCoord_Flatten for trace hit distance calculation
|
|
if (MaxUpsampleWeight == UpsampleWeights.x)
|
|
{
|
|
ReflectionScreenCoord_Flatten.xy = DownsampledScreenCoord00 + SampleOffsets[0];
|
|
}
|
|
else if (MaxUpsampleWeight == UpsampleWeights.y)
|
|
{
|
|
ReflectionScreenCoord_Flatten.xy = DownsampledScreenCoord00 + SampleOffsets[1];
|
|
}
|
|
else if (MaxUpsampleWeight == UpsampleWeights.z)
|
|
{
|
|
ReflectionScreenCoord_Flatten.xy = DownsampledScreenCoord00 + SampleOffsets[2];
|
|
}
|
|
else
|
|
{
|
|
ReflectionScreenCoord_Flatten.xy = DownsampledScreenCoord00 + SampleOffsets[3];
|
|
}
|
|
|
|
bool bHit;
|
|
Center.MinRayHitT = DecodeRayDistance(TraceHit[ReflectionScreenCoord_Flatten].x, bHit);
|
|
Center.WeightSum = 1.0f;
|
|
|
|
BackgroundVisibility = TraceBackgroundVisibility[ReflectionScreenCoord_Flatten];
|
|
}
|
|
#endif //STOCHASTIC_UPSAMPLE
|
|
}
|
|
#endif
|
|
|
|
float MinKernelRadiusInPixels = 0;
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Substrate.bStochasticLighting && !bMatchSampledMaterialClosureIndex)
|
|
{
|
|
// The center lighting does not match the current closure. It will only be used if we can't find good match among neighbors
|
|
CenterFallback.Lighting = Center.Lighting;
|
|
CenterFallback.WeightSum = 1.f;
|
|
CenterFallback.MinRayHitT = Center.MinRayHitT;
|
|
|
|
// Reset the center lighting, and will try to fill it with neighbors' values
|
|
Center.Lighting = 0.f;
|
|
Center.WeightSum = 0.f;
|
|
Center.MinRayHitT = FLOAT_32_MAX;
|
|
|
|
MinKernelRadiusInPixels = 1.f;
|
|
}
|
|
#endif
|
|
|
|
#if USE_SPATIAL_RECONSTRUCTION
|
|
{
|
|
const float AvgDownsampleFactor = (DownsampleFactor.x + DownsampleFactor.y) * 0.5f;
|
|
const float MaxDownsampleFactor = max(DownsampleFactor.x, DownsampleFactor.y);
|
|
|
|
float KernelRadiusInPixels = 0.0f;
|
|
if (Material.TopLayerRoughness > 0.05f)
|
|
{
|
|
KernelRadiusInPixels = max(AvgDownsampleFactor * SpatialReconstructionKernelRadius * saturate(Material.TopLayerRoughness * 8.0f), MinKernelRadiusInPixels);
|
|
}
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Newline(Context);
|
|
Print(Context, TEXT("Material.TopLayerRoughness: "));
|
|
Print(Context, Material.TopLayerRoughness);
|
|
Newline(Context);
|
|
Print(Context, TEXT("Material.Anisotropy: "));
|
|
Print(Context, Material.Anisotropy);
|
|
Newline(Context);
|
|
Print(Context, TEXT("KernelRadiusInPixels: "));
|
|
Print(Context, KernelRadiusInPixels);
|
|
Newline(Context);
|
|
Print(Context, TEXT("ReflectionLighting: "));
|
|
Print(Context, Center.Lighting);
|
|
Newline(Context);
|
|
Print(Context, TEXT("WeightSum: "));
|
|
Print(Context, Center.WeightSum);
|
|
}
|
|
#endif
|
|
|
|
// Skip reconstruction on mirror reflections
|
|
if (KernelRadiusInPixels > MaxDownsampleFactor)
|
|
{
|
|
uint2 RandomSeed = Rand3DPCG16(int3(ScreenCoord, ReflectionsStateFrameIndexMod8)).xy;
|
|
|
|
const float2 ScreenUV = (Coord.SvPosition + 0.5f) * View.BufferSizeAndInvSize.zw;
|
|
const float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, Material.SceneDepth);
|
|
const float3 V = -normalize(TranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin);
|
|
const float3x3 TangentBasis = GetTangentBasis(Material);
|
|
const float3 TangentV = mul(TangentBasis, V);
|
|
|
|
const bool bHasAnisotropy = HasAnisotropy(Material);
|
|
const float EffectiveRoughness = clamp(Material.TopLayerRoughness * SpatialReconstructionRoughnessScale, 0.0f, 1.0f);
|
|
float2 ax_ay = Pow2(EffectiveRoughness).xx;
|
|
if (bHasAnisotropy)
|
|
{
|
|
GetAnisotropicRoughness(ax_ay.x, Material.Anisotropy, ax_ay.x, ax_ay.y);
|
|
}
|
|
const float a2 = ax_ay.x * ax_ay.y;
|
|
|
|
float2 KernelDiameter = KernelRadiusInPixels;
|
|
float2 KernelMajorAxis = float2(KernelDiameter.x,0);
|
|
float2 KernelMinorAxis = float2(0, KernelDiameter.y);
|
|
|
|
if (bHasAnisotropy)
|
|
{
|
|
// Scale filtering kernel based on the anisotropy scaling
|
|
KernelDiameter.x = max(KernelDiameter.x * (1.0 + Material.Anisotropy), MaxDownsampleFactor);
|
|
KernelDiameter.y = max(KernelDiameter.y * (1.0 - Material.Anisotropy), MaxDownsampleFactor);
|
|
|
|
// Orient filtering kernel along the projected major/minor axis
|
|
float4 ProjX = mul(float4(TranslatedWorldPosition + TangentBasis[0], 1), View.TranslatedWorldToClip);
|
|
float4 ProjY = mul(float4(TranslatedWorldPosition + TangentBasis[1], 1), View.TranslatedWorldToClip);
|
|
ProjX /= ProjX.w;
|
|
ProjY /= ProjY.w;
|
|
ProjX.xy = ProjX.xy * float2(0.5f, -0.5f) + 0.5f;
|
|
ProjY.xy = ProjY.xy * float2(0.5f, -0.5f) + 0.5f;
|
|
|
|
KernelMajorAxis = KernelDiameter.x * normalize(ProjX.xy * View.ViewSizeAndInvSize.xy - (Coord.SvPosition + 0.5f));
|
|
KernelMinorAxis = KernelDiameter.y * normalize(ProjY.xy * View.ViewSizeAndInvSize.xy - (Coord.SvPosition + 0.5f));
|
|
}
|
|
|
|
// Reference RayHitT
|
|
const float CenterRayHitT = Center.MinRayHitT;
|
|
|
|
// Re-weight center sample
|
|
{
|
|
FRayData CenterRayData = GetRayData(ReflectionScreenCoord_Flatten);
|
|
float CenterSampleWeight = GetSampleWeight(CenterRayData.Direction, CenterRayData.PDF, TangentBasis, V, bHasAnisotropy, a2, ax_ay);
|
|
|
|
// We always want to take the center sample
|
|
CenterSampleWeight = max(CenterSampleWeight, 0.001f);
|
|
|
|
Center.Lighting *= CenterSampleWeight;
|
|
Center.WeightSum = CenterSampleWeight;
|
|
}
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Newline(Context);
|
|
Print(Context, TEXT("KernelMajorAxis: "));
|
|
Print(Context, KernelMajorAxis);
|
|
Newline(Context);
|
|
Print(Context, TEXT("KernelMinorAxis: "));
|
|
Print(Context, KernelMinorAxis);
|
|
Newline(Context);
|
|
Print(Context, TEXT("CenterRayHitT: "));
|
|
Print(Context, CenterRayHitT);
|
|
Newline(Context);
|
|
Print(Context, TEXT("CenterWeight: "));
|
|
Print(Context, Center.WeightSum);
|
|
}
|
|
#endif
|
|
|
|
FReflectionLighting Neighbor = InitReflectionLighting();
|
|
FReflectionLighting NeighborFallback = InitReflectionLighting();
|
|
NeighborFallback.MinRayHitT = Center.MinRayHitT;
|
|
|
|
for (uint NeighborIndex = 0; NeighborIndex < NumSpatialReconstructionSamples; ++NeighborIndex)
|
|
{
|
|
float2 NeighborOffsetInRect = Hammersley16(NeighborIndex, NumSpatialReconstructionSamples, RandomSeed);
|
|
float2 NeighborOffset = UniformSampleDiskConcentric(NeighborOffsetInRect);
|
|
NeighborOffset = NeighborOffset.x * KernelMajorAxis + NeighborOffset.y * KernelMinorAxis;
|
|
int2 NeighborScreenCoord = (int2)(ScreenCoord + NeighborOffset + 0.5f);
|
|
|
|
int2 NeighborTracingCoord = NeighborScreenCoord / DownsampleFactor;
|
|
if (all(and(NeighborTracingCoord >= ReflectionTracingViewMin, NeighborTracingCoord < ReflectionTracingViewMin + ReflectionTracingViewSize)))
|
|
{
|
|
int3 NeighborTracingCoord_Flatten = int3(NeighborTracingCoord, Coord.ClosureIndex);
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Substrate.bStochasticLighting)
|
|
{
|
|
NeighborTracingCoord_Flatten.z = 0;
|
|
}
|
|
#endif
|
|
|
|
// DownsampledDepth is valid discriminant to know if a pixel is valid (i.e. has initialized ray/luminance data)
|
|
float2 NeighborScreenUV = GetScreenUVFromReflectionTracingCoord(NeighborTracingCoord);
|
|
float NeighborSceneDepth = DownsampledDepth[NeighborTracingCoord_Flatten].x;
|
|
|
|
if (NeighborSceneDepth > 0.0f)
|
|
{
|
|
float3 NeighborTranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(NeighborScreenUV, NeighborSceneDepth);
|
|
FRayData RayData = GetRayData(NeighborTracingCoord_Flatten);
|
|
|
|
bool bHit;
|
|
float TraceHitDistance = DecodeRayDistance(TraceHit[NeighborTracingCoord_Flatten].x, bHit);
|
|
|
|
// Clamp to center distance - preserves contacts and prevents a bias toward trace that hit the background
|
|
TraceHitDistance = min(TraceHitDistance, CenterRayHitT);
|
|
|
|
float3 NeighborHitPosition = NeighborTranslatedWorldPosition + RayData.Direction * TraceHitDistance;
|
|
float DistanceToNeighborHit = length(NeighborHitPosition - TranslatedWorldPosition);
|
|
float3 DirectionToNeighborHit = RayData.Direction;
|
|
|
|
if (DistanceToNeighborHit > 0)
|
|
{
|
|
DirectionToNeighborHit = (NeighborHitPosition - TranslatedWorldPosition) / DistanceToNeighborHit;
|
|
}
|
|
|
|
const float SampleWeight = GetSampleWeight(DirectionToNeighborHit, RayData.PDF, TangentBasis, V, bHasAnisotropy, a2, ax_ay);
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Newline(Context);
|
|
Print(Context, TraceHitDistance);
|
|
Print(Context, SampleWeight);
|
|
}
|
|
#endif
|
|
|
|
if (SampleWeight > 1e-6)
|
|
{
|
|
float3 SampleRadiance = RGBToDenoiserSpace(TraceRadiance[NeighborTracingCoord_Flatten]);
|
|
|
|
bool bIsNeighborSampleValid = true;
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Substrate.bStochasticLighting)
|
|
{
|
|
const uint NClosureIndex = SubstrateUnpackClosureIndex(DownsampledClosureIndex[NeighborTracingCoord_Flatten.xy]);
|
|
bIsNeighborSampleValid = NClosureIndex == ClosureIndex;
|
|
if (!bIsNeighborSampleValid)
|
|
{
|
|
NeighborFallback.Lighting += SampleRadiance * SampleWeight;
|
|
NeighborFallback.WeightSum += SampleWeight;
|
|
NeighborFallback.MinRayHitT = min(NeighborFallback.MinRayHitT, TraceHitDistance);
|
|
}
|
|
}
|
|
#endif // SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED
|
|
|
|
if (bIsNeighborSampleValid)
|
|
{
|
|
Neighbor.Lighting += SampleRadiance * SampleWeight;
|
|
Neighbor.WeightSum += SampleWeight;
|
|
Center.MinRayHitT = min(Center.MinRayHitT, TraceHitDistance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no valid lighting sample has been found among neighbors,
|
|
// use their average despite not matching correctly
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Neighbor.WeightSum == 0)
|
|
{
|
|
Neighbor.Lighting = NeighborFallback.Lighting;
|
|
Neighbor.WeightSum = NeighborFallback.WeightSum;
|
|
Center.MinRayHitT = NeighborFallback.MinRayHitT;
|
|
}
|
|
#endif
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Newline(Context);
|
|
Print(Context, TEXT("Neighbor.WeightSum:"));
|
|
Print(Context, Neighbor.WeightSum);
|
|
}
|
|
#endif
|
|
|
|
// Re-weight neighborhood if we didn't find any good samples
|
|
const float RelativeSpatialReconstructionMinWeight = SpatialReconstructionMinWeight * Center.WeightSum;
|
|
if (Neighbor.WeightSum > 1e-6 && RelativeSpatialReconstructionMinWeight > Neighbor.WeightSum)
|
|
{
|
|
Neighbor.Lighting = (Neighbor.Lighting / Neighbor.WeightSum) * RelativeSpatialReconstructionMinWeight;
|
|
Neighbor.WeightSum = RelativeSpatialReconstructionMinWeight;
|
|
}
|
|
|
|
Center.Lighting += Neighbor.Lighting;
|
|
Center.WeightSum += Neighbor.WeightSum;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// If no valid lighting samples have been found among center + neighbors,
|
|
// use the center sample despite not matching correctly
|
|
#if SUBSTRATE_STOCHASTIC_LIGHTING_ALLOWED && !FRONT_LAYER_TRANSLUCENCY
|
|
if (Center.WeightSum < 1e-6)
|
|
{
|
|
Center.Lighting = CenterFallback.Lighting;
|
|
Center.WeightSum = CenterFallback.WeightSum;
|
|
Center.MinRayHitT = CenterFallback.MinRayHitT;
|
|
}
|
|
#endif
|
|
|
|
if (Center.WeightSum > 1e-6)
|
|
{
|
|
Center.Lighting = Center.Lighting / Center.WeightSum;
|
|
}
|
|
Center.Lighting = DenoiserSpaceToRGB(Center.Lighting);
|
|
|
|
#if DEBUG_MODE
|
|
if (bDebug)
|
|
{
|
|
Newline(Context);
|
|
Print(Context, TEXT("WeightSum: "));
|
|
Print(Context, Center.WeightSum);
|
|
Newline(Context);
|
|
Print(Context, TEXT("ReflectionLighting: "));
|
|
Print(Context, Center.Lighting);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
RWSpecularIndirect[Coord.SvPositionFlatten] = Center.Lighting;
|
|
RWSpecularIndirectDepth[Coord.SvPositionFlatten] = Center.MinRayHitT;
|
|
|
|
#if RESOLVE_BACKGROUND_VISIBILITY
|
|
RWBackgroundVisibility[Coord.SvPosition] = BackgroundVisibility;
|
|
#endif
|
|
} |