// 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 "LumenScreenProbeTileClassication.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 RWScreenProbeSceneDepth; RWTexture2D RWScreenProbeWorldSpeed; RWTexture2D RWScreenProbeWorldNormal; RWTexture2D RWScreenProbeTranslatedWorldPosition; #if SHORT_RANGE_AO_BENT_NORMAL Texture2DArray ShortRangeAOTexture; #else Texture2DArray ShortRangeAOTexture; #endif 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 RWScreenTileAdaptiveProbeHeader; RWTexture2D RWScreenTileAdaptiveProbeIndices; RWStructuredBuffer 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 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 RWIntegrateIndirectArgs; RWTexture2DArray RWDiffuseIndirect; RWTexture2DArray RWLightIsMoving; RWTexture2DArray RWBackfaceDiffuseIndirect; RWTexture2DArray RWRoughSpecularIndirect; #ifndef PERMUTATION_OVERFLOW_TILE #define PERMUTATION_OVERFLOW_TILE 0 #endif struct FScreenProbeIntegrateTileData { uint2 Coord; uint ClosureIndex; }; uint PackScreenProbeIntegrateTileData(FScreenProbeIntegrateTileData In) { return #if SUBSTRATE_ENABLED && SUBSTRATE_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 && SUBSTRATE_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) RWBuffer RWClearUnusedIntegrateTileIndirectArgs; RWBuffer RWClearUnusedIntegrateTileData; [numthreads(32, 1, 1)] void InitScreenProbeTileIndirectArgsCS( uint2 GroupId : SV_GroupID, uint3 DispatchThreadId : SV_DispatchThreadID, uint2 GroupThreadId : SV_GroupThreadID) { uint LinearThreadIndex = DispatchThreadId.x; const uint3 ClearValue = uint3(0, 1, 1); if (LinearThreadIndex == 0) { WriteDispatchIndirectArgs(RWClearUnusedIntegrateTileIndirectArgs, 0, ClearValue); } if (LinearThreadIndex < TILE_CLASSIFICATION_NUM) { WriteDispatchIndirectArgs(RWIntegrateIndirectArgs, LinearThreadIndex, ClearValue); } } RWStructuredBuffer RWIntegrateTileData; Texture2DArray LumenTileBitmask; uint2 ViewportTileMin; uint2 ViewportTileDimensions; uint2 ViewportTileDimensionsWithOverflow; uint2 ViewportIntegrateTileMin; uint2 ViewportIntegrateTileDimensions; uint MaxClosurePerPixel; groupshared uint SharedNumClearTiles; groupshared uint SharedNumTiles[TILE_CLASSIFICATION_NUM]; groupshared uint SharedIntegrateTileData[TILE_CLASSIFICATION_NUM * THREADGROUP_SIZE]; groupshared uint SharedGlobalClearTileOffset; 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 uint GetLumenTileMode(FScreenProbeIntegrateTileData TileData) { uint TileBitmask = 0; for (uint Y = 0; Y < INTEGRATE_DOWNSAMPLE_FACTOR; ++Y) { for (uint X = 0; X < INTEGRATE_DOWNSAMPLE_FACTOR; ++X) { uint3 TileCoord = uint3(TileData.Coord * INTEGRATE_DOWNSAMPLE_FACTOR + uint2(X, Y) + ViewportTileMin, TileData.ClosureIndex); if (all(TileCoord.xy < ViewportTileMin + ViewportTileDimensions)) { TileBitmask |= LumenTileBitmask[TileCoord]; } } } if (TileBitmask & LUMEN_TILE_BITMASK_GI_ALL) { return TILE_CLASSIFICATION_SUPPORT_ALL; } else if (TileBitmask & LUMEN_TILE_BITMASK_GI_IMPORTANCE_SAMPLE_BRDF) { return TILE_CLASSIFICATION_SUPPORT_IMPORTANCE_SAMPLE_BRDF; } else if (TileBitmask & LUMEN_TILE_BITMASK_GI_SIMPLE_DIFFUSE) { return TILE_CLASSIFICATION_SIMPLE_DIFFUSE; } return 0xFF; } [numthreads(THREADGROUP_SIZE, 1, 1)] void ScreenProbeTileClassificationBuildListsCS( uint2 GroupId : SV_GroupID, uint GroupThreadId : SV_GroupThreadID) { #if WAVE_OPS #if SUBSTRATE_ENABLED && SUBSTRATE_GBUFFER_FORMAT==1 && PERMUTATION_OVERFLOW_TILE const uint TileCount = Substrate.ClosureTileCountBuffer[0]; #endif #if SUBSTRATE_ENABLED && SUBSTRATE_GBUFFER_FORMAT==1 && PERMUTATION_OVERFLOW_TILE const uint LinearIndex = GroupId.x * THREADGROUP_SIZE + GroupThreadId; 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 < ViewportIntegrateTileDimensions); #else const uint2 ThreadOffset = ZOrder2D(GroupThreadId, log2(8)); FScreenProbeIntegrateTileData TileData; TileData.Coord = GroupId * 8 + ThreadOffset; TileData.ClosureIndex = 0; const bool bIsValid = all(TileData.Coord < ViewportIntegrateTileDimensions); #endif if (bIsValid) { uint Mode = GetLumenTileMode(TileData); // 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] // [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] [][][] // Active tiles for (uint ModeIndex = 0; ModeIndex < TILE_CLASSIFICATION_NUM; ++ModeIndex) { uint NumTilesInWave = WaveActiveCountBits(Mode == ModeIndex); uint GlobalTileOffset = 0; if (WaveIsFirstLane() && NumTilesInWave > 0) { InterlockedAdd(RWIntegrateIndirectArgs[TILE_CLASSIFICATION_ARGS_OFFSET + ModeIndex * DISPATCH_INDIRECT_UINT_COUNT], NumTilesInWave, GlobalTileOffset); } GlobalTileOffset = WaveReadLaneFirst(GlobalTileOffset); if (Mode == ModeIndex) { const uint ArgsOffset = GetTileDataOffset(ViewportTileDimensionsWithOverflow, ModeIndex, PERMUTATION_OVERFLOW_TILE); RWIntegrateTileData[ArgsOffset + GlobalTileOffset + WavePrefixCountBits(true)] = PackScreenProbeIntegrateTileData(TileData); } } // Unused tiles { uint NumTilesInWave = WaveActiveCountBits(Mode == 0xFF); uint GlobalTileOffset = 0; if (WaveIsFirstLane() && NumTilesInWave > 0) { InterlockedAdd(RWClearUnusedIntegrateTileIndirectArgs[0], NumTilesInWave, GlobalTileOffset); } GlobalTileOffset = WaveReadLaneFirst(GlobalTileOffset); if (Mode == 0xFF) { RWClearUnusedIntegrateTileData[GlobalTileOffset + WavePrefixCountBits(true)] = PackScreenProbeIntegrateTileData(TileData); } } } #else // !WAVE_OPS //@todo - parallel version if (GroupThreadId == 0) { #if SUBSTRATE_ENABLED && SUBSTRATE_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; } SharedNumClearTiles = 0; for (uint x = 0; x < THREADGROUP_SIZE; x++) { #if SUBSTRATE_ENABLED && SUBSTRATE_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 = GetLumenTileMode(TileData); if (Mode != 0xFF) { uint TileOffset = SharedNumTiles[Mode]; SharedIntegrateTileData[Mode * THREADGROUP_SIZE + TileOffset] = PackScreenProbeIntegrateTileData(TileData); SharedNumTiles[Mode] = TileOffset + 1; } else { // Pack clear tiles from the other end uint TileOffset = SharedNumClearTiles; SharedIntegrateTileData[THREADGROUP_SIZE - 1 - TileOffset] = PackScreenProbeIntegrateTileData(TileData); SharedNumClearTiles = 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]); } if (GroupThreadId == 0) { InterlockedAdd(RWClearUnusedIntegrateTileIndirectArgs[0], SharedNumClearTiles, SharedGlobalClearTileOffset); } 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]; } } if (GroupThreadId < SharedNumClearTiles) { RWClearUnusedIntegrateTileData[SharedGlobalClearTileOffset + GroupThreadId] = SharedIntegrateTileData[THREADGROUP_SIZE - 1 - GroupThreadId]; } #endif // !WAVE_OPS } #endif Texture2D ScreenProbeRadianceSHAmbient; Texture2D ScreenProbeRadianceSHDirectional; Texture2D 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 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 ScreenProbeRadianceWithBorder; Texture2D 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 ShortRangeGI; // Whether to integrate the SH along the reflection direction for rough specular, which has better view dependence but samples values below the visible horizon #define ROUGH_SPECULAR_ALONG_REFLECTION_DIRECTION 0 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_BENT_NORMAL // 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 = AddSH3(LightingSH, GetScreenProbeSH(ScreenProbeSample.AtlasCoord[1], ScreenProbeSample.Weights.y)); LightingSH = AddSH3(LightingSH, GetScreenProbeSH(ScreenProbeSample.AtlasCoord[2], ScreenProbeSample.Weights.z)); LightingSH = AddSH3(LightingSH, GetScreenProbeSH(ScreenProbeSample.AtlasCoord[3], ScreenProbeSample.Weights.w)); #endif #define SH_DIRECTIONAL_OCCLUSION 1 #if SH_DIRECTIONAL_OCCLUSION float3 SHDiffuseLighting = EvaluateSHIrradiance(ProbeLightingNormal, AO, LightingSH); #else FThreeBandSHVector DiffuseTransferSH = CalcDiffuseTransferSH3(ProbeLightingNormal, 1); float3 SHDiffuseLighting = max(float3(0.0f, 0.0f, 0.0f), DotSH3(LightingSH, DiffuseTransferSH)) * AO; #endif DiffuseLighting += 4 * PI * SHDiffuseLighting; #if ROUGH_SPECULAR_ALONG_REFLECTION_DIRECTION // 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 #ifdef ScreenProbeIntegrateClearUnusedTileDataCS Buffer ClearUnusedIntegrateTileData; /* * Clear integrate tile data for tiles which will be skipped due to tile classification, but still may be read by upsampling or ClampHistory in ScreenProbeTemporalReprojectionCS */ [numthreads(INTEGRATE_TILE_SIZE, INTEGRATE_TILE_SIZE, 1)] void ScreenProbeIntegrateClearUnusedTileDataCS( uint2 DispatchThreadId : SV_DispatchThreadID, uint GroupId : SV_GroupID, uint2 GroupThreadId : SV_GroupThreadID) { const FScreenProbeIntegrateTileData TileData = UnpackScreenProbeIntegrateTileData(ClearUnusedIntegrateTileData[GroupId]); 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 && SUBSTRATE_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))) { RWDiffuseIndirect[IntegrateCoord] = 0.0f; RWLightIsMoving[IntegrateCoord] = 0.0f; RWRoughSpecularIndirect[IntegrateCoord] = 0.0f; #if SUPPORT_BACKFACE_DIFFUSE RWBackfaceDiffuseIndirect[IntegrateCoord] = 0.0f; #endif } } #endif StructuredBuffer 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 && SUBSTRATE_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 = Rand16ToFloat(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 #if SHORT_RANGE_AO_BENT_NORMAL { float3 BentNormal = UnpackScreenBentNormal(ShortRangeAOTexture[IntegrateCoord]); AO = length(BentNormal); UnitBentNormal = AO > 0 ? BentNormal / AO : Material.WorldNormal; AO = saturate(AO); } #else { AO = ShortRangeAOTexture[IntegrateCoord]; } #endif 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 #define DEBUG_VISUALIZE_AO 0 #if DEBUG_VISUALIZE_AO DiffuseIndirectOutput = AO * View.PreExposure; #endif // FDiffuseIndirectCompositePS applies DiffuseColor RWDiffuseIndirect[IntegrateCoord] = DiffuseIndirectOutput; RWLightIsMoving[IntegrateCoord] = EncodedAlpha; #if SUPPORT_BACKFACE_DIFFUSE RWBackfaceDiffuseIndirect[IntegrateCoord] = BackfaceDiffuseLighting; #endif #if ROUGH_SPECULAR_ALONG_REFLECTION_DIRECTION 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; Buffer 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) { // Rescale from internal resolution to output resolution uint2 RectMin = ((TileData.Coord + 0) * INTEGRATE_TILE_SIZE) / View.ViewResolutionFraction; uint2 RectMax = ((TileData.Coord + 1) * INTEGRATE_TILE_SIZE) / View.ViewResolutionFraction; AddFilledQuadSS(RectMin, RectMax, 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 SUBSTRATE_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 SUBSTRATE_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 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 RWNumAdaptiveScreenProbes; Texture2D 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