// Copyright Epic Games, Inc. All Rights Reserved. #ifndef NUM_SAMPLES_PER_PIXEL_1D #define NUM_SAMPLES_PER_PIXEL_1D 1 #define NUM_SAMPLES_PER_PIXEL_2D_X 1 #define NUM_SAMPLES_PER_PIXEL_2D_Y 1 #endif #ifndef TILE_TYPE #define TILE_TYPE TILE_MODE_EMPTY #endif // When loading SSS checkerboard pixel, do not adjust DiffuseColor/SpecularColor to preserve specular and diffuse lighting values for each pixel #define ALLOW_SSS_MATERIAL_OVERRIDE 0 // Disable hair backlit during sample generation as Hair's TT term creates too much false positive: // the TT terms is very strong, and is in most case occluded by underlying geometry (head/body/...) #define HAIR_BSDF_BACKLIT 0 #define USE_IES_PROFILE 1 #define USE_LIGHT_FUNCTION_ATLAS 1 #include "../Common.ush" #include "/Engine/Shared/MegaLightsDefinitions.h" // Substrate tile for faster shading #if SUBSTRATE_ENABLED #if TILE_TYPE == TILE_MODE_SIMPLE_SHADING || TILE_TYPE == TILE_MODE_SIMPLE_SHADING_RECT || TILE_TYPE == TILE_MODE_SIMPLE_SHADING_RECT_TEXTURED || TILE_TYPE == TILE_MODE_EMPTY #define SUBSTRATE_FASTPATH 1 #define SUBSTRATE_SINGLEPATH 0 #define SUBSTRATE_COMPLEXSPECIALPATH 0 #elif TILE_TYPE == TILE_MODE_SINGLE_SHADING || TILE_TYPE == TILE_MODE_SINGLE_SHADING_RECT || TILE_TYPE == TILE_MODE_SINGLE_SHADING_RECT_TEXTURED #define SUBSTRATE_FASTPATH 0 #define SUBSTRATE_SINGLEPATH 1 #define SUBSTRATE_COMPLEXSPECIALPATH 0 #elif TILE_TYPE == TILE_MODE_COMPLEX_SHADING || TILE_TYPE == TILE_MODE_COMPLEX_SHADING_RECT || TILE_TYPE == TILE_MODE_COMPLEX_SHADING_RECT_TEXTURED #define SUBSTRATE_FASTPATH 0 #define SUBSTRATE_SINGLEPATH 0 #define SUBSTRATE_COMPLEXSPECIALPATH 0 #else // Special TILE_MODE_COMPLEX_SPECIAL_SHADING || TILE_MODE_COMPLEX_SPECIAL_SHADING_RECT || TILE_MODE_COMPLEX_SPECIAL_SHADING_RECT_TEXTURED #define SUBSTRATE_FASTPATH 0 #define SUBSTRATE_SINGLEPATH 0 #define SUBSTRATE_COMPLEXSPECIALPATH 1 #endif #endif #if SUBSTRATE_FASTPATH || SUBSTRATE_SINGLEPATH #define SUBSTRATE_LOAD_FROM_MATERIALCONTAINER 0 #endif #include "../BlueNoise.ush" #include "MegaLightsShading.ush" #include "MegaLightsRayTracing.ush" #include "MegaLightsSampling.ush" #include "../LightFunctionAtlas/LightFunctionAtlasCommon.usf" #include "../StochasticLighting/StochasticLightingCommon.ush" // Allow register spilling on certain platform to avoid shader compilation issue #if SUBSTRATE_ENABLED #pragma warning(disable:7203) #endif ADAPTIVE_LICM struct FLightTargetPDF { float Weight; }; FLightTargetPDF InitLightTargetPDF() { FLightTargetPDF LightTargetPDF; LightTargetPDF.Weight = 0.0f; return LightTargetPDF; } FLightTargetPDF GetLocalLightTargetPDF(FDeferredLightData LightData, float3 TranslatedWorldPosition, FMegaLightsMaterial Material, uint2 ScreenCoord, inout FShaderPrintContext DebugContext) { float3 CameraVector = normalize(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin); float4 LightAttenuation = 1.0f; float Dither = 0.5f; float SurfaceShadow = 1.0f; float AmbientOcclusion = 1.0f; LightData.ShadowedBits = 0; #if INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS || USE_HAIR_COMPLEX_TRANSMITTANCE if (Material.bNeedsComplexTransmittance) { LightData.HairTransmittance = EvaluateDualScattering(Material.DiffuseColor, Material.WorldNormal, Material.Roughness, CameraVector, -LightData.Direction); } #endif FDeferredLightingSplit SplitLighting = GetMegaLightsSplitLighting( TranslatedWorldPosition, CameraVector, Material, AmbientOcclusion, LightData, LightAttenuation, Dither, ScreenCoord, SurfaceShadow); //float Lum = Luminance(SplitLighting.DiffuseLighting.xyz + SplitLighting.SpecularLighting.xyz) * View.PreExposure; float Lum = SplitLighting.LightingLuminance * View.PreExposure; if (LightData.IESAtlasIndex >= 0 && Lum > 0.01f) { Lum *= ComputeLightProfileMultiplier(TranslatedWorldPosition, LightData.TranslatedWorldPosition, -LightData.Direction, LightData.Tangent, LightData.IESAtlasIndex); } // Simulate tonemapping FLightTargetPDF LightTargetPDF = InitLightTargetPDF(); LightTargetPDF.Weight = log2(Lum + 1.0f); return LightTargetPDF; } uint2 DownsampledViewMin; uint2 DownsampledViewSize; float MinSampleWeight; uint2 NumSamplesPerPixel; int UseIESProfiles; int UseLightFunctionAtlas; int bVisualizeLightLoopIterations; int DebugMode; uint DebugVisualizeLight; uint DebugLightId; RWTexture2D RWDownsampledSceneDepth; RWTexture2D RWDownsampledSceneWorldNormal; RWTexture2D RWLightSamples; RWTexture2D RWLightSampleRays; Texture2D PackedPixelDataTexture; Texture2D EncodedHistoryScreenCoordTexture; Texture2D MegaLightsDepthHistory; float4 HistorySubPixelGridSizeAndInvSize; uint2 HistoryScreenCoordDecodeShift; StructuredBuffer DownsampledTileAllocator; StructuredBuffer DownsampledTileData; StructuredBuffer VisibleLightHashHistory; StructuredBuffer VisibleLightMaskHashHistory; uint2 HistoryVisibleLightHashViewMinInTiles; uint2 HistoryVisibleLightHashViewSizeInTiles; float LightHiddenPDFWeight; float LightHiddenPDFWeightForHistoryMiss; float AreaLightHiddenPDFWeight; uint DownsampledTileDataStride; float2 DownsampledBufferInvSize; uint2 GetSampleCoord(uint2 DownsampledScreenCoord, uint LightSampleX, uint LightSampleY) { return DownsampledScreenCoord * NumSamplesPerPixel + uint2(LightSampleX, LightSampleY); } uint2 GetSampleCoord(uint2 DownsampledScreenCoord, uint LightSampleIndex) { return GetSampleCoord(DownsampledScreenCoord, LightSampleIndex % NUM_SAMPLES_PER_PIXEL_2D_X, LightSampleIndex / NUM_SAMPLES_PER_PIXEL_2D_X); } uint GetLightVisibilityMask(uint VisibleLightMaskHash[VISIBLE_LIGHT_HASH_SIZE], uint PrevLocalLightIndex) { uint Hash = PCGHash(PrevLocalLightIndex); uint WrappedLocalLightIndex = (Hash >> 16) % 32; uint VisibilityMask = (VisibleLightMaskHash[WrappedLocalLightIndex / 8] >> (4 * (WrappedLocalLightIndex % 8))) & 0xF; return VisibilityMask; } void SampleLight( uint2 ScreenCoord, float3 TranslatedWorldPosition, const FMegaLightsMaterial Material, uint LightingChannelMask, uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE], bool bHasValidHistory, uint ForwardLightIndex, FDeferredLightData LightData, uint LightSceneId, uint LightLightingChannelMask, uint PrevForwardLightIndex, inout FLightSampler LightSampler, inout FShaderPrintContext DebugContext) { if ((LightingChannelMask & LightLightingChannelMask) == 0) { return; } if (UseLightFunctionAtlas == 0) { LightData.LightFunctionAtlasLightIndex = 0; } if (UseIESProfiles == 0) { LightData.IESAtlasIndex = -1; } if (!IsRectLightTileType(TILE_TYPE)) { LightData.bRectLight = false; } if (!IsTexturedLightTileType(TILE_TYPE)) { LightData.RectLightData.AtlasData.AtlasMaxLevel = MAX_RECT_ATLAS_MIP; } FLightTargetPDF LightTargetPDF = GetLocalLightTargetPDF(LightData, TranslatedWorldPosition, Material, ScreenCoord, DebugContext); bool bWasVisibleInLastFrame = true; #if GUIDE_BY_HISTORY if (LightTargetPDF.Weight > MinSampleWeight && PrevForwardLightIndex >= 0) { bWasVisibleInLastFrame = GetLightVisibility(VisibleLightHash, PrevForwardLightIndex); } else { bWasVisibleInLastFrame = false; } #endif if (DebugContext.bIsActive) { Newline(DebugContext); Print(DebugContext, LightSceneId, Select(LightSceneId == DebugLightId, FontSelected, FontValue)); Print(DebugContext, ForwardLightIndex, Select(LightSceneId == DebugLightId, FontSelected, FontValue)); Print(DebugContext, LightTargetPDF.Weight, Select(LightTargetPDF.Weight > MinSampleWeight, FontWhite, FontGrey)); Print(DebugContext, LightData.LightFunctionAtlasLightIndex, Select(LightData.LightFunctionAtlasLightIndex != 0, FontWhite, FontGrey)); Print(DebugContext, LightData.IESAtlasIndex, Select(LightData.IESAtlasIndex != -1, FontWhite, FontGrey)); Print(DebugContext, bWasVisibleInLastFrame ? 1u : 0u, Select(bWasVisibleInLastFrame, FontWhite, FontGrey), 4, 1); if ((DebugVisualizeLight != 0 && LightSceneId == DebugLightId) || DebugVisualizeLight == 2) { const float4 DebugLightColor = bWasVisibleInLastFrame ? float4(0, 1, 0, 1) : float4(0, 0.5f, 0, 1); AddSphereTWS(DebugContext, LightData.TranslatedWorldPosition, 10.0f, DebugLightColor); AddLineTWS(DebugContext, TranslatedWorldPosition, LightData.TranslatedWorldPosition, DebugLightColor); } } if (LightTargetPDF.Weight > MinSampleWeight) { if (!bWasVisibleInLastFrame) { LightTargetPDF.Weight *= bHasValidHistory ? LightHiddenPDFWeight : LightHiddenPDFWeightForHistoryMiss; } AddLightSample(LightSampler, LightTargetPDF.Weight, ForwardLightIndex, bWasVisibleInLastFrame, LightData.bRadialLight); } } void SampleLight( uint2 ScreenCoord, float3 TranslatedWorldPosition, const FMegaLightsMaterial Material, uint LightingChannelMask, uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE], bool bHasValidHistory, uint LocalLightIndex, inout FLightSampler LightSampler, inout FShaderPrintContext DebugContext) { const FLocalLightData LocalLightData = GetLocalLightData(LocalLightIndex, 0); FDeferredLightData LightData = ConvertToDeferredLight(LocalLightData); SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LocalLightIndex, LightData, LocalLightData.Internal.LightSceneId, UnpackLightingChannelMask(LocalLightData), LocalLightData.Internal.PrevLocalLightIndex, LightSampler, DebugContext); } void SampleDirectionalLight( uint DirectionalLightIndex, uint2 ScreenCoord, float3 TranslatedWorldPosition, const FMegaLightsMaterial Material, uint LightingChannelMask, uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE], bool bHasValidHistory, inout FLightSampler LightSampler, inout FShaderPrintContext DebugContext) { uint LightIndex = ForwardLightStruct.DirectionalLightIndices[DirectionalLightIndex]; FForwardLightData ForwardLightData = GetForwardLightData(LightIndex, 0); FDeferredLightData LightData = ConvertToDeferredLight(ForwardLightData); LightData.bRadialLight = false; LightData.bInverseSquared = false; SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LightIndex, LightData, ForwardLightData.LightSceneId, UnpackLightingChannelMask(ForwardLightData), ForwardLightData.PrevLocalLightIndex, LightSampler, DebugContext); } #if DEBUG_MODE groupshared int SharedGridCellLightCount[2]; groupshared int SharedLightLoopIterationCount[2]; #endif /** * Run one thread per sample and generate new light samples for tracing */ [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void GenerateLightSamplesCS( uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID) { uint LinearThreadIndex = GroupThreadId.y * THREADGROUP_SIZE + GroupThreadId.x; uint DownsampledTileIndex = GroupId.x; if (DownsampledTileIndex < DownsampledTileAllocator[TILE_TYPE]) { uint LocalCandidateLightHiMask = 0; uint2 DownsampledTileCoord = UnpackTile(DownsampledTileData[DownsampledTileIndex + TILE_TYPE * DownsampledTileDataStride]); uint2 DownsampledScreenCoord = DownsampledTileCoord * TILE_SIZE + GroupThreadId.xy + DownsampledViewMin; int LightLoopIterationCount = 0; int GridCellLightCount = 0; const bool bForceSimpleShading = IsSimpleShadingTileType(TILE_TYPE); // Use a separate context for iteration count visualization so we don't display all the debug texts FShaderPrintContext DebugContext_VisIterations = InitDebugContext(all(GroupThreadId.xy == 0), float2(0, 0)); DebugContext_VisIterations.bIsActive = DebugContext_VisIterations.bIsActive && bVisualizeLightLoopIterations != 0; if (all(DownsampledScreenCoord < DownsampledViewMin + DownsampledViewSize)) { uint2 ScreenCoord = DownsampledScreenCoordToScreenCoord(DownsampledScreenCoord); FShaderPrintContext DebugContext = InitDebugContext(DownsampledScreenCoord, /*bDownsampled*/ true, float2(0.05, 0.05)); DebugContext.bIsActive = DebugContext.bIsActive && !bVisualizeLightLoopIterations; const float2 ScreenUV = (ScreenCoord + 0.5f) * View.BufferSizeAndInvSize.zw; const FMegaLightsMaterial Material = LoadMaterial(ScreenUV, ScreenCoord, bForceSimpleShading); const float SceneDepth = Material.Depth; if (SceneDepth > 0) { const float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth); const uint EyeIndex = 0; // TODO const uint GridIndex = ComputeLightGridCellIndex(ScreenCoord - View.ViewRectMin.xy, SceneDepth, EyeIndex); const FCulledLightsGridHeader CulledLightsGridHeader = GetCulledLightsGridHeader(GridIndex); const uint NumLightsInGridCell = min(CulledLightsGridHeader.NumMegaLights, GetMaxLightsPerCell()); const uint NumLocalLights = GetNumLocalLights(); const uint LightingChannelMask = GetSceneLightingChannel(ScreenCoord); bool bHasValidHistory = true; uint2 PrevScreenCoord = ScreenCoord; #define REPROJECT_HISTORY_FOR_GUIDING 1 #if GUIDE_BY_HISTORY && REPROJECT_HISTORY_FOR_GUIDING { FMegaLightsPackedPixelData PackedPixelData = (FMegaLightsPackedPixelData)PackedPixelDataTexture[ScreenCoord]; uint EncodedHistoryScreenCoord = EncodedHistoryScreenCoordTexture[ScreenCoord]; bHasValidHistory = PackedPixelData.AnyHistoryDepthValid(); float4 Decoded = DecodeHistoryScreenCoord(EncodedHistoryScreenCoord, HistoryScreenCoordDecodeShift, HistorySubPixelGridSizeAndInvSize.zw); PrevScreenCoord = uint2(Decoded.xy); } #endif if (DebugContext.bIsActive) { Print(DebugContext, TEXT("GenerateSamples"), FontTitle); Newline(DebugContext); Print(DebugContext, TEXT("ScreenCoord : ")); Print(DebugContext, ScreenCoord.x, FontValue); Print(DebugContext, ScreenCoord.y, FontValue); Newline(DebugContext); Print(DebugContext, TEXT("Roughness : ")); Print(DebugContext, Material.Roughness, FontValue); Newline(DebugContext); Print(DebugContext, TEXT("TileType : ")); PrintTileTypeString(DebugContext, TILE_TYPE, Select(IsSimpleShadingTileType(TILE_TYPE), FontGreen, FontRed)); Newline(DebugContext); Print(DebugContext, TEXT("View.PreExposure : ")); Print(DebugContext, View.PreExposure, FontValue); Newline(DebugContext); Print(DebugContext, TEXT("NumLightsInGridCell: ")); Print(DebugContext, NumLightsInGridCell, FontValue); Newline(DebugContext); Print(DebugContext, TEXT("NumLocalLights : ")); Print(DebugContext, NumLocalLights, Select(NumLocalLights < MAX_LOCAL_LIGHT_INDEX + 1, FontLightGreen, FontRed)); Newline(DebugContext); Print(DebugContext, TEXT("ValidGuideHistory : ")); Print(DebugContext, bHasValidHistory, FontValue); Newline(DebugContext); Print(DebugContext, TEXT("LightId | LocalLightId | Weight | LFAtlas | IESAtlas | History"), FontSilver); } #if GUIDE_BY_HISTORY // Bilinear filtering approximation using stochastic offset const float2 RandomVec2 = BlueNoiseVec2(DownsampledScreenCoord, MegaLightsStateFrameIndex); PrevScreenCoord += (RandomVec2 - 0.5f) * VISIBLE_LIGHT_HASH_TILE_SIZE; uint2 PrevScreenTileCoord = clamp(PrevScreenCoord / VISIBLE_LIGHT_HASH_TILE_SIZE, HistoryVisibleLightHashViewMinInTiles, HistoryVisibleLightHashViewMinInTiles + HistoryVisibleLightHashViewSizeInTiles - 1); uint HistoryBufferBase = VISIBLE_LIGHT_HASH_SIZE * (PrevScreenTileCoord.y * HistoryVisibleLightHashViewSizeInTiles.x + PrevScreenTileCoord.x); #endif uint VisibleLightHash[VISIBLE_LIGHT_HASH_SIZE]; uint VisibleLightMaskHash[VISIBLE_LIGHT_HASH_SIZE]; for (uint IndexInHash = 0; IndexInHash < VISIBLE_LIGHT_HASH_SIZE; ++IndexInHash) { VisibleLightHash[IndexInHash] = 0xFFFFFFFF; VisibleLightMaskHash[IndexInHash] = 0xFFFFFFFF; #if GUIDE_BY_HISTORY { VisibleLightHash[IndexInHash] = VisibleLightHashHistory[HistoryBufferBase + IndexInHash]; VisibleLightMaskHash[IndexInHash] = VisibleLightMaskHashHistory[HistoryBufferBase + IndexInHash]; } #endif } #if REFERENCE_MODE FLightSampler LightSampler; InitLightSamplerFromSequence(LightSampler, DownsampledScreenCoord, MegaLightsStateFrameIndex); #else const float RandomScalar = BlueNoiseScalar(DownsampledScreenCoord, MegaLightsStateFrameIndex); FLightSampler LightSampler = InitLightSamplerStratified(RandomScalar); #endif const uint ScalarGridIndex = WaveReadLaneFirst(GridIndex); const bool bScalarGridCell = WaveActiveAllTrue(ScalarGridIndex == GridIndex); GridCellLightCount = NumLightsInGridCell; if (bScalarGridCell) { FCulledLightsGridHeader CulledLightsGridHeader = GetCulledLightsGridHeader(ScalarGridIndex); uint NumLightsInGridCell = min(CulledLightsGridHeader.NumMegaLights, GetMaxLightsPerCell()); uint GridLightIndex = 0; while(GridLightIndex < NumLightsInGridCell) { uint LocalLightIndex = GetCulledLightDataGrid(CulledLightsGridHeader.MegaLightsDataStartIndex + GridLightIndex); if (LocalLightIndex >= MAX_LOCAL_LIGHT_INDEX) { break; } ++GridLightIndex; ++LightLoopIterationCount; SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LocalLightIndex, LightSampler, DebugContext); } } else { uint GridLightIndex = 0; while(GridLightIndex < NumLightsInGridCell) { const uint VectorLocalLightIndex = GetCulledLightDataGrid(CulledLightsGridHeader.MegaLightsDataStartIndex + GridLightIndex); if (VectorLocalLightIndex >= MAX_LOCAL_LIGHT_INDEX) { break; } ++LightLoopIterationCount; uint LocalLightIndex = WaveActiveMin(VectorLocalLightIndex); if (LocalLightIndex == VectorLocalLightIndex) { ++GridLightIndex; SampleLight(ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LocalLightIndex, LightSampler, DebugContext); } } } // sample directional lights for (uint Index = ForwardLightStruct.DirectionalMegaLightsSupportedStartIndex; Index < ForwardLightStruct.NumDirectionalLights; ++Index) { SampleDirectionalLight(Index, ScreenCoord, TranslatedWorldPosition, Material, LightingChannelMask, VisibleLightHash, bHasValidHistory, LightSampler, DebugContext); } if (DebugContext.bIsActive) { Newline(DebugContext); Print(DebugContext, TEXT("Weight sum : ")); Print(DebugContext, LightSampler.WeightSum, FontValue); } if (DebugContext.bIsActive) { Newline(DebugContext); Newline(DebugContext); Print(DebugContext, TEXT("LightId | Weight | History | AsVis | Trace | UV"), FontSilver); } // Finalize samples for (uint LightSampleIndex = 0; LightSampleIndex < NUM_SAMPLES_PER_PIXEL_1D; ++LightSampleIndex) { FCandidateLightSample CandidateLightSample = UnpackCandidateLightSample(LightSampler.PackedSamples[LightSampleIndex]); FLightSample LightSample = InitLightSample(); FLightSampleRay LightSampleRay = InitLightSampleRay(); LightSample.bVisible = true; LightSample.bGuidedAsVisible = true; LightSample.LocalLightIndex = CandidateLightSample.LocalLightIndex; LightSample.Weight = CandidateLightSample.Weight; const FForwardLightData ForwardLightData = GetForwardLightData(LightSample.LocalLightIndex, 0); FDeferredLightData LightData = ConvertToDeferredLight(ForwardLightData); uint2 SampleCoord = GetSampleCoord(DownsampledScreenCoord, LightSampleIndex); const bool bAreaLight = LightData.SourceRadius > 0.001f || LightData.SourceLength > 0.01f; if (bAreaLight) { #if REFERENCE_MODE FRandomSequence RandSequence = RandomSequenceCreate(uint3(SampleCoord, MegaLightsStateFrameIndex), LightSampleIndex, NUM_SAMPLES_1D); // The Get1D() are to skip the first two samples used in the lights selection above, ensuring area light uv sampling is uncorrelated to it. RandSequence.Get1D(); LightSampleRay.UV = RandSequence.Get2D(); #else LightSampleRay.UV = BlueNoiseVec2(SampleCoord, MegaLightsStateFrameIndex); #endif } LightSampleRay.bHair = Material.bIsHair; LightSampleRay.bBackfaceDiffuse = Material.bHasBackfaceDiffuse; #if GUIDE_BY_HISTORY if (bHasValidHistory && ForwardLightData.PrevLocalLightIndex >= 0) { uint VisibleInLastFrameMask = GetLightVisibilityMask(VisibleLightMaskHash, ForwardLightData.PrevLocalLightIndex); LightSample.bGuidedAsVisible = CandidateLightSample.bLightWasVisible; if (AreaLightHiddenPDFWeight < 1) { // If entire light is fully visible or invisible then no need to guide samples towards the visible parts if (CandidateLightSample.bLightWasVisible && VisibleInLastFrameMask != 0 && VisibleInLastFrameMask != 0xF && bAreaLight) { // Probabilities of hitting of 2x2 light UV regions based on the visibility from the previous frame float Weight00 = VisibleInLastFrameMask & 0x1 ? 1.0f : AreaLightHiddenPDFWeight; float Weight10 = VisibleInLastFrameMask & 0x2 ? 1.0f : AreaLightHiddenPDFWeight; float Weight01 = VisibleInLastFrameMask & 0x4 ? 1.0f : AreaLightHiddenPDFWeight; float Weight11 = VisibleInLastFrameMask & 0x8 ? 1.0f : AreaLightHiddenPDFWeight; // Warp samples across X to fit desired distribution float SplitX = (Weight00 + Weight01) / (Weight00 + Weight10 + Weight01 + Weight11); if (LightSampleRay.UV.x < SplitX) { LightSampleRay.UV.x = LightSampleRay.UV.x / SplitX; LightSampleRay.UV.x *= 0.5f; } else { LightSampleRay.UV.x = (LightSampleRay.UV.x - SplitX) / (1.0f - SplitX); LightSampleRay.UV.x = LightSampleRay.UV.x * 0.5f + 0.5f; } // Warp samples across Y to fit desired distribution float SplitY = LightSampleRay.UV.x < 0.5f ? (Weight00 / (Weight00 + Weight01)) : (Weight10 / (Weight10 + Weight11)); if (LightSampleRay.UV.y < SplitY) { LightSampleRay.UV.y = LightSampleRay.UV.y / SplitY; LightSampleRay.UV.y *= 0.5f; } else { LightSampleRay.UV.y = (LightSampleRay.UV.y - SplitY) / (1.0f - SplitY); LightSampleRay.UV.y = LightSampleRay.UV.y * 0.5f + 0.5f; } float SelectedWeight; if (LightSampleRay.UV.x < 0.5f) { SelectedWeight = LightSampleRay.UV.y < 0.5f ? Weight00 : Weight01; } else { SelectedWeight = LightSampleRay.UV.y < 0.5f ? Weight10 : Weight11; } // Fixup weights to match new sample distribution float PerLightWeightSum = Weight00 + Weight10 + Weight01 + Weight11; LightSample.bGuidedAsVisible = SelectedWeight >= PerLightWeightSum / 4.0f; LightSample.Weight = LightSample.Weight * SelectedWeight * 4.0f / PerLightWeightSum; } } } #endif if (LightSample.LocalLightIndex != MAX_LOCAL_LIGHT_INDEX) { const bool bCastShadows = UnpackCastShadow(ForwardLightData); LightSampleRay.bCompleted = bCastShadows ? false : true; LightSample.Weight = LightSampler.WeightSum / LightSample.Weight; } // Skip identical rays if (!bAreaLight) { // Only check next sample for performance reasons // Sometimes due to mixing hidden samples this may not merge all rays, but it should handle almost all of them if (LightSampleIndex + 1 < NUM_SAMPLES_PER_PIXEL_1D) { FCandidateLightSample NextCandidateLightSample = UnpackCandidateLightSample(LightSampler.PackedSamples[LightSampleIndex + 1]); if (NextCandidateLightSample.LocalLightIndex == CandidateLightSample.LocalLightIndex) { LightSample.Weight = 0.0f; LightSample.bVisible = false; LightSampleRay.bCompleted = true; } } } if (DebugContext.bIsActive) { Newline(DebugContext); Print(DebugContext, ForwardLightData.LightSceneId, Select(ForwardLightData.LightSceneId == DebugLightId, FontSelected, FontValue)); Print(DebugContext, CandidateLightSample.Weight, FontValue); const uint VisibleInLastFrameMask = GetLightVisibilityMask(VisibleLightMaskHash, ForwardLightData.PrevLocalLightIndex); uint VisibleInLastFrameMaskDebug = 0; VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x1 ? 1000 : 0; VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x2 ? 100 : 0; VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x4 ? 10 : 0; VisibleInLastFrameMaskDebug += VisibleInLastFrameMask & 0x8 ? 1 : 0; Print(DebugContext, VisibleInLastFrameMaskDebug, FontValue, 10, 0); Print(DebugContext, LightSample.bGuidedAsVisible ? 1u : 0u, FontValue, 8, 0); Print(DebugContext, LightSampleRay.bCompleted ? 0u : 1u, FontValue, 8, 0); Print(DebugContext, LightSampleRay.UV, Select(bAreaLight, FontValue, FontSilver), 8, 3); if (DebugMode == DEBUG_MODE_VISUALIZE_SAMPLING) { const uint2 SampleCoord = GetSampleCoord(DownsampledScreenCoord, LightSampleIndex); const FLightSampleTrace LightSampleTrace = GetLightSampleTrace(TranslatedWorldPosition, LightSample.LocalLightIndex, LightSampleRay.UV); float4 RayColor = float4(LightData.Color.xyz / Luminance(LightData.Color.xyz), 1.0f); AddLineTWS(DebugContext, TranslatedWorldPosition, TranslatedWorldPosition + LightSampleTrace.Direction * LightSampleTrace.Distance, RayColor); } } RWLightSamples[SampleCoord] = PackLightSample(LightSample); RWLightSampleRays[SampleCoord] = PackLightSampleRay(LightSampleRay); } } else { for (uint LightSampleIndex = 0; LightSampleIndex < NUM_SAMPLES_PER_PIXEL_1D; ++LightSampleIndex) { RWLightSamples[GetSampleCoord(DownsampledScreenCoord, LightSampleIndex)] = PackLightSample(InitLightSample()); RWLightSampleRays[GetSampleCoord(DownsampledScreenCoord, LightSampleIndex)] = PackLightSampleRay(InitLightSampleRay()); } } AddTextBackground(DebugContext, FontBackground); RWDownsampledSceneDepth[DownsampledScreenCoord] = SceneDepth; RWDownsampledSceneWorldNormal[DownsampledScreenCoord] = EncodeNormal(Material.WorldNormalForPositionBias); } #if DEBUG_MODE if (bVisualizeLightLoopIterations != 0) { GridCellLightCount = WaveActiveMax(GridCellLightCount); LightLoopIterationCount = WaveActiveMax(LightLoopIterationCount); if (WaveGetLaneCount() < THREADGROUP_SIZE * THREADGROUP_SIZE) { uint GroupThreadIndex = GroupThreadId.y * THREADGROUP_SIZE + GroupThreadId.x; if (GroupThreadIndex % 32u == 0) { SharedGridCellLightCount[GroupThreadIndex / 32u] = GridCellLightCount; SharedLightLoopIterationCount[GroupThreadIndex / 32u] = LightLoopIterationCount; } GroupMemoryBarrierWithGroupSync(); GridCellLightCount = max(SharedGridCellLightCount[0], SharedGridCellLightCount[1]); LightLoopIterationCount = max(SharedLightLoopIterationCount[0], SharedLightLoopIterationCount[1]); } } if (DebugContext_VisIterations.bIsActive) { float HueRed = LinearRGB_2_HSV(ColorRed.xyz).x; float HueGreen = LinearRGB_2_HSV(ColorGreen.xyz).x; float HueBlue = LinearRGB_2_HSV(ColorBlue.xyz).x; float QuadHue = HueBlue; if (GridCellLightCount > 0) { if (2 * LightLoopIterationCount <= 3 * GridCellLightCount) { // blue -> green QuadHue = lerp(HueBlue, HueGreen, saturate(float(LightLoopIterationCount - GridCellLightCount) * 2 / GridCellLightCount)); } else { // green -> red QuadHue = lerp(HueGreen, HueRed, saturate(float(2 * LightLoopIterationCount - 3 * GridCellLightCount) / GridCellLightCount)); } } float4 QuadColor = float4(HSV_2_LinearRGB(float3(QuadHue, 1, 1)), 0.5); uint2 TileSize = uint2(TILE_SIZE, TILE_SIZE) / GetDownsampleFactor(); AddFilledQuadSS( DebugContext_VisIterations, ((DownsampledTileCoord + 0) * TileSize.x) / View.ViewResolutionFraction, ((DownsampledTileCoord + 1) * TileSize.y) / View.ViewResolutionFraction, QuadColor); AddQuadSS( DebugContext_VisIterations, ((DownsampledTileCoord + 0) * TileSize.x) / View.ViewResolutionFraction, ((DownsampledTileCoord + 1) * TileSize.y) / View.ViewResolutionFraction, ColorWhite); } #endif // DEBUG_MODE } } /** * Clear data for empty tiles, which won't be processed by GenerateLightSamplesCS */ [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void ClearLightSamplesCS( uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID) { uint DownsampledTileIndex = GroupId.x; if (DownsampledTileIndex < DownsampledTileAllocator[TILE_MODE_EMPTY]) { uint2 DownsampledTileCoord = UnpackTile(DownsampledTileData[DownsampledTileIndex + TILE_MODE_EMPTY * DownsampledTileDataStride]); uint2 DownsampledScreenCoord = DownsampledTileCoord * TILE_SIZE + GroupThreadId.xy + DownsampledViewMin; if (all(DownsampledScreenCoord < DownsampledViewMin + DownsampledViewSize)) { for (uint LightSampleY = 0; LightSampleY < NumSamplesPerPixel.y; ++LightSampleY) { for (uint LightSampleX = 0; LightSampleX < NumSamplesPerPixel.x; ++LightSampleX) { const uint2 SampleCoord = GetSampleCoord(DownsampledScreenCoord, LightSampleX, LightSampleY); RWLightSamples[SampleCoord] = PackLightSample(InitLightSample()); RWLightSampleRays[SampleCoord] = PackLightSampleRay(InitLightSampleRay()); } } } } }