// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "../MonteCarlo.ush" #include "../ReflectionEnvironmentShared.ush" #define RADIANCE_PROBE_MAX_CLIPMAPS 6 #define INVALID_PROBE_INDEX 0xFFFFFFFF #if IS_MATERIAL_SHADER #define RADIANCE_CACHE_PARAMETERS_IN_UB 1 #else #define RADIANCE_CACHE_PARAMETERS_IN_UB 0 #endif #ifndef RADIANCE_CACHE_DEPTH_TEST_SPHERE_PARALLAX #define RADIANCE_CACHE_DEPTH_TEST_SPHERE_PARALLAX 0 #endif #if RADIANCE_CACHE_PARAMETERS_IN_UB #define RadianceProbeIndirectionTexture RadianceCacheInterpolation.RadianceProbeIndirectionTexture #define RadianceCacheFinalRadianceAtlas RadianceCacheInterpolation.RadianceCacheFinalRadianceAtlas #define RadianceCacheFinalSkyVisibilityAtlas RadianceCacheInterpolation.RadianceCacheFinalSkyVisibilityAtlas #define RadianceCacheFinalIrradianceAtlas RadianceCacheInterpolation.RadianceCacheFinalIrradianceAtlas #define RadianceCacheProbeOcclusionAtlas RadianceCacheInterpolation.RadianceCacheProbeOcclusionAtlas #define RadianceProbeSettings RadianceCacheInterpolation.RadianceProbeSettings #define ClipmapCornerTWSAndCellSize RadianceCacheInterpolation.ClipmapCornerTWSAndCellSize #define ReprojectionRadiusScale RadianceCacheInterpolation.ReprojectionRadiusScale #define InvClipmapFadeSize RadianceCacheInterpolation.InvClipmapFadeSize #define ProbeAtlasResolutionInProbes RadianceCacheInterpolation.ProbeAtlasResolutionInProbes #define InvProbeFinalRadianceAtlasResolution RadianceCacheInterpolation.InvProbeFinalRadianceAtlasResolution #define InvProbeFinalIrradianceAtlasResolution RadianceCacheInterpolation.InvProbeFinalIrradianceAtlasResolution #define InvProbeDepthAtlasResolution RadianceCacheInterpolation.InvProbeDepthAtlasResolution #define NumRadianceProbeClipmaps RadianceCacheInterpolation.NumRadianceProbeClipmaps #define RadianceProbeClipmapResolution RadianceCacheInterpolation.RadianceProbeClipmapResolution #define RadianceProbeResolution RadianceCacheInterpolation.RadianceProbeResolution #define FinalProbeResolution RadianceCacheInterpolation.FinalProbeResolution #define CalculateIrradiance RadianceCacheInterpolation.CalculateIrradiance #define IrradianceProbeResolution RadianceCacheInterpolation.IrradianceProbeResolution #define OcclusionProbeResolution RadianceCacheInterpolation.OcclusionProbeResolution #define FinalRadianceAtlasMaxMip RadianceCacheInterpolation.FinalRadianceAtlasMaxMip #define RadianceCacheOneOverCachedLightingPreExposure RadianceCacheInterpolation.RadianceCacheOneOverCachedLightingPreExposure #define OverrideCacheOcclusionLighting RadianceCacheInterpolation.OverrideCacheOcclusionLighting #define ShowBlackRadianceCacheLighting RadianceCacheInterpolation.ShowBlackRadianceCacheLighting #define ProbeWorldOffset RadianceCacheInterpolation.ProbeWorldOffset #define RadianceCacheDepthAtlas RadianceCacheInterpolation.RadianceCacheDepthAtlas #define ProbeAtlasResolutionModuloMask RadianceCacheInterpolation.ProbeAtlasResolutionModuloMask #define ProbeAtlasResolutionDivideShift RadianceCacheInterpolation.ProbeAtlasResolutionDivideShift #else Texture3D RadianceProbeIndirectionTexture; Texture2D RadianceCacheFinalRadianceAtlas; Texture2D RadianceCacheFinalSkyVisibilityAtlas; Texture2D RadianceCacheFinalIrradianceAtlas; Texture2D RadianceCacheProbeOcclusionAtlas; Texture2D RadianceCacheDepthAtlas; StructuredBuffer ProbeWorldOffset; float4 RadianceProbeSettings[RADIANCE_PROBE_MAX_CLIPMAPS]; float4 ClipmapCornerTWSAndCellSize[RADIANCE_PROBE_MAX_CLIPMAPS]; float ReprojectionRadiusScale; float InvClipmapFadeSize; uint2 ProbeAtlasResolutionInProbes; float2 InvProbeFinalRadianceAtlasResolution; float2 InvProbeFinalIrradianceAtlasResolution; float2 InvProbeDepthAtlasResolution; uint NumRadianceProbeClipmaps; uint RadianceProbeClipmapResolution; // Resolution of Octahedral layout during tracing uint RadianceProbeResolution; // Resolution of Octahedral layout during sampling uint FinalProbeResolution; uint CalculateIrradiance; uint IrradianceProbeResolution; // Resolution of Octahedral layout for sampling uint OcclusionProbeResolution; uint FinalRadianceAtlasMaxMip; float RadianceCacheOneOverCachedLightingPreExposure; uint OverrideCacheOcclusionLighting; uint ShowBlackRadianceCacheLighting; uint ProbeAtlasResolutionModuloMask; uint ProbeAtlasResolutionDivideShift; #endif float GetRadianceProbeTMin(uint ClipmapIndex) { return RadianceProbeSettings[ClipmapIndex].x; // must match with LumenRadianceCacheInterpolation.h } // The minimum coordinate of the clipmap, in translated world space float3 GetRadianceProbeClipmapCornerTWS(uint ClipmapIndex) { return ClipmapCornerTWSAndCellSize[ClipmapIndex].xyz; // must match with LumenRadianceCacheInterpolation.h } float GetRadianceProbeClipmapCellSize(uint ClipmapIndex) { return ClipmapCornerTWSAndCellSize[ClipmapIndex].w; // must match with LumenRadianceCacheInterpolation.h } float3 GetRadianceProbeCoordFloat(float3 ProbeWorldPosition, uint ClipmapIndex) { const float3 ProbeTranslatedWorldPosition = ProbeWorldPosition + DFHackToFloat(PrimaryView.PreViewTranslation); const float3 CornerTranslatedWorldPosition = GetRadianceProbeClipmapCornerTWS(ClipmapIndex); const float3 CornerToProbe = ProbeTranslatedWorldPosition - CornerTranslatedWorldPosition; const float CellSize = GetRadianceProbeClipmapCellSize(ClipmapIndex); return CornerToProbe / CellSize; } int3 GetRadianceProbeCoord(float3 ProbeWorldPosition, uint ClipmapIndex) { // Use floor() to round negative numbers down return floor(GetRadianceProbeCoordFloat(ProbeWorldPosition, ClipmapIndex)); } int3 GetRadianceProbeBottomCornerCoord(float3 ProbeWorldPosition, uint ClipmapIndex) { return GetRadianceProbeCoord(ProbeWorldPosition - 0.5f, ClipmapIndex); } float3 GetProbeTranslatedWorldPositionNoOffset(uint3 ProbeCoord, uint ClipmapIndex) { const float3 CornerTranslatedWorldPosition = GetRadianceProbeClipmapCornerTWS(ClipmapIndex); const float CellSize = GetRadianceProbeClipmapCellSize(ClipmapIndex); const float3 CornerToProbe = (ProbeCoord + 0.5) * CellSize; return CornerTranslatedWorldPosition + CornerToProbe; } float3 GetProbeWorldPosition(uint3 ProbeCoord, uint ClipmapIndex, uint ProbeIndex) { return GetProbeTranslatedWorldPositionNoOffset(ProbeCoord, ClipmapIndex) + ProbeWorldOffset[ProbeIndex].xyz - DFHackToFloat(PrimaryView.PreViewTranslation); } bool IsValidRadianceCacheClipmap(uint FRadianceCacheCoverage) { return FRadianceCacheCoverage < NumRadianceProbeClipmaps; } uint GetRadianceProbeClipmap(float3 WorldSpacePosition, float ClipmapDitherRandom) { uint ClipmapIndex = 0; for (; ClipmapIndex < NumRadianceProbeClipmaps; ++ClipmapIndex) { float3 ProbeCoordFloat = GetRadianceProbeCoordFloat(WorldSpacePosition, ClipmapIndex); float3 BottomEdgeFades = saturate((ProbeCoordFloat - .5f) * InvClipmapFadeSize); float3 TopEdgeFades = saturate(((float3)RadianceProbeClipmapResolution - .5f - ProbeCoordFloat) * InvClipmapFadeSize); float EdgeFade = min(min3(BottomEdgeFades.x, BottomEdgeFades.y, BottomEdgeFades.z), min3(TopEdgeFades.x, TopEdgeFades.y, TopEdgeFades.z)); if (EdgeFade > ClipmapDitherRandom) { return ClipmapIndex; } } return NumRadianceProbeClipmaps; } struct FRadianceCacheCoverage { uint ClipmapIndex; // The minimum distance that must be traced before interpolating from the Radiance Cache, to prevent leaking float MinTraceDistanceBeforeInterpolation; // Whether the Radiance Cache covers the queried position bool bValid; }; FRadianceCacheCoverage InitRadianceCacheCoverage() { FRadianceCacheCoverage Out; Out.ClipmapIndex = 0; Out.MinTraceDistanceBeforeInterpolation = 10000000.0f; Out.bValid = false; return Out; } // Only positions that were marked during FMarkUsedRadianceCacheProbes can be queried, this version does not check if the position was marked correctly // See UnmappedDebugColor for visualizing these errors // GetRadianceCacheCoverageWithUncertainCoverage can be used for interpolating to positions with uncertain coverage FRadianceCacheCoverage GetRadianceCacheCoverage(float3 RayOrigin, float3 RayDirection, float ClipmapDitherRandom) { FRadianceCacheCoverage Coverage = InitRadianceCacheCoverage(); Coverage.ClipmapIndex = GetRadianceProbeClipmap(RayOrigin, ClipmapDitherRandom); if (Coverage.ClipmapIndex < NumRadianceProbeClipmaps) { Coverage.bValid = true; float CellOcclusionDistance = GetRadianceProbeClipmapCellSize(Coverage.ClipmapIndex) * sqrt(3.0f); Coverage.MinTraceDistanceBeforeInterpolation = GetRadianceProbeTMin(Coverage.ClipmapIndex) + CellOcclusionDistance; } return Coverage; } struct FRadianceCacheSample { float3 Radiance; float SkyVisibility; float TraceHitDistance; }; FRadianceCacheSample SampleRadianceCacheProbe(uint ProbeIndex, float3 WorldSpaceDirection, float MipLevel) { FRadianceCacheSample Sample; Sample.Radiance = 0.0f; Sample.SkyVisibility = 0.0f; Sample.TraceHitDistance = 0.0f; float2 ProbeUV = InverseEquiAreaSphericalMapping(WorldSpaceDirection); #define VISUALIZE_PROBE_DEPTH 0 #if VISUALIZE_PROBE_DEPTH { uint2 ProbeAtlasCoord = RadianceProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float2 ProbeTexelCoord = ProbeUV * RadianceProbeResolution; float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeTexelCoord) * InvProbeDepthAtlasResolution; Sample.Radiance = RadianceCacheDepthAtlas.SampleLevel(GlobalPointClampedSampler, ProbeAtlasUV, MipLevel).x / 10000.0f; } #else { uint2 ProbeAtlasCoord = FinalProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float2 ProbeTexelCoord = ProbeUV * RadianceProbeResolution + (1u << FinalRadianceAtlasMaxMip); float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeTexelCoord) * InvProbeFinalRadianceAtlasResolution; Sample.Radiance = 0.0f; // Uncomment to show bright green when an unallocated probe is sampled //Sample.Radiance = float3(0.0f, 10.0f, 0.0f); if (ProbeIndex != INVALID_PROBE_INDEX) { Sample.Radiance = RadianceCacheFinalRadianceAtlas.SampleLevel(GlobalBilinearClampedSampler, ProbeAtlasUV, MipLevel) * RadianceCacheOneOverCachedLightingPreExposure; } #if RADIANCE_CACHE_SKY_VISIBILITY if (ProbeIndex != INVALID_PROBE_INDEX) { Sample.SkyVisibility = RadianceCacheFinalSkyVisibilityAtlas.SampleLevel(GlobalBilinearClampedSampler, ProbeAtlasUV, MipLevel); } #endif } #endif { uint2 ProbeAtlasCoord = RadianceProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float2 ProbeTexelCoord = ProbeUV * RadianceProbeResolution; float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeTexelCoord) * InvProbeDepthAtlasResolution; Sample.TraceHitDistance = RadianceCacheDepthAtlas.SampleLevel(GlobalPointClampedSampler, ProbeAtlasUV, MipLevel).x; } return Sample; } uint GetProbeIndexFromIndirectionTexture(uint3 ProbeCoord, uint ClipmapIndex) { uint3 ProbeIndirectionTextureCoord = uint3(ProbeCoord.x + ClipmapIndex * RadianceProbeClipmapResolution, ProbeCoord.yz); return RadianceProbeIndirectionTexture.Load(uint4(ProbeIndirectionTextureCoord, 0)); } FRadianceCacheSample SampleRadianceCacheProbeWithParallaxCorrection(uint3 ProbeCoord, uint ProbeClipmapIndex, float3 WorldSpacePosition, float3 WorldSpaceDirection, float MipLevel) { float ProbeTMin = GetRadianceProbeTMin(ProbeClipmapIndex); uint ProbeIndex = GetProbeIndexFromIndirectionTexture(ProbeCoord, ProbeClipmapIndex); float3 ProbeWorldPosition = GetProbeWorldPosition(ProbeCoord, ProbeClipmapIndex, ProbeIndex); float3 ReprojectedDirection = WorldSpaceDirection; float CorrectionFactor = 1.0f; #define SIMPLE_SPHERE_PARALLAX 1 #define TRACE_THROUGH_PROBE_DEPTHS_REFERENCE 0 #if SIMPLE_SPHERE_PARALLAX float ReprojectionRadius = ReprojectionRadiusScale * ProbeTMin; float T = RayIntersectSphere(WorldSpacePosition, WorldSpaceDirection, float4(ProbeWorldPosition, ReprojectionRadius)).y; float3 IntersectionPosition = WorldSpacePosition + WorldSpaceDirection * T; ReprojectedDirection = IntersectionPosition - ProbeWorldPosition; // Cancel out the attenuation effect when moving towards/away from a probe texel to mitigate the grid like pattern // CorrectionFactor = T^2 / R^2 / dot(normalize(ReprojectedDirection), WorldSpaceDirection) CorrectionFactor = T * T / (ReprojectionRadius * dot(ReprojectedDirection, WorldSpaceDirection)); // Depth test the parallax corrected direction, if it's distant lighting then we can skip the parallax correction and the bias that comes with it without leaking // Currently disabled, needs more work #if RADIANCE_CACHE_DEPTH_TEST_SPHERE_PARALLAX && 0 { float2 ProbeUV = InverseEquiAreaSphericalMapping(ReprojectedDirection); uint2 ProbeAtlasCoord = RadianceProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeUV * RadianceProbeResolution) * InvProbeDepthAtlasResolution; float2 ProbeGatherAtlasUV = (floor(ProbeAtlasUV / InvProbeDepthAtlasResolution - .5f) + 1.0f) * InvProbeDepthAtlasResolution; float4 HitDistanceFromProbeGather = RadianceCacheDepthAtlas.GatherRed(GlobalPointClampedSampler, ProbeGatherAtlasUV).wzxy; float HitDistanceFromProbe = min(min(HitDistanceFromProbeGather.x, HitDistanceFromProbeGather.y), min(HitDistanceFromProbeGather.z, HitDistanceFromProbeGather.w)); if (HitDistanceFromProbe > ReprojectionRadius * 2.0f) { ReprojectedDirection = WorldSpaceDirection; } } #endif #elif TRACE_THROUGH_PROBE_DEPTHS_REFERENCE //@note - no depth mips implemented float3 ProbeTraceStart = WorldSpacePosition + WorldSpaceDirection * RayIntersectSphere(WorldSpacePosition, WorldSpaceDirection, float4(ProbeWorldPosition, ProbeTMin)).y; float3 ProbeTraceEnd = ProbeWorldPosition + WorldSpaceDirection * 10000.0f; float3 ProbeTraceDirection = ProbeTraceEnd - ProbeTraceStart; uint2 ProbeAtlasCoord = RadianceProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float NumSamples = 100.0f; for (float StepIndex = 0; StepIndex < NumSamples; StepIndex++) { float3 StepPosition = ProbeTraceStart + StepIndex / (NumSamples - 1) * ProbeTraceDirection; float3 ProbeToStepPosition = StepPosition - ProbeWorldPosition; float2 ProbeUV = InverseEquiAreaSphericalMapping(ProbeToStepPosition); float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeUV * RadianceProbeResolution) * InvProbeDepthAtlasResolution; float StepHitDistanceFromProbe = RadianceCacheDepthAtlas.SampleLevel(GlobalPointClampedSampler, ProbeAtlasUV, MipLevel).x; float StepRayDistanceFromProbeSq = dot(ProbeToStepPosition, ProbeToStepPosition); if (StepHitDistanceFromProbe * StepHitDistanceFromProbe < StepRayDistanceFromProbeSq) { ReprojectedDirection = ProbeToStepPosition; break; } } #endif FRadianceCacheSample RadianceCacheSample = SampleRadianceCacheProbe(ProbeIndex, ReprojectedDirection, MipLevel); RadianceCacheSample.Radiance *= CorrectionFactor; return RadianceCacheSample; } FRadianceCacheSample LerpRadianceCacheSample(FRadianceCacheSample SampleA, FRadianceCacheSample SampleB, float Alpha) { FRadianceCacheSample Sample; Sample.Radiance = lerp(SampleA.Radiance, SampleB.Radiance, Alpha); Sample.SkyVisibility = lerp(SampleA.SkyVisibility, SampleB.SkyVisibility, Alpha); Sample.TraceHitDistance = lerp(SampleA.TraceHitDistance, SampleB.TraceHitDistance, Alpha); return Sample; } FRadianceCacheSample RadianceCacheResolveSky(FRadianceCacheSample Sample, float3 WorldSpaceDirection) { #if RADIANCE_CACHE_SKY_VISIBILITY if (Sample.SkyVisibility > 0.0f && ReflectionStruct.SkyLightParameters.y > 0) { float SkyAverageBrightness = 1.0f; float3 SkyRadiance = GetSkyLightReflection(WorldSpaceDirection, /*Roughness*/ 0.0f, SkyAverageBrightness); Sample.Radiance = lerp(Sample.Radiance, SkyRadiance, Sample.SkyVisibility); } #endif return Sample; } FRadianceCacheSample SampleRadianceCacheInterpolated(FRadianceCacheCoverage Coverage, float3 WorldSpacePosition, float3 WorldSpaceDirection, float ConeHalfAngle, float RandomScalarForStochasticInterpolation) { // Caller should have branched #define DEBUG_VISUALIZE_INVALID_COVERAGE 0 #if DEBUG_VISUALIZE_INVALID_COVERAGE if (!Coverage.bValid) { return View.StateFrameIndexMod8 == 0 ? float3(10, 0, 0) : float3(0, 0, 0); } #endif float3 ProbeCoordFloat = GetRadianceProbeCoordFloat(WorldSpacePosition, Coverage.ClipmapIndex); //float ConeHalfAngle = acosFast(1.0f - NumTexels * NumTexels / (float)(RadianceProbeResolution * RadianceProbeResolution)); float NumTexels = sqrt(1.0f - cos(ConeHalfAngle)) * RadianceProbeResolution; float MipLevel = clamp(log2(NumTexels), 0, (float)FinalRadianceAtlasMaxMip); float3 CornerProbeCoordFloat = ProbeCoordFloat - 0.5f; int3 CornerProbeCoord = floor(CornerProbeCoordFloat); float3 LerpAlphas = frac(CornerProbeCoordFloat); #if RADIANCE_CACHE_STOCHASTIC_INTERPOLATION // Stochastic interpolation approximating full 2x2x2 trilinear filter float4 InterpolationWeights0; InterpolationWeights0.x = (1.0f - LerpAlphas.x) * (1.0f - LerpAlphas.y) * (1.0f - LerpAlphas.z); InterpolationWeights0.y = LerpAlphas.x * (1.0f - LerpAlphas.y) * (1.0f - LerpAlphas.z); InterpolationWeights0.z = (1.0f - LerpAlphas.x) * LerpAlphas.y * (1.0f - LerpAlphas.z); InterpolationWeights0.w = LerpAlphas.x * LerpAlphas.y * (1.0f - LerpAlphas.z); float4 InterpolationWeights1 = 1.0f; InterpolationWeights1.x = (1.0f - LerpAlphas.x) * (1.0f - LerpAlphas.y) * LerpAlphas.z; InterpolationWeights1.y = LerpAlphas.x * (1.0f - LerpAlphas.y) * LerpAlphas.z; InterpolationWeights1.z = (1.0f - LerpAlphas.x) * LerpAlphas.y * LerpAlphas.z; InterpolationWeights1.w = LerpAlphas.x * LerpAlphas.y * LerpAlphas.z; // Two-sample variant //uint3 StochasticOffset0 = GetStochasticTrilinearOffset((RandomScalarForStochasticInterpolation + 0.0f) / 2.0f, InterpolationWeights0, InterpolationWeights1); //uint3 StochasticOffset1 = GetStochasticTrilinearOffset((RandomScalarForStochasticInterpolation + 1.0f) / 2.0f, InterpolationWeights0, InterpolationWeights1); //FRadianceCacheSample Sample0 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + StochasticOffset0, Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); //FRadianceCacheSample Sample1 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + StochasticOffset1, Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); //FRadianceCacheSample InterpolatedSample = LerpRadianceCacheSample(Sample0, Sample1, 0.5f); uint3 StochasticOffset = GetStochasticTrilinearOffset(RandomScalarForStochasticInterpolation, InterpolationWeights0, InterpolationWeights1); FRadianceCacheSample InterpolatedSample = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + StochasticOffset, Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); #else FRadianceCacheSample Sample000 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 0, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample001 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 0, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample010 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 1, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample011 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(0, 1, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample100 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 0, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample101 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 0, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample110 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 1, 0), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample Sample111 = SampleRadianceCacheProbeWithParallaxCorrection(CornerProbeCoord + int3(1, 1, 1), Coverage.ClipmapIndex, WorldSpacePosition, WorldSpaceDirection, MipLevel); FRadianceCacheSample ZLerp00 = LerpRadianceCacheSample(Sample000, Sample001, LerpAlphas.z); FRadianceCacheSample ZLerp01 = LerpRadianceCacheSample(Sample010, Sample011, LerpAlphas.z); FRadianceCacheSample ZLerp10 = LerpRadianceCacheSample(Sample100, Sample101, LerpAlphas.z); FRadianceCacheSample ZLerp11 = LerpRadianceCacheSample(Sample110, Sample111, LerpAlphas.z); FRadianceCacheSample YLerp0 = LerpRadianceCacheSample(ZLerp00, ZLerp01, LerpAlphas.y); FRadianceCacheSample YLerp1 = LerpRadianceCacheSample(ZLerp10, ZLerp11, LerpAlphas.y); FRadianceCacheSample InterpolatedSample = LerpRadianceCacheSample(YLerp0, YLerp1, LerpAlphas.x); #endif InterpolatedSample = RadianceCacheResolveSky(InterpolatedSample, WorldSpaceDirection); return InterpolatedSample; } void SampleRadianceCacheAndApply(FRadianceCacheCoverage Coverage, float3 WorldSpacePosition, float3 WorldSpaceDirection, float ConeHalfAngle, float RandomScalarForStochasticInterpolation, inout float3 Lighting, inout float Transparency, out float TraceHitDistance) { FRadianceCacheSample RadianceCacheSample = SampleRadianceCacheInterpolated(Coverage, WorldSpacePosition, WorldSpaceDirection, ConeHalfAngle, RandomScalarForStochasticInterpolation); if (OverrideCacheOcclusionLighting > 0) { Lighting = RadianceCacheSample.Radiance; } else if (ShowBlackRadianceCacheLighting == 0) { Lighting += RadianceCacheSample.Radiance * Transparency; } TraceHitDistance = RadianceCacheSample.TraceHitDistance; Transparency = 0.0f; } void SampleRadianceCacheAndApply(FRadianceCacheCoverage Coverage, float3 WorldSpacePosition, float3 WorldSpaceDirection, float ConeHalfAngle, float RandomScalarForStochasticInterpolation, inout float3 Lighting, inout float Transparency) { float UnusedTraceHitDistance; SampleRadianceCacheAndApply(Coverage, WorldSpacePosition, WorldSpaceDirection, ConeHalfAngle, RandomScalarForStochasticInterpolation, Lighting, Transparency, UnusedTraceHitDistance); } float3 SampleIrradianceCacheProbe(uint ProbeIndex, float3 WorldSpaceDirection) { //@todo - move out of loop float2 ProbeUV = InverseEquiAreaSphericalMapping(WorldSpaceDirection); uint FinalIrradianceProbeResolution = IrradianceProbeResolution + 2 * (1u << FinalRadianceAtlasMaxMip); uint2 ProbeAtlasCoord = FinalIrradianceProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float2 ProbeTexelCoord = ProbeUV * IrradianceProbeResolution + (1u << FinalRadianceAtlasMaxMip); float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeTexelCoord) * InvProbeFinalIrradianceAtlasResolution; float3 UnmappedDebugColor = 0.0f; // Show bright green when an unallocated probe is sampled //UnmappedDebugColor = float3(0.0f, 10.0f, 0.0f); return ProbeIndex == INVALID_PROBE_INDEX ? UnmappedDebugColor : RadianceCacheFinalIrradianceAtlas.SampleLevel(GlobalBilinearClampedSampler, ProbeAtlasUV, 0.0f); } float2 SampleProbeOcclusion(uint ProbeIndex, float3 WorldSpaceDirection) { float2 ProbeUV = InverseEquiAreaSphericalMapping(WorldSpaceDirection); uint FinalOcclusionProbeResolution = OcclusionProbeResolution + 2 * (1u << FinalRadianceAtlasMaxMip); uint2 ProbeAtlasCoord = FinalOcclusionProbeResolution * uint2(ProbeIndex & ProbeAtlasResolutionModuloMask, ProbeIndex >> ProbeAtlasResolutionDivideShift); float2 ProbeTexelCoord = ProbeUV * OcclusionProbeResolution + (1u << FinalRadianceAtlasMaxMip); float2 ProbeAtlasUV = (ProbeAtlasCoord + ProbeTexelCoord) / float2(ProbeAtlasResolutionInProbes * FinalOcclusionProbeResolution); return RadianceCacheProbeOcclusionAtlas.SampleLevel(GlobalBilinearClampedSampler, ProbeAtlasUV, 0.0f); } float3 SampleIrradianceCacheProbeCoord(uint3 ProbeCoord, uint ProbeClipmapIndex, float3 WorldSpaceDirection) { uint ProbeIndex = GetProbeIndexFromIndirectionTexture(ProbeCoord, ProbeClipmapIndex); return SampleIrradianceCacheProbe(ProbeIndex, WorldSpaceDirection); } float3 SampleIrradianceCacheInterpolated(float3 WorldSpacePosition, float3 WorldSpaceDirection, float3 BiasOffset, uint ClipmapIndex) { float3 ProbeCoordFloat = GetRadianceProbeCoordFloat(WorldSpacePosition, ClipmapIndex); float3 CornerProbeCoordFloat = ProbeCoordFloat - .5f; int3 CornerProbeCoord = floor(CornerProbeCoordFloat); float3 LerpAlphas = frac(CornerProbeCoordFloat); float3 Irradiance = 0; float TotalWeight = 0; #define PROBE_OCCLUSION_INTERPOLATION 1 #if PROBE_OCCLUSION_INTERPOLATION for (uint NeighborIndex = 0; NeighborIndex < 8; NeighborIndex++) { uint3 ProbeOffset = uint3((NeighborIndex & 4) >> 2, (NeighborIndex & 2) >> 1, NeighborIndex & 1); uint3 ProbeCoord = CornerProbeCoord + ProbeOffset; uint ProbeIndex = GetProbeIndexFromIndirectionTexture(ProbeCoord, ClipmapIndex); float3 ProbeWorldPosition = GetProbeWorldPosition(ProbeCoord, ClipmapIndex, ProbeIndex); float3 SamplePosition = WorldSpacePosition + BiasOffset; float3 SamplePositionToProbe = ProbeWorldPosition - SamplePosition; float DistanceToProbe = length(SamplePositionToProbe); float SoftFalloff = (dot(normalize(ProbeWorldPosition - WorldSpacePosition), WorldSpaceDirection) + 1) * .5f; float Weight = SoftFalloff * SoftFalloff + .2f; //@todo - trilinear weight ignores probe offset float3 TrilinearWeights = max(select(ProbeOffset > 0, LerpAlphas, 1 - LerpAlphas), .001f); Weight *= TrilinearWeights.x * TrilinearWeights.y * TrilinearWeights.z; float2 MeanAndMeanSq = SampleProbeOcclusion(ProbeIndex, -SamplePositionToProbe); if (DistanceToProbe > MeanAndMeanSq.x) { float Variance = abs(Square(MeanAndMeanSq.x) - MeanAndMeanSq.y); float VisibilityWeight = Variance / (Variance + Square(DistanceToProbe - MeanAndMeanSq.x)); Weight *= max(VisibilityWeight * VisibilityWeight * VisibilityWeight, 0); } float WeightThreshold = .2f; if (Weight < WeightThreshold) { Weight *= Square(Weight) / Square(WeightThreshold); } float3 SampleIrradiance = SampleIrradianceCacheProbe(ProbeIndex, WorldSpaceDirection); Irradiance += sqrt(SampleIrradiance) * Weight; TotalWeight += Weight; } Irradiance = Square(Irradiance / TotalWeight); #else float3 Lighting000 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(0, 0, 0), ClipmapIndex, WorldSpaceDirection); float3 Lighting001 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(0, 0, 1), ClipmapIndex, WorldSpaceDirection); float3 Lighting010 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(0, 1, 0), ClipmapIndex, WorldSpaceDirection); float3 Lighting011 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(0, 1, 1), ClipmapIndex, WorldSpaceDirection); float3 Lighting100 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(1, 0, 0), ClipmapIndex, WorldSpaceDirection); float3 Lighting101 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(1, 0, 1), ClipmapIndex, WorldSpaceDirection); float3 Lighting110 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(1, 1, 0), ClipmapIndex, WorldSpaceDirection); float3 Lighting111 = SampleIrradianceCacheProbeCoord(CornerProbeCoord + int3(1, 1, 1), ClipmapIndex, WorldSpaceDirection); float3 ZLerp00 = lerp(Lighting000, Lighting001, LerpAlphas.z); float3 ZLerp01 = lerp(Lighting010, Lighting011, LerpAlphas.z); float3 ZLerp10 = lerp(Lighting100, Lighting101, LerpAlphas.z); float3 ZLerp11 = lerp(Lighting110, Lighting111, LerpAlphas.z); float3 YLerp0 = lerp(ZLerp00, ZLerp01, LerpAlphas.y); float3 YLerp1 = lerp(ZLerp10, ZLerp11, LerpAlphas.y); Irradiance = lerp(YLerp0, YLerp1, LerpAlphas.x); #endif return Irradiance; }