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

1852 lines
74 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#define USE_HAIR_COMPLEX_TRANSMITTANCE 1
#include "../Common.ush"
#include "LumenMaterial.ush"
#include "../DeferredShadingCommon.ush"
#include "../BRDF.ush"
#include "LumenScreenProbeCommon.ush"
#include "../MonteCarlo.ush"
#include "../ShadingModelsSampling.ush"
#include "../SHCommon.ush"
#include "../SceneTextureParameters.ush"
#include "../SphericalGaussian.ush"
#include "../FastMath.ush"
#include "../ClearCoatCommon.ush"
#include "LumenRadianceCacheMarkCommon.ush"
#include "../TextureSampling.ush"
#include "../MortonCode.ush"
#include "LumenScreenSpaceBentNormal.ush"
#include "LumenReflectionsCombine.ush"
#include "LumenFloatQuantization.ush"
#include "../ReflectionEnvironmentShared.ush"
#include "../StochasticLighting/StochasticLightingCommon.ush"
#ifndef THREADGROUP_SIZE
#define THREADGROUP_SIZE 1
#endif
#ifndef INTEGRATE_TILE_CLASSIFICATION_MODE
#define INTEGRATE_TILE_CLASSIFICATION_MODE 0
#endif
#define MIN_PROBE_INTERPOLATION_WEIGHT 0.01f
#define MIN_PROBE_INTERPOLATION_FALLBACK_WEIGHT 0.0001f
RWTexture2D<uint> RWScreenProbeSceneDepth;
RWTexture2D<uint> RWScreenProbeWorldSpeed;
RWTexture2D<UNORM float2> RWScreenProbeWorldNormal;
RWTexture2D<float4> RWScreenProbeTranslatedWorldPosition;
Texture2DArray<uint> ShortRangeAOTexture;
float ScreenProbeInterpolationDepthWeight;
float ScreenProbeInterpolationDepthWeightForFoliage;
///////////////////////////////////////////////////////////////////////////////////////////////////
struct FScreenProbeMaterial
{
float3 WorldNormal;
float SceneDepth;
bool bIsValid;
bool bHasBackfaceDiffuse;
bool bHair;
};
FScreenProbeMaterial GetScreenProbeMaterial(uint2 PixelPos)
{
const FLumenMaterialData Material = ReadMaterialData(PixelPos);
FScreenProbeMaterial Out;
Out.WorldNormal = Material.WorldNormal;
Out.SceneDepth = Material.SceneDepth;
Out.bIsValid = IsValid(Material);
Out.bHasBackfaceDiffuse = HasBackfaceDiffuse(Material);
Out.bHair = IsHair(Material);
return Out;
}
float3 GetMaterialWorldNormal(uint2 PixelPos)
{
return ReadMaterialData(PixelPos).WorldNormal;
}
uint2 IntegrateViewMin;
uint2 IntegrateViewSize;
///////////////////////////////////////////////////////////////////////////////////////////////////
void WriteDownsampledProbeMaterial(float2 ScreenUV, uint2 ScreenProbeAtlasCoord, FScreenProbeMaterial ProbeMaterial)
{
float EncodedDepth = ProbeMaterial.SceneDepth;
if (!ProbeMaterial.bIsValid)
{
// Store unlit in sign bit
EncodedDepth *= -1.0f;
}
RWScreenProbeSceneDepth[ScreenProbeAtlasCoord] = asuint(EncodedDepth);
RWScreenProbeWorldNormal[ScreenProbeAtlasCoord] = UnitVectorToOctahedron(ProbeMaterial.WorldNormal) * 0.5 + 0.5;
float3 ProbeWorldVelocity;
float3 ProbeTranslatedWorldPosition;
{
float2 ProbeScreenPosition = (ScreenUV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
float ProbeDeviceZ = ConvertToDeviceZ(ProbeMaterial.SceneDepth);
float3 ProbeHistoryScreenPosition = GetHistoryScreenPositionIncludingTAAJitter(ProbeScreenPosition, ScreenUV, ProbeDeviceZ);
ProbeTranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(ProbeScreenPosition, ProbeMaterial.SceneDepth), ProbeMaterial.SceneDepth, 1), View.ScreenToTranslatedWorld).xyz;
ProbeWorldVelocity = ProbeTranslatedWorldPosition - GetPrevTranslatedWorldPosition(ProbeHistoryScreenPosition);
}
RWScreenProbeWorldSpeed[ScreenProbeAtlasCoord] = EncodeScreenProbeSpeed(length(ProbeWorldVelocity), ProbeMaterial.bHasBackfaceDiffuse, ProbeMaterial.bHair);
RWScreenProbeTranslatedWorldPosition[ScreenProbeAtlasCoord] = float4(ProbeTranslatedWorldPosition, 0.0f);
}
#ifdef ScreenProbeDownsampleDepthUniformCS
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void ScreenProbeDownsampleDepthUniformCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint2 ScreenProbeAtlasCoord = DispatchThreadId.xy;
if (all(ScreenProbeAtlasCoord < ScreenProbeAtlasViewSize))
{
uint2 ScreenProbeScreenPosition = GetUniformScreenProbeScreenPosition(ScreenProbeAtlasCoord);
float2 ScreenUV = (ScreenProbeScreenPosition + .5f) * View.BufferSizeAndInvSize.zw;
WriteDownsampledProbeMaterial(ScreenUV, ScreenProbeAtlasCoord, GetScreenProbeMaterial(ScreenProbeScreenPosition));
}
}
#endif
float GetScreenProbeDepthFromUAV(uint2 ScreenProbeAtlasCoord)
{
return asfloat(RWScreenProbeSceneDepth[ScreenProbeAtlasCoord]);
}
void CalculateUniformUpsampleInterpolationWeights(
float2 ScreenCoord,
float2 NoiseOffset,
float3 WorldPosition,
float SceneDepth,
float3 WorldNormal,
uniform bool bIsUpsamplePass,
bool bFoliage,
out uint2 ScreenTileCoord00,
out float4 InterpolationWeights)
{
uint2 ScreenProbeFullResScreenCoord = clamp(ScreenCoord.xy - View.ViewRectMin.xy - GetScreenTileJitter(SCREEN_TEMPORAL_INDEX) + NoiseOffset, 0.0f, View.ViewSizeAndInvSize.xy - 1.0f);
ScreenTileCoord00 = min(ScreenProbeFullResScreenCoord / ScreenProbeDownsampleFactor, (uint2)ScreenProbeViewSize - 2);
uint BilinearExpand = 1;
float2 BilinearWeights = (ScreenProbeFullResScreenCoord - ScreenTileCoord00 * ScreenProbeDownsampleFactor + BilinearExpand) / (float)(ScreenProbeDownsampleFactor + 2 * BilinearExpand);
float4 CornerDepths;
CornerDepths.x = bIsUpsamplePass ? GetScreenProbeDepth(ScreenTileCoord00) : GetScreenProbeDepthFromUAV(ScreenTileCoord00);
CornerDepths.y = bIsUpsamplePass ? GetScreenProbeDepth(ScreenTileCoord00 + int2(1, 0)) : GetScreenProbeDepthFromUAV(ScreenTileCoord00 + int2(1, 0));
CornerDepths.z = bIsUpsamplePass ? GetScreenProbeDepth(ScreenTileCoord00 + int2(0, 1)) : GetScreenProbeDepthFromUAV(ScreenTileCoord00 + int2(0, 1));
CornerDepths.w = bIsUpsamplePass ? GetScreenProbeDepth(ScreenTileCoord00 + int2(1, 1)) : GetScreenProbeDepthFromUAV(ScreenTileCoord00 + int2(1, 1));
InterpolationWeights = float4(
(1 - BilinearWeights.y) * (1 - BilinearWeights.x),
(1 - BilinearWeights.y) * BilinearWeights.x,
BilinearWeights.y * (1 - BilinearWeights.x),
BilinearWeights.y * BilinearWeights.x);
float4 DepthWeights;
#define PLANE_WEIGHTING 1
#if PLANE_WEIGHTING
{
float4 ScenePlane = float4(WorldNormal, dot(WorldPosition, WorldNormal));
float3 Position00 = GetWorldPositionFromScreenUV(GetScreenUVFromScreenTileCoord(ScreenTileCoord00), CornerDepths.x);
float3 Position10 = GetWorldPositionFromScreenUV(GetScreenUVFromScreenTileCoord(ScreenTileCoord00 + uint2(1, 0)), CornerDepths.y);
float3 Position01 = GetWorldPositionFromScreenUV(GetScreenUVFromScreenTileCoord(ScreenTileCoord00 + uint2(0, 1)), CornerDepths.z);
float3 Position11 = GetWorldPositionFromScreenUV(GetScreenUVFromScreenTileCoord(ScreenTileCoord00 + uint2(1, 1)), 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 = abs(PlaneDistances / SceneDepth);
DepthWeights = select(CornerDepths > 0, exp2((bFoliage ? ScreenProbeInterpolationDepthWeightForFoliage : ScreenProbeInterpolationDepthWeight) * RelativeDepthDifference), 0.0);
}
#else
{
float4 DepthDifference = abs(CornerDepths - SceneDepth.xxxx);
float4 RelativeDepthDifference = DepthDifference / SceneDepth;
DepthWeights = CornerDepths > 0 ? exp2(-100.0f * (RelativeDepthDifference * RelativeDepthDifference)) : 0;
}
#endif
InterpolationWeights *= DepthWeights;
}
RWTexture2D<uint> RWScreenTileAdaptiveProbeHeader;
RWTexture2D<uint> RWScreenTileAdaptiveProbeIndices;
RWStructuredBuffer<uint> RWAdaptiveScreenProbeData;
struct FScreenProbeSample
{
uint2 AtlasCoord[4];
float4 Weights;
};
float GetAdaptiveProbeInterpolationWeight(float2 ScreenCoord, float4 ScenePlane, float SceneDepth, bool bFoliage, uint2 AdaptiveProbeScreenPosition, float AdaptiveProbeDepth)
{
float NewDepthWeight = 0;
bool bPlaneWeighting = true;
if (bPlaneWeighting)
{
float3 ProbePosition = GetWorldPositionFromScreenUV(GetScreenUVFromScreenProbePosition(AdaptiveProbeScreenPosition), AdaptiveProbeDepth);
float PlaneDistance = abs(dot(float4(ProbePosition, -1), ScenePlane));
float RelativeDepthDifference = abs(PlaneDistance / SceneDepth);
NewDepthWeight = exp2((bFoliage ? ScreenProbeInterpolationDepthWeightForFoliage : ScreenProbeInterpolationDepthWeight) * RelativeDepthDifference);
}
else
{
float DepthDifference = abs(AdaptiveProbeDepth - SceneDepth);
float RelativeDepthDifference = DepthDifference / SceneDepth;
NewDepthWeight = AdaptiveProbeDepth > 0 ? exp2(-100.0f * (RelativeDepthDifference * RelativeDepthDifference)) : 0;
}
float2 DistanceToScreenProbe = abs(AdaptiveProbeScreenPosition - ScreenCoord);
float NewCornerWeight = 1.0f - saturate(min(DistanceToScreenProbe.x, DistanceToScreenProbe.y) / (float)ScreenProbeDownsampleFactor);
float NewInterpolationWeight = NewDepthWeight * NewCornerWeight;
return NewInterpolationWeight;
}
void CalculateUpsampleInterpolationWeights(
float2 ScreenCoord,
float2 NoiseOffset,
float3 WorldPosition,
float SceneDepth,
float3 WorldNormal,
bool bIsUpsamplePass,
bool bUseAdaptiveProbes,
bool bFoliage,
out FScreenProbeSample ScreenProbeSample)
{
uint2 ScreenTileCoord00;
CalculateUniformUpsampleInterpolationWeights(ScreenCoord, NoiseOffset, WorldPosition, SceneDepth, WorldNormal, bIsUpsamplePass, bFoliage, ScreenTileCoord00, ScreenProbeSample.Weights);
ScreenProbeSample.AtlasCoord[0] = ScreenTileCoord00;
ScreenProbeSample.AtlasCoord[1] = ScreenTileCoord00 + uint2(1, 0);
ScreenProbeSample.AtlasCoord[2] = ScreenTileCoord00 + uint2(0, 1);
ScreenProbeSample.AtlasCoord[3] = ScreenTileCoord00 + uint2(1, 1);
if (bUseAdaptiveProbes)
{
float4 ScenePlane = float4(WorldNormal, dot(WorldPosition, WorldNormal));
UNROLL
for (uint CornerIndex = 0; CornerIndex < 4; CornerIndex++)
{
if (ScreenProbeSample.Weights[CornerIndex] < MIN_PROBE_INTERPOLATION_WEIGHT)
{
uint2 ScreenTileCoord = ScreenTileCoord00 + uint2(CornerIndex % 2, CornerIndex / 2);
uint NumAdaptiveProbes = bIsUpsamplePass ? ScreenTileAdaptiveProbeHeader[ScreenTileCoord] : RWScreenTileAdaptiveProbeHeader[ScreenTileCoord];
for (uint AdaptiveProbeListIndex = 0; AdaptiveProbeListIndex < NumAdaptiveProbes; AdaptiveProbeListIndex++)
{
uint2 AdaptiveProbeCoord = GetAdaptiveProbeCoord(ScreenTileCoord, AdaptiveProbeListIndex);
uint AdaptiveProbeIndex = bIsUpsamplePass ? ScreenTileAdaptiveProbeIndices[AdaptiveProbeCoord] : RWScreenTileAdaptiveProbeIndices[AdaptiveProbeCoord];
uint ScreenProbeIndex = AdaptiveProbeIndex + NumUniformScreenProbes;
uint2 ScreenProbeScreenPosition = bIsUpsamplePass ? GetScreenProbeScreenPosition(ScreenProbeIndex) : DecodeScreenProbeData(RWAdaptiveScreenProbeData[AdaptiveProbeIndex]);
uint2 ScreenProbeAtlasCoord = uint2(ScreenProbeIndex % ScreenProbeAtlasViewSize.x, ScreenProbeIndex / ScreenProbeAtlasViewSize.x);
float ProbeDepth = bIsUpsamplePass ? GetScreenProbeDepth(ScreenProbeAtlasCoord) : GetScreenProbeDepthFromUAV(ScreenProbeAtlasCoord);
const float NewInterpolationWeight = GetAdaptiveProbeInterpolationWeight(
ScreenCoord,
ScenePlane,
SceneDepth,
bFoliage,
ScreenProbeScreenPosition,
ProbeDepth);
if (NewInterpolationWeight > ScreenProbeSample.Weights[CornerIndex])
{
ScreenProbeSample.Weights[CornerIndex] = NewInterpolationWeight;
ScreenProbeSample.AtlasCoord[CornerIndex] = ScreenProbeAtlasCoord;
}
}
}
}
}
}
RWBuffer<uint> RWScreenProbeIndirectArgs;
void WriteArgs2D(uint Index, uint GroupSize, uint2 ThreadCount)
{
WriteDispatchIndirectArgs(RWScreenProbeIndirectArgs, Index,
(ThreadCount.x + GroupSize - 1) / GroupSize,
(ThreadCount.y + GroupSize - 1) / GroupSize,
1);
}
#ifdef SetupAdaptiveProbeIndirectArgsCS
[numthreads(1, 1, 1)]
void SetupAdaptiveProbeIndirectArgsCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint2 AtlasSizeInProbes = uint2(ScreenProbeAtlasViewSize.x, (GetNumScreenProbes() + ScreenProbeAtlasViewSize.x - 1) / ScreenProbeAtlasViewSize.x);
// Must match EScreenProbeIndirectArgs in C++
WriteArgs2D(0, PROBE_THREADGROUP_SIZE_2D, AtlasSizeInProbes * PROBE_THREADGROUP_SIZE_2D); // GroupPerProbe
WriteArgs2D(1, PROBE_THREADGROUP_SIZE_2D, AtlasSizeInProbes); // ThreadPerProbe
WriteArgs2D(2, 16, AtlasSizeInProbes * ScreenProbeTracingOctahedronResolution); // TraceCompaction
WriteArgs2D(3, PROBE_THREADGROUP_SIZE_2D, AtlasSizeInProbes * ScreenProbeTracingOctahedronResolution); // ThreadPerTrace,
WriteArgs2D(4, PROBE_THREADGROUP_SIZE_2D, AtlasSizeInProbes * ScreenProbeGatherOctahedronResolution);
WriteArgs2D(5, PROBE_THREADGROUP_SIZE_2D, AtlasSizeInProbes * ScreenProbeGatherOctahedronResolutionWithBorder);
}
#endif
#ifdef MarkRadianceProbesUsedByScreenProbesCS
[numthreads(PROBE_THREADGROUP_SIZE_2D, PROBE_THREADGROUP_SIZE_2D, 1)]
void MarkRadianceProbesUsedByScreenProbesCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint2 ScreenProbeAtlasCoord = DispatchThreadId.xy;
uint ScreenProbeIndex = ScreenProbeAtlasCoord.y * ScreenProbeAtlasViewSize.x + ScreenProbeAtlasCoord.x;
uint2 ScreenProbeScreenPosition = GetScreenProbeScreenPosition(ScreenProbeIndex);
if (ScreenProbeIndex < GetNumScreenProbes() && ScreenProbeAtlasCoord.x < ScreenProbeAtlasViewSize.x)
{
float2 ScreenUV = GetScreenUVFromScreenProbePosition(ScreenProbeScreenPosition);
float SceneDepth = GetScreenProbeDepth(ScreenProbeAtlasCoord);
float3 WorldPosition = GetWorldPositionFromScreenUV(ScreenUV, SceneDepth);
if (SceneDepth > 0)
{
uint ClipmapIndex = GetRadianceProbeClipmapForMark(WorldPosition, 0);
if (IsValidRadianceCacheClipmapForMark(ClipmapIndex))
{
//@todo - cull by screen size
//@todo - cull probes too small for voxel tracing and too large for max trace distance
MarkPositionUsedInIndirectionTexture(WorldPosition, ClipmapIndex);
}
}
}
}
#endif
#ifdef MarkRadianceProbesUsedByHairStrandsCS
int2 HairStrandsResolution;
float2 HairStrandsInvResolution;
uint HairStrandsMip;
#include "../HairStrands/HairStrandsTileCommon.ush"
[numthreads(64, 1, 1)]
void MarkRadianceProbesUsedByHairStrandsCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint2 PixelCoords = 0;
{
const uint TileLinearIndex = DispatchThreadId.x;
const uint TileCount = HairStrands.HairTileCount[HAIRTILE_HAIR_ALL];
if (TileLinearIndex >= TileCount)
return;
// Position of a 8x8 tile
PixelCoords = HairStrands.HairTileData[TileLinearIndex];
}
// HZB starts at MIP1
const float SceneDepth = ConvertFromDeviceZ(HairStrands.HairOnlyDepthClosestHZBTexture.Load(uint3(PixelCoords, HairStrandsMip - 1)).r);
// Two "mark-used" methods:
// * Fast : use the center of the tile to estimate the used probe
// * Accurate: compute the AABB of the tile in world-space, and mark all used radiance probes
#define MARK_PROBE_FAST 0
#define MARK_PROBE_ACCUMRATE 1
#define HAIRSTRANDS_MARK_USED_METHOD MARK_PROBE_ACCUMRATE
if (SceneDepth > 0)
#if HAIRSTRANDS_MARK_USED_METHOD == MARK_PROBE_ACCUMRATE
{
const float3 P0 = GetWorldPositionFromScreenUV((PixelCoords + uint2(0, 0)) * HairStrandsInvResolution, SceneDepth);
const float3 P1 = GetWorldPositionFromScreenUV((PixelCoords + uint2(1, 1)) * HairStrandsInvResolution, SceneDepth);
const float3 MinAABB = min(P0, P1);
const float3 MaxAABB = max(P0, P1);
const FRadianceProbeCoord MinAABBProbe = GetRadianceProbeCoord(MinAABB, .01f);
const FRadianceProbeCoord MaxAABBProbe = GetRadianceProbeCoord(MaxAABB, .01f);
// If AABB is withing the same interpolated probes
if (all(MinAABBProbe.ProbeMinCoord == MaxAABBProbe.ProbeMinCoord) && MinAABBProbe.ClipmapIndex == MaxAABBProbe.ClipmapIndex)
{
if (IsValidRadianceCacheClipmapForMark(MinAABBProbe.ClipmapIndex))
{
MarkPositionUsedInIndirectionTexture(MinAABB, MinAABBProbe.ClipmapIndex);
}
}
else
{
const float3 P0 = float3(MinAABB.x, MinAABB.y, MinAABB.z);
const float3 P1 = float3(MaxAABB.x, MinAABB.y, MinAABB.z);
const float3 P2 = float3(MinAABB.x, MaxAABB.y, MinAABB.z);
const float3 P3 = float3(MaxAABB.x, MaxAABB.y, MinAABB.z);
const float3 P4 = float3(MinAABB.x, MinAABB.y, MaxAABB.z);
const float3 P5 = float3(MaxAABB.x, MinAABB.y, MaxAABB.z);
const float3 P6 = float3(MinAABB.x, MaxAABB.y, MaxAABB.z);
const float3 P7 = float3(MaxAABB.x, MaxAABB.y, MaxAABB.z);
// If AABB is within the same clipmap
if (MinAABBProbe.ClipmapIndex == MaxAABBProbe.ClipmapIndex)
{
const uint ClipmapIndex = MinAABBProbe.ClipmapIndex;
if (IsValidRadianceCacheClipmapForMark(ClipmapIndex))
{
MarkPositionUsedInIndirectionTexture(P0, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P1, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P2, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P3, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P4, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P5, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P6, ClipmapIndex);
MarkPositionUsedInIndirectionTexture(P7, ClipmapIndex);
}
}
else
{
// If AABB is crossing clipmap boundary
const uint ClipmapIndex0 = GetRadianceProbeClipmapForMark(P0); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex0)) { MarkPositionUsedInIndirectionTexture(P0, ClipmapIndex0); }
const uint ClipmapIndex1 = GetRadianceProbeClipmapForMark(P1); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex1)) { MarkPositionUsedInIndirectionTexture(P1, ClipmapIndex1); }
const uint ClipmapIndex2 = GetRadianceProbeClipmapForMark(P2); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex2)) { MarkPositionUsedInIndirectionTexture(P2, ClipmapIndex2); }
const uint ClipmapIndex3 = GetRadianceProbeClipmapForMark(P3); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex3)) { MarkPositionUsedInIndirectionTexture(P3, ClipmapIndex3); }
const uint ClipmapIndex4 = GetRadianceProbeClipmapForMark(P4); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex4)) { MarkPositionUsedInIndirectionTexture(P4, ClipmapIndex4); }
const uint ClipmapIndex5 = GetRadianceProbeClipmapForMark(P5); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex5)) { MarkPositionUsedInIndirectionTexture(P5, ClipmapIndex5); }
const uint ClipmapIndex6 = GetRadianceProbeClipmapForMark(P6); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex6)) { MarkPositionUsedInIndirectionTexture(P6, ClipmapIndex6); }
const uint ClipmapIndex7 = GetRadianceProbeClipmapForMark(P7); if (IsValidRadianceCacheClipmapForMark(ClipmapIndex7)) { MarkPositionUsedInIndirectionTexture(P7, ClipmapIndex7); }
}
}
}
#else // HAIRSTRANDS_MARK_USED_METHOD == MARK_PROBE_FAST
{
const float3 P0 = GetWorldPositionFromScreenUV((PixelCoords + float2(0.5f, 0.5f)) * HairStrandsInvResolution, SceneDepth);
const uint ClipmapIndex = GetRadianceProbeClipmapForMark(P0);
if (IsValidRadianceCacheClipmapForMark(ClipmapIndex))
{
MarkPositionUsedInIndirectionTexture(P0, ClipmapIndex);
}
}
#endif // HAIRSTRANDS_MARK_USED_METHOD
}
#endif
#define INTEGRATE_TILE_SIZE 8
#define INTEGRATE_TILE_SIZE_DIV_AS_SHIFT 3
#if SUBSTRATE_ENABLED && (INTEGRATE_TILE_SIZE != SUBSTRATE_TILE_SIZE)
#error Lumen diffuse integrate tile size needs to have the same size as the Substrate tiles
#endif
#define TILE_CLASSIFICATION_SIMPLE_DIFFUSE 0
#define TILE_CLASSIFICATION_SUPPORT_IMPORTANCE_SAMPLE_BRDF 1
// SampleBxDF bloats VGPR requirements due to Hair shading
#define TILE_CLASSIFICATION_SUPPORT_ALL 2
#define TILE_CLASSIFICATION_NUM 3
RWBuffer<uint> RWIntegrateIndirectArgs;
RWTexture2DArray<float3> RWDiffuseIndirect;
RWTexture2DArray<UNORM float> RWLightIsMoving;
RWTexture2DArray<float3> RWBackfaceDiffuseIndirect;
RWTexture2DArray<float3> RWRoughSpecularIndirect;
RWTexture2DArray<uint> RWTileClassificationModes;
uint DefaultDiffuseIntegrationMethod;
float MaxRoughnessToEvaluateRoughSpecular;
float MaxRoughnessToEvaluateRoughSpecularForFoliage;
// Computes the lerp factor to diffuse for rough specular, as an optimization to skip rough specular computations
// 1 = fully diffuse and rough reflection computation can be skipped
float RoughReflectionsDiffuseLerp(FLumenMaterialData Material)
{
const bool bHasBackfaceDiffuse = HasBackfaceDiffuse(Material);
// Can skip rough reflections if this will be anyway handled by the dedicated Lumen reflections
const float LumenSpecularRayAlpha = LumenCombineReflectionsAlpha(Material.Roughness, bHasBackfaceDiffuse);
if (LumenSpecularRayAlpha >= 1.0f && !NeedsLumenClearCoatRoughReflections(Material.TopLayerRoughness, IsClearCoat(Material)))
{
return 1.0f;
}
float FadeLength = 0.2f;
return saturate((Material.Roughness - (bHasBackfaceDiffuse ? MaxRoughnessToEvaluateRoughSpecularForFoliage : MaxRoughnessToEvaluateRoughSpecular) + FadeLength) / FadeLength);
}
groupshared uint SharedTileClassification[TILE_CLASSIFICATION_NUM];
// Highest quality and fastest for diffuse
#define DIFFUSE_INTEGRATION_SPHERICAL_HARMONIC 0
// Noisy and slow, but handles any shading model and GBuffer bent normal AO
#define DIFFUSE_INTEGRATION_IMPORTANCE_SAMPLE_BRDF 1
// Slow reference
#define DIFFUSE_INTEGRATION_NUMERICAL_INTEGRAL 2
uint GetDiffuseIntegrationMethod(FLumenMaterialData In)
{
uint DiffuseIntegrationMethod = DefaultDiffuseIntegrationMethod;
if (In.bRequiresBxDFImportanceSampling)
{
DiffuseIntegrationMethod = DIFFUSE_INTEGRATION_IMPORTANCE_SAMPLE_BRDF;
}
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
if (In.DiffuseIndirectSampleOcclusion != 0)
{
DiffuseIntegrationMethod = DIFFUSE_INTEGRATION_IMPORTANCE_SAMPLE_BRDF;
}
#endif
return DiffuseIntegrationMethod;
}
#ifndef PERMUTATION_OVERFLOW_TILE
#define PERMUTATION_OVERFLOW_TILE 0
#endif
struct FScreenProbeIntegrateTileData
{
uint2 Coord;
uint ClosureIndex;
};
uint PackScreenProbeIntegrateTileData(FScreenProbeIntegrateTileData In)
{
return
#if SUBSTRATE_ENABLED && SUBTRATE_GBUFFER_FORMAT==1 && SUBSTRATE_MATERIAL_CLOSURE_COUNT > 1
((In.ClosureIndex & 0x7) << 24) |
#endif
PackTileCoord12bits(In.Coord);
}
FScreenProbeIntegrateTileData UnpackScreenProbeIntegrateTileData(uint In)
{
FScreenProbeIntegrateTileData Out;
Out.Coord = UnpackTileCoord12bits(In);
Out.ClosureIndex = SUBSTRATE_ENABLED && SUBTRATE_GBUFFER_FORMAT == 1 && SUBSTRATE_MATERIAL_CLOSURE_COUNT > 1? ((In>>24) & 0x7) : 0;
return Out;
}
// Return the indirect args offset depending on the permutation type (i.e., primary/overflow)
#define TILE_CLASSIFICATION_ARGS_OFFSET (PERMUTATION_OVERFLOW_TILE * TILE_CLASSIFICATION_NUM * DISPATCH_INDIRECT_UINT_COUNT)
#ifdef ScreenProbeTileClassificationMarkCS
#if OUTPUT_DOWNSAMPLED_DEPTH
RWTexture2D<float> RWDownsampledSceneDepth;
RWTexture2D<UNORM float3> RWDownsampledSceneWorldNormal;
#endif
[numthreads(INTEGRATE_TILE_SIZE, INTEGRATE_TILE_SIZE, 1)]
void ScreenProbeTileClassificationMarkCS(
uint2 GroupId : SV_GroupID,
uint2 DispatchThreadId : SV_DispatchThreadID,
uint2 GroupThreadId : SV_GroupThreadID)
{
if (DispatchThreadId.x < TILE_CLASSIFICATION_NUM * DISPATCH_INDIRECT_UINT_COUNT)
{
RWIntegrateIndirectArgs[TILE_CLASSIFICATION_ARGS_OFFSET + DispatchThreadId.x] = (DispatchThreadId.x % DISPATCH_INDIRECT_UINT_COUNT == 1 || DispatchThreadId.x % DISPATCH_INDIRECT_UINT_COUNT == 2) ? 1 : 0;
}
if (GroupThreadId.x < TILE_CLASSIFICATION_NUM)
{
SharedTileClassification[GroupThreadId.x] = 0;
}
GroupMemoryBarrierWithGroupSync();
const FLumenMaterialCoord Coord = GetLumenMaterialCoordDownsampled(DispatchThreadId, GroupId, GroupThreadId, INTEGRATE_DOWNSAMPLE_FACTOR, IntegrateViewMin, IntegrateViewSize);
const uint3 IntegrateCoord = Coord.DownsampledCoord;
const uint3 FlattenTileCoord = uint3(uint2(IntegrateCoord.xy - IntegrateViewMin) >> INTEGRATE_TILE_SIZE_DIV_AS_SHIFT, Coord.ClosureIndex);
if (Coord.bIsValid)
{
const FLumenMaterialData Material = ReadMaterialData(Coord, MaxRoughnessToTrace);
if (IsValid(Material))
{
uint TileClassification = TILE_CLASSIFICATION_SIMPLE_DIFFUSE;
if (IsHair(Material))
{
TileClassification = TILE_CLASSIFICATION_SUPPORT_ALL;
}
else
{
uint DiffuseIntegrationMethod = GetDiffuseIntegrationMethod(Material);
if (DiffuseIntegrationMethod == DIFFUSE_INTEGRATION_IMPORTANCE_SAMPLE_BRDF)
{
TileClassification = TILE_CLASSIFICATION_SUPPORT_IMPORTANCE_SAMPLE_BRDF;
}
const float DiffuseLerp = RoughReflectionsDiffuseLerp(Material);
if (DiffuseLerp < 1.0f)
{
TileClassification = TILE_CLASSIFICATION_SUPPORT_IMPORTANCE_SAMPLE_BRDF;
}
}
SharedTileClassification[TileClassification] = 1;
}
#if OUTPUT_DOWNSAMPLED_DEPTH && INTEGRATE_DOWNSAMPLE_FACTOR != 1
{
if (IntegrateCoord.z == 0)
{
RWDownsampledSceneDepth[IntegrateCoord.xy] = Material.SceneDepth;
RWDownsampledSceneWorldNormal[IntegrateCoord.xy] = EncodeNormal(Material.WorldNormal);
}
}
#endif
}
GroupMemoryBarrierWithGroupSync();
uint MaxTileClassification = 0xFF;
UNROLL
for (uint i = 0; i < TILE_CLASSIFICATION_NUM; i++)
{
if (SharedTileClassification[i] > 0)
{
MaxTileClassification = i;
}
}
if (all(GroupThreadId == 0) && Coord.bIsAnyValid)
{
RWTileClassificationModes[FlattenTileCoord] = MaxTileClassification;
}
// Clear DiffuseIndirect values the integration shader won't run on, as those values may get later read by GetFilteredNeighborhoodLighting or ClampHistory in ScreenProbeTemporalReprojectionCS
if (MaxTileClassification == 0xFF && Coord.bIsValid)
{
RWDiffuseIndirect[IntegrateCoord] = 0.0f;
RWLightIsMoving[IntegrateCoord] = 0.0f;
RWRoughSpecularIndirect[IntegrateCoord] = 0.0f;
#if SUPPORT_BACKFACE_DIFFUSE
RWBackfaceDiffuseIndirect[IntegrateCoord] = 0.0f;
#endif
}
}
#endif
RWStructuredBuffer<uint> RWIntegrateTileData;
Texture2DArray<uint> TileClassificationModes;
uint2 ViewportTileDimensions;
uint2 ViewportTileDimensionsWithOverflow;
uint MaxClosurePerPixel;
groupshared uint SharedNumTiles[TILE_CLASSIFICATION_NUM];
groupshared uint SharedIntegrateTileData[TILE_CLASSIFICATION_NUM * THREADGROUP_SIZE];
groupshared uint SharedGlobalTileOffset[TILE_CLASSIFICATION_NUM];
uint GetTileDataOffset(uint2 InViewportIntegrateTileDimensions, uint InMode, bool bOverflow)
{
// We don't know in advance the number of tiles to be stored for each technique. To avoid a double pass, we precompute conservative offfset.
// * Closure 0 are stored as = Technique . (TileDimensions.x . TileDimensions.y)
// * Closure 1-N are stored as = Technique . (TileDimensions.x . TileDimensions.y . MaxClosureCount) + Closure0Offset
const uint ViewportTileCount_Closure0 = InViewportIntegrateTileDimensions.x * InViewportIntegrateTileDimensions.y;
const uint ViewportTileCount_Closure1N = InViewportIntegrateTileDimensions.x * InViewportIntegrateTileDimensions.y * (MaxClosurePerPixel-1u);
uint Out = 0;
if (bOverflow)
{
Out = ViewportTileCount_Closure0 * TILE_CLASSIFICATION_NUM + ViewportTileCount_Closure1N * InMode;
}
else
{
Out = ViewportTileCount_Closure0 * InMode;
}
return Out;
}
#ifdef ScreenProbeTileClassificationBuildListsCS
[numthreads(THREADGROUP_SIZE, 1, 1)]
void ScreenProbeTileClassificationBuildListsCS(
uint2 GroupId : SV_GroupID,
uint GroupThreadId : SV_GroupThreadID)
{
//@todo - parallel version
if (GroupThreadId == 0)
{
#if SUBSTRATE_ENABLED && SUBTRATE_GBUFFER_FORMAT==1 && PERMUTATION_OVERFLOW_TILE
const uint TileCount = Substrate.ClosureTileCountBuffer[0];
#endif
UNROLL
for (uint i = 0; i < TILE_CLASSIFICATION_NUM; i++)
{
SharedNumTiles[i] = 0;
}
for (uint x = 0; x < THREADGROUP_SIZE; x++)
{
#if SUBSTRATE_ENABLED && SUBTRATE_GBUFFER_FORMAT==1 && PERMUTATION_OVERFLOW_TILE
const uint LinearIndex = GroupId.x * THREADGROUP_SIZE + x;
const bool bIsTileValid = LinearIndex < Substrate.ClosureTileCountBuffer[0];
FScreenProbeIntegrateTileData TileData = (FScreenProbeIntegrateTileData)0;
if (bIsTileValid)
{
const FSubstrateClosureTile Tile = UnpackClosureTile(Substrate.ClosureTileBuffer[LinearIndex]);
TileData.Coord = Tile.TileCoord;
TileData.ClosureIndex = Tile.ClosureIndex;
}
const bool bIsValid = bIsTileValid && all(TileData.Coord < ViewportTileDimensions);
#else
const uint2 ThreadOffset = ZOrder2D(x, log2(8));
FScreenProbeIntegrateTileData TileData;
TileData.Coord = GroupId * 8 + ThreadOffset;
TileData.ClosureIndex = 0;
const bool bIsValid = all(TileData.Coord < ViewportTileDimensions);
#endif
if (bIsValid)
{
uint Mode = TileClassificationModes[uint3(TileData.Coord, TileData.ClosureIndex)];
if (Mode != 0xFF)
{
uint TileOffset = SharedNumTiles[Mode];
SharedIntegrateTileData[Mode * THREADGROUP_SIZE + TileOffset] = PackScreenProbeIntegrateTileData(TileData);
SharedNumTiles[Mode] = TileOffset + 1;
}
}
}
}
GroupMemoryBarrierWithGroupSync();
// Hierarchical view of RWIntegrateIndirectArgs and RWIntegrateTileData layout
// [ Primary space ] [ Overflow space ]
// [ Viewport0 ] [ Viewport1 ] [ Viewport0 ] [ Viewport1 ]
// [Mode0][Mode1][Mode2] [Mode0][Mode1][Mode2] [Mode0][Mode1][Mode2] [Mode0][Mode1][Mode2]
// [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][]
if (GroupThreadId < TILE_CLASSIFICATION_NUM)
{
uint Mode = GroupThreadId;
InterlockedAdd(RWIntegrateIndirectArgs[TILE_CLASSIFICATION_ARGS_OFFSET + Mode * DISPATCH_INDIRECT_UINT_COUNT], SharedNumTiles[Mode], SharedGlobalTileOffset[Mode]);
}
GroupMemoryBarrierWithGroupSync();
for (uint ModeIndex = 0; ModeIndex < TILE_CLASSIFICATION_NUM; ModeIndex++)
{
if (GroupThreadId < SharedNumTiles[ModeIndex])
{
const uint ArgsOffset = GetTileDataOffset(ViewportTileDimensionsWithOverflow, ModeIndex, PERMUTATION_OVERFLOW_TILE);
RWIntegrateTileData[ArgsOffset + SharedGlobalTileOffset[ModeIndex] + GroupThreadId] = SharedIntegrateTileData[ModeIndex * THREADGROUP_SIZE + GroupThreadId];
}
}
}
#endif
Texture2D<float3> ScreenProbeRadianceSHAmbient;
Texture2D<float4> ScreenProbeRadianceSHDirectional;
Texture2D<float3> ScreenProbeIrradianceWithBorder;
FThreeBandSHVectorRGB GetScreenProbeSH(uint2 ScreenProbeAtlasCoord, float InterpolationWeight)
{
float3 AmbientVector = ScreenProbeRadianceSHAmbient[ScreenProbeAtlasCoord].xyz;
float4 SHCoefficients0Red = ScreenProbeRadianceSHDirectional[uint2(ScreenProbeAtlasCoord.x + 0 * ScreenProbeAtlasViewSize.x, ScreenProbeAtlasCoord.y)];
float4 SHCoefficients1Red = ScreenProbeRadianceSHDirectional[uint2(ScreenProbeAtlasCoord.x + 1 * ScreenProbeAtlasViewSize.x, ScreenProbeAtlasCoord.y)];
float4 SHCoefficients0Green = ScreenProbeRadianceSHDirectional[uint2(ScreenProbeAtlasCoord.x + 2 * ScreenProbeAtlasViewSize.x, ScreenProbeAtlasCoord.y)];
float4 SHCoefficients1Green = ScreenProbeRadianceSHDirectional[uint2(ScreenProbeAtlasCoord.x + 3 * ScreenProbeAtlasViewSize.x, ScreenProbeAtlasCoord.y)];
float4 SHCoefficients0Blue = ScreenProbeRadianceSHDirectional[uint2(ScreenProbeAtlasCoord.x + 4 * ScreenProbeAtlasViewSize.x, ScreenProbeAtlasCoord.y)];
float4 SHCoefficients1Blue = ScreenProbeRadianceSHDirectional[uint2(ScreenProbeAtlasCoord.x + 5 * ScreenProbeAtlasViewSize.x, ScreenProbeAtlasCoord.y)];
#if SH_QUANTIZE_DIRECTIONAL_COEFFICIENTS
float4 SHDenormalizationScales0 = float4(
0.488603f / 0.282095f,
0.488603f / 0.282095f,
0.488603f / 0.282095f,
1.092548f / 0.282095f);
float4 SHDenormalizationScales1 = float4(
1.092548f / 0.282095f,
4.0f * 0.315392f / 0.282095f,
1.092548f / 0.282095f,
2.0f * 0.546274f / 0.282095f);
SHCoefficients0Red = (SHCoefficients0Red * 2 - 1) * AmbientVector.x * SHDenormalizationScales0;
SHCoefficients1Red = (SHCoefficients1Red * 2 - 1) * AmbientVector.x * SHDenormalizationScales1;
SHCoefficients0Green = (SHCoefficients0Green * 2 - 1) * AmbientVector.y * SHDenormalizationScales0;
SHCoefficients1Green = (SHCoefficients1Green * 2 - 1) * AmbientVector.y * SHDenormalizationScales1;
SHCoefficients0Blue = (SHCoefficients0Blue * 2 - 1) * AmbientVector.z * SHDenormalizationScales0;
SHCoefficients1Blue = (SHCoefficients1Blue * 2 - 1) * AmbientVector.z * SHDenormalizationScales1;
#endif
FThreeBandSHVectorRGB LightingSH;
LightingSH.R.V0 = float4(AmbientVector.x, SHCoefficients0Red.xyz) * InterpolationWeight;
LightingSH.R.V1 = float4(SHCoefficients0Red.w, SHCoefficients1Red.xyz) * InterpolationWeight;
LightingSH.R.V2 = SHCoefficients1Red.w * InterpolationWeight;
LightingSH.G.V0 = float4(AmbientVector.y, SHCoefficients0Green.xyz) * InterpolationWeight;
LightingSH.G.V1 = float4(SHCoefficients0Green.w, SHCoefficients1Green.xyz) * InterpolationWeight;
LightingSH.G.V2 = SHCoefficients1Green.w * InterpolationWeight;
LightingSH.B.V0 = float4(AmbientVector.z, SHCoefficients0Blue.xyz) * InterpolationWeight;
LightingSH.B.V1 = float4(SHCoefficients0Blue.w, SHCoefficients1Blue.xyz) * InterpolationWeight;
LightingSH.B.V2 = SHCoefficients1Blue.w * InterpolationWeight;
return LightingSH;
}
float3 GetScreenProbeIrradiance(uint2 ScreenProbeAtlasCoord, float2 IrradianceProbeUV)
{
float2 IrradianceProbeUVCoord = IrradianceProbeUV * IRRADIANCE_PROBE_RES + 1.0f;
float2 AtlasUV = (ScreenProbeAtlasCoord * IRRADIANCE_PROBE_WITH_BORDER_RES + IrradianceProbeUVCoord) / (ScreenProbeAtlasBufferSize * IRRADIANCE_PROBE_WITH_BORDER_RES);
return ScreenProbeIrradianceWithBorder.SampleLevel(GlobalBilinearClampedSampler, AtlasUV, 0).xyz;
}
Texture2D<float> ScreenProbeExtraAOWithBorder;
float GetScreenProbeExtraAO(uint2 ScreenProbeAtlasCoord, float2 IrradianceProbeUV)
{
float2 IrradianceProbeUVCoord = IrradianceProbeUV * IRRADIANCE_PROBE_RES + 1.0f;
float2 AtlasUV = (ScreenProbeAtlasCoord * IRRADIANCE_PROBE_WITH_BORDER_RES + IrradianceProbeUVCoord) / (ScreenProbeAtlasBufferSize * IRRADIANCE_PROBE_WITH_BORDER_RES);
return ScreenProbeExtraAOWithBorder.SampleLevel(GlobalBilinearClampedSampler, AtlasUV, 0);
}
// Bias the important sampling of SampleBxDF(SHADING_TERM_SPECULAR, ....)
float4 BiasBSDFImportantSample(float4 E)
{
float Bias = 1.0 - 0.1;
E.y = (E.y - 0.5) * Bias + 0.5;
return E;
}
Texture2D<float3> ScreenProbeRadianceWithBorder;
Texture2D<float3> ScreenProbeRadiance;
float3 InterpolateFromScreenProbes(float3 ConeDirection, float MipLevel, FScreenProbeSample ScreenProbeSample)
{
float2 ProbeUV = InverseEquiAreaSphericalMapping(ConeDirection);
#define COMBINED_FACTORS 1
#if COMBINED_FACTORS
float2 AtlasUVMul = SampleRadianceAtlasUVMul;
float2 AtlasUVAdd = ProbeUV * SampleRadianceProbeUVMul + SampleRadianceProbeUVAdd;
#else
float BorderSize = exp2(ScreenProbeGatherMaxMip);
float2 ProbeCoord = ProbeUV * ScreenProbeGatherOctahedronResolution;
float2 InvBufferSize = 1.0f / (float2)(ScreenProbeGatherOctahedronResolutionWithBorder * ScreenProbeAtlasBufferSize);
float2 AtlasUVMul = ScreenProbeGatherOctahedronResolutionWithBorder * InvBufferSize;
float2 AtlasUVAdd = (ProbeCoord + BorderSize) * InvBufferSize;
#endif
float2 UV0 = ScreenProbeSample.AtlasCoord[0] * AtlasUVMul + AtlasUVAdd;
float3 InterpolatedRadiance = ScreenProbeRadianceWithBorder.SampleLevel(GlobalBilinearClampedSampler, UV0, MipLevel).xyz * ScreenProbeSample.Weights.x;
#if !STOCHASTIC_PROBE_INTERPOLATION
float2 UV1 = ScreenProbeSample.AtlasCoord[1] * AtlasUVMul + AtlasUVAdd;
InterpolatedRadiance += ScreenProbeRadianceWithBorder.SampleLevel(GlobalBilinearClampedSampler, UV1, MipLevel).xyz * ScreenProbeSample.Weights.y;
float2 UV2 = ScreenProbeSample.AtlasCoord[2] * AtlasUVMul + AtlasUVAdd;
InterpolatedRadiance += ScreenProbeRadianceWithBorder.SampleLevel(GlobalBilinearClampedSampler, UV2, MipLevel).xyz * ScreenProbeSample.Weights.z;
float2 UV3 = ScreenProbeSample.AtlasCoord[3] * AtlasUVMul + AtlasUVAdd;
InterpolatedRadiance += ScreenProbeRadianceWithBorder.SampleLevel(GlobalBilinearClampedSampler, UV3, MipLevel).xyz * ScreenProbeSample.Weights.w;
#endif
return InterpolatedRadiance;
}
float FullResolutionJitterWidth;
FBxDFSample SampleBxDFWrapper(const uint TermMask, FLumenMaterialData MaterialData, float3 V, float4 E)
{
#if INTEGRATE_TILE_CLASSIFICATION_MODE == TILE_CLASSIFICATION_SIMPLE_DIFFUSE || INTEGRATE_TILE_CLASSIFICATION_MODE == TILE_CLASSIFICATION_SUPPORT_IMPORTANCE_SAMPLE_BRDF
return SampleSimpleBxDF(TermMask, MaterialData, V, E);
#else
return SampleBxDF(TermMask, MaterialData, V, E);
#endif
}
float3 InterpolateScreenProbeIrradiance(FScreenProbeSample ScreenProbeSample, float3 WorldNormal)
{
float2 IrradianceProbeUV = InverseEquiAreaSphericalMapping(WorldNormal);
float3 Irradiance = GetScreenProbeIrradiance(ScreenProbeSample.AtlasCoord[0], IrradianceProbeUV) * ScreenProbeSample.Weights.x;
#if !STOCHASTIC_PROBE_INTERPOLATION
Irradiance += GetScreenProbeIrradiance(ScreenProbeSample.AtlasCoord[1], IrradianceProbeUV) * ScreenProbeSample.Weights.y;
Irradiance += GetScreenProbeIrradiance(ScreenProbeSample.AtlasCoord[2], IrradianceProbeUV) * ScreenProbeSample.Weights.z;
Irradiance += GetScreenProbeIrradiance(ScreenProbeSample.AtlasCoord[3], IrradianceProbeUV) * ScreenProbeSample.Weights.w;
#endif
return Irradiance;
}
float InterpolateScreenProbeExtraAO(FScreenProbeSample ScreenProbeSample, float3 WorldNormal)
{
float2 IrradianceProbeUV = InverseEquiAreaSphericalMapping(WorldNormal);
float AO = GetScreenProbeExtraAO(ScreenProbeSample.AtlasCoord[0], IrradianceProbeUV) * ScreenProbeSample.Weights.x;
#if !STOCHASTIC_PROBE_INTERPOLATION
AO += GetScreenProbeExtraAO(ScreenProbeSample.AtlasCoord[1], IrradianceProbeUV) * ScreenProbeSample.Weights.y;
AO += GetScreenProbeExtraAO(ScreenProbeSample.AtlasCoord[2], IrradianceProbeUV) * ScreenProbeSample.Weights.z;
AO += GetScreenProbeExtraAO(ScreenProbeSample.AtlasCoord[3], IrradianceProbeUV) * ScreenProbeSample.Weights.w;
#endif
return AO;
}
uint ApplyMaterialAO;
float MaxAOMultibounceAlbedo;
float LumenFoliageOcclusionStrength;
uint LumenReflectionInputIsSSR;
float3 EvaluateDiffuse(FScreenProbeSample ScreenProbeSample, FLumenMaterialData Material, float3 RoughSpecularMainDirection, bool bHasBackfaceDiffuse, float3 UnitBentNormal, float AO, inout float3 BackfaceDiffuseLighting, inout float3 RoughSpecularLighting)
{
float3 DiffuseLighting = 0.0f;
const float PreintegratedTwoSidedBxDF = 1.0f / PI;
float3 ProbeLightingNormal = Material.WorldNormal;
#if SHORT_RANGE_AO
// Material normal is a better bent normal approximation when there's no occlusion
ProbeLightingNormal = normalize(lerp(UnitBentNormal, Material.WorldNormal, AO));
#endif
#if PROBE_IRRADIANCE_FORMAT == PROBE_IRRADIANCE_FORMAT_SH3
{
FThreeBandSHVectorRGB LightingSH = GetScreenProbeSH(ScreenProbeSample.AtlasCoord[0], ScreenProbeSample.Weights.x);
#if !STOCHASTIC_PROBE_INTERPOLATION
LightingSH = AddSH(LightingSH, GetScreenProbeSH(ScreenProbeSample.AtlasCoord[1], ScreenProbeSample.Weights.y));
LightingSH = AddSH(LightingSH, GetScreenProbeSH(ScreenProbeSample.AtlasCoord[2], ScreenProbeSample.Weights.z));
LightingSH = AddSH(LightingSH, GetScreenProbeSH(ScreenProbeSample.AtlasCoord[3], ScreenProbeSample.Weights.w));
#endif
float3 SHDiffuseLighting = EvaluateSHIrradiance(ProbeLightingNormal, AO, LightingSH);
DiffuseLighting += 4 * PI * SHDiffuseLighting;
#if ROUGH_SPECULAR_SAMPLING_MODE == 1
// We do not have any valid bent normal / AO for the reflection so assuming a fully open cone.
//@todo - replace with SH fit of GGX
const float3 SHRoughSpecularLighting = EvaluateSHIrradiance(RoughSpecularMainDirection, AO, LightingSH);
RoughSpecularLighting += (4 * PI * SHRoughSpecularLighting) / PI;
#else
RoughSpecularLighting += 0; // Should not be used in this case.
#endif
#if SUPPORT_BACKFACE_DIFFUSE
if (bHasBackfaceDiffuse)
{
float3 BackfaceSHDiffuseLighting = EvaluateSHIrradiance(-Material.WorldNormal, 1.0f, LightingSH);
BackfaceDiffuseLighting += 4 * PI * PreintegratedTwoSidedBxDF * BackfaceSHDiffuseLighting;
}
#endif
}
#else
{
DiffuseLighting += InterpolateScreenProbeIrradiance(ScreenProbeSample, ProbeLightingNormal) * AO;
RoughSpecularLighting += InterpolateScreenProbeIrradiance(ScreenProbeSample, RoughSpecularMainDirection) * AO;
#if SUPPORT_BACKFACE_DIFFUSE
if (bHasBackfaceDiffuse)
{
BackfaceDiffuseLighting += PreintegratedTwoSidedBxDF * InterpolateScreenProbeIrradiance(ScreenProbeSample, -Material.WorldNormal);
}
#endif
}
#endif
// AO was already applied to DiffuseLighting, so apply here only AO boost approximating multi-bounce GI
// It would be more correct to apply it before (e.g. inside EvaluateSHIrradiance), but this is cheaper and looks pretty similar
if (AO > 0.0f)
{
DiffuseLighting *= AOMultiBounce(min(Material.DiffuseAlbedo, MaxAOMultibounceAlbedo), AO) / AO;
}
return DiffuseLighting;
}
float SimpleSpecularShading(float Roughness, float3 L, float3 V, half3 N)
{
const float NoV = saturate(dot(N, V));
float3 H = normalize(V + L);
float NoH = saturate(dot(N, H));
// Generalized microfacet specular
float D = D_GGX(Pow4(Roughness), NoH);
float Vis = Vis_Implicit();
return D * Vis;
}
float3 SimpleSpecularShading2(float Roughness, float3 SpecularColor, float3 L, float3 V, float3 N)
{
const float NoV = saturate(dot(N, V));
float NoL = saturate(dot(N, L));
float3 H = normalize(V + L);
float NoH = saturate(dot(N, H));
float VoH = saturate(dot(V, H));
float a2 = Pow4(Roughness);
// Generalized microfacet specular
float D = D_GGX(a2, NoH);
float Vis = Vis_SmithJointApprox(a2, NoV, NoL);
float3 F = F_Schlick(SpecularColor, VoH);
return (D * Vis) * F;
}
#define TONEMAP_DURING_INTEGRATION 1
float3 TonemapLighting(float3 Lighting)
{
#if TONEMAP_DURING_INTEGRATION
return Lighting / (1.0f + Luminance(Lighting));
#else
return Lighting;
#endif
}
float3 InverseTonemapLighting(float3 TonemappedLighting)
{
#if TONEMAP_DURING_INTEGRATION
return TonemappedLighting / (1.0f - Luminance(TonemappedLighting));
#else
return TonemappedLighting;
#endif
}
#define TILE_CLASSIFICATION_DISABLED TILE_CLASSIFICATION_NUM
StructuredBuffer<uint> IntegrateTileData;
#ifdef ScreenProbeIntegrateCS
[numthreads(INTEGRATE_TILE_SIZE, INTEGRATE_TILE_SIZE, 1)]
void ScreenProbeIntegrateCS(
uint2 DispatchThreadId : SV_DispatchThreadID,
uint GroupId : SV_GroupID,
uint2 GroupThreadId : SV_GroupThreadID)
{
#if INTEGRATE_TILE_CLASSIFICATION_MODE < TILE_CLASSIFICATION_DISABLED
const uint ArgsOffset = GetTileDataOffset(ViewportTileDimensionsWithOverflow, INTEGRATE_TILE_CLASSIFICATION_MODE, PERMUTATION_OVERFLOW_TILE);
// See data layout in ScreenProbeTileClassificationBuildListsCS
const FScreenProbeIntegrateTileData TileData = UnpackScreenProbeIntegrateTileData(IntegrateTileData[ArgsOffset + GroupId]);
#else
FScreenProbeIntegrateTileData TileData;
TileData.Coord = DispatchThreadId >> INTEGRATE_TILE_SIZE_DIV_AS_SHIFT;
TileData.ClosureIndex = 0;
#endif
FLumenMaterialCoord ScreenCoord;
ScreenCoord.SvPosition = (TileData.Coord * INTEGRATE_TILE_SIZE + GroupThreadId) * INTEGRATE_DOWNSAMPLE_FACTOR + View.ViewRectMin.xy + GetDownsampledCoordJitter(TileData.Coord * INTEGRATE_TILE_SIZE + GroupThreadId, INTEGRATE_DOWNSAMPLE_FACTOR);
// When downsampling fill last row and column even if jitter pushes it out of bounds
ScreenCoord.SvPosition.xy = min(ScreenCoord.SvPosition.xy, View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw - 1);
ScreenCoord.ClosureIndex = SUBSTRATE_ENABLED && SUBTRATE_GBUFFER_FORMAT == 1 && PERMUTATION_OVERFLOW_TILE ? TileData.ClosureIndex : 0;
ScreenCoord.SvPositionFlatten = uint3(ScreenCoord.SvPosition, ScreenCoord.ClosureIndex);
uint3 IntegrateCoord;
IntegrateCoord.xy = TileData.Coord * INTEGRATE_TILE_SIZE + GroupThreadId + IntegrateViewMin;
IntegrateCoord.z = ScreenCoord.ClosureIndex;
if (and(all(IntegrateCoord.xy >= IntegrateViewMin), all(IntegrateCoord.xy < IntegrateViewMin + IntegrateViewSize)))
{
FLumenMaterialData Material = ReadMaterialData(ScreenCoord, MaxRoughnessToTrace);
if (IsValid(Material))
{
const float2 ScreenUV = (ScreenCoord.SvPosition + 0.5f) * View.BufferSizeAndInvSize.zw;
const float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, Material.SceneDepth);
const float3 WorldPosition = TranslatedWorldPosition - DFHackToFloat(PrimaryView.PreViewTranslation);
const float3 WorldNormal = Material.WorldNormal;
float2 NoiseOffset = 0.0f;
if (FullResolutionJitterWidth > 0)
{
//@todo - expose fade distance
float EffectiveJitterWidth = FullResolutionJitterWidth * lerp(1.0f, .5f, saturate((Material.SceneDepth - 500.0f) / 500.0f));
//uint2 RandomSeed = Rand3DPCG16(int3(Coord.SvPosition, GENERAL_TEMPORAL_INDEX)).xy;
//float2 ScreenTileJitterE = Hammersley16(0, 1, RandomSeed);
float2 ScreenTileJitterE = BlueNoiseVec2(ScreenCoord.SvPosition, SCREEN_PROBE_JITTER_INDEX);
float2 JitterNoiseOffset = (ScreenTileJitterE * 2 - 1) * ScreenProbeDownsampleFactor * EffectiveJitterWidth;
float2 JitteredScreenUV = (clamp(ScreenCoord.SvPosition + JitterNoiseOffset, View.ViewRectMin.xy, View.ViewRectMin.xy + View.ViewSizeAndInvSize.xy - 1.0f)) * View.BufferSizeAndInvSize.zw;
float JitteredSceneDepth = CalcSceneDepth(JitteredScreenUV);
float DepthWeight;
{
float4 ScenePlane = float4(Material.WorldNormal, dot(WorldPosition, Material.WorldNormal));
float3 JitteredPosition = GetWorldPositionFromScreenUV(JitteredScreenUV, JitteredSceneDepth);
float PlaneDistance = abs(dot(float4(JitteredPosition, -1), ScenePlane));
float RelativeDepthDifference = PlaneDistance / Material.SceneDepth;
DepthWeight = exp2(-1000000.0f * (RelativeDepthDifference * RelativeDepthDifference));
}
if (DepthWeight > .01f)
{
NoiseOffset = JitterNoiseOffset;
}
}
FScreenProbeSample ScreenProbeSample = (FScreenProbeSample) 0;
CalculateUpsampleInterpolationWeights(
ScreenCoord.SvPosition,
NoiseOffset,
WorldPosition,
Material.SceneDepth,
Material.WorldNormal,
/*bIsUpsamplePass*/ true,
/*bUseAdaptiveProbes*/ true,
/*bFoliage*/ Material.bHasBackfaceDiffuse,
ScreenProbeSample);
const bool bLightingIsValid = dot(ScreenProbeSample.Weights, 1) >= MIN_PROBE_INTERPOLATION_WEIGHT;
const bool bSampleProbes = dot(ScreenProbeSample.Weights, 1) >= MIN_PROBE_INTERPOLATION_FALLBACK_WEIGHT;
if (bSampleProbes)
{
ScreenProbeSample.Weights /= max(dot(ScreenProbeSample.Weights, 1), MIN_PROBE_INTERPOLATION_FALLBACK_WEIGHT);
}
else
{
ScreenProbeSample.Weights = 0.0f;
}
FScreenProbeSample StochasticScreenProbeSample = ScreenProbeSample;
#if STOCHASTIC_PROBE_INTERPOLATION
{
// Pick a single best sample in a stochastic manner
//float RandomValue = InterleavedGradientNoise(Coord.SvPosition, GENERAL_TEMPORAL_INDEX);
float RandomValue = min(BlueNoiseScalar(ScreenCoord.SvPosition, SCREEN_PROBE_JITTER_INDEX), .99f);
float WeightSum = dot(ScreenProbeSample.Weights, 1.0f);
RandomValue *= WeightSum;
uint2 PickedScreenProbeAtlasCoord = 0;
if (RandomValue >= ScreenProbeSample.Weights[0] + ScreenProbeSample.Weights[1] + ScreenProbeSample.Weights[2])
{
PickedScreenProbeAtlasCoord = ScreenProbeSample.AtlasCoord[3];
}
else if (RandomValue >= ScreenProbeSample.Weights[0] + ScreenProbeSample.Weights[1])
{
PickedScreenProbeAtlasCoord = ScreenProbeSample.AtlasCoord[2];
}
else if (RandomValue >= ScreenProbeSample.Weights[0])
{
PickedScreenProbeAtlasCoord = ScreenProbeSample.AtlasCoord[1];
}
else
{
PickedScreenProbeAtlasCoord = ScreenProbeSample.AtlasCoord[0];
}
StochasticScreenProbeSample.AtlasCoord[0] = PickedScreenProbeAtlasCoord;
StochasticScreenProbeSample.AtlasCoord[1] = PickedScreenProbeAtlasCoord;
StochasticScreenProbeSample.AtlasCoord[2] = PickedScreenProbeAtlasCoord;
StochasticScreenProbeSample.AtlasCoord[3] = PickedScreenProbeAtlasCoord;
StochasticScreenProbeSample.Weights = float4(bSampleProbes ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f);
ScreenProbeSample = StochasticScreenProbeSample;
}
#endif // STOCHASTIC_PROBE_INTERPOLATION
float3 V = -GetCameraVectorFromTranslatedWorldPosition(TranslatedWorldPosition);
float3 UnitBentNormal = Material.WorldNormal;
float AO = 1.0f;
float3 DiffuseLighting = 0;
float3 RoughSpecularLighting = 0;
float3 BackfaceDiffuseLighting = 0;
#if SHORT_RANGE_AO
{
float3 BentNormal = UnpackScreenBentNormal(ShortRangeAOTexture[IntegrateCoord]);
AO = length(BentNormal);
UnitBentNormal = AO > 0 ? BentNormal / AO : Material.WorldNormal;
AO = saturate(AO);
if (Material.bHasBackfaceDiffuse)
{
AO = lerp(1, AO, LumenFoliageOcclusionStrength);
}
}
#endif // SHORT_RANGE_AO
#if SCREEN_PROBE_EXTRA_AO
{
AO = min(AO, InterpolateScreenProbeExtraAO(ScreenProbeSample, Material.WorldNormal));
}
#endif
const uint DiffuseIntegrationMethod = GetDiffuseIntegrationMethod(Material);
if (DiffuseIntegrationMethod == DIFFUSE_INTEGRATION_SPHERICAL_HARMONIC)
{
float3 R = 2.0f * dot(V, Material.WorldNormal) * Material.WorldNormal - V;
R = normalize(GetOffSpecularPeakReflectionDir(Material.WorldNormal, R, Material.Roughness));
DiffuseLighting += EvaluateDiffuse(ScreenProbeSample, Material, R, Material.bHasBackfaceDiffuse, UnitBentNormal, AO, BackfaceDiffuseLighting, RoughSpecularLighting);
}
#if INTEGRATE_TILE_CLASSIFICATION_MODE != TILE_CLASSIFICATION_SIMPLE_DIFFUSE
else if (DiffuseIntegrationMethod == DIFFUSE_INTEGRATION_IMPORTANCE_SAMPLE_BRDF)
{
// This could be configurable if not for GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
uint NumPixelSamples = INDIRECT_SAMPLE_COUNT;
const uint TermMask = SHADING_TERM_DIFFUSE | SHADING_TERM_HAIR_R | SHADING_TERM_HAIR_TT | SHADING_TERM_HAIR_TRT;
//@todo - calculate based on solid angle
float DiffuseMipLevel = ScreenProbeGatherMaxMip;
FSphericalGaussian HemisphereSG = Hemisphere_ToSphericalGaussian(Material.WorldNormal);
FSphericalGaussian VisibleSG = BentNormalAO_ToSphericalGaussian(UnitBentNormal, AO);
for (uint PixelRayIndex = 0; PixelRayIndex < NumPixelSamples; PixelRayIndex += 1)
{
float4 E = ComputeIndirectLightingSampleE(ScreenCoord.SvPosition, PixelRayIndex, NumPixelSamples);
FBxDFSample BxDFSample = SampleBxDFWrapper(TermMask, Material, V, E);
float3 InterpolatedRadiance = InterpolateFromScreenProbes(BxDFSample.L, DiffuseMipLevel, StochasticScreenProbeSample);
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
// GetDiffuseIndirectSampleOcclusion in BasePassPixelShader encodes visibility bit using the same ComputeIndirectLightingSampleE
bool bIsBentNormalOccluded = ApplyMaterialAO > 0 ? ((Material.DiffuseIndirectSampleOcclusion & (1u << PixelRayIndex)) != 0) : false;
float DirectionVisibility = bIsBentNormalOccluded ? 0 : 1;
#else
float DirectionVisibility = 1.0f;
#endif
#if SHORT_RANGE_AO || SCREEN_PROBE_EXTRA_AO
float LVisibility = saturate(Evaluate(VisibleSG, BxDFSample.L) / Evaluate(HemisphereSG, BxDFSample.L));
// #lumen_todo: temporarily apply scalar AO at the end in order to match ApplyDuringIntegration 0 path better
//DirectionVisibility *= LVisibility;
#endif
DiffuseLighting += InterpolatedRadiance * BxDFSample.Weight * DirectionVisibility;
}
DiffuseLighting = DiffuseLighting * PI / ((float)NumPixelSamples);
DiffuseLighting *= DistantIlluminationRescale(min(Material.DiffuseAlbedo, MaxAOMultibounceAlbedo), AO);
#if GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
if (ApplyMaterialAO > 0)
{
DiffuseLighting *= AOMultiBounce(min(Material.DiffuseAlbedo, MaxAOMultibounceAlbedo), Material.MaterialAO) * (Material.MaterialAO > 0.0 ? rcp(Material.MaterialAO) : 0.0);
}
#endif
}
#endif // INTEGRATE_TILE_CLASSIFICATION_MODE != TILE_CLASSIFICATION_SIMPLE_DIFFUSE
#if INTEGRATE_TILE_CLASSIFICATION_MODE == TILE_CLASSIFICATION_DISABLED
else if (DiffuseIntegrationMethod == DIFFUSE_INTEGRATION_NUMERICAL_INTEGRAL)
{
int NumValidSamples = 0;
const float InvScreenProbeResolution = 1.0f / ScreenProbeGatherOctahedronResolution;
float ScreenProbeResolutionFloat = ScreenProbeGatherOctahedronResolution;
FSphericalGaussian HemisphereSG = Hemisphere_ToSphericalGaussian(Material.WorldNormal);
FSphericalGaussian VisibleSG = BentNormalAO_ToSphericalGaussian(UnitBentNormal, AO);
#if SUPPORT_BACKFACE_DIFFUSE
const bool bHasBackfaceDiffuse = Material.bHasBackfaceDiffuse;
#else
const bool bHasBackfaceDiffuse = false;
#endif
for (float Y = 0; Y < ScreenProbeResolutionFloat; Y++)
{
for (float X = 0; X < ScreenProbeResolutionFloat; X++)
{
float2 ProbeTexelCenter = float2(0.5, 0.5);
float2 ProbeUV = (float2(X, Y) + ProbeTexelCenter) * InvScreenProbeResolution;
float3 WorldConeDirection = EquiAreaSphericalMapping(ProbeUV);
float NdotL = dot(WorldConeDirection, Material.WorldNormal);
if (NdotL > 0 || bHasBackfaceDiffuse)
{
float SampleWeight = saturate(NdotL);
float3 InterpolatedRadiance;
uint2 ProbeCoord = uint2(X, Y);
InterpolatedRadiance = ScreenProbeSample.Weights.x > 0 ? ScreenProbeRadiance.Load(int3(ScreenProbeSample.AtlasCoord[0] * ScreenProbeGatherOctahedronResolution + ProbeCoord, 0)).xyz * ScreenProbeSample.Weights.x : 0;
if (ScreenProbeSample.Weights.y > 0)
{
InterpolatedRadiance += ScreenProbeRadiance.Load(int3(ScreenProbeSample.AtlasCoord[1] * ScreenProbeGatherOctahedronResolution + ProbeCoord, 0)).xyz * ScreenProbeSample.Weights.y;
}
if (ScreenProbeSample.Weights.z > 0)
{
InterpolatedRadiance += ScreenProbeRadiance.Load(int3(ScreenProbeSample.AtlasCoord[2] * ScreenProbeGatherOctahedronResolution + ProbeCoord, 0)).xyz * ScreenProbeSample.Weights.z;
}
if (ScreenProbeSample.Weights.w > 0)
{
InterpolatedRadiance += ScreenProbeRadiance.Load(int3(ScreenProbeSample.AtlasCoord[3] * ScreenProbeGatherOctahedronResolution + ProbeCoord, 0)).xyz * ScreenProbeSample.Weights.w;
}
float DirectionalOcclusion = 1.0f;
#if SHORT_RANGE_AO || SCREEN_PROBE_EXTRA_AO
float LVisibility = saturate(Evaluate(VisibleSG, WorldConeDirection) / Evaluate(HemisphereSG, WorldConeDirection));
// #lumen_todo: temporarily apply scalar AO at the end in order to match ApplyDuringIntegration 0 path better
//DirectionalOcclusion *= LVisibility;
#endif
DiffuseLighting += InterpolatedRadiance * SampleWeight * DirectionalOcclusion;
if (bHasBackfaceDiffuse)
{
// SUBSTRATE_TODO: this sampling routine only match the 'wrap' case, but not the other routine. Need to revist this.
// Match TwoSidedBxDF
half Wrap = 0.5;
half WrapNoL = saturate((-dot(Material.WorldNormal, WorldConeDirection) + Wrap) / Square(1 + Wrap));
half VoL = dot(V, WorldConeDirection);
float Scatter = D_GGX(0.6 * 0.6, saturate(-VoL));
float BackfaceLightingSampleWeight = WrapNoL * Scatter;
BackfaceDiffuseLighting += InterpolatedRadiance * BackfaceLightingSampleWeight;
}
NumValidSamples++;
}
}
}
if (NumValidSamples > 0)
{
float NormalizeTerm = 2 * PI / (NumValidSamples);
DiffuseLighting *= NormalizeTerm;
BackfaceDiffuseLighting *= NormalizeTerm;
}
DiffuseLighting *= DistantIlluminationRescale(min(Material.DiffuseAlbedo, MaxAOMultibounceAlbedo), AO);
}
#endif // INTEGRATE_TILE_CLASSIFICATION_MODE == TILE_CLASSIFICATION_DISABLED
#if !GBUFFER_HAS_DIFFUSE_SAMPLE_OCCLUSION
if (ApplyMaterialAO > 0)
{
DiffuseLighting *= AOMultiBounce(min(Material.DiffuseAlbedo, MaxAOMultibounceAlbedo), Material.MaterialAO);
}
#endif
float LightingIsMoving = GetScreenProbeMoving(StochasticScreenProbeSample.AtlasCoord[0]) * StochasticScreenProbeSample.Weights.x;
#if !STOCHASTIC_PROBE_INTERPOLATION
LightingIsMoving += GetScreenProbeMoving(StochasticScreenProbeSample.AtlasCoord[1]) * StochasticScreenProbeSample.Weights.y;
LightingIsMoving += GetScreenProbeMoving(StochasticScreenProbeSample.AtlasCoord[2]) * StochasticScreenProbeSample.Weights.z;
LightingIsMoving += GetScreenProbeMoving(StochasticScreenProbeSample.AtlasCoord[3]) * StochasticScreenProbeSample.Weights.w;
#endif
// EncodedAlpha is stored to an R8 texture so the clamp needs to be >= 1/255 which is slightly less than 0.004
float EncodedAlpha = bLightingIsValid ? max(LightingIsMoving, 0.004f) : 0.0f;
float3 DiffuseIndirectOutput = DiffuseLighting * Diffuse_Lambert(float3(1, 1, 1));
#define DEBUG_VISUALIZE_PROBE_WORLD_SPEED 0
#if DEBUG_VISUALIZE_PROBE_WORLD_SPEED
float InterpolatedWorldSpeed = GetScreenProbeSpeed(ScreenProbeSample.AtlasCoord[0]) * StochasticScreenProbeSample.Weights.x
+ GetScreenProbeSpeed(ScreenProbeSample.AtlasCoord[1]) * StochasticScreenProbeSample.Weights.y
+ GetScreenProbeSpeed(ScreenProbeSample.AtlasCoord[2]) * StochasticScreenProbeSample.Weights.z
+ GetScreenProbeSpeed(ScreenProbeSample.AtlasCoord[3]) * StochasticScreenProbeSample.Weights.w;
DiffuseIndirectOutput = abs(InterpolatedWorldSpeed) / 2.0f;
#endif
#define DEBUG_VISUALIZE_INVALID_UPSAMPLE 0
#if DEBUG_VISUALIZE_INVALID_UPSAMPLE
if (!bLightingIsValid)
{
DiffuseIndirectOutput = float3(10, 0, 0);
}
#endif
#define DEBUG_VISUALIZE_TILE_CLASSIFICATION 0
#if DEBUG_VISUALIZE_TILE_CLASSIFICATION
float3 FastModeColor = float3(0, 1, 0);
float3 SlowModeColor = float3(1, 0, 0);
float3 TileClassificationColoring = lerp(FastModeColor, SlowModeColor, INTEGRATE_TILE_CLASSIFICATION_MODE / (float)(TILE_CLASSIFICATION_NUM - 1));
DiffuseIndirectOutput = TileClassificationColoring;
#endif
// FDiffuseIndirectCompositePS applies DiffuseColor
RWDiffuseIndirect[IntegrateCoord] = DiffuseIndirectOutput;
RWLightIsMoving[IntegrateCoord] = EncodedAlpha;
#if SUPPORT_BACKFACE_DIFFUSE
RWBackfaceDiffuseIndirect[IntegrateCoord] = BackfaceDiffuseLighting;
#endif
#if ROUGH_SPECULAR_SAMPLING_MODE == 1
float3 SpecularLighting = RoughSpecularLighting;
#else
float3 SpecularLighting = DiffuseLighting / PI;
#endif
const float DiffuseLerp = RoughReflectionsDiffuseLerp(Material);
// Prevent NaNs from ImportanceSampleVisibleGGX
Material.Roughness = max(Material.Roughness, 0.01f);
const float LumenSpecularRayAlpha = LumenCombineReflectionsAlpha(Material.Roughness, HasBackfaceDiffuse(Material));
// Rough-Specular
#if INTEGRATE_TILE_CLASSIFICATION_MODE != TILE_CLASSIFICATION_SIMPLE_DIFFUSE
uint NumSpecularSamples = 4;
if (DiffuseLerp < 1.0f)
{
// Prevent low roughness values that we can't support through screen probes with acceptable quality, eg clearcoat with bottom layer roughness 0
// Clamp to ~2x2 fooprint in a 16x16 probe
Material.Roughness = max(Material.Roughness, 0.2f);
// Optionally get the bottom normal (only when clear coat is enabled on the material, otherwise return the world normal)
Material.WorldNormal = GetClearCoatBottomNormal(Material);
//@todo - derive mip from cone angle from roughness
// Approximation made to move out of inner loop
float RayPDFForMip = 1.0f;
float SolidAngleSample = 1.0 / (NumSpecularSamples * RayPDFForMip);
float CosConeHalfAngle = 1.0 - SolidAngleSample / (2.0 * PI);
float NumTexels = sqrt(1.0f - CosConeHalfAngle) * ScreenProbeGatherOctahedronResolution;
float MipLevel = clamp(log2(NumTexels), 0, ScreenProbeGatherMaxMip);
FSphericalGaussian HemisphereSG = Hemisphere_ToSphericalGaussian(Material.WorldNormal);
FSphericalGaussian VisibleSG = BentNormalAO_ToSphericalGaussian(UnitBentNormal, AO);
float3 RoughSpecularLighting = 0.0f;
for (uint TracingRayIndex = 0; TracingRayIndex < NumSpecularSamples; TracingRayIndex++)
{
float4 E = ComputeIndirectLightingSampleE(ScreenCoord.SvPosition, TracingRayIndex, NumSpecularSamples);
E = BiasBSDFImportantSample(E);
FBxDFSample BxDFSample = SampleBxDFWrapper(SHADING_TERM_SPECULAR, Material, V, E);
float3 InterpolatedRadiance = InterpolateFromScreenProbes(BxDFSample.L, MipLevel, StochasticScreenProbeSample);
float DirectionVisibility = 1.0f;
#if SHORT_RANGE_AO || SCREEN_PROBE_EXTRA_AO
float LVisibility = saturate(Evaluate(VisibleSG, BxDFSample.L) / Evaluate(HemisphereSG, BxDFSample.L));
DirectionVisibility *= LVisibility;
#endif
RoughSpecularLighting += TonemapLighting(InterpolatedRadiance * BxDFSample.Weight * DirectionVisibility);
}
RoughSpecularLighting = InverseTonemapLighting(RoughSpecularLighting / (float)NumSpecularSamples);
SpecularLighting = lerp(RoughSpecularLighting, SpecularLighting, DiffuseLerp);
}
#endif // Rough-Specular
#if DEBUG_VISUALIZE_TILE_CLASSIFICATION
SpecularLighting = TileClassificationColoring;
#endif
RWRoughSpecularIndirect[IntegrateCoord] = SpecularLighting;
}
else
{
RWDiffuseIndirect[IntegrateCoord] = 0;
RWLightIsMoving[IntegrateCoord] = 0;
RWRoughSpecularIndirect[IntegrateCoord] = 0;
#if SUPPORT_BACKFACE_DIFFUSE
RWBackfaceDiffuseIndirect[IntegrateCoord] = 0;
#endif
}
}
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// Debug screen probe material classification
#ifdef ScreenProbeDebugMain
#include "../ShaderPrint.ush"
uint LayerCount;
int2 ViewportIntegrateTileDimensions;
Buffer<uint> IntegrateIndirectArgs;
// Return the number of tile
uint GetTileCount(uint InMode, bool bOverflow)
{
const uint Offset = InMode * DISPATCH_INDIRECT_UINT_COUNT + (bOverflow ? TILE_CLASSIFICATION_NUM * DISPATCH_INDIRECT_UINT_COUNT : 0);
return IntegrateIndirectArgs[Offset];
}
FFontColor GetValidColor(bool bIsValid)
{
FFontColor C = FontLightRed;
if (bIsValid) { C = FontLightGreen; }
return C;
}
void PrintTile(inout FShaderPrintContext Context, uint LinearCoord, uint InMode, bool bOverflow, uint TileIndexFilter, float4 TileColor)
{
if (LinearCoord < GetTileCount(InMode, bOverflow))
{
const uint TileDataOffset = LinearCoord + GetTileDataOffset(ViewportIntegrateTileDimensions, InMode, bOverflow);
const FScreenProbeIntegrateTileData TileData = UnpackScreenProbeIntegrateTileData(IntegrateTileData[TileDataOffset]);
if (TileData.ClosureIndex == TileIndexFilter)
{
AddFilledQuadSS(TileData.Coord * INTEGRATE_TILE_SIZE, TileData.Coord * INTEGRATE_TILE_SIZE + INTEGRATE_TILE_SIZE, TileColor);
}
}
}
void PrintTileLegend(inout FShaderPrintContext Context)
{
Print(Context, TEXT("Simple "), FontGreen); Newline(Context);
Print(Context, TEXT("ImportanceSample "), FontOrange); Newline(Context);
Print(Context, TEXT("All "), FontCyan); Newline(Context);
}
void PrintTiles(inout FShaderPrintContext Context, uint LinearCoord, bool bOverflow, int TileIndex)
{
const float Alpha = 0.5f;
float4 TileColor_Simple = ColorGreen; TileColor_Simple.a = Alpha;
float4 TileColor_IS = ColorOrange; TileColor_IS.a = Alpha;
float4 TileColor_All = ColorCyan; TileColor_All.a = Alpha;
PrintTile(Context, LinearCoord, TILE_CLASSIFICATION_SIMPLE_DIFFUSE, bOverflow, TileIndex, TileColor_Simple);
PrintTile(Context, LinearCoord, TILE_CLASSIFICATION_SUPPORT_IMPORTANCE_SAMPLE_BRDF, bOverflow, TileIndex, TileColor_IS);
PrintTile(Context, LinearCoord, TILE_CLASSIFICATION_SUPPORT_ALL, bOverflow, TileIndex, TileColor_All);
}
void PrintTileStats(inout FShaderPrintContext Context, bool bOverflow)
{
const uint Simple = GetTileCount(0, bOverflow);
const uint IS = GetTileCount(1, bOverflow);
const uint All = GetTileCount(2, bOverflow);
const uint Total = Simple + IS + All;
Print(Context, TEXT("Simple : "), FontSilver); Print(Context, Simple, FontSilver); Newline(Context);
Print(Context, TEXT("ImportanceSample : "), FontSilver); Print(Context, IS, FontSilver); Newline(Context);
Print(Context, TEXT("All : "), FontSilver); Print(Context, All, FontSilver); Newline(Context);
Print(Context, TEXT("Total : "), FontSilver); Print(Context, Total, GetValidColor(Total > 0)); Newline(Context);
}
[numthreads(1, 1, 1)]
void ScreenProbeDebugMain(uint3 DispatchThreadId : SV_DispatchThreadID)
{
FShaderPrintContext Context = InitShaderPrintContext(all(DispatchThreadId == 0), uint2(50, 100));
if (Context.bIsActive)
{
Print(Context, TEXT("Lumen Screen Probe"), FontOrange);
Newline(Context);
#if SUBTRATE_GBUFFER_FORMAT==1
uint2 TileRes = Substrate.TileCount;
uint AllocatedTileCount = Substrate.ClosureTileCountBuffer[0];
bool bOverflowValid = AllocatedTileCount > 0;
#else
uint2 TileRes = ViewportIntegrateTileDimensions;
uint AllocatedTileCount = 0;
bool bOverflowValid = false;
#endif
const FFontColor TileResColor = GetValidColor(true);
const FFontColor LayerResColor = GetValidColor(AllocatedTileCount > 0);
Print(Context, TEXT("Tile Count : "), FontSilver); Print(Context, TileRes.x, TileResColor, 3, 3); Print(Context, TEXT(" x "), TileResColor); Print(Context, TileRes.y, TileResColor); Newline(Context);
Print(Context, TEXT("Layer Count : "), FontSilver); Print(Context, LayerCount, LayerResColor, 3, 3); Newline(Context);
Print(Context, TEXT("Extra tile Count : "), FontSilver); Print(Context, AllocatedTileCount, LayerResColor, 3, 3); Newline(Context);
Newline(Context);
// Primary
{
Print(Context, TEXT("Primary "), FontOrange); Newline(Context);
PrintTileStats(Context, false);
Newline(Context);
}
// Overflow
if (bOverflowValid)
{
Print(Context, TEXT("Overflow "), FontOrange); Newline(Context);
PrintTileStats(Context, true);
Newline(Context);
}
}
PrintTileLegend(Context);
Newline(Context);
uint LayerIndex = 0;
#if SUBTRATE_GBUFFER_FORMAT==1
if (LayerCount > 1)
{
LayerIndex = AddSlider(Context, TEXT("Tile index"), 0, FontSilver, 0.f, SUBSTRATE_MAX_CLOSURE_COUNT);
LayerIndex = clamp(LayerIndex, 0u, LayerCount-1u);
Print(Context, LayerIndex, FontEmerald); Newline(Context);
}
#endif
// Draw tiles
{
const uint LinearCoord = DispatchThreadId.x + DispatchThreadId.y * ViewportIntegrateTileDimensions.x;
PrintTiles(Context, LinearCoord, LayerIndex > 0, LayerIndex);
}
}
#endif // ScreenProbeDebugMain
#ifdef NUM_SAMPLES_PER_UNIFORM_PROBE
uint2 GetAdaptiveSampleCoord(uint2 UniformScreenProbeCoord, uint SampleIndex)
{
uint2 UniformSampleScreenCoord = UniformScreenProbeCoord * ScreenProbeDownsampleFactor + GetScreenTileJitter(SCREEN_TEMPORAL_INDEX) + View.ViewRectMinAndSize.xy;
uint2 SampleSeed = Rand3DPCG16(int3(UniformScreenProbeCoord, SCREEN_TEMPORAL_INDEX)).xy;
uint2 AdaptiveSampleScreenCoord = UniformSampleScreenCoord + clamp(Hammersley16(SampleIndex, NUM_SAMPLES_PER_UNIFORM_PROBE, SampleSeed) * ScreenProbeDownsampleFactor, 0, ScreenProbeDownsampleFactor - 1);
return AdaptiveSampleScreenCoord;
}
#endif
#ifdef ScreenProbeAdaptivePlacementMarkCS
#define NUM_UNIFORM_PROBES_PER_GROUP uint2(THREADGROUP_SIZE / NUM_SAMPLES_PER_UNIFORM_PROBE_X, THREADGROUP_SIZE / NUM_SAMPLES_PER_UNIFORM_PROBE_Y)
#define SHARED_PROBE_PLACEMENT_MASK_SIZE_X (THREADGROUP_SIZE / NUM_SAMPLES_PER_UNIFORM_PROBE_X)
#define SHARED_PROBE_PLACEMENT_MASK_SIZE_Y (THREADGROUP_SIZE / NUM_SAMPLES_PER_UNIFORM_PROBE_Y)
RWTexture2D<uint> RWAdaptiveProbePlacementMask;
groupshared uint SharedProbePlacementMask[SHARED_PROBE_PLACEMENT_MASK_SIZE_X][SHARED_PROBE_PLACEMENT_MASK_SIZE_Y];
/**
* Mark all valid adaptive probe placement locations in RWAdaptiveProbePlacementMask
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void ScreenProbeAdaptivePlacementMarkCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
if (all(GroupThreadId.xy < uint2(SHARED_PROBE_PLACEMENT_MASK_SIZE_X, SHARED_PROBE_PLACEMENT_MASK_SIZE_Y)))
{
SharedProbePlacementMask[GroupThreadId.x][GroupThreadId.y] = 0;
}
GroupMemoryBarrierWithGroupSync();
uint2 NumSamplesPerUniformProbe2D = uint2(NUM_SAMPLES_PER_UNIFORM_PROBE_X, NUM_SAMPLES_PER_UNIFORM_PROBE_Y);
uint2 LocalUniformProbeOffset = GroupThreadId.xy / NumSamplesPerUniformProbe2D;
uint2 SampleIndex2D = GroupThreadId.xy % NumSamplesPerUniformProbe2D;
uint2 UniformScreenProbeCoord = GroupId.xy * NUM_UNIFORM_PROBES_PER_GROUP + LocalUniformProbeOffset;
uint SampleIndex = SampleIndex2D.x + NumSamplesPerUniformProbe2D.x * SampleIndex2D.y;
uint2 AdaptiveSampleScreenCoord = GetAdaptiveSampleCoord(UniformScreenProbeCoord, SampleIndex);
bool bAllocateProbe = false;
// Find probes to allocate
if (all(AdaptiveSampleScreenCoord < View.ViewRectMinAndSize.xy + View.ViewRectMinAndSize.zw))
{
FScreenProbeMaterial ScreenProbeMaterial = GetScreenProbeMaterial(AdaptiveSampleScreenCoord);
if (ScreenProbeMaterial.bIsValid)
{
float2 ScreenUV = (AdaptiveSampleScreenCoord + .5f) * View.BufferSizeAndInvSize.zw;
float3 WorldPosition = GetWorldPositionFromScreenUV(ScreenUV, ScreenProbeMaterial.SceneDepth);
float2 NoiseOffset = 0.0f;
FScreenProbeSample ScreenProbeSample = (FScreenProbeSample)0;
CalculateUpsampleInterpolationWeights(
AdaptiveSampleScreenCoord,
NoiseOffset,
WorldPosition,
ScreenProbeMaterial.SceneDepth,
ScreenProbeMaterial.WorldNormal,
/*bIsUpsamplePass*/ true,
/*bUseAdaptiveProbes*/ false,
/*bFoliage*/ ScreenProbeMaterial.bHasBackfaceDiffuse,
ScreenProbeSample);
bAllocateProbe = dot(ScreenProbeSample.Weights, 1) < MIN_PROBE_INTERPOLATION_WEIGHT;
}
}
if (bAllocateProbe)
{
uint SampleMask = 1u << SampleIndex;
InterlockedOr(SharedProbePlacementMask[LocalUniformProbeOffset.x][LocalUniformProbeOffset.y], SampleMask);
}
GroupMemoryBarrierWithGroupSync();
if (SampleIndex == 0 && all(UniformScreenProbeCoord < ScreenProbeViewSize))
{
RWAdaptiveProbePlacementMask[UniformScreenProbeCoord] = SharedProbePlacementMask[LocalUniformProbeOffset.x][LocalUniformProbeOffset.y];
}
}
#endif
#ifdef ScreenProbeAdaptivePlacementSpawnCS
#define NUM_UNIFORM_PROBES_PER_GROUP uint2(THREADGROUP_SIZE / NUM_SAMPLES_PER_UNIFORM_PROBE_X, THREADGROUP_SIZE / NUM_SAMPLES_PER_UNIFORM_PROBE_Y)
RWStructuredBuffer<uint> RWNumAdaptiveScreenProbes;
Texture2D<uint> AdaptiveProbePlacementMask;
groupshared uint SharedNumProbesToAllocate;
groupshared uint SharedAdaptiveProbeBaseIndex;
/**
* Loop over all valid adaptive probe locations (based on AdaptiveProbePlacementMask) and spawn probes.
* In order to minimize number of probes we also check whether probe is already covered by previous spawned probes (samples with a lower index)
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void ScreenProbeAdaptivePlacementSpawnCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
if (all(GroupThreadId == 0))
{
SharedNumProbesToAllocate = 0;
}
GroupMemoryBarrierWithGroupSync();
uint2 NumSamplesPerUniformProbe2D = uint2(NUM_SAMPLES_PER_UNIFORM_PROBE_X, NUM_SAMPLES_PER_UNIFORM_PROBE_Y);
uint2 LocalUniformProbeOffset = GroupThreadId.xy / NumSamplesPerUniformProbe2D;
uint2 SampleIndex2D = GroupThreadId.xy % NumSamplesPerUniformProbe2D;
uint2 UniformScreenProbeCoord = GroupId.xy * NUM_UNIFORM_PROBES_PER_GROUP + LocalUniformProbeOffset;
uint SampleIndex = SampleIndex2D.x + NumSamplesPerUniformProbe2D.x * SampleIndex2D.y;
bool bPlaceProbe = false;
uint2 AdaptiveSampleScreenCoord = 0;
FScreenProbeMaterial ScreenProbeMaterial = (FScreenProbeMaterial) 0;
if (all(UniformScreenProbeCoord < ScreenProbeViewSize))
{
AdaptiveSampleScreenCoord = GetAdaptiveSampleCoord(UniformScreenProbeCoord, SampleIndex);
uint SampleMask = 1u << SampleIndex;
if (AdaptiveProbePlacementMask[UniformScreenProbeCoord] & SampleMask)
{
bPlaceProbe = true;
uint2 ScreenProbeFullResScreenCoord = clamp(AdaptiveSampleScreenCoord.xy - View.ViewRectMin.xy - GetScreenTileJitter(SCREEN_TEMPORAL_INDEX), 0.0f, View.ViewSizeAndInvSize.xy - 1.0f);
uint2 CornerScreenTileCoord00 = min(ScreenProbeFullResScreenCoord / ScreenProbeDownsampleFactor, (uint2)ScreenProbeViewSize - 2);
ScreenProbeMaterial = GetScreenProbeMaterial(AdaptiveSampleScreenCoord);
float2 ScreenUV = (AdaptiveSampleScreenCoord + .5f) * View.BufferSizeAndInvSize.zw;
float3 WorldPosition = GetWorldPositionFromScreenUV(ScreenUV, ScreenProbeMaterial.SceneDepth);
float4 ScenePlane = float4(ScreenProbeMaterial.WorldNormal, dot(WorldPosition, ScreenProbeMaterial.WorldNormal));
// Check whether previous samples already cover this point
float4 CornerWeights = 0.0f;
for (uint CornerIndex = 0; CornerIndex < 4; ++CornerIndex)
{
uint2 CornerScreenTileCoord = CornerScreenTileCoord00 + uint2(CornerIndex % 2, CornerIndex / 2);
uint CornerPlacementMask = AdaptiveProbePlacementMask[CornerScreenTileCoord];
while (CornerPlacementMask != 0)
{
const uint CornerSampleIndex = firstbitlow(CornerPlacementMask);
const uint CornerBitMask = 1u << CornerSampleIndex;
CornerPlacementMask ^= CornerBitMask;
if (CornerSampleIndex >= SampleIndex)
{
break;
}
uint2 NeighborUniformScreenProbeCoord = CornerScreenTileCoord;
uint2 NeighborAdaptiveSampleScreenCoord = GetAdaptiveSampleCoord(NeighborUniformScreenProbeCoord, CornerSampleIndex);
FScreenProbeMaterial NeighborScreenProbeMaterial = GetScreenProbeMaterial(NeighborAdaptiveSampleScreenCoord);
const float NewInterpolationWeight = GetAdaptiveProbeInterpolationWeight(
AdaptiveSampleScreenCoord,
ScenePlane,
ScreenProbeMaterial.SceneDepth,
/*bFoliage*/ ScreenProbeMaterial.bHasBackfaceDiffuse,
/*ScreenProbeScreenPosition*/ NeighborAdaptiveSampleScreenCoord,
/*ProbeDepth*/ NeighborScreenProbeMaterial.SceneDepth);
CornerWeights[CornerIndex] = max(CornerWeights[CornerIndex], NewInterpolationWeight);
if (dot(CornerWeights, 1.0f) >= MIN_PROBE_INTERPOLATION_WEIGHT)
{
bPlaceProbe = false;
break;
}
}
if (!bPlaceProbe)
{
break;
}
}
}
}
uint SharedListIndex = 0;
if (bPlaceProbe)
{
InterlockedAdd(SharedNumProbesToAllocate, 1, SharedListIndex);
}
GroupMemoryBarrierWithGroupSync();
if (all(GroupThreadId == 0))
{
InterlockedAdd(RWNumAdaptiveScreenProbes[0], SharedNumProbesToAllocate, SharedAdaptiveProbeBaseIndex);
}
GroupMemoryBarrierWithGroupSync();
uint AdaptiveProbeIndex = SharedAdaptiveProbeBaseIndex + SharedListIndex;
if (bPlaceProbe && AdaptiveProbeIndex < MaxNumAdaptiveProbes)
{
RWAdaptiveScreenProbeData[AdaptiveProbeIndex] = EncodeScreenProbeData(AdaptiveSampleScreenCoord);
uint2 ScreenTileCoord = GetScreenTileCoord(AdaptiveSampleScreenCoord);
uint TileProbeIndex;
InterlockedAdd(RWScreenTileAdaptiveProbeHeader[ScreenTileCoord], 1, TileProbeIndex);
uint2 AdaptiveProbeCoord = GetAdaptiveProbeCoord(ScreenTileCoord, TileProbeIndex);
RWScreenTileAdaptiveProbeIndices[AdaptiveProbeCoord] = AdaptiveProbeIndex;
float2 ScreenUV = (AdaptiveSampleScreenCoord + .5f) * View.BufferSizeAndInvSize.zw;
uint ScreenProbeIndex = NumUniformScreenProbes + AdaptiveProbeIndex;
uint2 ScreenProbeAtlasCoord = uint2(ScreenProbeIndex % ScreenProbeAtlasViewSize.x, ScreenProbeIndex / ScreenProbeAtlasViewSize.x);
WriteDownsampledProbeMaterial(ScreenUV, ScreenProbeAtlasCoord, ScreenProbeMaterial);
}
}
#endif