// Copyright Epic Games, Inc. All Rights Reserved. #include "../Common.ush" #include "LumenMaterial.ush" #include "../DeferredShadingCommon.ush" #include "../RayTracing/RayTracingRayCone.ush" #include "LumenCardCommon.ush" #include "LumenTracingCommon.ush" #include "LumenSoftwareRayTracing.ush" #include "LumenRadianceCacheCommon.ush" #include "LumenReflectionCommon.ush" #include "../SceneTextureParameters.ush" #include "../SHCommon.ush" #include "../ShaderPrint.ush" #define HZB_TRACE_INCLUDE_FULL_RES_DEPTH 1 #include "LumenScreenTracing.ush" #include "LumenHairTracing.ush" #ifndef THREADGROUP_SIZE #define THREADGROUP_SIZE 1 #endif #define DEBUG_VISUALIZE_TRACE_TYPES 0 RWTexture2DArray RWTraceHit; RWTexture2DArray RWTraceRadiance; RWTexture2DArray RWTraceMaterialId; RWTexture2DArray RWTraceBackgroundVisibility; #ifdef ReflectionClearTracesCS [numthreads(REFLECTION_THREADGROUP_SIZE_1D, 1, 1)] void ReflectionClearTracesCS( uint GroupId : SV_GroupID, uint GroupThreadId : SV_GroupThreadID) { FReflectionTileData TileData; uint2 TmpReflectionTracingCoord = GetReflectionTracingScreenCoord(GroupId/*TileCoord*/, GroupThreadId/*InnerTileCoord*/, TileData).xy; const FReflectionTracingCoord ReflectionTracingCoord = GetReflectionTracingCoord(TmpReflectionTracingCoord, TileData.ClosureIndex); RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = 0.0f; RWTraceHit[ReflectionTracingCoord.CoordFlatten] = EncodeRayDistance(0.0f, false); #if CLEAT_TRACE_MATERIAL_ID { RWTraceMaterialId[ReflectionTracingCoord.CoordFlatten] = PackTraceMaterialId(false, 0); } #endif #if CLEAR_BACKGROUND_VISIBILITY { RWTraceBackgroundVisibility[ReflectionTracingCoord.CoordFlatten] = 1.0f; } #endif } #endif float MaxTraceDistance; float MaxHierarchicalScreenTraceIterations; float RelativeDepthThickness; float HistoryDepthTestRelativeThickness; uint MinimumTracingThreadOccupancy; float4 FirstPersonWorldSpaceRepresentationBounds; // Collective bounding sphere for all the invisible primitives tagged as FirstPersonWorldSpaceRepresentation float FirstPersonMinimumHitDistanceOnScreenTraceMiss; #ifdef ReflectionTraceScreenTexturesCS [numthreads(REFLECTION_THREADGROUP_SIZE_1D, 1, 1)] void ReflectionTraceScreenTexturesCS( uint GroupId : SV_GroupID, uint GroupThreadId : SV_GroupThreadID) { FReflectionTileData TileData; const uint2 TmpReflectionTracingCoord = GetReflectionTracingScreenCoord(GroupId/*TileCoord*/, GroupThreadId/*InnerTileCoord*/, TileData).xy; const FReflectionTracingCoord ReflectionTracingCoord = GetReflectionTracingCoord(TmpReflectionTracingCoord, TileData.ClosureIndex); if (all(ReflectionTracingCoord.Coord < ReflectionTracingViewMin + ReflectionTracingViewSize)) { float2 ScreenUV = GetScreenUVFromReflectionTracingCoord(ReflectionTracingCoord.Coord); float SceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten, 0)).x; if (SceneDepth > 0.0f) { float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth); FRayData RayData = GetRayData(ReflectionTracingCoord.CoordFlatten); float3 TranslatedRayOrigin = TranslatedWorldPosition; // Bias along the normal to avoid self-intersecting camera facing scene depth texels // HZB traversal uses point filtering, so scene depth texels stick out from the plane they are representing { float2 CornerScreenUV = ScreenUV + .5f * View.BufferSizeAndInvSize.zw; float2 CornerScreenPosition = (CornerScreenUV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy; float3 CornerTranslatedWorldPosition = mul(float4(GetScreenPositionForProjectionType(CornerScreenPosition, SceneDepth), SceneDepth, 1), View.ScreenToTranslatedWorld).xyz; #if SUBTRATE_GBUFFER_FORMAT==1 const float3 WorldNormal = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(CornerScreenUV * View.BufferSizeAndInvSize.xy, 0))).WorldNormal; #else const float3 WorldNormal = GetGBufferDataFromSceneTextures(ScreenUV).WorldNormal; #endif // Project texel corner onto normal float NormalBias = abs(dot(CornerTranslatedWorldPosition - TranslatedWorldPosition, WorldNormal)) * 2.0f; TranslatedRayOrigin += NormalBias * WorldNormal; float4 RayStartClip = mul(float4(TranslatedRayOrigin, 1.0f), View.TranslatedWorldToClip); // Skip screen traces if the normal bias pushed our starting point off-screen, as TraceScreen() doesn't handle this if (any(RayStartClip.xy < -RayStartClip.w) || any(RayStartClip.xy > RayStartClip.w)) { return; } } bool bHit; bool bUncertain; float3 HitUVz; float3 LastVisibleHitUVz; float HitTileZ; TraceScreen( TranslatedRayOrigin, RayData.Direction, RayData.RadianceCacheMaxTraceDistance, HZBUvFactorAndInvFactor, MaxHierarchicalScreenTraceIterations, RelativeDepthThickness, 0, MinimumTracingThreadOccupancy, bHit, bUncertain, HitUVz, LastVisibleHitUVz, HitTileZ); #if USE_HAIRSTRANDS_SCREEN if (!bHit) { bool Hair_bHit; bool Hair_bUncertain; float3 Hair_HitUVz; float3 Hair_LastVisibleHitUVz; float Hair_HitTileZ; TraceHairScreen( TranslatedWorldPosition, RayData.Direction, RayData.RadianceCacheMaxTraceDistance, HZBUvFactorAndInvFactor, MaxHierarchicalScreenTraceIterations, RelativeDepthThickness, 0, Hair_bHit, Hair_bUncertain, Hair_HitUVz, Hair_LastVisibleHitUVz, Hair_HitTileZ); if (Hair_bHit && !Hair_bUncertain) { bHit = Hair_bHit; HitUVz = Hair_HitUVz; LastVisibleHitUVz = Hair_LastVisibleHitUVz; bUncertain = Hair_bUncertain; HitTileZ = Hair_HitTileZ; } } #endif bHit = bHit && !bUncertain; if (bHit) { float2 HitScreenUV = HitUVz.xy; float2 HitScreenPosition = (HitUVz.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy; float HitDeviceZ = HitTileZ; float3 HitHistoryScreenPosition = GetHistoryScreenPosition(HitScreenPosition, HitScreenUV, HitDeviceZ); float Vignette = min(ComputeHitVignetteFromScreenPos(HitScreenPosition), ComputeHitVignetteFromScreenPos(HitHistoryScreenPosition.xy)); float Noise = InterleavedGradientNoise(ReflectionTracingCoord.Coord + 0.5f, ReflectionsStateFrameIndexMod8); // Skip reporting a hit if near the edge of the screen if (Vignette < Noise) { bHit = false; HitUVz = LastVisibleHitUVz; } if (bHit) { // Calculate the expected depth of the pixel last frame float PrevDeviceZ = HitHistoryScreenPosition.z; // Lookup the actual depth at the same screen position last frame float2 HitHistoryScreenUVForDepth = HitHistoryScreenPosition.xy * PrevScreenPositionScaleBiasForDepth.xy + PrevScreenPositionScaleBiasForDepth.zw; float HistoryDeviceZ = Texture2DSampleLevel(HistorySceneDepth, GlobalPointClampedSampler, HitHistoryScreenUVForDepth, 0).x; bHit = abs(HistoryDeviceZ - PrevDeviceZ) < HistoryDepthTestRelativeThickness * lerp(.5f, 2.0f, Noise); if (!bHit) { // We found a hit but discarded it, rewind to the last time we know the ray hadn't hit for the next method to resume from HitUVz = LastVisibleHitUVz; } } if (bHit) { float2 HitHistoryScreenUV = HitHistoryScreenPosition.xy * PrevScreenPositionScaleBias.xy + PrevScreenPositionScaleBias.zw; HitHistoryScreenUV = clamp(HitHistoryScreenUV, PrevSceneColorBilinearUVMin, PrevSceneColorBilinearUVMax); float3 Lighting = SampleScreenColor(PrevSceneColorTexture, GlobalPointClampedSampler, HitHistoryScreenUV).xyz * PrevSceneColorPreExposureCorrection; float MaxLighting = max3(Lighting.x, Lighting.y, Lighting.z); if (MaxLighting > MaxRayIntensity) { Lighting *= MaxRayIntensity / MaxLighting; } #if DEBUG_VISUALIZE_TRACE_TYPES RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = float3(.5f, 0, 0) * View.PreExposure; #else RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = Lighting; #endif } } // Make the ray slightly longer as distance reported from screen space traces isn't very reliable. // This way we can skip tracing if we reached trace max distance. If not, then the next pass will correct for it using RT pullback bias. float HitDistanceOffset = !bHit && !bUncertain ? 2.0f : 0.0f; float HitDistance = min(sqrt(ComputeRayHitSqrDistance(TranslatedWorldPosition, HitUVz)) + HitDistanceOffset, MaxTraceDistance); #if HAS_FIRST_PERSON_GBUFFER_BIT // First person world space representation primitives are invisible to the camera but visible in shadows and HWRT reflections. // Screen traces can miss intersections with these invisible meshes, so when HWRT picks up, we end up seeing holes and other artifacts // in the reflected meshes. To work around this issue, we move the ray position at which we hand off to HWRT outside the bounds of the // invisible geometry such that HWRT has a chance to intersect it. if (!bHit && FirstPersonWorldSpaceRepresentationBounds.w > 0.0f) { const float2 Intersections = RayIntersectSphere(TranslatedRayOrigin, RayData.Direction, FirstPersonWorldSpaceRepresentationBounds); if (all(Intersections != -1.0f)) { HitDistance = min(HitDistance, max(Intersections.x, FirstPersonMinimumHitDistanceOnScreenTraceMiss)); } } #endif // HAS_FIRST_PERSON_GBUFFER_BIT RWTraceHit[ReflectionTracingCoord.CoordFlatten] = EncodeRayDistance(HitDistance, bHit); } } } #endif RWBuffer RWCompactedTraceTexelAllocator; RWBuffer RWReflectionCompactionIndirectArgs; Buffer ReflectionTracingTileIndirectArgs; uint CompactionThreadGroupSize; #ifdef SetupCompactionIndirectArgsCS [numthreads(64, 1, 1)] void SetupCompactionIndirectArgsCS(uint GroupThreadId : SV_GroupThreadID) { uint NumTracingThreads = ReflectionTracingTileIndirectArgs[0] * REFLECTION_THREADGROUP_SIZE_1D; uint NumCompactionGroups = DivideAndRoundUp(NumTracingThreads, CompactionThreadGroupSize); if (GroupThreadId < 3) { RWReflectionCompactionIndirectArgs[GroupThreadId] = GroupThreadId == 0 ? NumCompactionGroups : 1; } if (GroupThreadId == 0) { RWCompactedTraceTexelAllocator[0] = 0; } } #endif RWBuffer RWCompactedTraceTexelData; // LumenReflections::ETraceCompactionMode #define TRACE_COMPACTION_MODE_DEFAULT 0 #define TRACE_COMPACTION_MODE_FAR_FIELD 1 #define TRACE_COMPACTION_MODE_HIT_LIGHTING 2 uint CullByDistanceFromCamera; float CompactionTracingEndDistanceFromCamera; float CompactionMaxTraceDistance; float NearFieldMaxTraceDistance; float FarFieldDitheredStartDistanceFactor; Texture2DArray TraceMaterialId; #ifdef ReflectionCompactTracesCS groupshared uint SharedGlobalTraceTexelStartOffset; //@todo - ordered compaction for non-wave ops path #if WAVE_OPS groupshared uint SharedGroupSum; #else groupshared uint SharedTraceTexelAllocator; groupshared uint SharedTraceTexels[THREADGROUP_SIZE]; #endif #if COMPILER_SUPPORTS_WAVE_SIZE && WAVE_OPS WAVESIZE(32) #endif [numthreads(THREADGROUP_SIZE, 1, 1)] void ReflectionCompactTracesCS( uint GroupId : SV_GroupID, uint GroupThreadId : SV_GroupThreadID) { if (GroupThreadId == 0) #if WAVE_OPS { SharedGroupSum = 0; } #else { SharedTraceTexelAllocator = 0; } GroupMemoryBarrierWithGroupSync(); #endif uint ReflectionTileIndex = GroupId * (THREADGROUP_SIZE / REFLECTION_THREADGROUP_SIZE_1D) + GroupThreadId / REFLECTION_THREADGROUP_SIZE_1D; uint ReflectionGroupThreadId = GroupThreadId % REFLECTION_THREADGROUP_SIZE_1D; uint TraceTexelForThisThread = 0; bool bTraceValid = false; if (ReflectionTileIndex < ReflectionTracingTileIndirectArgs[0]) { FReflectionTileData TileData; const uint2 TmpReflectionTracingCoord = GetReflectionTracingScreenCoordZOrder(ReflectionTileIndex/*TileCoord*/, ReflectionGroupThreadId/*InnerTileCoord*/, TileData); const FReflectionTracingCoord ReflectionTracingCoord = GetReflectionTracingCoord(TmpReflectionTracingCoord, TileData.ClosureIndex); if (all(ReflectionTracingCoord.Coord < ReflectionTracingViewMin + ReflectionTracingViewSize)) { float2 ScreenUV = GetScreenUVFromReflectionTracingCoord(ReflectionTracingCoord.Coord); float SceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten, 0)).x; bool bHit; float TraceHitDistance = DecodeRayDistance(TraceHit[ReflectionTracingCoord.CoordFlatten].x, bHit); float MaxTraceDistance = CompactionMaxTraceDistance; bool bAcceptTrace = false; #if TRACE_COMPACTION_MODE == TRACE_COMPACTION_MODE_HIT_LIGHTING { const FTraceMaterialId MaterialId = UnpackTraceMaterialId(TraceMaterialId[ReflectionTracingCoord.CoordFlatten]); if (bHit && MaterialId.bNeedsHitLightingPass) { bAcceptTrace = true; } } #elif TRACE_COMPACTION_MODE == TRACE_COMPACTION_MODE_FAR_FIELD { bAcceptTrace = !bHit; } #else { bAcceptTrace = !bHit; } #endif if (SceneDepth > 0 && bAcceptTrace && (CullByDistanceFromCamera == 0 || SceneDepth < CompactionTracingEndDistanceFromCamera) && TraceHitDistance <= CompactionMaxTraceDistance) { #if WAVE_OPS bTraceValid = true; TraceTexelForThisThread = EncodeTraceTexel(ReflectionTracingCoord.Coord, ReflectionTracingCoord.ClosureIndex); #else uint SharedTexelOffset; InterlockedAdd(SharedTraceTexelAllocator, 1, SharedTexelOffset); SharedTraceTexels[SharedTexelOffset] = EncodeTraceTexel(ReflectionTracingCoord.Coord, ReflectionTracingCoord.ClosureIndex); #endif } } } GroupMemoryBarrierWithGroupSync(); #if WAVE_OPS const uint LastLaneIndex = WaveGetLaneCount() - 1; const uint LaneIndex = WaveGetLaneIndex(); const uint OffsetInWave = WavePrefixCountBits(bTraceValid); uint OffsetInGroup = 0; if (LaneIndex == LastLaneIndex) { const uint ThisWaveSum = OffsetInWave + (bTraceValid ? 1 : 0); InterlockedAdd(SharedGroupSum, ThisWaveSum, OffsetInGroup); } OffsetInGroup = WaveReadLaneAt(OffsetInGroup, LastLaneIndex) + OffsetInWave; GroupMemoryBarrierWithGroupSync(); // Allocate this group's compacted traces from the global allocator if (GroupThreadId == 0) { InterlockedAdd(RWCompactedTraceTexelAllocator[0], SharedGroupSum, SharedGlobalTraceTexelStartOffset); } GroupMemoryBarrierWithGroupSync(); if (bTraceValid) { RWCompactedTraceTexelData[SharedGlobalTraceTexelStartOffset + OffsetInGroup] = TraceTexelForThisThread; } #else // !WAVE_OPS uint ThreadIndex = GroupThreadId; if (ThreadIndex == 0) { InterlockedAdd(RWCompactedTraceTexelAllocator[0], SharedTraceTexelAllocator, SharedGlobalTraceTexelStartOffset); } GroupMemoryBarrierWithGroupSync(); if (ThreadIndex < SharedTraceTexelAllocator) { RWCompactedTraceTexelData[SharedGlobalTraceTexelStartOffset + ThreadIndex] = SharedTraceTexels[ThreadIndex]; } #endif } #endif #ifdef SetupCompactedTracesIndirectArgsCS RWBuffer RWReflectionCompactTracingIndirectArgs; RWBuffer RWReflectionCompactRayTraceDispatchIndirectArgs; [numthreads(1, 1, 1)] void SetupCompactedTracesIndirectArgsCS() { // THREADGROUP_SIZE_64 WriteDispatchIndirectArgs(RWReflectionCompactTracingIndirectArgs, 0, DivideAndRoundUp(CompactedTraceTexelAllocator[0],64u), 1, 1); // THREADGROUP_SIZE_32 WriteDispatchIndirectArgs(RWReflectionCompactTracingIndirectArgs, 1, DivideAndRoundUp(CompactedTraceTexelAllocator[0],32u), 1, 1); // THREADGROUP_SIZE_256 WriteDispatchIndirectArgs(RWReflectionCompactTracingIndirectArgs, 2, DivideAndRoundUp(CompactedTraceTexelAllocator[0],256u), 1, 1); // Ray tracing dispatch dimensions are defined simply in terms of threads/rays, not thread groups. WriteDispatchIndirectArgs(RWReflectionCompactRayTraceDispatchIndirectArgs, 0, CompactedTraceTexelAllocator[0], 1, 1); } #endif #ifdef ReflectionSortTracesByMaterialCS #ifndef THREADGROUP_SIZE_1D #define THREADGROUP_SIZE_1D 1 #endif #define NUM_BINS (THREADGROUP_SIZE_1D / 2) groupshared uint Bins[NUM_BINS]; #if DIM_WAVE_OPS groupshared uint SharedGroupSum; #define Offsets Bins // No need for a separate array of offsets as prefix sum is computed in-place #else // DIM_WAVE_OPS #if NUM_BINS > 0 groupshared uint Offsets[NUM_BINS]; #else groupshared uint Offsets[1]; #endif #endif // DIM_WAVE_OPS #define ELEMENTS_PER_THREAD 16 #define NUM_ELEMENTS THREADGROUP_SIZE_1D * ELEMENTS_PER_THREAD #if COMPILER_SUPPORTS_WAVE_SIZE && DIM_WAVE_OPS WAVESIZE(32) #endif [numthreads(THREADGROUP_SIZE_1D, 1, 1)] void ReflectionSortTracesByMaterialCS( uint GroupId : SV_GroupID, uint GroupThreadId : SV_GroupThreadID) { const uint GroupOffset = GroupId * NUM_ELEMENTS; #if DIM_WAVE_OPS if (GroupThreadId == 0) { SharedGroupSum = 0; } #endif if (GroupThreadId < NUM_BINS) { Bins[GroupThreadId] = 0; Offsets[GroupThreadId] = 0; } GroupMemoryBarrierWithGroupSync(); uint Hash[NUM_ELEMENTS / THREADGROUP_SIZE_1D]; uint TraceDataCache[NUM_ELEMENTS / THREADGROUP_SIZE_1D]; uint TraceBinCache[NUM_ELEMENTS / THREADGROUP_SIZE_1D]; for (int i = GroupThreadId; i < NUM_ELEMENTS; i += THREADGROUP_SIZE_1D) { uint RayIndex = GroupOffset + i; if (RayIndex < CompactedTraceTexelAllocator[0]) { uint PackedTraceData = CompactedTraceTexelData[RayIndex]; FReflectionTracingCoord ReflectionTracingCoord = DecodeTraceTexel(PackedTraceData); const FTraceMaterialId MaterialId = UnpackTraceMaterialId(TraceMaterialId[ReflectionTracingCoord.CoordFlatten]); uint BinIndex = MaterialId.MaterialId % NUM_BINS; TraceDataCache[i / THREADGROUP_SIZE_1D] = PackedTraceData; TraceBinCache[i / THREADGROUP_SIZE_1D] = BinIndex; InterlockedAdd(Bins[BinIndex], 1, Hash[i / THREADGROUP_SIZE_1D]); } } GroupMemoryBarrierWithGroupSync(); #if DIM_WAVE_OPS { const uint LastLaneIndex = WaveGetLaneCount() - 1; const uint LaneIndex = WaveGetLaneIndex(); const uint BinIndex = GroupThreadId; uint Value = 0; if (BinIndex < NUM_BINS) { Value = Bins[BinIndex]; } uint BinOffset = WavePrefixSum(Value); const uint ThisWaveSum = BinOffset + Value; uint WaveOffset = 0; if (LaneIndex == LastLaneIndex && ThisWaveSum > 0) { InterlockedAdd(SharedGroupSum, ThisWaveSum, WaveOffset); } BinOffset += WaveReadLaneAt(WaveOffset, LastLaneIndex); GroupMemoryBarrierWithGroupSync(); if (BinIndex < NUM_BINS) { Offsets[BinIndex] = BinOffset; } } #else // DIM_WAVE_OPS if (GroupThreadId < NUM_BINS) { for (int i = 0; i < GroupThreadId; ++i) { Offsets[GroupThreadId] += Bins[i]; } } #endif // DIM_WAVE_OPS GroupMemoryBarrierWithGroupSync(); for (int i = GroupThreadId; i < NUM_ELEMENTS; i += THREADGROUP_SIZE_1D) { uint RayIndex = GroupOffset + i; if (RayIndex < CompactedTraceTexelAllocator[0]) { uint BinIndex = TraceBinCache[i / THREADGROUP_SIZE_1D]; uint OutputIndex = GroupOffset + Offsets[BinIndex] + Hash[i / THREADGROUP_SIZE_1D]; RWCompactedTraceTexelData[OutputIndex] = TraceDataCache[i / THREADGROUP_SIZE_1D]; } } } #endif uint CardGridPixelSizeShift; float3 CardGridZParams; uint3 CullGridSize; uint ComputeCardGridCellIndex(uint2 PixelPos, float SceneDepth) { uint ZSlice = (uint)(max(0, log2(SceneDepth * CardGridZParams.x + CardGridZParams.y) * CardGridZParams.z)); ZSlice = min(ZSlice, (uint)(CullGridSize.z - 1)); uint3 GridCoordinate = uint3(PixelPos >> CardGridPixelSizeShift, ZSlice); uint GridIndex = (GridCoordinate.z * CullGridSize.y + GridCoordinate.y) * CullGridSize.x + GridCoordinate.x; return GridIndex; } float SurfaceBias; float CardTraceEndDistanceFromCamera; float MaxMeshSDFTraceDistance; void TraceMeshSDFs(FReflectionTracingCoord ReflectionTracingCoord, float TraceHitDistance) { float2 ScreenUV = GetScreenUVFromReflectionTracingCoord(ReflectionTracingCoord.Coord); float SceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten, 0)).x; float3 WorldPosition = GetWorldPositionFromScreenUV(ScreenUV, SceneDepth); float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth); FRayData RayData = GetRayData(ReflectionTracingCoord.CoordFlatten); float3 SamplePosition = WorldPosition + SurfaceBias * RayData.Direction; float3 SampleTranslatedPosition = TranslatedWorldPosition + SurfaceBias * RayData.Direction; float PullbackForSurfaceExpand = SurfaceBias; FRayCone RayCone = (FRayCone)0; RayCone.SpreadAngle = View.EyeToPixelSpreadAngle; RayCone = PropagateRayCone(RayCone, RayData.ConeHalfAngle, SceneDepth); FConeTraceInput TraceInput; TraceInput.Setup(SamplePosition, SampleTranslatedPosition, RayData.Direction, RayCone.SpreadAngle, 0.0f, max(TraceHitDistance - PullbackForSurfaceExpand, 0.0f), RayData.RadianceCacheMaxTraceDistance, 1.0f); TraceInput.bHiResSurface = UseHighResSurface != 0; TraceInput.VoxelTraceStartDistance = MaxMeshSDFTraceDistance; TraceInput.DitherScreenCoord = ReflectionTracingCoord.CoordFlatten.xy; uint CardGridCellIndex = ComputeCardGridCellIndex(ReflectionTracingCoord.Coord * ReflectionDownsampleFactorXY, SceneDepth); TraceInput.NumMeshSDFs = NumGridCulledMeshSDFObjects[CardGridCellIndex]; TraceInput.MeshSDFStartOffset = GridCulledMeshSDFObjectStartOffsetArray[CardGridCellIndex]; TraceInput.CardInterpolateInfluenceRadius = 10; // Only expand the SDF surface when the ray traversal is far enough away to not self-intersect TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance = false; TraceInput.InitialMaxDistance = 0; FConeTraceResult TraceResult; ConeTraceLumenSceneCards(TraceInput, TraceResult); TraceInput.NumHeightfields = NumGridCulledHeightfieldObjects[CardGridCellIndex]; TraceInput.HeightfieldStartOffset = GridCulledHeightfieldObjectStartOffsetArray[CardGridCellIndex]; ConeTraceLumenSceneHeightfields(TraceInput, TraceResult); // Trace against hair voxel structure, if enabled and not already done by other tracing method #if USE_HAIRSTRANDS_VOXEL { float HairTraceDistance = min(RayData.RadianceCacheMaxTraceDistance, TraceResult.OpaqueHitDistance); bool bHairHit; float HairTransparency; float HairHitT; TraceHairVoxels( ReflectionTracingCoord.Coord, SceneDepth, // Use (Translated)WorldPosition instead of SamplePosition, as the bias is too strong otherwise. This is not an issue as // the voxel structure does not cause any self shadowing issue TranslatedWorldPosition, RayData.Direction, HairTraceDistance, true, bHairHit, HairTransparency, HairHitT); if (bHairHit && HairHitT < HairTraceDistance) { TraceResult.Lighting *= HairTransparency; TraceResult.Transparency *= HairTransparency; TraceResult.OpaqueHitDistance = min(HairHitT, TraceResult.OpaqueHitDistance); } } #endif float3 Lighting = TraceResult.Lighting * View.PreExposure; float Transparency = TraceResult.Transparency; float OpaqueHitDistance = TraceResult.OpaqueHitDistance; float DistanceFromViewpoint = GetDistanceToCameraFromViewVector(DFHackToFloat(PrimaryView.WorldCameraOrigin) - WorldPosition); float DistanceFade = saturate(4 * DistanceFromViewpoint / CardTraceEndDistanceFromCamera - 3); DistanceFade = max(DistanceFade, saturate((OpaqueHitDistance - MaxMeshSDFTraceDistance * .7f) / (MaxMeshSDFTraceDistance * .3f))); if(SampleHeightFog > 0) { float CoverageForFog = saturate(1.0 - Transparency); // LUMEN_TODO single evaluation for now even if we can hit translucent hair above. float3 OriginToCollider = RayData.Direction * TraceResult.OpaqueHitDistance; Lighting.rgb = GetFogOnLuminance(Lighting.rgb, CoverageForFog, TranslatedWorldPosition, RayData.Direction, TraceResult.OpaqueHitDistance); } Transparency = lerp(Transparency, 1, DistanceFade); Lighting += GetSkylightLeakingForReflections(RayData.Direction, TraceResult.GeometryWorldNormal, OpaqueHitDistance) * View.PreExposure; float MaxLighting = max3(Lighting.x, Lighting.y, Lighting.z); if (MaxLighting > MaxRayIntensity) { Lighting *= MaxRayIntensity / MaxLighting; } #if DEBUG_VISUALIZE_TRACE_TYPES RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = float3(0, .5f, 0) * View.PreExposure; #else RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = Lighting; #endif bool bHit = false; if (Transparency < InterleavedGradientNoise(ReflectionTracingCoord.Coord + 0.5f, 0)) { bHit = true; } RWTraceHit[ReflectionTracingCoord.CoordFlatten] = EncodeRayDistance(OpaqueHitDistance, bHit); } #if THREADGROUP_SIZE_32 #define REFLECTION_TRACE_THREADGROUP_SIZE_1D 32 #else #define REFLECTION_TRACE_THREADGROUP_SIZE_1D 64 #endif #ifdef ReflectionTraceMeshSDFsCS [numthreads(REFLECTION_TRACE_THREADGROUP_SIZE_1D, 1, 1)] void ReflectionTraceMeshSDFsCS(uint DispatchThreadId : SV_DispatchThreadID) { if (DispatchThreadId < CompactedTraceTexelAllocator[0]) { const FReflectionTracingCoord ReflectionTracingCoord = DecodeTraceTexel(CompactedTraceTexelData[DispatchThreadId]); bool bUnused; float TraceHitDistance = DecodeRayDistance(RWTraceHit[ReflectionTracingCoord.CoordFlatten], bUnused); TraceMeshSDFs(ReflectionTracingCoord, TraceHitDistance); } } #endif void TraceVoxels(FReflectionTracingCoord ReflectionTracingCoord, float TraceHitDistance) { float2 ScreenUV = GetScreenUVFromReflectionTracingCoord(ReflectionTracingCoord.Coord); float SceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten, 0)).x; float3 WorldPosition = GetWorldPositionFromScreenUV(ScreenUV, SceneDepth); // LUMEN_LWC_TODO float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth); FRayData RayData = GetRayData(ReflectionTracingCoord.CoordFlatten); float3 SamplePosition = WorldPosition + SurfaceBias * RayData.Direction; float3 SampleTranslatedPosition = TranslatedWorldPosition + SurfaceBias * RayData.Direction; FRayCone RayCone = (FRayCone)0; RayCone.SpreadAngle = View.EyeToPixelSpreadAngle; RayCone = PropagateRayCone(RayCone, RayData.ConeHalfAngle, SceneDepth); FConeTraceInput TraceInput; TraceInput.Setup(SamplePosition, SampleTranslatedPosition, RayData.Direction, RayCone.SpreadAngle, 0.0f, 0.0f, RayData.RadianceCacheMaxTraceDistance, 1.0f); TraceInput.DitherScreenCoord = ReflectionTracingCoord.Coord; TraceInput.bHiResSurface = UseHighResSurface != 0; uint ClipmapForSurfaceExpand = ComputeGlobalDistanceFieldClipmapIndex(TranslatedWorldPosition + TraceHitDistance * RayData.Direction); // Pull back from where the previous tracing method left off so that we start outside the surface, so that the SDF expansion works properly and prevents leaking through thin objects float PullbackForSurfaceExpand = GlobalVolumeTranslatedCenterAndExtent[ClipmapForSurfaceExpand].w * GlobalVolumeTexelSize * 4.0f; TraceInput.VoxelTraceStartDistance = max(TraceHitDistance - PullbackForSurfaceExpand, 0.0f); // Only expand the SDF surface when the ray traversal is far enough away to not self-intersect TraceInput.bExpandSurfaceUsingRayTimeInsteadOfMaxDistance = false; TraceInput.InitialMaxDistance = 0; // Dither step factor to smooth out SDF stepping artifacts, but only when near mirror float StepFactorNoise = lerp(.95f, 1.0f / .95f, InterleavedGradientNoise(ReflectionTracingCoord.Coord, ReflectionsStateFrameIndexMod8)); TraceInput.SDFStepFactor = lerp(StepFactorNoise, 1.0f, saturate(RayData.ConeHalfAngle / (PI / 256.0f))); FConeTraceResult TraceResult = (FConeTraceResult)0; TraceResult.Lighting = 0; TraceResult.Transparency = 1; TraceResult.OpaqueHitDistance = TraceInput.MaxTraceDistance; TraceResult.GeometryWorldNormal = 0.0f; #if TRACE_GLOBAL_SDF { ConeTraceLumenSceneVoxels(TraceInput, TraceResult); } #endif // Trace against hair voxel structure, if enabled and not already done by other tracing method #if USE_HAIRSTRANDS_VOXEL { float HairTraceDistance = min(RayData.RadianceCacheMaxTraceDistance, TraceResult.OpaqueHitDistance); bool bHairHit; float HairTransparency; float HairHitT; TraceHairVoxels( ReflectionTracingCoord.Coord, SceneDepth, // Use (Translated)WorldPosition instead of SamplePosition, as the bias is too strong otherwise. This is not an issue as // the voxel structure does not cause any self shadowing issue TranslatedWorldPosition, RayData.Direction, HairTraceDistance, true, bHairHit, HairTransparency, HairHitT); if (bHairHit && HairHitT < HairTraceDistance) { TraceResult.Lighting *= HairTransparency; TraceResult.Transparency *= HairTransparency; TraceResult.OpaqueHitDistance = min(HairHitT, TraceResult.OpaqueHitDistance); } } #endif bool bHit = false; if (TraceResult.Transparency <= .5f) { TraceHitDistance = TraceResult.OpaqueHitDistance; bHit = true; } else { TraceHitDistance = GetMaxHitDistance(); } #if SAMPLE_SCENE_COLOR if (bHit) { float3 HitTranslatedWorldPosition = SampleTranslatedPosition + RayData.Direction * TraceResult.OpaqueHitDistance; SampleSceneColorAtHit(HitTranslatedWorldPosition, TraceResult.GeometryWorldNormal, ReflectionTracingCoord.Coord, RelativeDepthThickness, TraceResult.Lighting); } #endif float CoverageForFog = saturate(1.0 - TraceResult.Transparency); #if DISTANT_SCREEN_TRACES if (!bHit) { float3 TranslatedDistantRayOrigin = TranslatedWorldPosition; float MaxTraceDistanceForDistantScreenTrace = DistantScreenTraceMaxTraceDistance; // Start the distant linear screen traces where we have no fallback for HZB screen traces ClipRayToStartOutsideGlobalSDF(TranslatedDistantRayOrigin, RayData.Direction, MaxTraceDistanceForDistantScreenTrace); if (MaxTraceDistanceForDistantScreenTrace > 0) { float4 StartRayClip = mul(float4(TranslatedDistantRayOrigin, 1.0), View.TranslatedWorldToClip); // If we start tracing from outside the screen (from a position on the screen) then we know we will never hit any valueable screen information. // So we skip the tracing if the start position is clipped outside of the view frustum. if (StartRayClip.w >= 0.0 && all(-StartRayClip.ww <= StartRayClip.xy) && all(StartRayClip.xy <= StartRayClip.ww)) { // Update SceneDepth according to the new start position moved to the edge of the clipmap. This is important for the SSR HZB tracing to be correct. float StartRayDeviceZ = StartRayClip.z / StartRayClip.w; float DistantTracingSceneDepth = ConvertFromDeviceZ(StartRayDeviceZ); DistantScreenTrace( ReflectionTracingCoord.Coord + 0.5f, HZBUvFactorAndInvFactor, TranslatedDistantRayOrigin, RayData.Direction, MaxTraceDistanceForDistantScreenTrace, DistantTracingSceneDepth, bHit, TraceResult.Lighting); } } } #endif if (!bHit) { #if RADIANCE_CACHE if (RayData.RadianceCacheMaxTraceDistance < MaxTraceDistance * .99f) { FRadianceCacheCoverage Coverage = GetRadianceCacheCoverage(WorldPosition, RayData.Direction, InterleavedGradientNoise(ReflectionTracingCoord.Coord, ReflectionsStateFrameIndexMod8)); SampleRadianceCacheAndApply(Coverage, WorldPosition, RayData.Direction, RayData.ConeHalfAngle, /*RandomScalarForStochasticIterpolation*/ 0.5f, TraceResult.Lighting, TraceResult.Transparency); TraceHitDistance = RayData.RadianceCacheMaxTraceDistance; } else #endif { TraceHitDistance = MaxTraceDistance; ApplySkylightToTraceResult(RayData.Direction, TraceResult); CoverageForFog = 1.0f; } } TraceResult.Lighting += GetSkylightLeakingForReflections(TraceResult.GeometryWorldNormal, RayData.Direction, TraceResult.OpaqueHitDistance); TraceResult.Lighting *= View.PreExposure; if (SampleHeightFog > 0) { float3 OriginToCollider = RayData.Direction * TraceHitDistance; TraceResult.Lighting.rgb = GetFogOnLuminance(TraceResult.Lighting.rgb, CoverageForFog, TranslatedWorldPosition, RayData.Direction, TraceHitDistance); } float MaxLighting = max3(TraceResult.Lighting.x, TraceResult.Lighting.y, TraceResult.Lighting.z); if (MaxLighting > MaxRayIntensity) { TraceResult.Lighting *= MaxRayIntensity / MaxLighting; } #if DEBUG_VISUALIZE_TRACE_TYPES RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = float3(0, 0, .5f) * View.PreExposure; #else RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = TraceResult.Lighting; #endif RWTraceHit[ReflectionTracingCoord.CoordFlatten] = EncodeRayDistance(TraceHitDistance, bHit); } #ifdef ReflectionTraceVoxelsCS [numthreads(REFLECTION_TRACE_THREADGROUP_SIZE_1D, 1, 1)] void ReflectionTraceVoxelsCS(uint DispatchThreadId : SV_DispatchThreadID) { if (DispatchThreadId < CompactedTraceTexelAllocator[0]) { const FReflectionTracingCoord ReflectionTracingCoord = DecodeTraceTexel(CompactedTraceTexelData[DispatchThreadId]); bool bUnused; float TraceHitDistance = DecodeRayDistance(RWTraceHit[ReflectionTracingCoord.CoordFlatten], bUnused); TraceVoxels(ReflectionTracingCoord, TraceHitDistance); } } #endif #ifdef VisualizeReflectionTracesCS [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void VisualizeReflectionTracesCS( uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID) { if (all(DispatchThreadId == 0)) { int2 QueryProbeScreenPosition = View.CursorPosition.x >= 0 ? View.CursorPosition * View.ViewResolutionFraction / ReflectionDownsampleFactorXY : -1; if (all(QueryProbeScreenPosition >= ReflectionTracingViewMin) && all(QueryProbeScreenPosition < ReflectionTracingViewMin + ReflectionTracingViewSize)) { const FReflectionTracingCoord ReflectionTracingCoord = GetReflectionTracingCoord(QueryProbeScreenPosition, 0 /*TODO*/); float2 ScreenUV = GetScreenUVFromReflectionTracingCoord(ReflectionTracingCoord.Coord); float SceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten,0)).x; float3 WorldPosition = GetWorldPositionFromScreenUV(ScreenUV, SceneDepth); // LUMEN_LWC_TODO float3 TranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, SceneDepth); FRayData RayData = GetRayData(ReflectionTracingCoord.CoordFlatten); bool bHit; float TraceHitDistance = DecodeRayDistance(TraceHit[ReflectionTracingCoord.CoordFlatten].x, bHit); float3 RayHitRadiance = TraceRadiance[ReflectionTracingCoord.CoordFlatten]; FShaderPrintContext Context = InitShaderPrintContext(true, float2(0.1, 0.1)); Print(Context, TEXT("Pixel: ")); Print(Context, QueryProbeScreenPosition.x); Print(Context, QueryProbeScreenPosition.y); Newline(Context); if (SceneDepth > 0.0f) { Print(Context, TEXT("Distance: ")); Print(Context, TraceHitDistance); Newline(Context); Print(Context, TEXT("Radiance: ")); Print(Context, RayHitRadiance.x); Print(Context, RayHitRadiance.y); Print(Context, RayHitRadiance.z); const float3 HitPoint = TranslatedWorldPosition + RayData.Direction * TraceHitDistance; AddLineTWS(Context, TranslatedWorldPosition, HitPoint, float4(RayHitRadiance, 1.0f)); AddCrossTWS(Context, HitPoint, 2.0f, float4(1, 1, 0, 1)); } } } } #endif