// Copyright Epic Games, Inc. All Rights Reserved. // Ensure that DiffuseAlbedo is not overridden on SSS material (as we don't split lighting with the Lumen/RT integrator) #define SUBSTRATE_SSS_MATERIAL_OVERRIDE 0 #define SUBSTRATE_COMPLEXSPECIALPATH 1 #define SUBSTRATE_GLINTS_ALLOWED 0 #define SUBSTREATE_SIMPLEVOLUME_ENVALBEDO_LUT_JITTERING 1 #include "/Engine/Shared/RayTracingTypes.h" #include "../Common.ush" #include "../MonteCarlo.ush" #include "../SceneTextureParameters.ush" // When tracing from compute, SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE=0 is not automatically detected, so we notify the use of raytracing here. #define SUBSTRATE_MATERIALCONTAINER_IS_VIEWRESOURCE 0 // Additional rewiring to make DeferredShadingCommon happy #define PreIntegratedGF ReflectionStruct.PreIntegratedGF #define PreIntegratedGFSampler GlobalBilinearClampedSampler #include "LumenMaterial.ush" #include "LumenCardCommon.ush" #include "LumenTracingCommon.ush" #include "LumenReflectionCommon.ush" //#include "LumenVisualizeTraces.ush" #include "LumenRadianceCacheCommon.ush" // TODO: Trace hair voxels? //#define USE_HAIRSTRANDS_VOXEL DIM_HAIRSTRANDS_VOXEL //#include "LumenHairTracing.ush" #include "../PathTracing/Material/PathTracingFresnel.ush" #ifndef THREADGROUP_SIZE_2D #define THREADGROUP_SIZE_2D 8 #endif #ifndef THREADGROUP_SIZE_1D #define THREADGROUP_SIZE_1D THREADGROUP_SIZE_2D * THREADGROUP_SIZE_2D #endif #if LUMEN_HARDWARE_RAYTRACING #include "LumenHardwareRayTracingCommon.ush" RaytracingAccelerationStructure TLAS; RaytracingAccelerationStructure FarFieldTLAS; uint TranslucencyForceOpaque; uint HitLightingShadowMode; uint HitLightingShadowTranslucencyMode; uint HitLightingDirectLighting; uint HitLightingSkylight; uint UseReflectionCaptures; float NearFieldMaxTraceDistance; uint MaxTraversalIterations; uint MeshSectionVisibilityTest; float SecondaryPathStartBias; float SecondaryPathStartNormalBias; float PathThroughputThreshold; int MaxPrimaryHitEvents; int MaxSecondaryHitEvents; uint SampleTranslucentReflectionInReflections; RWTexture2DArray RWTraceRadiance; RWTexture2DArray RWTraceBackgroundVisibility; RWTexture2DArray RWTraceHit; void TraceRayWithPotentialRetrace( RaytracingAccelerationStructure TLAS, FRayDesc Ray, FRayTracedLightingContext Context, inout FLumenRayHitBookmark Bookmark, inout FPackedMaterialClosestHitPayload Payload) { TraceLumenHitLightingRay(TLAS, Ray, Context, Bookmark, Payload); #if AVOID_SELF_INTERSECTIONS_MODE == AVOID_SELF_INTERSECTIONS_MODE_RETRACE if (!Context.bIsFarFieldRay && Payload.IsHit()) { float SkipDistance = -1; if (Payload.IsTwoSided() && Payload.HitT < LumenHardwareRayTracingUniformBuffer.SkipTwoSidedHitDistance) { SkipDistance = Payload.HitT + 0.01f; } else if (!Payload.IsTwoSided() && Payload.HitT < LumenHardwareRayTracingUniformBuffer.SkipBackFaceHitDistance) { SkipDistance = LumenHardwareRayTracingUniformBuffer.SkipBackFaceHitDistance; } if (SkipDistance > 0.0f) { Ray.TMin = SkipDistance; TraceLumenHitLightingRay(TLAS, Ray, Context, Bookmark, Payload); } } #endif } bool IsPrimaryRay(int SecondaryPathIndex) { return SecondaryPathIndex == 0; } #if USE_RAY_TRACED_REFRACTION bool IsRayBent(int SecondaryPathIndex, float3 PrimaryPathThroughput) { const uint RayBentBitMask = 0x80000000; if (IsPrimaryRay(SecondaryPathIndex)) { return (asuint(PrimaryPathThroughput.x) & RayBentBitMask) != 0; } else { return (asuint(PrimaryPathThroughput.y) & RayBentBitMask) != 0; } } void SetRayBent(bool bValue, int SecondaryPathIndex, inout float3 PrimaryPathThroughput) { const uint RayBentBitMask = bValue ? 0x80000000 : 0; if (IsPrimaryRay(SecondaryPathIndex)) { PrimaryPathThroughput.x = asfloat((asuint(PrimaryPathThroughput.x) & 0x7fffffff) | RayBentBitMask); } else { PrimaryPathThroughput.y = asfloat((asuint(PrimaryPathThroughput.y) & 0x7fffffff) | RayBentBitMask); } } void UpdatePrimaryPathThroughput(float3 NewValue, inout float3 PrimaryPathThroughput) { const uint RayBentBitMask = 0x80000000; PrimaryPathThroughput.x = asfloat((asuint(PrimaryPathThroughput.x) & RayBentBitMask) | asuint(NewValue.x)); PrimaryPathThroughput.y = asfloat((asuint(PrimaryPathThroughput.y) & RayBentBitMask) | asuint(NewValue.y)); PrimaryPathThroughput.z = NewValue.z; } #else // !USE_RAY_TRACED_REFRACTION bool IsRayBent(int SecondaryPathIndex, float3 PrimaryPathThroughput) { return false; } void SetRayBent(bool bValue, int SecondaryPathIndex, inout float3 PrimaryPathThroughput) {} void UpdatePrimaryPathThroughput(float3 NewValue, inout float3 PrimaryPathThroughput) { PrimaryPathThroughput = NewValue; } #endif FLumenHitLightingMaterial SanitizeLumenMaterial(FLumenHitLightingMaterial LumenMaterial) { #if !USE_RAY_TRACED_REFRACTION LumenMaterial.Ior = 1.0f; #endif return LumenMaterial; } FRayTracedLightingResult TraceRayLighting_Internal( RaytracingAccelerationStructure TLAS, RaytracingAccelerationStructure FarFieldTLAS, FRayDesc Ray, FRayTracedLightingContext Context, FRandomSequence RandSequence, inout FLumenRayHitBookmark Bookmark) { FRayTracedLightingResult Result = CreateRayTracedLightingResult(Ray); Result.bIsHit = false; Result.TraceHitDistance = 0; const bool bSkipCountingTranslucentBackfaceHit = true; float3 PrimaryPathThroughput = 1.0f; float3 PrimaryRayOrigin = Ray.Origin; float3 PrimaryRayDirection = Ray.Direction; float PrimaryRayTMax = Ray.TMax; float MaxSecondaryPathLuminance = -1.0f; for (int PrimaryPathIndex = 0; PrimaryPathIndex < MaxPrimaryHitEvents; ++PrimaryPathIndex) { float3 SecondaryPathThroughput = abs(PrimaryPathThroughput); float ReflectionPathRoughness = 0.0f; float SecondaryPathLuminance = 0.0f; float CurrentPathT = length(PrimaryRayOrigin - View.TranslatedWorldCameraOrigin); Ray.Origin = PrimaryRayOrigin; Ray.Direction = PrimaryRayDirection; Ray.TMin = IsRayBent(0, PrimaryPathThroughput) ? 0.0f : 0.05f; Ray.TMax = IsRayBent(0, PrimaryPathThroughput) ? NearFieldMaxTraceDistance : PrimaryRayTMax; // Reset secondary ray bent bit SetRayBent(false, 1, PrimaryPathThroughput); for (int SecondaryPathIndex = 0; SecondaryPathIndex < MaxSecondaryHitEvents + 1; ++SecondaryPathIndex) { // Check back face hits once the ray bent so it can be refracted again Context.CullingMode = IsRayBent(SecondaryPathIndex, PrimaryPathThroughput) ? 0 : RAY_FLAG_CULL_BACK_FACING_TRIANGLES; // Primary rays are clipped against opaque scene depth so they won't hit opaque as long as they are not bent Context.InstanceMask = IsPrimaryRay(SecondaryPathIndex) && !IsRayBent(SecondaryPathIndex, PrimaryPathThroughput) ? RAY_TRACING_MASK_TRANSLUCENT : RAY_TRACING_MASK_OPAQUE | RAY_TRACING_MASK_TRANSLUCENT | (HAS_FIRST_PERSON_GBUFFER_BIT ? RAY_TRACING_MASK_OPAQUE_FP_WORLD_SPACE : 0); FPackedMaterialClosestHitPayload Payload = (FPackedMaterialClosestHitPayload)0; Payload.SetLumenPayload(); // Used for stochastic quantization when Substrate is enabled Payload.SetRandom(RandSequence.Get1D()); if (!IsPrimaryRay(SecondaryPathIndex)) { Payload.SetIndirectLightingRay(); } if (Context.bHitLightingSkylight) { Payload.SetEnableSkyLightContribution(); } TraceRayWithPotentialRetrace(TLAS, Ray, Context, Bookmark, Payload); if (Payload.IsHit() && (Payload.IsFrontFace() || Payload.IsTwoSided())) { Result.bIsHit = true; CurrentPathT += Payload.HitT; float3 RayHitTranslatedWorldPosition = Ray.Origin + Ray.Direction * Payload.HitT; float NextReflectionRayAlpha = 0.0f; float SurfaceCoverage = Payload.GetBlendingMode() == RAY_TRACING_BLEND_MODE_OPAQUE ? 1.f : Payload.GetOpacity(); FLumenHitLightingMaterial LumenMaterial = SanitizeLumenMaterial(GetLumenHitLightingMaterial(Context, Payload, Ray)); // Accumulate roughness in order to terminate recursive rays earlier on rough surfaces ReflectionPathRoughness = 1.0f - (1.0f - ReflectionPathRoughness) * (1.0f - LumenMaterial.TopLayerRoughness); // Blend between rough reflections approximation and a single reflection ray if ((Context.bHitLightingDirectLighting || Context.bHitLightingSkylight) && SecondaryPathIndex < MaxSecondaryHitEvents) { NextReflectionRayAlpha = LumenCombineReflectionsAlpha(ReflectionPathRoughness, /*bHasBackfaceDiffuse*/ false); } float3 Radiance = 0.0f; if (Payload.IsValid()) { Radiance += CalculateLightingAtHit(TLAS, FarFieldTLAS, Ray, Context, RandSequence, LumenMaterial, NextReflectionRayAlpha, Payload); } Radiance += Payload.GetRadiance(); // Emissive #if SUBSTRATE_ENABLED Radiance = SurfaceCoverage * SecondaryPathThroughput * Radiance; #else Radiance = (Payload.GetBlendingMode() == RAY_TRACING_BLEND_MODE_ALPHA_COMPOSITE ? 1.0f : SurfaceCoverage) * SecondaryPathThroughput * Radiance; #endif if (!IsPrimaryRay(SecondaryPathIndex)) { SecondaryPathLuminance += Luminance(Radiance); } Result.Radiance += Radiance; bool bHitTranslucent = Payload.GetBlendingMode() != RAY_TRACING_BLEND_MODE_OPAQUE; float3 ReflectionPathThroughput = 0.0f; float3 WorldH = Payload.GetWorldNormal(); // Compute single reflection ray throughput if (NextReflectionRayAlpha > 0.0f) { ReflectionPathThroughput = SecondaryPathThroughput * NextReflectionRayAlpha #if SUBSTRATE_ENABLED * SurfaceCoverage #else * (Payload.GetBlendingMode() == RAY_TRACING_BLEND_MODE_ALPHA_COMPOSITE ? 1.0f : SurfaceCoverage) #endif * LumenMaterial.TopLayerSpecularColor; } // Update path throughput if (bHitTranslucent) { float3 NextPathVertexThroughput = 1.0f - SurfaceCoverage; // RefractedThroughputPreCoverage is only setup correctly with Format=1 // For now enable this only with Substrate's Format=1 to minimize visual differences in EngineTest. #if SUBSTRATE_ENABLED && SUBSTRATE_GBUFFER_FORMAT==1 NextPathVertexThroughput += LumenMaterial.RefractedThroughputPreCoverage * SurfaceCoverage; #endif SecondaryPathThroughput *= NextPathVertexThroughput; } else { SecondaryPathThroughput = ReflectionPathThroughput; } bool bSampleReflection = any(ReflectionPathThroughput > PathThroughputThreshold); if (!bSampleReflection) { ReflectionPathThroughput = 0.0f; } // Sample GGX half vector if needed if (LumenMaterial.TopLayerRoughness > 0.0f && (bSampleReflection || (LumenMaterial.Ior > 1.0f && any(SecondaryPathThroughput > PathThroughputThreshold)))) { float GGXSamplingBias = 0.1f; float2 E = RandSequence.Get2D(); E.y *= 1.0f - GGXSamplingBias; float3x3 TangentBasis = GetTangentBasis(Payload.GetWorldNormal()); float3 TangentV = mul(TangentBasis, -Ray.Direction); float4 GGXSample = ImportanceSampleVisibleGGX(E, Pow2(LumenMaterial.TopLayerRoughness), TangentV); WorldH = mul(GGXSample.xyz, TangentBasis); } // Handle refraction if (bHitTranslucent && any(SecondaryPathThroughput > PathThroughputThreshold)) { if (LumenMaterial.Ior > 1.0f) { SetRayBent(true, SecondaryPathIndex, PrimaryPathThroughput); const float RandSample = 1.0f; // Always sample refraction float Eta = LumenMaterial.Ior; float3 RefractedDirection; float Fresnel; bool bRefracted = SampleRefraction(Ray.Direction, WorldH, Eta, RandSample, RefractedDirection, Fresnel); // verify(bRefracted); if (IsPrimaryRay(SecondaryPathIndex)) { // Save the primary ray before we branch off to the secondary ray so we can restart primary later PrimaryRayOrigin = RayHitTranslatedWorldPosition - 0.05f * (Payload.IsFrontFace() ? 1.0f : -1.0f) * Payload.GetGeometryNormal(); PrimaryRayDirection = RefractedDirection; } else { bool bPickRefractionRay = true; if (bSampleReflection && SampleTranslucentReflectionInReflections != 0) { float RefractionWeight = Luminance(SecondaryPathThroughput) * Fresnel; float ReflectionWeight = Luminance(ReflectionPathThroughput) * (1.0f - Fresnel); float RefractionThreshold = RefractionWeight / max(RefractionWeight + ReflectionWeight, 1e-5f); float E = RandSequence.Get1D(); bPickRefractionRay = E < RefractionThreshold; } if (bPickRefractionRay) { Ray.Origin = RayHitTranslatedWorldPosition - 0.05f * (Payload.IsFrontFace() ? 1.0f : -1.0f) * Payload.GetGeometryNormal(); Ray.Direction = RefractedDirection; Ray.TMin = 0; Ray.TMax = NearFieldMaxTraceDistance; bSampleReflection = false; } } } else // Ray goes straight through { if (IsPrimaryRay(SecondaryPathIndex)) { PrimaryRayOrigin = RayHitTranslatedWorldPosition; PrimaryRayDirection = Ray.Direction; if (IsRayBent(SecondaryPathIndex, PrimaryPathThroughput)) { PrimaryRayOrigin += 0.05f * PrimaryRayDirection; } else { // Do not extend camera rays so we can just trace against translucent instances PrimaryRayTMax -= Payload.HitT; } } else { bool bPickRefractionRay = true; if (bSampleReflection && SampleTranslucentReflectionInReflections != 0) { float RefractionWeight = Luminance(SecondaryPathThroughput); float ReflectionWeight = Luminance(ReflectionPathThroughput); float RefractionThreshold = RefractionWeight / max(RefractionWeight + ReflectionWeight, 1e-5f); float E = RandSequence.Get1D(); bPickRefractionRay = E < RefractionThreshold; } if (bPickRefractionRay) { Ray.Origin = RayHitTranslatedWorldPosition + 0.05f * Ray.Direction; Ray.TMin = 0; Ray.TMax = NearFieldMaxTraceDistance; bSampleReflection = false; } } } } if (IsPrimaryRay(SecondaryPathIndex)) { if (bHitTranslucent) { UpdatePrimaryPathThroughput(SecondaryPathThroughput, PrimaryPathThroughput); } else { // Primary rays stop once they hit opaque UpdatePrimaryPathThroughput(0.0f, PrimaryPathThroughput); PrimaryPathIndex = MaxPrimaryHitEvents - 1; } } // Handle reflection if (bSampleReflection) { SecondaryPathThroughput = ReflectionPathThroughput; Ray.Direction = reflect(Ray.Direction, WorldH); Ray.TMax = NearFieldMaxTraceDistance; float3 GeometryNormal = (Payload.IsFrontFace() ? 1.0f : -1.0f) * Payload.GetGeometryNormal(); if (IsPrimaryRay(SecondaryPathIndex)) { // This is done to better match the behavior of LumenReflectionHardwareRayTracing where reflection ray starting // points are biased differently float3 NormalBias = saturate(dot(GeometryNormal, Ray.Direction)) * SecondaryPathStartNormalBias * GeometryNormal; Ray.Origin = RayHitTranslatedWorldPosition + NormalBias; Ray.TMin = SecondaryPathStartBias; } else { Ray.Origin = RayHitTranslatedWorldPosition + 0.05f * GeometryNormal; Ray.TMin = 0; } } if ((IsPrimaryRay(SecondaryPathIndex) && !bSampleReflection) || all(SecondaryPathThroughput <= PathThroughputThreshold)) { break; } } else if (Payload.IsHit()) // Hitting one-sided back face { // verify(IsRayBent(SecondaryPathIndex, PrimaryPathThroughput)); bool bHitTranslucent = Payload.GetBlendingMode() != RAY_TRACING_BLEND_MODE_OPAQUE; if (bHitTranslucent) { float3 RayHitTranslatedWorldPosition = Ray.Origin + Ray.Direction * Payload.HitT; FLumenHitLightingMaterial LumenMaterial = SanitizeLumenMaterial(GetLumenHitLightingMaterial(Context, Payload, Ray)); bool bRefracted = true; if (LumenMaterial.Ior > 1.0f) { SetRayBent(true, SecondaryPathIndex, PrimaryPathThroughput); const float RandSample = 1.0f; // Always sample refraction or TIR float Eta = rcp(LumenMaterial.Ior); float3 RefractedDirection; float Fresnel; bRefracted = SampleRefraction(Ray.Direction, Payload.GetWorldNormal(), Eta, RandSample, RefractedDirection, Fresnel); Ray.Origin = RayHitTranslatedWorldPosition + (bRefracted ? 0.05f : -0.05f) * Payload.GetGeometryNormal(); Ray.Direction = RefractedDirection; Ray.TMin = 0; Ray.TMax = NearFieldMaxTraceDistance; } else { Ray.Origin = RayHitTranslatedWorldPosition + 0.05f * Ray.Direction; Ray.TMin = 0; Ray.TMax = NearFieldMaxTraceDistance; } if (IsPrimaryRay(SecondaryPathIndex)) { PrimaryRayOrigin = Ray.Origin; PrimaryRayDirection = Ray.Direction; if (bSkipCountingTranslucentBackfaceHit && bRefracted) { --PrimaryPathIndex; } // No secondary ray when hitting translucent back face break; } // Do not skip if TIR. The ray may never escape and hang the GPU if (bSkipCountingTranslucentBackfaceHit && bRefracted) { --SecondaryPathIndex; } } else // hit opaque back face { if (IsPrimaryRay(SecondaryPathIndex)) { Result.bIsHit = true; UpdatePrimaryPathThroughput(0.0f, PrimaryPathThroughput); PrimaryPathIndex = MaxPrimaryHitEvents - 1; } break; } } else // Ray miss { if (IsPrimaryRay(SecondaryPathIndex)) { if (IsRayBent(SecondaryPathIndex, PrimaryPathThroughput)) { Result.Radiance += SecondaryPathThroughput * EvaluateSkyRadiance(Ray.Direction); UpdatePrimaryPathThroughput(0.0f, PrimaryPathThroughput); } // Break the primary loop PrimaryPathIndex = MaxPrimaryHitEvents - 1; } else { float3 Radiance = SecondaryPathThroughput * EvaluateSkyRadiance(Ray.Direction); SecondaryPathLuminance += Luminance(Radiance); Result.Radiance += Radiance; CurrentPathT = MaxHalfFloat; } break; } } // Secondary path // Use the PathT corresponds to the brightest secondary path as HitT. This provides better temporal // reprojection when there is a dominant reflection layer. This doesn't work for refraction but refracted // background color should probably be stored and filtered separately from translucency and specular (TODO). // This is also not what spatial reconstruction in LumenReflectionResolveCS expects. It expects the HitT // of the first reflection ray hit not the length of the entire path but rough reflections seem to work fine if (SecondaryPathLuminance > MaxSecondaryPathLuminance) { MaxSecondaryPathLuminance = SecondaryPathLuminance; Result.TraceHitDistance = CurrentPathT; } if (all(abs(PrimaryPathThroughput) <= PathThroughputThreshold)) { break; } } // Primary path Result.BackgroundVisibility = abs(PrimaryPathThroughput); return Result; } LUMEN_HARDWARE_RAY_TRACING_ENTRY(RayTracedTranslucencyHardwareRayTracing) { uint ThreadIndex = DispatchThreadIndex.x; uint GroupIndex = DispatchThreadIndex.y; uint DispatchThreadId = GroupIndex * THREADGROUP_SIZE_1D + ThreadIndex; if (DispatchThreadId < CompactedTraceTexelAllocator[0]) { FReflectionTracingCoord ReflectionTracingCoord = DecodeTraceTexel(CompactedTraceTexelData[DispatchThreadId]); ReflectionTracingCoord.CoordFlatten.z = 0; ReflectionTracingCoord.ClosureIndex = 0; float2 ScreenUV = GetScreenUVFromReflectionTracingCoord(ReflectionTracingCoord.Coord); uint2 ScreenCoord = ScreenUV * View.BufferSizeAndInvSize.xy; uint LinearCoord = ScreenCoord.y * View.BufferSizeAndInvSize.x + ScreenCoord.x; float FrontLayerSceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten, 0)).x; float3 FrontLayerTranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, FrontLayerSceneDepth); float OpaqueSceneDepth = CalcSceneDepth(ScreenUV); float3 OpaqueTranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, OpaqueSceneDepth); FRayDesc Ray; Ray.Direction = GetCameraVectorFromTranslatedWorldPosition(FrontLayerTranslatedWorldPosition); Ray.Origin = View.TranslatedWorldCameraOrigin; Ray.TMin = 0; Ray.TMax = dot(OpaqueTranslatedWorldPosition - Ray.Origin, Ray.Direction) - 0.1f; FRayCone RayCone; RayCone.Width = 0; RayCone.SpreadAngle = View.EyeToPixelSpreadAngle; FRayTracedLightingContext Context = CreateRayTracedLightingContext( RayCone, ReflectionTracingCoord.Coord, LinearCoord, /*CullingMode*/ RAY_FLAG_CULL_BACK_FACING_TRIANGLES, MaxTraversalIterations, MeshSectionVisibilityTest != 0); Context.InstanceMask = RAY_TRACING_MASK_TRANSLUCENT; Context.bHiResSurface = UseHighResSurface != 0; Context.bHitLightingDirectLighting = HitLightingDirectLighting != 0; Context.bHitLightingSkylight = HitLightingSkylight != 0; Context.bUseReflectionCaptures = UseReflectionCaptures != 0; Context.bForceOpaque = TranslucencyForceOpaque; Context.HitLightingShadowMode = HitLightingShadowMode; Context.HitLightingShadowTranslucencyMode = HitLightingShadowTranslucencyMode; Context.HitLightingShadowMaxTraceDistance = NearFieldMaxTraceDistance; // TODO: Investigate improved stratification in image space by retrieving the actual pixel coordinate and sample index FRandomSequence RandSequence = RandomSequenceCreate(Context.LinearCoord, ReflectionsStateFrameIndex); Context.bUseBookmark = false; FLumenRayHitBookmark Bookmark; FRayTracedLightingResult Result = TraceRayLighting_Internal(TLAS, FarFieldTLAS, Ray, Context, RandSequence, Bookmark); // TODO: Sky leaking? Result.Radiance *= View.PreExposure; float MaxLighting = max3(Result.Radiance.x, Result.Radiance.y, Result.Radiance.z); if (SampleHeightFog > 0) { float CoverageForFog = 1.0; // There is always something of the sky fallback. float ReceiverSceneDepth = DownsampledDepth.Load(int4(ReflectionTracingCoord.CoordFlatten, 0)).x; float3 ReceiverTranslatedWorldPosition = GetTranslatedWorldPositionFromScreenUV(ScreenUV, FrontLayerSceneDepth); float DistanceToReceiver = length(ReceiverTranslatedWorldPosition); float3 CameraToReceiver = ReceiverTranslatedWorldPosition / DistanceToReceiver; Result.Radiance = GetFogOnLuminance(Result.Radiance, CoverageForFog, View.TranslatedWorldCameraOrigin, CameraToReceiver, DistanceToReceiver); } if (MaxLighting > MaxRayIntensity) { Result.Radiance *= MaxRayIntensity / MaxLighting; } RWTraceRadiance[ReflectionTracingCoord.CoordFlatten] = MakeFinite(Result.Radiance); RWTraceBackgroundVisibility[ReflectionTracingCoord.CoordFlatten] = MakeFinite(Result.BackgroundVisibility); RWTraceHit[ReflectionTracingCoord.CoordFlatten] = EncodeRayDistance(Result.TraceHitDistance, Result.bIsHit); // TODO: Visualize coherency? } } #endif // LUMEN_HARDWARE_RAYTRACING