// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================================= PathTracingRayGenShader.usf: Reference path tracing ===============================================================================================*/ #define SIMPLIFIED_MATERIAL_SHADER 1 #define RANDSEQ_UNROLL_SOBOL 0 #define USE_PATH_TRACING_LIGHT_GRID 1 #include "/Engine/Private/Common.ush" #include "/Engine/Private/PostProcessCommon.ush" #include "/Engine/Private/RectLight.ush" #include "/Engine/Private/RayTracing/RayTracingCommon.ush" #include "/Engine/Private/RayTracing/RayTracingDirectionalLight.ush" #include "/Engine/Private/RayTracing/RayTracingRectLight.ush" #include "/Engine/Private/RayTracing/RayTracingSphereLight.ush" #include "/Engine/Private/RayTracing/RayTracingCapsuleLight.ush" #include "/Engine/Private/PathTracing/PathTracingCommon.ush" #include "/Engine/Private/PathTracing/Light/PathTracingLightGrid.ush" #include "/Engine/Private/RayTracing/RayTracingHitGroupCommon.ush" #include "BatchedTiles.ush" #include "/Engine/Private/PathTracing/Material/FirstBounceRayGuidingCommon.ush" int NumRayGuidingTrialSamples; RWTexture2D RayGuidingLuminance; Texture2D RayGuidingCDFX; Texture2D RayGuidingCDFY; #include "IrradianceCachingCommon.ush" #include "/Engine/Private/ShadingModels.ush" #include "/Engine/Private/PathTracing/Utilities/PathTracingRandomSequence.ush" #include "/Engine/Private/PathTracing/Light/PathTracingLightSampling.ush" #include "/Engine/Private/PathTracing/Material/PathTracingMaterialSampling.ush" #include "/Engine/Private/PathTracing/Material/PathTracingRadianceProbe.ush" #include "/Engine/Shared/LightDefinitions.h" #include "LightmapEncoding.ush" #include "LightmapCommon.ush" int LastInvalidationFrame; int NumTotalSamples; Texture2D GBufferWorldPosition; Texture2D GBufferWorldNormal; Texture2D GBufferShadingNormal; RWTexture2D IrradianceAndSampleCount; RWTexture2D SHDirectionality; RWTexture2D SHCorrectionAndStationarySkyLightBentNormal; RWTexture2D OutputTileAtlas; void GenerateCosineNormalRay( float3 TranslatedWorldPosition, float3 WorldNormal, inout RandomSequence RandSequence, out float3 RayOrigin, out float3 RayDirection, out float3 TangentDirection, out float RayTMin, out float RayTMax, out float RayPdf ) { // Draw random variable float2 BufferSize = View.BufferSizeAndInvSize.xy; float2 RandSample = RandomSequence_GenerateSample2D(RandSequence); // Perform cosine-hemispherical sampling and convert to world-space float4 Direction_Tangent = CosineSampleHemisphere(RandSample); TangentDirection = Direction_Tangent.xyz; float3 Direction_World = TangentToWorld(Direction_Tangent.xyz, WorldNormal); RayOrigin = TranslatedWorldPosition; RayDirection = Direction_World; RayTMin = 0.01; RayTMax = 1e20; RayPdf = 1.0f; } struct FThreeBandSHVectorFloat { float4 V0; float4 V1; float V2; }; struct FThreeBandSHVectorRGBFloat { FThreeBandSHVectorFloat R; FThreeBandSHVectorFloat G; FThreeBandSHVectorFloat B; }; FThreeBandSHVectorFloat SHBasisFunction3Float(float3 InputVector) { FThreeBandSHVectorFloat Result; // These are derived from simplifying SHBasisFunction in C++ Result.V0.x = 0.282095f; Result.V0.y = -0.488603f * InputVector.y; Result.V0.z = 0.488603f * InputVector.z; Result.V0.w = -0.488603f * InputVector.x; half3 VectorSquared = InputVector * InputVector; Result.V1.x = 1.092548f * InputVector.x * InputVector.y; Result.V1.y = -1.092548f * InputVector.y * InputVector.z; Result.V1.z = 0.315392f * (3.0f * VectorSquared.z - 1.0f); Result.V1.w = -1.092548f * InputVector.x * InputVector.z; Result.V2 = 0.546274f * (VectorSquared.x - VectorSquared.y); return Result; } #define WorldPositionScalar 0.00001f #ifndef USE_IRRADIANCE_CACHING #define USE_IRRADIANCE_CACHING 0 #endif static const uint MaxBounces = 32; // 0: only Material sampling // 1: only Light sampling // 2: both Material and Light static const uint MISMode = 2; static const uint ApproximateCaustics = 1; static const uint EnableCameraBackfaceCulling = 0; static const uint EnableEmissive = 1; static const float MaxNormalBias = 0.1; RaytracingAccelerationStructure TLAS; uint SceneVisibleLightCount; void AccumulateRadiance(inout float3 TotalRadiance, float3 PathRadiance) { float MaxPathIntensity = 0; if (MaxPathIntensity > 0) { // User asked for path contributions to be clamped to reduce fireflies. // Depending on how aggressive this value is, the image could be quite biased TotalRadiance += min(PathRadiance, MaxPathIntensity); } else { // Just average values directly TotalRadiance += PathRadiance; } } bool IsInvalidSurfaceHit(FPathTracingPayload Payload) { return !Payload.IsFrontFace() && !Payload.IsMaterialTransmissive() && !Payload.IsMaterialTwoSided(); } FPathTracingPayload TraceTransparentRay(FRayDesc Ray, bool IsCameraRay, bool LastBounce, bool IncludeEmission, uint2 LaunchIndex, uint NumLights, inout RandomSequence RandSequence, inout float3 PathThroughput, inout float3 Radiance) { const uint RayFlags = IsCameraRay && EnableCameraBackfaceCulling ? RAY_FLAG_CULL_BACK_FACING_TRIANGLES : 0; const uint MissShaderIndex = 0; float SelectionWeightSum = 0; float3 PayloadThroughput = PathThroughput; FPathTracingPayload Payload; if (!IncludeEmission && LastBounce) { Payload.SetMiss(); PathThroughput = 0; return Payload; } for (;;) { FPackedPathTracingPayload PackedPayload = InitPathTracingPayload(IsCameraRay ? PATHTRACER_SCATTER_CAMERA : PATHTRACER_SCATTER_DIFFUSE, 1.0); // Trace the ray TraceRay( TLAS, RayFlags, PATHTRACER_MASK_ALL, RAY_TRACING_SHADER_SLOT_MATERIAL, RAY_TRACING_NUM_SHADER_SLOTS, MissShaderIndex, Ray.GetNativeDesc(), PackedPayload); // Loop over lights to capture their contribution // #dxr_todo: if we have lots of lights, having some hierarchical structure would be better .... for (uint LightId = 0; LightId < NumLights; ++LightId) { FRayDesc LightRay = Ray; LightRay.TMax = PackedPayload.IsMiss() ? Ray.TMax : PackedPayload.HitT; float3 LightRadiance = TraceLight(LightRay, LightId).Radiance; AccumulateRadiance(Radiance, PathThroughput * LightRadiance); } if (PackedPayload.IsMiss()) { // Ray didn't hit any real geometry, so nothing left to do break; } // Unpack the payload FPathTracingPayload HitPayload = UnpackPathTracingPayload(PackedPayload, Ray); // add in surface emission if (IncludeEmission) { AccumulateRadiance(Radiance, PathThroughput * HitPayload.Radiance); } if (!LastBounce) { float3 Contrib = PathThroughput * EstimateMaterialAlbedo(HitPayload); float SelectionWeight = max3(Contrib.x, Contrib.y, Contrib.z); SelectionWeightSum += SelectionWeight; bool AcceptHit = false; // weighted reservoir sampling if (SelectionWeight > 0) { if (SelectionWeight < SelectionWeightSum) { // the acceptance probability is not 1.0 // generate a random number to see if we should accept this hit float RandValue = RandomSequence_GenerateSample1D(RandSequence); AcceptHit = RandValue * SelectionWeightSum < SelectionWeight; } else { // accept automatically on the first hit AcceptHit = true; } } if (AcceptHit) { // stash this hit for next time Payload = HitPayload; PayloadThroughput = PathThroughput / SelectionWeight; } } if (IsInvalidSurfaceHit(HitPayload)) { // Propagate to Payload and terminate immediately upon invalid surface hits Payload = HitPayload; break; } // prepare next step around the loop // retrace the exact same ray with TMin one ulp past the hit we just found PathThroughput *= HitPayload.TransparencyColor; Ray.TMin = asfloat(asuint(HitPayload.HitT) + 1); if (all(PathThroughput == 0)) { break; } } if (SelectionWeightSum > 0) { // if we stored a valid hit in the payload, reset the path throughput to this point PathThroughput = PayloadThroughput * SelectionWeightSum; } else { PathThroughput = 0; Payload.SetMiss(); } return Payload; } float3 TraceTransparentVisibilityRay(FRayDesc Ray, float PathRoughness) { const uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER; const uint InstanceInclusionMask = PATHTRACER_MASK_SHADOW; const uint RayContributionToHitGroupIndex = RAY_TRACING_SHADER_SLOT_SHADOW; const uint MultiplierForGeometryContributionToShaderIndex = RAY_TRACING_NUM_SHADER_SLOTS; const uint MissShaderIndex = 0; FPackedPathTracingPayload PackedPayload = InitPathTracingVisibilityPayload(PathRoughness); TraceRay( TLAS, RayFlags, InstanceInclusionMask, RayContributionToHitGroupIndex, MultiplierForGeometryContributionToShaderIndex, MissShaderIndex, Ray.GetNativeDesc(), PackedPayload); if (PackedPayload.IsHit()) { // We didn't run the miss shader, therefore we must have hit something opaque (or reached full opacity) return 0.0; } return PackedPayload.GetRayThroughput(); } void PathTracingKernel( in uint RenderPassIndex, float3 TranslatedWorldPosition, float3 ShadingNormal, inout RandomSequence RandSequence, inout uint SampleIndex, inout bool bIsValidSample, inout float3 RadianceValue, inout float3 RadianceDirection, inout float3 DirectLightingIrradiance, #ifdef LIGHTMAP_PATH_TRACING_MAIN_RG inout FL2SHAndCorrection DirectLightingSH, #else inout FThreeBandSHVectorRGBFloat DirectLightingSH, #endif inout float LuminanceForFirstBounceRayGuiding, inout float3 SkyLightBentNormal, inout float4 PrimaryRandSample) { uint2 LaunchIndex = uint2(0, 0); #if USE_IRRADIANCE_CACHING bool bShouldEmitGeometryHitPoint = false; FFinalGatherHitPoint IrradianceCacheFinalGatherHitPoint = (FFinalGatherHitPoint)0; float RadianceProbe_Pdf = 1.0f / (2 * PI); uint NewEntrySize = 32; int NearestCacheEntryID = -1; #endif float3 Radiance = 0; // GPULightmass's SampleEmitter(): generate a fake camera ray hitting the texel FRayDesc Ray; Ray.Origin = TranslatedWorldPosition + ShadingNormal; Ray.Direction = -ShadingNormal; Ray.TMin = -0.01f; Ray.TMax = 1.01f; // This array will hold a CDF for light picking // Seed the array with a uniform CDF at first so that we always have a valid CDF float LightPickingCdf[RAY_TRACING_LIGHT_COUNT_MAXIMUM]; Ray.Direction = normalize(Ray.Direction); // path state variables (these cary information between bounces) float3 PathThroughput = 1.0; float PathRoughness = 0; // number of directly visible lights for the first bounce uint NumVisibleLights = SceneVisibleLightCount; for (int Bounce = 0; Bounce <= MaxBounces; Bounce++) { const bool bIsCameraRay = Bounce == 0; const bool bIsLastBounce = Bounce == MaxBounces; const bool bIncludeEmissive = EnableEmissive; FPathTracingPayload Payload = (FPathTracingPayload)0; if (bIsCameraRay) { // GPULightmass fakes a 'camera' ray that hits the lightmap texel perpendicularly Payload.TranslatedWorldPos = TranslatedWorldPosition; Payload.WorldNormal = ShadingNormal; Payload.WorldGeoNormal = ShadingNormal; Payload.Radiance = 0; Payload.BaseColor = 1; Payload.SubsurfaceColor = 0; Payload.BSDFOpacity = 1; Payload.TransparencyColor = 0; Payload.ShadingModelID = SHADINGMODELID_DEFAULT_LIT; Payload.PrimitiveLightingChannelMask = 0b111; Payload.HitT = 1.0f; Payload.SetFrontFace(); } else { Payload = TraceTransparentRay(Ray, false , bIsLastBounce, bIncludeEmissive, LaunchIndex, NumVisibleLights, RandSequence, PathThroughput, Radiance); } if (Payload.IsMiss()) { // we didn't hit anything selectable for further shading, we are done break; } if (IsInvalidSurfaceHit(Payload)) { bIsValidSample = false; break; } #if USE_IRRADIANCE_CACHING float3 TexelPosition = TranslatedWorldPosition - DFHackToFloat(PrimaryView.PreViewTranslation); float3 HitPosition = Payload.TranslatedWorldPos - DFHackToFloat(PrimaryView.PreViewTranslation); float DistanceFromTexelToHit = length(HitPosition - TexelPosition); bool bRejectedByCornerRejection = RenderPassIndex >= GetIrradianceCachingQuality() && DistanceFromTexelToHit < IrradianceCachingParameters.Spacing * IrradianceCachingParameters.CornerRejection; // Compute an overestimated upper bound for the number of samples we need for this texel by assuming // all the rays from all IC building passes are sent from this single texel float CellArea = IrradianceCachingParameters.Spacing * IrradianceCachingParameters.Spacing; float TotalSamplesOverAllICPasses = GPreviewLightmapPhysicalTileSize * GPreviewLightmapPhysicalTileSize * GetIrradianceCachingQuality(); float DistanceFraction = CellArea / (2 * PI * DistanceFromTexelToHit * DistanceFromTexelToHit); // Cache entries too far away have a very low chance of being reused // Don't bother placing or looking them up bool bRejectedByMaxLookupDistace = false; if (TotalSamplesOverAllICPasses * DistanceFraction < 1) { bRejectedByMaxLookupDistace = true; } if (NearestCacheEntryID == -1 && Bounce >= 1 && !bRejectedByCornerRejection && !bRejectedByMaxLookupDistace) { bool bIrradianceQuerySuccessful = false; bool bGeometryQuerySuccessful = false; uint NearestRecordIndex = 0; float3 RecordIrradiance; uint Index; if (ICHashTableFind(EncodeICHashKey(HitPosition, Payload.WorldNormal, IrradianceCachingParameters.Spacing), Index)) { uint RecordIndex = IrradianceCachingParameters.RWHashToIndex[Index]; uint4 RecordIrradianceAndSampleCount = IrradianceCachingParameters.IrradianceCacheRecords[RecordIndex]; uint BackfaceHitsCount = IrradianceCachingParameters.IrradianceCacheRecordBackfaceHits[RecordIndex].a; // We accept a cache entry, if: // 1) The entry has been built by other lightmap tiles and contains more samples than we will ever need. // 2) We're outside of IC building passes. An entry with low sample count means we just need that much - still, we're going to require a minimum of 4 samples to remove outliers from potential cache thrashing bool bShouldAcceptCacheEntry = false; uint SampleCountThreshold; if (RenderPassIndex < GetIrradianceCachingQuality()) { SampleCountThreshold = TotalSamplesOverAllICPasses * DistanceFraction; } else { SampleCountThreshold = 4; } bShouldAcceptCacheEntry = RecordIrradianceAndSampleCount.w >= SampleCountThreshold; #if IC_BACKFACE_DETECTION // Reject the entry if it can see back faces if (RenderPassIndex >= GetIrradianceCachingQuality() && BackfaceHitsCount > RecordIrradianceAndSampleCount.w * BackfaceThresholdRejection) { bShouldAcceptCacheEntry = false; } // Terminate the path if the entry contains too many back face hits which means it is very likely to be fully inside geometry if (RenderPassIndex >= GetIrradianceCachingQuality() && BackfaceHitsCount > RecordIrradianceAndSampleCount.w * BackfaceThresholdInsideGeometry) { bIsValidSample = false; break; } #endif if (bShouldAcceptCacheEntry) { bIrradianceQuerySuccessful = true; RecordIrradiance = asfloat(RecordIrradianceAndSampleCount.xyz) / RecordIrradianceAndSampleCount.w; } bGeometryQuerySuccessful = true; NearestRecordIndex = RecordIndex; } if (bIrradianceQuerySuccessful) { // Successful query. Terminate path immediately. Radiance += RecordIrradiance * PathThroughput; LuminanceForFirstBounceRayGuiding += Luminance(RecordIrradiance * PathThroughput); break; } else { // Failed query. if (Bounce == 1) { // Prevent modification to the cache outside IC building passes if (RenderPassIndex < GetIrradianceCachingQuality()) { if (!bGeometryQuerySuccessful) { // Only use the very first few passes to place new entries to reduce cache thrashing bool bShouldPlaceNewEntry = RenderPassIndex < 16; if (bShouldPlaceNewEntry) { bShouldEmitGeometryHitPoint = true; IrradianceCacheFinalGatherHitPoint.WorldPosition = HitPosition; IrradianceCacheFinalGatherHitPoint.WorldNormal = Payload.WorldNormal; NewEntrySize = IrradianceCachingParameters.Spacing; break; } } else { NearestCacheEntryID = NearestRecordIndex; } } } } } #endif FLightLoopCount LightLoopCount = LightGridLookup(Payload.TranslatedWorldPos); // Choose a random number for both Light sampling and BxDF sampling float4 RandSample = RandomSequence_GenerateSample4D(RandSequence); float LightPickingCdfSum = 0; // If we are using Light sampling and the material can use it ... if (MISMode != 0 && SceneLightCount > 0) { // Choose a light and sample it float3 TranslatedWorldPos = Payload.TranslatedWorldPos; float3 WorldNormal = Payload.WorldNormal; uint PrimitiveLightingChannelMask = Payload.PrimitiveLightingChannelMask; bool IsTransmissiveMaterial = ENABLE_TRANSMISSION && Payload.IsMaterialTransmissive(); for (uint Index = 0, Num = LightLoopCount.NumLights; Index < Num; ++Index) { uint LightIndex = GetLightId(Index, LightLoopCount); LightPickingCdfSum += EstimateLight(LightIndex, TranslatedWorldPos, WorldNormal, PrimitiveLightingChannelMask, IsTransmissiveMaterial); LightPickingCdf[Index] = LightPickingCdfSum; } if (LightPickingCdfSum > 0) { // init worked int LightId; float LightPickPdf = 0; SelectLight(RandSample.x * LightPickingCdfSum, LightLoopCount.NumLights, LightPickingCdf, LightId, LightPickPdf); LightId = GetLightId(LightId, LightLoopCount); FLightSample LightSample = SampleLight(LightId, RandSample.yz, TranslatedWorldPos, WorldNormal); LightPickPdf /= LightPickingCdfSum; LightSample.RadianceOverPdf /= LightPickPdf; LightSample.Pdf *= LightPickPdf; if (LightSample.Pdf > 0) { float3 Visibility = float3(1, 1, 1); if (CastsShadow(LightId)) { // for transmissive materials, bias the position to the other side of the surface if the light is coming from behind const float SignedPositionBias = IsTransmissiveMaterial ? sign(dot(Payload.WorldNormal, LightSample.Direction)) : 1.0; FRayDesc LightRay; LightRay.Origin = TranslatedWorldPos; LightRay.TMin = 0; LightRay.Direction = LightSample.Direction; LightRay.TMax = LightSample.Distance; ApplyRayBias(LightRay.Origin, Payload.HitT, SignedPositionBias * Payload.WorldGeoNormal); float AvgRoughness = ApproximateCaustics ? GetAverageRoughness(Payload) : 0.0; Visibility = TraceTransparentVisibilityRay(LightRay, AvgRoughness); LightSample.RadianceOverPdf *= Visibility; } // #dxr_todo: Is it cheaper to fire the ray first? Or eval the material first? if (any(LightSample.RadianceOverPdf > 0)) { // Evaluate material FMaterialEval MaterialEval; if (Bounce == 0) { MaterialEval = RadianceProbe_EvalMaterial(LightSample.Direction, Payload); } else { MaterialEval = EvalMaterial(-Ray.Direction, LightSample.Direction, Payload, float2(1.0, 0.0)); } // Record the contribution float3 LightContrib = PathThroughput * LightSample.RadianceOverPdf * MaterialEval.Weight * MaterialEval.Pdf; float3 BentNormalVector = LightSample.Direction / LightSample.Pdf / PI * Visibility; if (MISMode == 2) { LightContrib *= MISWeightPower(LightSample.Pdf, MaterialEval.Pdf); BentNormalVector *= MISWeightPower(LightSample.Pdf, MaterialEval.Pdf); } // Record the contribution if (Bounce > 0) { AccumulateRadiance(Radiance, LightContrib); LuminanceForFirstBounceRayGuiding += Luminance(LightContrib); } else { // GPU Lightmass records contribution of direct lighting separately, and only for static lights if (!IsStationary(LightId)) { #ifdef LIGHTMAP_PATH_TRACING_MAIN_RG float TangentZ = saturate(dot(LightSample.Direction, Payload.WorldNormal)); DirectLightingSH.AddIncomingRadiance(Luminance(LightContrib), LightSample.Direction, TangentZ); DirectLightingIrradiance += LightContrib * TangentZ; #else DirectLightingSH.R.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.r; DirectLightingSH.R.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.r; DirectLightingSH.R.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.r; DirectLightingSH.G.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.g; DirectLightingSH.G.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.g; DirectLightingSH.G.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.g; DirectLightingSH.B.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.b; DirectLightingSH.B.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.b; DirectLightingSH.B.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.b; #endif } if (IsStationary(LightId) && IsEnvironmentLight(LightId) && CastsShadow(LightId)) { SkyLightBentNormal += BentNormalVector; } } } } } } // Sample material FMaterialSample MaterialSample; if (Bounce == 0) { MaterialSample = RadianceProbe_SampleMaterial(Payload, RandSample.xyz); } else { MaterialSample = SampleMaterial(-Ray.Direction, Payload, RandSample.xyz); } if (MaterialSample.Pdf < 0 || asuint(MaterialSample.Pdf) > 0x7F800000) { // Pdf became invalid (either negative or NaN) Radiance = float3(1, 0, 1); break; } if (!(MaterialSample.Pdf > 0)) { // No valid direction -- we are done break; } float3 NextPathThroughput = PathThroughput * MaterialSample.Weight; if (!any(NextPathThroughput > 0)) { // no energy left in this path break; } // Russian roulette: // The probability of keeping the path should be roughly proportional to the weight at the current shade point, // but just using MaterialWeight would miss out on cases where the path throughput changes color (like in a cornell // box when bouncing between walls of different colors). So use the ratio of the brightest color channel in the // previous and next throughput. // The second tweak is to add a sqrt() around the probability to soften the termination probability (paths will last // a little longer). This allows paths to go a bit deeper than the naive heuristic while still allowing them to terminate // early. This makes RR effective from the very first bounce without needing to delay it. float ContinuationProb = sqrt(saturate(max(NextPathThroughput.x, max(NextPathThroughput.y, NextPathThroughput.z)) / max(PathThroughput.x, max(PathThroughput.y, PathThroughput.z)))); if (ContinuationProb < 1) { // If there is some chance we should terminate the ray, draw an extra random value float RussianRouletteRand = RandSample.w; //RussianRouletteRand = RandomSequence_GenerateSample1D(RandSequence); if (RussianRouletteRand >= ContinuationProb) { // stochastically terminate the path break; } PathThroughput = NextPathThroughput / ContinuationProb; } else { PathThroughput = NextPathThroughput; } // Update ray according to material sample Ray.Origin = Payload.TranslatedWorldPos; Ray.Direction = MaterialSample.Direction; Ray.TMin = 0; Ray.TMax = RAY_DEFAULT_T_MAX; ApplyRayBias(Ray.Origin, Payload.HitT, MaterialSample.PositionBiasSign * Payload.WorldGeoNormal); if (ApproximateCaustics) { // enlarge roughness based on the chosen lobe roughness PathRoughness = max(PathRoughness, MaterialSample.Roughness); } if (Bounce == 0) { RadianceDirection = Ray.Direction; #if USE_IRRADIANCE_CACHING RadianceProbe_Pdf = MaterialSample.Pdf; #endif PrimaryRandSample = RandSample; } // If we are using Material sampling for lights if (MISMode != 1) { // Check which lights can be seen by the material ray and trace a dedicated shadow ray // While it would be possible to just loop around and use the indirect ray to do this, it would prevent the application // of shadow ray specific logic for transparent shadows or various per light tricks like shadow casting const bool bUseMIS = MISMode == 2 && LightPickingCdfSum > 0; for (uint Index = 0, Num = LightLoopCount.NumMISLights; Index < Num; ++Index) { uint LightId = GetLightId(Index, LightLoopCount); if ((Payload.PrimitiveLightingChannelMask & GetLightingChannelMask(LightId)) == 0) { // light does not affect the current ray continue; } FLightHit LightResult = TraceLight(Ray, LightId); if (LightResult.IsMiss()) { continue; } float3 LightContrib = PathThroughput * LightResult.Radiance; float3 BentNormalVector = PathThroughput * MaterialSample.Direction / PI; if (bUseMIS) { float PreviousCdfValue = 0.0; BRANCH if (Index > 0) { PreviousCdfValue = LightPickingCdf[Index - 1]; } float LightPickPdf = (LightPickingCdf[Index] - PreviousCdfValue) / LightPickingCdfSum; LightContrib *= MISWeightPower(MaterialSample.Pdf, LightResult.Pdf * LightPickPdf); // Separate direct lighting (bounce 0) for GPU Lightmass BentNormalVector *= MISWeightPower(MaterialSample.Pdf, LightResult.Pdf * LightPickPdf); } if (any(LightContrib > 0)) { if (CastsShadow(LightId)) { FRayDesc LightRay = Ray; LightRay.TMax = LightResult.HitT; float3 Visibility = TraceTransparentVisibilityRay(LightRay, PathRoughness); LightContrib *= Visibility; // Separate direct lighting (bounce 0) for GPU Lightmass BentNormalVector *= Visibility; } if (Bounce > 0) { // the light made some contribution, and there was nothing along the shadow ray AccumulateRadiance(Radiance, LightContrib); } else // Bounce == 0 { if (!IsStationary(LightId)) { #ifdef LIGHTMAP_PATH_TRACING_MAIN_RG float TangentZ = saturate(dot(MaterialSample.Direction, Payload.WorldNormal)); DirectLightingSH.AddIncomingRadiance(Luminance(LightContrib), MaterialSample.Direction, TangentZ); DirectLightingIrradiance += LightContrib * TangentZ; #else DirectLightingSH.R.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.r; DirectLightingSH.R.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.r; DirectLightingSH.R.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.r; DirectLightingSH.G.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.g; DirectLightingSH.G.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.g; DirectLightingSH.G.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.g; DirectLightingSH.B.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.b; DirectLightingSH.B.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.b; DirectLightingSH.B.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.b; #endif } if (IsStationary(LightId) && IsEnvironmentLight(LightId) && CastsShadow(LightId)) { SkyLightBentNormal += BentNormalVector; } } LuminanceForFirstBounceRayGuiding += Luminance(LightContrib); } } } // from this point on, we don't need to include lights in the trace call // because NEE handled it for us NumVisibleLights = 0; } #if USE_IRRADIANCE_CACHING if(bIsValidSample) { if(bShouldEmitGeometryHitPoint) { EmitGeometryHitPoint(IrradianceCacheFinalGatherHitPoint, NewEntrySize); } if(NearestCacheEntryID != -1) { float3 IrradianceToAccumulate = Radiance * RadianceProbe_Pdf; // Revert the radiance probe pdf as we're caching the first bounce outgoing radiance ATOMIC_ADD_FLOAT(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].r, IrradianceToAccumulate.r); ATOMIC_ADD_FLOAT(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].g, IrradianceToAccumulate.g); ATOMIC_ADD_FLOAT(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].b, IrradianceToAccumulate.b); InterlockedAdd(IrradianceCachingParameters.IrradianceCacheRecords[NearestCacheEntryID].a, 1); } } else { #if IC_BACKFACE_DETECTION if(NearestCacheEntryID != -1) { InterlockedAdd(IrradianceCachingParameters.IrradianceCacheRecordBackfaceHits[NearestCacheEntryID].a, 1); } #endif } #endif RadianceValue = Radiance; } [shader("raygeneration")] void LightmapPathTracingMainRG() { uint2 BatchedLaunchIndex = DispatchRaysIndex().xy; uint2 LaunchIndex = uint2(BatchedLaunchIndex.x % GPreviewLightmapPhysicalTileSize, BatchedLaunchIndex.y); int TileIndex = BatchedLaunchIndex.x / GPreviewLightmapPhysicalTileSize; uint2 TexelIndexInPool = LaunchIndex + BatchedTiles[TileIndex].WorkingSetPosition; uint2 TexelIndexInScratch = LaunchIndex + BatchedTiles[TileIndex].ScratchPosition; float3 WorldNormal = GBufferWorldNormal[TexelIndexInScratch].xyz; float3 ShadingNormal = GBufferShadingNormal[TexelIndexInScratch].xyz; bool bMaterialTwoSided = GBufferShadingNormal[TexelIndexInScratch].w; float4 EncodedGBufferWorldPosition = GBufferWorldPosition[TexelIndexInScratch]; bool bGBufferSampleValid = true; if (EncodedGBufferWorldPosition.w == 0.0f) { bGBufferSampleValid = false; } float3 TranslatedWorldPosition = EncodedGBufferWorldPosition.xyz / WorldPositionScalar + DFHackToFloat(PrimaryView.PreViewTranslation); // RT_LWC_TODO int EffectiveRenderPassIndex = BatchedTiles[TileIndex].RenderPassIndex; #if USE_IRRADIANCE_CACHING if (EffectiveRenderPassIndex >= GetIrradianceCachingQuality()) { EffectiveRenderPassIndex -= GetIrradianceCachingQuality(); #if USE_FIRST_BOUNCE_RAY_GUIDING if (EffectiveRenderPassIndex >= NumRayGuidingTrialSamples) { EffectiveRenderPassIndex -= NumRayGuidingTrialSamples; } #endif } #endif if (EffectiveRenderPassIndex == 0) { IrradianceAndSampleCount[TexelIndexInPool]= float4(0, 0, 0, 0); SHDirectionality[TexelIndexInPool] = float4(0, 0, 0, 0); SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool] = float4(0, 0, 0, 0); } if (NumTotalSamples <= 0 || (NumTotalSamples > 0 && BatchedTiles[TileIndex].RenderPassIndex < NumTotalSamples - 1)) { bool bAnyHemispherePathSampleValid = false; if (bGBufferSampleValid) { // Needs a seed that is only related to position in virtual space to avoid seams due to per-tile calculation uint2 VirtualTextureSpacePosition = BatchedTiles[TileIndex].VirtualTilePosition + LaunchIndex - uint2(2, 2); uint Seed = VirtualTextureSpacePosition.y * BatchedTiles[TileIndex].LightmapSize.x + VirtualTextureSpacePosition.x; float4 PrimaryRandSample = float4(-1, -1, -1, -1); // Evaluate both hemispheres for two sided materials and add the lighting up naively UNROLL_N(2) for (int HemisphereIndex = 0; HemisphereIndex < (bMaterialTwoSided ? 2 : 1); HemisphereIndex++) { RandomSequence RandSequence; RandomSequence_Initialize(RandSequence, Seed, EffectiveRenderPassIndex); uint SampleIndex = 2; float3 RadianceValue = 0; float3 RadianceDirection = 0; float3 DirectLightingIrradiance = 0; #ifdef LIGHTMAP_PATH_TRACING_MAIN_RG FL2SHAndCorrection DirectLightingSH = (FL2SHAndCorrection)0; #else FThreeBandSHVectorRGBFloat DirectLightingSH = (FThreeBandSHVectorRGBFloat)0; #endif float LuminanceForFirstBounceRayGuiding = 0; float3 SkyLightBentNormal = 0; float3 EffectiveShadingNormal = HemisphereIndex == 0 ? ShadingNormal : -ShadingNormal; bool bPathSampleValid = true; PathTracingKernel( BatchedTiles[TileIndex].RenderPassIndex, TranslatedWorldPosition, EffectiveShadingNormal, RandSequence, SampleIndex, bPathSampleValid, RadianceValue, RadianceDirection, DirectLightingIrradiance, DirectLightingSH, LuminanceForFirstBounceRayGuiding, SkyLightBentNormal, PrimaryRandSample ); if (any(isnan(RadianceValue)) || any(RadianceValue < 0) || any(isinf(RadianceValue))) { bPathSampleValid = false; } if (bPathSampleValid) { #if USE_FIRST_BOUNCE_RAY_GUIDING int MinRenderPassIndex = 0; int MaxRenderPassIndex = NumRayGuidingTrialSamples; #if USE_IRRADIANCE_CACHING MinRenderPassIndex += GetIrradianceCachingQuality(); MaxRenderPassIndex += GetIrradianceCachingQuality(); #endif if (BatchedTiles[TileIndex].RenderPassIndex >= MinRenderPassIndex && BatchedTiles[TileIndex].RenderPassIndex < MaxRenderPassIndex) { float2 Point = PrimaryRandSample.yx; int2 ClusterPosition = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) / TEXEL_CLUSTER_SIZE; float2 JitteredBin = Point * DIRECTIONAL_BINS_ONE_DIM;// - float2(0.5f, 0.5f) + PrimaryRandSample.xy; int2 PositionInBin = clamp(JitteredBin, float2(0, 0), float2(DIRECTIONAL_BINS_ONE_DIM - 1, DIRECTIONAL_BINS_ONE_DIM - 1)); int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + PositionInBin; { float Illuminance = LuminanceForFirstBounceRayGuiding * saturate(dot(RadianceDirection, EffectiveShadingNormal)); // For positive floats we can cast them to uint and use atomic max directly InterlockedMax(RayGuidingLuminance[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + FinalPosition], asuint(max(Illuminance, 0))); } } #endif { FL2SHAndCorrection SH = (FL2SHAndCorrection)0; float TangentZ = saturate(dot(RadianceDirection, EffectiveShadingNormal)); SH.AddIncomingRadiance(Luminance(RadianceValue), RadianceDirection, TangentZ); IrradianceAndSampleCount[TexelIndexInPool].rgb += float3(RadianceValue * TangentZ / PI); SHDirectionality[TexelIndexInPool] += SH.L2SHCoefficients; SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x += SH.Correction; } #ifdef LIGHTMAP_PATH_TRACING_MAIN_RG { IrradianceAndSampleCount[TexelIndexInPool].rgb += DirectLightingIrradiance / PI; SHDirectionality[TexelIndexInPool] += DirectLightingSH.L2SHCoefficients; SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x += DirectLightingSH.Correction; } #endif SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].yzw += SkyLightBentNormal; } bAnyHemispherePathSampleValid = bAnyHemispherePathSampleValid || bPathSampleValid; } } uint SampleCount = asuint(IrradianceAndSampleCount[TexelIndexInPool].w); #if 0 // Smooth invalidation. This path is currently disabled to avoid inconsistency when saving baked results. if (BatchedTiles[TileIndex].FrameIndex < LastInvalidationFrame) { if (SampleCount > 0) { float L = Luminance(IrradianceAndSampleCount[TexelIndexInPool].rgb / SampleCount); int HistoryWeight = 2.0f / (1.0f + L * L); IrradianceAndSampleCount[TexelIndexInPool].rgb = IrradianceAndSampleCount[TexelIndexInPool].rgb / SampleCount * HistoryWeight; SHDirectionality[TexelIndexInPool] = SHDirectionality[TexelIndexInPool] / SampleCount * HistoryWeight; SampleCount = HistoryWeight; } } #endif if (bAnyHemispherePathSampleValid) SampleCount++; IrradianceAndSampleCount[TexelIndexInPool].a = asfloat(SampleCount); } #if 0 // Debug: Ray guiding PDF visualization { int2 ClusterPosition = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) / TEXEL_CLUSTER_SIZE; int2 PositionInBin = clamp((int2)LaunchIndex - int2(2, 2), int2(0, 0), int2(63, 63)) % DIRECTIONAL_BINS_ONE_DIM; int2 FinalPosition = ClusterPosition * DIRECTIONAL_BINS_ONE_DIM + PositionInBin; IrradianceAndSampleCount[TexelIndexInPool].rgb = ( asfloat(RayGuidingCDF[BatchedTiles[TileIndex].WorkingSetPosition / GPreviewLightmapPhysicalTileSize * CDF_TILE_SIZE + LaunchIndex]).xxx / (1 + asuint(IrradianceAndSampleCount[TexelIndexInPool].a)) ); SHDirectionality[TexelIndexInPool] = float4(1, 0, 0, 0); SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x = 1; } #endif #if 0 // Debug: GBuffer shading normal visualization { const half LogBlackPoint = 0.01858136; float3 ShadingNormal = GBufferShadingNormal[TexelIndexInScratch].xyz * 0.5f + 0.5f; IrradianceAndSampleCount[TexelIndexInPool].rgb = ShadingNormal; IrradianceAndSampleCount[TexelIndexInPool].a = asfloat(1); SHDirectionality[TexelIndexInPool] = float4(1, 0, 0, 0); SHCorrectionAndStationarySkyLightBentNormal[TexelIndexInPool].x = 1; } #endif } #include "BrickAllocationDefs.ush" uint FrameNumber; float4 VolumeMin; float4 VolumeSize; int3 IndirectionTextureDim; Texture3D IndirectionTexture; Buffer BrickRequests; int NumTotalBricks; int BrickBatchOffset; RWTexture3D AmbientVector; RWTexture3D SHCoefficients0R; RWTexture3D SHCoefficients1R; RWTexture3D SHCoefficients0G; RWTexture3D SHCoefficients1G; RWTexture3D SHCoefficients0B; RWTexture3D SHCoefficients1B; RWTexture3D SkyBentNormal; RWTexture3D DirectionalLightShadowing; uint MortonEncode3(uint3 Pixel) { uint Morton = MortonCode3(Pixel.x & 0xFF) | (MortonCode3(Pixel.y & 0xFF) << 1) | (MortonCode3(Pixel.z & 0xFF) << 1); return Morton; } // Must match C++ struct FLightShaderConstants { float3 PositionHigh; float InvRadius; float3 Color; float FalloffExponent; float3 Direction; float SpecularScale; float DiffuseScale; float3 Tangent; float SourceRadius; float3 PositionLow; float SourceLength; float2 SpotAngles; float SoftSourceRadius; float RectLightBarnCosAngle; float RectLightBarnLength; void FillLightShaderParameters(inout FLightShaderParameters LightShaderParameters) { const FDFVector3 WorldPosition = MakeDFVector3(PositionHigh, PositionLow); LightShaderParameters.TranslatedWorldPosition = DFFastAddDemote(WorldPosition, PrimaryView.PreViewTranslation); LightShaderParameters.InvRadius = InvRadius; LightShaderParameters.Color = Color; LightShaderParameters.FalloffExponent = FalloffExponent; LightShaderParameters.Direction = Direction; LightShaderParameters.SpecularScale = SpecularScale; LightShaderParameters.DiffuseScale = DiffuseScale; LightShaderParameters.Tangent = Tangent; LightShaderParameters.SourceRadius = SourceRadius; LightShaderParameters.SpotAngles = SpotAngles; LightShaderParameters.SoftSourceRadius = SoftSourceRadius; LightShaderParameters.SourceLength = SourceLength; LightShaderParameters.RectLightBarnCosAngle = RectLightBarnCosAngle; LightShaderParameters.RectLightBarnLength = RectLightBarnLength; } }; bool GenerateOcclusionRay( int LightType, FLightShaderParameters LightParameters, float3 TranslatedWorldPosition, float3 WorldNormal, float2 RandSample, inout float3 RayOrigin, inout float3 RayDirection, inout float RayTMin, inout float RayTMax ) { if (LightType == LIGHT_TYPE_DIRECTIONAL) { GenerateDirectionalLightOcclusionRay( LightParameters, TranslatedWorldPosition, WorldNormal, RandSample, /* out */ RayOrigin, /* out */ RayDirection, /* out */ RayTMin, /* out */ RayTMax); } else if (LightType == LIGHT_TYPE_POINT || LightType == LIGHT_TYPE_SPOT) { if (LightType == LIGHT_TYPE_SPOT) { // before generating a shadow ray, make sure we are inside the cone of the light float3 LightDirection = normalize(LightParameters.TranslatedWorldPosition - TranslatedWorldPosition); float CosAngle = LightParameters.SpotAngles.x; if (!(dot(LightDirection, LightParameters.Direction) >= CosAngle)) { // outside the cone of the light, skip all work return false; } } float RayPdf; if (LightParameters.SourceLength > 0.0) { return GenerateCapsuleLightOcclusionRayWithSolidAngleSampling( LightParameters, TranslatedWorldPosition, WorldNormal, RandSample, /* out */ RayOrigin, /* out */ RayDirection, /* out */ RayTMin, /* out */ RayTMax, /* out */ RayPdf); } return GenerateSphereLightOcclusionRayWithSolidAngleSampling( LightParameters, TranslatedWorldPosition, WorldNormal, RandSample, /* out */ RayOrigin, /* out */ RayDirection, /* out */ RayTMin, /* out */ RayTMax, /* out */ RayPdf); } else if (LightType == LIGHT_TYPE_RECT) { float RayPdf = 0.0; return GenerateRectLightOcclusionRay( LightParameters, TranslatedWorldPosition, WorldNormal, RandSample, /* out */ RayOrigin, /* out */ RayDirection, /* out */ RayTMin, /* out */ RayTMax, /* out */ RayPdf); } return true; } StructuredBuffer LightShaderParametersArray; [shader("raygeneration")] void VolumetricLightmapPathTracingMainRG() { uint BrickVolumeSize = 4 * 4 * 4; uint BrickIndex = DispatchRaysIndex().x / BrickVolumeSize + BrickBatchOffset; if (BrickIndex >= NumTotalBricks) return; uint CellIndex = DispatchRaysIndex().x % BrickVolumeSize; uint3 CellPosInBrick = ComputeBrickLayoutPosition(CellIndex, uint3(4, 4, 4)); uint PaddedBrickSize = 4 + 1; uint3 VoxelPos = ComputeBrickLayoutPosition(DispatchRaysIndex().x / BrickVolumeSize, uint3(256, 256, 256)) * PaddedBrickSize + CellPosInBrick; bool bIsValidSample = true; int EffectiveRenderPassIndex = FrameNumber; #if USE_IRRADIANCE_CACHING if (EffectiveRenderPassIndex >= GetIrradianceCachingQuality()) { EffectiveRenderPassIndex -= GetIrradianceCachingQuality(); } #endif if (EffectiveRenderPassIndex == 0) { AmbientVector[VoxelPos] = float4(0, 0, 0, 0); SHCoefficients0R[VoxelPos] = float4(0, 0, 0, 0); SHCoefficients1R[VoxelPos] = float4(0, 0, 0, 0); SHCoefficients0G[VoxelPos] = float4(0, 0, 0, 0); SHCoefficients1G[VoxelPos] = float4(0, 0, 0, 0); SHCoefficients0B[VoxelPos] = float4(0, 0, 0, 0); SHCoefficients1B[VoxelPos] = float4(0, 0, 0, 0); SkyBentNormal[VoxelPos] = float4(0, 0, 0, 0); } int3 CellPosInVLM = (BrickRequests[BrickIndex].xyz * 4 + CellPosInBrick) * BrickRequests[BrickIndex].w; uint Seed = ComputeBrickLinearAddress(CellPosInVLM, IndirectionTextureDim * 4); float3 RandSample = float3( Halton(MortonEncode3(CellPosInVLM) + EffectiveRenderPassIndex, 2), Halton(MortonEncode3(CellPosInVLM) + EffectiveRenderPassIndex, 3), Halton(MortonEncode3(CellPosInVLM) + EffectiveRenderPassIndex, 5) ); float3 Jitter = RandSample; float3 JitteredCellPosInVLM = (BrickRequests[BrickIndex].xyz * 4 + CellPosInBrick + Jitter - float3(0.5, 0.5, 0.5)) * BrickRequests[BrickIndex].w; float3 DetailCellSize = VolumeSize.xyz / IndirectionTextureDim / 4; float3 WorldPosition = VolumeMin.xyz + DetailCellSize * JitteredCellPosInVLM; // RT_LWC_TODO float3 TranslatedWorldPosition = WorldPosition + DFHackToFloat(PrimaryView.PreViewTranslation); // RT_LWC_TODO int NumValidHalfSamples = 0; UNROLL_N(2) for (int HalfSampleIndex = 0; HalfSampleIndex < 2; HalfSampleIndex++) { float3 ShadingNormal = float3(0, 0, (HalfSampleIndex == 0) ? 1 : -1); float3 RadianceValue = 0; float3 RadianceDirection = 0; float3 DirectLightingIrradiance = 0; #ifdef LIGHTMAP_PATH_TRACING_MAIN_RG FL2SHAndCorrection DirectLightingSH = (FL2SHAndCorrection)0; #else FThreeBandSHVectorRGBFloat DirectLightingSH = (FThreeBandSHVectorRGBFloat)0; #endif float LuminanceForFirstBounceRayGuiding = 0; float3 SkyLightBentNormal = 0; float4 PrimaryRandSample; if (bIsValidSample) { RandomSequence RandSequence; RandomSequence_Initialize(RandSequence, Seed, EffectiveRenderPassIndex); uint SampleIndex = 4; PathTracingKernel( FrameNumber, TranslatedWorldPosition, ShadingNormal, RandSequence, SampleIndex, bIsValidSample, RadianceValue, RadianceDirection, DirectLightingIrradiance, DirectLightingSH, LuminanceForFirstBounceRayGuiding, SkyLightBentNormal, PrimaryRandSample); } if (any(isnan(RadianceValue)) || any(RadianceValue < 0)) { bIsValidSample = false; } if (bIsValidSample) { NumValidHalfSamples++; FThreeBandSHVectorRGBFloat SH; SH.R.V0 = SHBasisFunction3Float(RadianceDirection).V0 * RadianceValue.r; SH.R.V1 = SHBasisFunction3Float(RadianceDirection).V1 * RadianceValue.r; SH.R.V2 = SHBasisFunction3Float(RadianceDirection).V2 * RadianceValue.r; SH.G.V0 = SHBasisFunction3Float(RadianceDirection).V0 * RadianceValue.g; SH.G.V1 = SHBasisFunction3Float(RadianceDirection).V1 * RadianceValue.g; SH.G.V2 = SHBasisFunction3Float(RadianceDirection).V2 * RadianceValue.g; SH.B.V0 = SHBasisFunction3Float(RadianceDirection).V0 * RadianceValue.b; SH.B.V1 = SHBasisFunction3Float(RadianceDirection).V1 * RadianceValue.b; SH.B.V2 = SHBasisFunction3Float(RadianceDirection).V2 * RadianceValue.b; AmbientVector[VoxelPos].x += SH.R.V0.x; AmbientVector[VoxelPos].y += SH.G.V0.x; AmbientVector[VoxelPos].z += SH.B.V0.x; SHCoefficients0R[VoxelPos] += float4(SH.R.V0.yzw, SH.R.V1.x); SHCoefficients1R[VoxelPos] += float4(SH.R.V1.yzw, SH.R.V2); SHCoefficients0G[VoxelPos] += float4(SH.G.V0.yzw, SH.G.V1.x); SHCoefficients1G[VoxelPos] += float4(SH.G.V1.yzw, SH.G.V2); SHCoefficients0B[VoxelPos] += float4(SH.B.V0.yzw, SH.B.V1.x); SHCoefficients1B[VoxelPos] += float4(SH.B.V1.yzw, SH.B.V2); if (HalfSampleIndex == 0) { SkyBentNormal[VoxelPos].rgb += SkyLightBentNormal; } #ifndef LIGHTMAP_PATH_TRACING_MAIN_RG AmbientVector[VoxelPos].x += DirectLightingSH.R.V0.x; AmbientVector[VoxelPos].y += DirectLightingSH.G.V0.x; AmbientVector[VoxelPos].z += DirectLightingSH.B.V0.x; SHCoefficients0R[VoxelPos] += float4(DirectLightingSH.R.V0.yzw, DirectLightingSH.R.V1.x); SHCoefficients1R[VoxelPos] += float4(DirectLightingSH.R.V1.yzw, DirectLightingSH.R.V2); SHCoefficients0G[VoxelPos] += float4(DirectLightingSH.G.V0.yzw, DirectLightingSH.G.V1.x); SHCoefficients1G[VoxelPos] += float4(DirectLightingSH.G.V1.yzw, DirectLightingSH.G.V2); SHCoefficients0B[VoxelPos] += float4(DirectLightingSH.B.V0.yzw, DirectLightingSH.B.V1.x); SHCoefficients1B[VoxelPos] += float4(DirectLightingSH.B.V1.yzw, DirectLightingSH.B.V2); #endif } } if (NumValidHalfSamples > 0) { uint SampleCount = asuint(AmbientVector[VoxelPos].w); AmbientVector[VoxelPos].w = asfloat(SampleCount + 1); } // 32 samples for single sample stationary directional light shadowing if (FrameNumber < 32 && length(LightShaderParametersArray[0].Direction) > 0) { if (FrameNumber == 0) { DirectionalLightShadowing[VoxelPos] = 0; } FLightShaderParameters LightParameters; LightShaderParametersArray[0].FillLightShaderParameters(LightParameters); float2 RandSample = float2( Halton(StrongIntegerHash(Seed) + FrameNumber, 5), Halton(StrongIntegerHash(Seed) + FrameNumber, 7) ); FRayDesc Ray; bool bIsValidRay = GenerateOcclusionRay( LIGHT_TYPE_DIRECTIONAL, LightParameters, TranslatedWorldPosition, float3(0, 0, 1), RandSample, /* out */ Ray.Origin, /* out */ Ray.Direction, /* out */ Ray.TMin, /* out */ Ray.TMax); if (bIsValidRay) { uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH; FMinimalPayload MinimalPayload = TraceLightmapVisibilityRay( TLAS, RayFlags, Ray); DirectionalLightShadowing[VoxelPos] += (MinimalPayload.IsMiss() ? 1 : 0) / 32.0f; } } } Buffer LightTypeArray; Buffer ChannelIndexArray; Buffer LightSampleIndexArray; RWTexture2D ShadowMask; RWTexture2D ShadowMaskSampleCount; [shader("raygeneration")] void StationaryLightShadowTracingMainRG() { uint2 BatchedLaunchIndex = DispatchRaysIndex().xy; uint2 LaunchIndex = uint2(BatchedLaunchIndex.x % GPreviewLightmapPhysicalTileSize, BatchedLaunchIndex.y); int TileIndex = BatchedLaunchIndex.x / GPreviewLightmapPhysicalTileSize; uint2 TexelIndexInPool = LaunchIndex + BatchedTiles[TileIndex].WorkingSetPosition; uint2 TexelIndexInScratch = LaunchIndex + BatchedTiles[TileIndex].ScratchPosition; int ChannelIndex = ChannelIndexArray[TileIndex]; bool bGBufferSampleValid = true; float4 EncodedGBufferWorldPosition = GBufferWorldPosition[TexelIndexInScratch]; if (EncodedGBufferWorldPosition.w == 0.0f) { bGBufferSampleValid = false; } float3 TranslatedWorldPosition = EncodedGBufferWorldPosition.xyz / WorldPositionScalar + DFHackToFloat(PrimaryView.PreViewTranslation); // RT_LWC_TODO if (bGBufferSampleValid) { float3 WorldNormal = GBufferWorldNormal[TexelIndexInScratch].xyz; float3 ShadingNormal = GBufferShadingNormal[TexelIndexInScratch].xyz; bool bMaterialTwoSided = GBufferShadingNormal[TexelIndexInScratch].w; // Needs a seed that is only related to position in virtual space to avoid seams due to per-tile calculation uint2 VirtualTextureSpacePosition = BatchedTiles[TileIndex].VirtualTilePosition + LaunchIndex - uint2(2, 2); uint Seed = VirtualTextureSpacePosition.y * BatchedTiles[TileIndex].LightmapSize.x + VirtualTextureSpacePosition.x; int LightType = LightTypeArray[TileIndex]; FLightShaderParameters LightParameters; LightShaderParametersArray[TileIndex].FillLightShaderParameters(LightParameters); bool bAnyHemisphereRaySampleValid = false; uint Visibility = 0; UNROLL_N(2) for (int HemisphereIndex = 0; HemisphereIndex < (bMaterialTwoSided ? 2 : 1); HemisphereIndex++) { float2 RandSample = float2( Halton(StrongIntegerHash(Seed) + LightSampleIndexArray[TileIndex], 5), Halton(StrongIntegerHash(Seed) + LightSampleIndexArray[TileIndex], 7) ); float3 EffectiveWorldNormal = HemisphereIndex == 0 ? WorldNormal : -WorldNormal; bool bRaySampleValid = true; FRayDesc Ray; bRaySampleValid &= GenerateOcclusionRay( LightType, LightParameters, TranslatedWorldPosition, EffectiveWorldNormal, RandSample, /* out */ Ray.Origin, /* out */ Ray.Direction, /* out */ Ray.TMin, /* out */ Ray.TMax); ApplyRayBias(Ray.Origin, LightType == LIGHT_TYPE_DIRECTIONAL ? 1.0f : max(Ray.TMax - Ray.TMin, 0.0f), EffectiveWorldNormal); if (bRaySampleValid) { uint RayFlags = RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH; FMinimalPayload MinimalPayload = TraceLightmapVisibilityRay( TLAS, RayFlags, Ray); Visibility = max(Visibility, MinimalPayload.IsMiss() ? 1 : 0); } bAnyHemisphereRaySampleValid = bAnyHemisphereRaySampleValid || bRaySampleValid; } if (bAnyHemisphereRaySampleValid) { ShadowMask[TexelIndexInPool][ChannelIndex] = asfloat(asuint(ShadowMask[TexelIndexInPool][ChannelIndex]) + Visibility); uint SampleCount = asuint(ShadowMaskSampleCount[TexelIndexInPool][ChannelIndex]); SampleCount++; ShadowMaskSampleCount[TexelIndexInPool][ChannelIndex] = asfloat(SampleCount); } } }