1852 lines
74 KiB
HLSL
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 |