// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= =============================================================================*/ #include "Common.ush" #include "DeferredShadingCommon.ush" #include "BRDF.ush" #if defined(DXC_GROUPSHARED_ALIGNMENT_WORKAROUND) && DXC_GROUPSHARED_ALIGNMENT_WORKAROUND // Workaround for shader compiler bug affecting certain platforms // Align groupshared SH data if the shader compiler requires it #define NEED_SH_VECTOR_PADDING 1 #endif #include "SHCommon.ush" #include "ReflectionEnvironmentShared.ush" #include "MonteCarlo.ush" #include "SkyLightingShared.ush" struct FCopyToCubeFaceVSOutput { float2 UV : TEXCOORD0; float3 ScreenVector : TEXCOORD1; float4 Position : SV_POSITION; }; void CopyToCubeFaceVS( in float2 InPosition : ATTRIBUTE0, in float2 InUV : ATTRIBUTE1, out FCopyToCubeFaceVSOutput Out ) { DrawRectangle(float4(InPosition.xy, 0, 1), InUV, Out.Position, Out.UV); Out.ScreenVector = ScreenVectorFromScreenRect(float4(Out.Position.xy, 1, 0)); } int CubeFace; Texture2D SceneColorTexture; SamplerState SceneColorSampler; #if !SHADING_PATH_DEFERRED Texture2D SceneDepthTexture; #endif SamplerState SceneDepthSampler; /** * X = 0 if capturing sky light, 1 if capturing reflection capture with MaxDistance fade, 2 otherwise, * Y = Sky distance threshold, * Z = whether a skylight's lower hemisphere should be replaced with LowerHemisphereColor. */ float4 SkyLightCaptureParameters; float4 LowerHemisphereColor; float4 ColorAlphaMultiplier; float ClampToFP16; void CopySceneColorToCubeFaceColorPS( FCopyToCubeFaceVSOutput Input, out float4 OutColor : SV_Target0 ) { float SceneDepth = SCENE_TEXTURES_DISABLED_SCENE_DEPTH_VALUE; #if !SCENE_TEXTURES_DISABLED SceneDepth = ConvertFromDeviceZ(Texture2DSample(SceneDepthTexture, SceneDepthSampler, Input.UV).r); #endif float3 SceneColor = Texture2DSample(SceneColorTexture, SceneColorSampler, Input.UV).rgb; // Convert INF's to valid values SceneColor = ClampToHalfFloatRange(SceneColor); float3 TranslatedWorldPosition = Input.ScreenVector * SceneDepth + View.TranslatedWorldCameraOrigin; float Alpha = 1; if (SkyLightCaptureParameters.x == 0) { // Assuming we're on a planet and no sky lighting is coming from below the horizon // This is important to avoid leaking from below since we are integrating incoming lighting and shadowing separately if (Input.ScreenVector.z < 0 && SkyLightCaptureParameters.z >= 1) { SceneColor = lerp(SceneColor, LowerHemisphereColor.rgb, LowerHemisphereColor.a); } } else if (SkyLightCaptureParameters.x == 1) { float RadialDistance = length(TranslatedWorldPosition - View.TranslatedWorldCameraOrigin); float MaxDistance = SkyLightCaptureParameters.y; // Setup alpha to fade out smoothly past the max distance // This allows a local reflection capture to only provide reflections where it has valid data, falls back to sky cubemap Alpha = 1 - smoothstep(.8f * MaxDistance, MaxDistance, RadialDistance); } // We need pre-multiplied alpha for correct filtering // However, we need to compute average brightness before masking out sky areas, so premultiplying happens later OutColor = float4(SceneColor, Alpha); } float3 GetCubemapVector(float2 ScaledUVs, int InCubeFace) { float3 CubeCoordinates; //@todo - this could be a 3x3 matrix multiply if (InCubeFace == 0) { CubeCoordinates = float3(1, -ScaledUVs.y, -ScaledUVs.x); } else if (InCubeFace == 1) { CubeCoordinates = float3(-1, -ScaledUVs.y, ScaledUVs.x); } else if (InCubeFace == 2) { CubeCoordinates = float3(ScaledUVs.x, 1, ScaledUVs.y); } else if (InCubeFace == 3) { CubeCoordinates = float3(ScaledUVs.x, -1, -ScaledUVs.y); } else if (InCubeFace == 4) { CubeCoordinates = float3(ScaledUVs.x, -ScaledUVs.y, 1); } else { CubeCoordinates = float3(-ScaledUVs.x, -ScaledUVs.y, -1); } return CubeCoordinates; } void GetCubemapTangent(int InCubeFace, inout float3 OutX, inout float3 OutY) { if (InCubeFace == 0) { OutX = float3(0,-1, 0); OutY = float3(0, 0,-1); } else if (InCubeFace == 1) { OutX = float3(0,-1, 0); OutY = float3(0, 0, 1); } else if (InCubeFace == 2) { OutX = float3(1, 0, 0); OutY = float3(0, 0, 1); } else if (InCubeFace == 3) { OutX = float3(1, 0, 0); OutY = float3(0, 0,-1); } else if (InCubeFace == 4) { OutX = float3(1, 0, 0); OutY = float3(0,-1, 0); } else { OutX = float3(-1, 0, 0); OutY = float3( 0,-1, 0); } } float CubeTexelWeight( float3 N ) { uint Axis = 2; if( abs(N.x) >= abs(N.y) && abs(N.x) >= abs(N.z) ) { Axis = 0; } else if( abs(N.y) > abs(N.z) ) { Axis = 1; } N = Axis == 0 ? N.zyx : N; N = Axis == 1 ? N.xzy : N; float2 UV = N.xy / N.z; float VecLengthSqr = 1 + dot( UV, UV ); return 4.0 / ( sqrt( VecLengthSqr ) * VecLengthSqr ); } TextureCube SourceCubemapTexture; SamplerState SourceCubemapSampler; float2 SinCosSourceCubemapRotation; float2 SvPositionToUVScale; void CopyCubemapToCubeFaceColorPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); // Rotate around Z axis CubeCoordinates.xy = float2(dot(CubeCoordinates.xy, float2(SinCosSourceCubemapRotation.y, -SinCosSourceCubemapRotation.x)), dot(CubeCoordinates.xy, SinCosSourceCubemapRotation)); OutColor = TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, CubeCoordinates, 0); if (SkyLightCaptureParameters.x > 0) { // Assuming we're on a planet and no sky lighting is coming from below the horizon // This is important to avoid leaking from below since we are integrating incoming lighting and shadowing separately if (CubeCoordinates.z < 0 && SkyLightCaptureParameters.z >= 1) { OutColor.rgb = lerp(OutColor.rgb, LowerHemisphereColor.rgb, LowerHemisphereColor.a); } } OutColor.a = 1; OutColor *= ColorAlphaMultiplier; OutColor = ClampToFP16 == 0.0f ? OutColor : clamp(OutColor, float4(0.0f, 0.0f, 0.0f, 0.0f), float4(MaxHalfFloat, MaxHalfFloat, MaxHalfFloat, 1.0f)); } void CopyCubemapToIlluminanceMeterCubemapPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { ResolvedView = ResolveView(); float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); float NoL = saturate(normalize(CubeCoordinates).z); //float NoL = CubeCoordinates.z > 0.0 ? 1 : 0; // See https://www.ppsloan.org/publications/StupidSH36.pdf, p.9. float Tmp= 1.0f + ScaledUVs.x * ScaledUVs.x + ScaledUVs.y * ScaledUVs.y; float dA = 4.0f / (sqrt(Tmp) * Tmp); // Not really dA, but using same variable name const float3 L = TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, CubeCoordinates, 0).rgb * ResolvedView.SkyLightColor.rgb; OutColor = float4(L * NoL* dA, dA); } uint MipIndex; uint NumMips; #ifdef USE_COMPUTE int FaceThreadGroupSize; int2 ValidDispatchCoord; RWTexture2DArray OutTextureMipColor; // All slices, i.e. faces, of a cube map mip level #endif #ifdef USE_COMPUTE [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void DownsampleCS(uint3 ThreadId : SV_DispatchThreadID) { const uint2 FaceCoord = uint2(ThreadId.x % uint(FaceThreadGroupSize), ThreadId.y); if (any(FaceCoord >= uint2(ValidDispatchCoord))) { return; } const int SelectedCubeFace = int(ThreadId.x) / FaceThreadGroupSize; float2 ScaledUVs = ((float2(FaceCoord) + 0.5f) / float2(ValidDispatchCoord)) * 2.0f - 1.0f; float4 OutColor; #else // USE_COMPUTE void DownsamplePS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; const int SelectedCubeFace = CubeFace; #endif // USE_COMPUTE float3 CubeCoordinates = GetCubemapVector(ScaledUVs, SelectedCubeFace); uint MipSize = 1u << ( NumMips - MipIndex - 1 ); float3 TangentZ = normalize( CubeCoordinates ); float3 TangentX = normalize( cross( GetCubemapVector( ScaledUVs + float2(0,1), SelectedCubeFace), TangentZ ) ); float3 TangentY = cross( TangentZ, TangentX ); const float SampleOffset = 2.0 * 2 / MipSize; float2 Offsets[] = { float2(-1, -1) * 0.7, float2( 1, -1) * 0.7, float2(-1, 1) * 0.7, float2( 1, 1) * 0.7, float2( 0, -1), float2(-1, 0), float2( 1, 0), float2( 0, 1), }; OutColor = SourceCubemapTexture.SampleLevel(SourceCubemapSampler, CubeCoordinates, 0 ); UNROLL for( uint i = 0; i < 8; i++ ) { float Weight = 0.375; float3 SampleDir = CubeCoordinates; SampleDir += TangentX * ( Offsets[i].x * SampleOffset ); SampleDir += TangentY * ( Offsets[i].y * SampleOffset ); OutColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, SampleDir, 0 ) * Weight; } OutColor *= rcp( 1.0 + 1.0 + 2.0 ); #ifdef USE_COMPUTE OutTextureMipColor[uint3(FaceCoord, SelectedCubeFace)] = OutColor; #endif } void DownsampleMaxPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; const int SelectedCubeFace = CubeFace; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, SelectedCubeFace); uint MipSize = 1u << (NumMips - MipIndex - 1); float3 TangentX = 0.0f; float3 TangentY = 0.0f; GetCubemapTangent(SelectedCubeFace, TangentX, TangentY); const float SampleOffset = 1.0f / MipSize; // Offset from the connect of 4 texels towards their center. float2 Offsets[] = { float2(-0.5,-0.5), float2( 0.5,-0.5), float2( 0.5, 0.5), float2(-0.5, 0.5), }; OutColor = 0; UNROLL for (uint i = 0; i < 4; i++) { float3 SampleDir = CubeCoordinates; SampleDir += TangentX * (Offsets[i].x * SampleOffset); SampleDir += TangentY * (Offsets[i].y * SampleOffset); OutColor = max(OutColor, SourceCubemapTexture.SampleLevel(SourceCubemapSampler, SampleDir, 0)); } } void DownsampleIntegrateCubemapIlluminancePS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { OutColor = 0; float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; const int SelectedCubeFace = CubeFace; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, SelectedCubeFace); uint MipSize = 1u << (NumMips - MipIndex - 1); float3 TangentX = 0.0f; float3 TangentY = 0.0f; GetCubemapTangent(SelectedCubeFace, TangentX, TangentY); const float SampleOffset = 1.0f / MipSize; // Offset from the connect of 4 texels towards their center. float2 Offsets[] = { float2(-0.5,-0.5), float2( 0.5,-0.5), float2( 0.5, 0.5), float2(-0.5, 0.5), }; OutColor = 0; UNROLL for (uint i = 0; i < 4; i++) { float3 SampleDir = CubeCoordinates; SampleDir += TangentX * (Offsets[i].x * SampleOffset); SampleDir += TangentY * (Offsets[i].y * SampleOffset); OutColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, SampleDir, 0); } } #if APPLY_LOWER_HEMISPHERE_COLOR_PIXELSHADER int ApplyLowerHemisphereColor; float4 LowerHemisphereSolidColor; int ApplyLowResCloudTexture; Texture2D LowResCloudTexture; SamplerState LowResCloudSampler; // This pixel shader is designed to work with RenderRealTimeReflectionHeightFogVS void ApplyLowerHemisphereColorPS( in float4 SVPos : SV_POSITION, out float4 OutLuminanceAlpha : SV_Target0) { ResolvedView = ResolveView(); float2 UV = SVPos.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); float3 N = normalize(CubeCoordinates); OutLuminanceAlpha = float4(0.0f, 0.0f, 0.0f, 1.0f); if (ApplyLowResCloudTexture > 0) { OutLuminanceAlpha = LowResCloudTexture.Sample(LowResCloudSampler, UV); } if (N.z < 0.0f && ApplyLowerHemisphereColor > 0) { float Coverage = 0.0f; Coverage = pow(saturate(LowerHemisphereSolidColor.a), 2.2); Coverage = saturate(LowerHemisphereSolidColor.a); const float Exposure = ResolvedView.RealTimeReflectionCapture ? ResolvedView.RealTimeReflectionCapturePreExposure : ResolvedView.PreExposure; OutLuminanceAlpha = float4(lerp(OutLuminanceAlpha.rgb, LowerHemisphereSolidColor.rgb * Exposure, Coverage), OutLuminanceAlpha.a * (1.0 - Coverage)); } } #endif int NumCaptureArrayMips; /** Cube map array of reflection captures. */ TextureCube ReflectionEnvironmentColorTexture; SamplerState ReflectionEnvironmentColorSampler; #if COMPUTEBRIGHTNESS_PIXELSHADER void ComputeBrightnessMain(out float4 OutColor : SV_Target0) { // Sample the 6 1x1 cube faces and average float3 AverageColor = TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(1, 0, 0), NumCaptureArrayMips - 1).rgb; AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(-1, 0, 0), NumCaptureArrayMips - 1).rgb; AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 1, 0), NumCaptureArrayMips - 1).rgb; AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, -1, 0), NumCaptureArrayMips - 1).rgb; AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 0, 1), NumCaptureArrayMips - 1).rgb; AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 0, -1), NumCaptureArrayMips - 1).rgb; OutColor = dot(AverageColor / 6, .3333f); } #endif #if COMPUTEMAXLUMINANCE_PIXELSHADER void ComputeCubeMaxLuminancePS(out float4 OutColor : SV_Target0) { // Sample the 6 1x1 cube faces and average float3 MaxLuminanceRGB = TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(1, 0, 0), NumCaptureArrayMips - 1).rgb; MaxLuminanceRGB = max(MaxLuminanceRGB, TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(-1, 0, 0), NumCaptureArrayMips - 1).rgb); MaxLuminanceRGB = max(MaxLuminanceRGB, TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3( 0, 1, 0), NumCaptureArrayMips - 1).rgb); MaxLuminanceRGB = max(MaxLuminanceRGB, TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3( 0,-1, 0), NumCaptureArrayMips - 1).rgb); MaxLuminanceRGB = max(MaxLuminanceRGB, TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3( 0, 0, 1), NumCaptureArrayMips - 1).rgb); MaxLuminanceRGB = max(MaxLuminanceRGB, TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3( 0, 0,-1), NumCaptureArrayMips - 1).rgb); float MaxLuminance = max(MaxLuminanceRGB.r, max(MaxLuminanceRGB.g, MaxLuminanceRGB.b)); OutColor = MaxLuminance; } #endif float4 SampleCubemap(float3 Coordinates, uint InMipIndex) { return TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, Coordinates, InMipIndex); } uint SourceMipIndex; float4 SampleCubemap(float3 Coordinates) { return SampleCubemap(Coordinates, SourceMipIndex); } void DownsamplePS_Mobile( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); OutColor = SampleCubemap(CubeCoordinates); } #if SHADING_PATH_MOBILE // There's a precision problem in OpenGL because the default precision in the Pixel Shader is set to half, even for constant values, use Hammersley16 instead. #define HammersleyDistribution Hammersley16 #else #define HammersleyDistribution Hammersley #endif #ifdef USE_COMPUTE int CubeFaceOffset; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void FilterCS(uint3 ThreadId : SV_DispatchThreadID) { const uint2 FaceCoord = uint2(ThreadId.x % uint(FaceThreadGroupSize), ThreadId.y); if (any(FaceCoord >= uint2(ValidDispatchCoord))) { return; } const int SelectedCubeFace = CubeFaceOffset + int(ThreadId.x) / FaceThreadGroupSize; float2 ScaledUVs = ((float2(FaceCoord) + 0.5f) / float2(ValidDispatchCoord)) * 2.0f - 1.0f; float4 OutColor; #else // USE_COMPUTE void FilterPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; const int SelectedCubeFace = CubeFace; #endif // USE_COMPUTE float3 CubeCoordinates = GetCubemapVector(ScaledUVs, SelectedCubeFace); float3 N = normalize(CubeCoordinates); float3x3 TangentToWorld = GetTangentBasis( N ); float Roughness = ComputeReflectionCaptureRoughnessFromMip( MipIndex, NumMips - 1 ); if( Roughness < 0.01 ) { OutColor = SourceCubemapTexture.SampleLevel(SourceCubemapSampler, CubeCoordinates, 0 ); // Prevent NaNs from going into the cube map OutColor = -min(-OutColor, 0); #ifdef USE_COMPUTE OutTextureMipColor[uint3(FaceCoord, SelectedCubeFace)] = OutColor; #endif return; } uint CubeSize = 1u << ( NumMips - 1 ); const float SolidAngleTexel = 4.0f * PI / float(6.0f * CubeSize * CubeSize) * 2.0f; #if 0 // Reference const uint NumSamples = 1024; #elif SHADING_PATH_MOBILE // Mobile path - a bit lower quality uint NumSamples = 0; if (Roughness <= 0.1) { NumSamples = 16; } else if (Roughness <= 0.4) { NumSamples = 32; } else { NumSamples = 64; } #else // General real time const uint NumSamples = Roughness < 0.1 ? 32 : 64; #endif float4 FilteredColor = 0; BRANCH if( Roughness > 0.99 ) { // Roughness=1, GGX is constant. Use cosine distribution instead LOOP for( uint i = 0; i < NumSamples; i++ ) { float2 E = HammersleyDistribution( i, NumSamples, 0 ); float3 L = CosineSampleHemisphere( E ).xyz; float NoL = L.z; float PDF = NoL / PI; float SolidAngleSample = 1.0 / ( NumSamples * PDF ); float Mip = 0.5 * log2( SolidAngleSample / SolidAngleTexel ); L = mul( L, TangentToWorld ); FilteredColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, L, Mip ); } OutColor = FilteredColor / NumSamples; } else { float Weight = 0; LOOP for( uint i = 0; i < NumSamples; i++ ) { float2 E = HammersleyDistribution( i, NumSamples, 0 ); // 6x6 Offset rows. Forms uniform star pattern //uint2 Index = uint2( i % 6, i / 6 ); //float2 E = ( Index + 0.5 ) / 5.8; //E.x = frac( E.x + (Index.y & 1) * (0.5 / 6.0) ); E.y *= 0.995; float3 H = ImportanceSampleGGX( E, Pow4(Roughness) ).xyz; float3 L = 2 * H.z * H - float3(0,0,1); float NoL = L.z; float NoH = H.z; if( NoL > 0 ) { //float TexelWeight = CubeTexelWeight( L ); //float SolidAngleTexel = SolidAngleAvgTexel * TexelWeight; //float PDF = D_GGX( Pow4(Roughness), NoH ) * NoH / (4 * VoH); float PDF = D_GGX( Pow4(Roughness), NoH ) * 0.25; float SolidAngleSample = 1.0 / ( NumSamples * PDF ); float Mip = 0.5 * log2( SolidAngleSample / SolidAngleTexel ); float ConeAngle = acos( 1 - SolidAngleSample / (2*PI) ); L = mul( L, TangentToWorld ); FilteredColor += SourceCubemapTexture.SampleLevel(SourceCubemapSampler, L, Mip ) * NoL; Weight += NoL; } } OutColor = FilteredColor / Weight; } // Prevent NaNs from going into the cube map OutColor = -min(-OutColor, 0); #ifdef USE_COMPUTE OutTextureMipColor[uint3(FaceCoord, SelectedCubeFace)] = OutColor; #endif } float4 CoefficientMask0; float4 CoefficientMask1; float CoefficientMask2; int NumSamples; void DiffuseIrradianceCopyPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float2 UV = SvPosition.xy * SvPositionToUVScale; float2 ScaledUVs = UV * 2 - 1; float3 CubeCoordinates = normalize(GetCubemapVector(ScaledUVs, CubeFace)); float SquaredUVs = 1 + dot(ScaledUVs, ScaledUVs); // Dividing by NumSamples here to keep the sum in the range of fp16, once we get down to the 1x1 mip float TexelWeight = 4 / (sqrt(SquaredUVs) * SquaredUVs); FThreeBandSHVector SHCoefficients = SHBasisFunction3(CubeCoordinates); float CurrentSHCoefficient = dot(SHCoefficients.V0, CoefficientMask0) + dot(SHCoefficients.V1, CoefficientMask1) + SHCoefficients.V2 * CoefficientMask2; float3 TexelLighting = SampleCubemap(CubeCoordinates).rgb; OutColor = float4(TexelLighting * CurrentSHCoefficient * TexelWeight, TexelWeight); } float4 Sample01; float4 Sample23; void DiffuseIrradianceAccumulatePS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float4 AccumulatedValue = 0; float2 UV = SvPosition.xy * SvPositionToUVScale; { float2 ScaledUVs = saturate(UV + Sample01.xy) * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); AccumulatedValue += SampleCubemap(CubeCoordinates); } { float2 ScaledUVs = saturate(UV + Sample01.zw) * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); AccumulatedValue += SampleCubemap(CubeCoordinates); } { float2 ScaledUVs = saturate(UV + Sample23.xy) * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); AccumulatedValue += SampleCubemap(CubeCoordinates); } { float2 ScaledUVs = saturate(UV + Sample23.zw) * 2 - 1; float3 CubeCoordinates = GetCubemapVector(ScaledUVs, CubeFace); AccumulatedValue += SampleCubemap(CubeCoordinates); } OutColor = float4(AccumulatedValue.rgb / 4.0f, AccumulatedValue.a / 4.0f); } void AccumulateCubeFacesPS( out float4 OutColor : SV_Target0 ) { float4 AccumulatedValue; AccumulatedValue = TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(1, 0, 0), SourceMipIndex); AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(-1, 0, 0), SourceMipIndex); AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, 1, 0), SourceMipIndex); AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, -1, 0), SourceMipIndex); AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, 0, 1), SourceMipIndex); AccumulatedValue += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(0, 0, -1), SourceMipIndex); OutColor = float4(4 * PI * AccumulatedValue.rgb / ( max(AccumulatedValue.a, .00001f)), 0); } #ifdef SHADER_DIFFUSE_TO_SH #include "SHCommon.ush" #include "MonteCarlo.ush" RWStructuredBuffer OutIrradianceEnvMapSH; float UniformSampleSolidAngle; #define THREADGROUP_SIZE (THREADGROUP_SIZE_X * THREADGROUP_SIZE_Y) groupshared FThreeBandSHVectorRGB IrradianceSHShared[THREADGROUP_SIZE]; FThreeBandSHVectorRGB SampleSHRGB(in float3 SampleDirection, in float weight, in float MipLevel) { const float3 SampleColor = SourceCubemapTexture.SampleLevel(SourceCubemapSampler, SampleDirection, MipLevel).rgb; FThreeBandSHVector Sh3Vector = SHBasisFunction3(SampleDirection); FThreeBandSHVectorRGB Result; Result.R = MulSH3(Sh3Vector, SampleColor.r * weight); Result.G = MulSH3(Sh3Vector, SampleColor.g * weight); Result.B = MulSH3(Sh3Vector, SampleColor.b * weight); return Result; } [numthreads(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y, 1)] void ComputeSkyEnvMapDiffuseIrradianceCS(uint3 ThreadId : SV_DispatchThreadID) { const uint LinearIndex = THREADGROUP_SIZE_X * ThreadId.y + ThreadId.x; #if 1 // For a 128x128 cubemap, sampling mip level 2 with only 8x8 samples matches closely the super sampled version. const float3 SampleDirection = UniformSampleSphere((float2(ThreadId.xy)+0.5f) / float2(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y)).xyz; IrradianceSHShared[LinearIndex] = SampleSHRGB(SampleDirection, UniformSampleSolidAngle, MipIndex); #else FThreeBandSHVectorRGB IrradianceSHAcc; IrradianceSHAcc.R.V0 = IrradianceSHAcc.R.V1 = 0.0f; IrradianceSHAcc.R.V2 = 0.0f; IrradianceSHAcc.G.V0 = IrradianceSHAcc.G.V1 = 0.0f; IrradianceSHAcc.G.V2 = 0.0f; IrradianceSHAcc.B.V0 = IrradianceSHAcc.B.V1 = 0.0f; IrradianceSHAcc.B.V2 = 0.0f; // Uniform super sampling. // For a 128x128 cubemap, sampling mip level 0 with only 4x4 samples matches closely reference. const uint MipLevel = 0; const float SuperSampleAxisCount = 4.0f; const float SuperSampleCount = SuperSampleAxisCount * SuperSampleAxisCount; const float SuperSampleUniformSampleSolidAngle = UniformSampleSolidAngle / SuperSampleCount; for (float U = 0.0f; U < 1.0f; U += 1.0f / SuperSampleAxisCount) { for (float V = 0.0f; V < 1.0f; V += 1.0f / SuperSampleAxisCount) { const float3 SampleDirection = UniformSampleSphere((float2(ThreadId.xy) + float2(U,V)) / float2(THREADGROUP_SIZE_X, THREADGROUP_SIZE_Y)).xyz; IrradianceSHAcc = AddSH(IrradianceSHAcc, SampleSHRGB(SampleDirection, SuperSampleUniformSampleSolidAngle, MipLevel)); } } IrradianceSHShared[LinearIndex] = IrradianceSHAcc; #endif #if THREADGROUP_SIZE != 64 #error That is the only reduction supported today #endif // Wait for all group threads to be done GroupMemoryBarrierWithGroupSync(); if (LinearIndex < 32) { IrradianceSHShared[LinearIndex] = AddSH(IrradianceSHShared[LinearIndex], IrradianceSHShared[LinearIndex + 32]); } GroupMemoryBarrierWithGroupSync(); if (LinearIndex < 16) { IrradianceSHShared[LinearIndex] = AddSH(IrradianceSHShared[LinearIndex], IrradianceSHShared[LinearIndex + 16]); } GroupMemoryBarrierWithGroupSync(); // The smallest wave size is 16 on Intel hardware. So now we can do simple math operations without group sync. if (LinearIndex < 8) { IrradianceSHShared[LinearIndex] = AddSH(IrradianceSHShared[LinearIndex], IrradianceSHShared[LinearIndex + 8]); } if (LinearIndex < 4) { IrradianceSHShared[LinearIndex] = AddSH(IrradianceSHShared[LinearIndex], IrradianceSHShared[LinearIndex + 4]); } if (LinearIndex < 2) { IrradianceSHShared[LinearIndex] = AddSH(IrradianceSHShared[LinearIndex], IrradianceSHShared[LinearIndex + 2]); } if (LinearIndex < 1) { FThreeBandSHVectorRGB SkyIrradiance = AddSH(IrradianceSHShared[LinearIndex], IrradianceSHShared[LinearIndex + 1]); // Pack the SH coefficients in a way that makes applying the lighting use the least shader instructions // This has the diffuse convolution coefficients baked in. See "Stupid Spherical Harmonics (SH) Tricks". // Also see UpdateSkyIrradianceGpuBuffer. const float SqrtPI = sqrt(PI); const float Coefficient0 = 1.0f / (2.0f * SqrtPI); const float Coefficient1 = sqrt(3.0f) / (3.0f * SqrtPI); const float Coefficient2 = sqrt(15.0f) / (8.0f * SqrtPI); const float Coefficient3 = sqrt(5.0f) / (16.0f * SqrtPI); const float Coefficient4 = 0.5f * Coefficient2; OutIrradianceEnvMapSH[0].x = -Coefficient1 * SkyIrradiance.R.V0[3]; OutIrradianceEnvMapSH[0].y = -Coefficient1 * SkyIrradiance.R.V0[1]; OutIrradianceEnvMapSH[0].z = Coefficient1 * SkyIrradiance.R.V0[2]; OutIrradianceEnvMapSH[0].w = Coefficient0 * SkyIrradiance.R.V0[0] - Coefficient3 * SkyIrradiance.R.V1[2];//[6]; OutIrradianceEnvMapSH[1].x = -Coefficient1 * SkyIrradiance.G.V0[3]; OutIrradianceEnvMapSH[1].y = -Coefficient1 * SkyIrradiance.G.V0[1]; OutIrradianceEnvMapSH[1].z = Coefficient1 * SkyIrradiance.G.V0[2]; OutIrradianceEnvMapSH[1].w = Coefficient0 * SkyIrradiance.G.V0[0] - Coefficient3 * SkyIrradiance.G.V1[2];//[6]; OutIrradianceEnvMapSH[2].x = -Coefficient1 * SkyIrradiance.B.V0[3]; OutIrradianceEnvMapSH[2].y = -Coefficient1 * SkyIrradiance.B.V0[1]; OutIrradianceEnvMapSH[2].z = Coefficient1 * SkyIrradiance.B.V0[2]; OutIrradianceEnvMapSH[2].w = Coefficient0 * SkyIrradiance.B.V0[0] - Coefficient3 * SkyIrradiance.B.V1[2];//[6]; OutIrradianceEnvMapSH[3].x = Coefficient2 * SkyIrradiance.R.V1[0];//[4]; OutIrradianceEnvMapSH[3].y = -Coefficient2 * SkyIrradiance.R.V1[1];//V[5]; OutIrradianceEnvMapSH[3].z = 3.0f * Coefficient3 * SkyIrradiance.R.V1[2];//[6]; OutIrradianceEnvMapSH[3].w = -Coefficient2 * SkyIrradiance.R.V1[3];//[7]; OutIrradianceEnvMapSH[4].x = Coefficient2 * SkyIrradiance.G.V1[0];//[4]; OutIrradianceEnvMapSH[4].y = -Coefficient2 * SkyIrradiance.G.V1[1];//[5]; OutIrradianceEnvMapSH[4].z = 3.0f * Coefficient3 * SkyIrradiance.G.V1[2];//[6]; OutIrradianceEnvMapSH[4].w = -Coefficient2 * SkyIrradiance.G.V1[3];//[7]; OutIrradianceEnvMapSH[5].x = Coefficient2 * SkyIrradiance.B.V1[0];//[4]; OutIrradianceEnvMapSH[5].y = -Coefficient2 * SkyIrradiance.B.V1[1];//[5]; OutIrradianceEnvMapSH[5].z = 3.0f * Coefficient3 * SkyIrradiance.B.V1[2];//[6]; OutIrradianceEnvMapSH[5].w = -Coefficient2 * SkyIrradiance.B.V1[3];//[7]; OutIrradianceEnvMapSH[6].x = Coefficient4 * SkyIrradiance.R.V2;//[8]; OutIrradianceEnvMapSH[6].y = Coefficient4 * SkyIrradiance.G.V2;//[8]; OutIrradianceEnvMapSH[6].z = Coefficient4 * SkyIrradiance.B.V2;//[8]; OutIrradianceEnvMapSH[6].w = 1.0f; // We inverse the weights applied onto the non directional SH component to recover the average sky irradiance. const float3 DummyVector = float3(0.0, 0.0, 1.0); const float SHBasisL0Weight = SHBasisFunction3(DummyVector).V0.x; const float InvSHBand0Weights = 1.0f / (SHBasisL0Weight * 4.0f * PI); const float SkyAverageIrradianceR = SkyIrradiance.R.V0.x * InvSHBand0Weights; const float SkyAverageIrradianceG = SkyIrradiance.G.V0.x * InvSHBand0Weights; const float SkyAverageIrradianceB = SkyIrradiance.B.V0.x * InvSHBand0Weights; const float SkyAverageIrradiance = dot(float3(SkyAverageIrradianceR, SkyAverageIrradianceG, SkyAverageIrradianceB), .3333f); OutIrradianceEnvMapSH[7].x = SkyAverageIrradiance; OutIrradianceEnvMapSH[7].y = SkyAverageIrradiance; OutIrradianceEnvMapSH[7].z = SkyAverageIrradiance; OutIrradianceEnvMapSH[7].w = SkyAverageIrradiance; } } #endif #ifdef REALTIME_REFLECTION_HEIGHT_FOG #include "HeightFogCommon.ush" float3 SkyLightPosition; #if PERMUTATION_DEPTHTEXTURE Texture2D DepthTexture; #endif void RenderRealTimeReflectionHeightFogVS( in uint VertexId : SV_VertexID, out float4 Position : SV_POSITION, out float3 OutScreenVector : TEXCOORD0) { float2 UV = -1.0f; UV = VertexId == 1 ? float2(-1.0f, 3.0f) : UV; UV = VertexId == 2 ? float2(3.0f, -1.0f) : UV; Position = float4(UV, 0.5f, 1.0f); OutScreenVector = ScreenVectorFromScreenRect(float4(Position.xy, 1, 0)); } void RenderRealTimeReflectionHeightFogPS( in float4 SVPos : SV_POSITION, in float3 ScreenVector : TEXCOORD0, out float4 OutLuminance : SV_Target0 ) { ResolvedView = ResolveView(); #if PERMUTATION_DEPTHTEXTURE float DeviceZ = DepthTexture.Load(int3(SVPos.xy, 0)).x; #else float DeviceZ = FarDepthValue; #endif #if HAS_INVERTED_Z_BUFFER DeviceZ = max(1.0e-10, DeviceZ); // For ConvertFromDeviceZ to return a valid value (non inf). #endif float SceneDepth = ConvertFromDeviceZ(DeviceZ); float3 WorldPositionRelativeToCamera = ScreenVector.xyz * SceneDepth; // Evaluate the height fog the actual sky light position to match the capture position. const float ExcludeDistance = 0; const float EyeIndex = 0; const bool bOverrideOrigin = true; const float3 OverrideRayStartRelativeToOrigin = SkyLightPosition; const float OverrideRayLength = length(WorldPositionRelativeToCamera); const float3 OverrideRayDir = WorldPositionRelativeToCamera / max(0.000001, OverrideRayLength); float4 HeightFogInscatteringAndOpacity = GetExponentialHeightFog(WorldPositionRelativeToCamera, ExcludeDistance, EyeIndex, PrimaryView, bOverrideOrigin, OverrideRayStartRelativeToOrigin, OverrideRayDir, OverrideRayLength); const float OutputPreExposure = (ResolvedView.RealTimeReflectionCapture ? ResolvedView.RealTimeReflectionCapturePreExposure : ResolvedView.PreExposure); HeightFogInscatteringAndOpacity.rgb *= OutputPreExposure; OutLuminance = HeightFogInscatteringAndOpacity; } #endif #ifdef VISUALIZE_ILLUMINANCE_METER #include "/Engine/Private/ShaderPrint.ush" #include "/Engine/Private/SkyAtmosphereCommon.ush" int CubeMipIndexToSampleIlluminance; float3 SkyLightCaptureWorlPos; TextureCube SkyLightCubeTexture; SamplerState SkyLightCubeSampler; float3 GetIlluminance() { float4 IlluminanceAcc = 0; IlluminanceAcc += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3(-1, 0, 0), CubeMipIndexToSampleIlluminance); IlluminanceAcc += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3( 1, 0, 0), CubeMipIndexToSampleIlluminance); IlluminanceAcc += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3( 0,-1, 0), CubeMipIndexToSampleIlluminance); IlluminanceAcc += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3( 0, 1, 0), CubeMipIndexToSampleIlluminance); IlluminanceAcc += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3( 0, 0,-1), CubeMipIndexToSampleIlluminance); IlluminanceAcc += TextureCubeSampleLevel(SourceCubemapTexture, SourceCubemapSampler, float3( 0, 0, 1), CubeMipIndexToSampleIlluminance); // See https://www.ppsloan.org/publications/StupidSH36.pdf, p.9. IlluminanceAcc.rgb *= 4.0 * PI / IlluminanceAcc.a; return IlluminanceAcc.rgb; } float4 SampleCubeAsLatLon(TextureCube CubeTexture, SamplerState CubeSampler, float2 UV) { float HAngle = UV.x * 2.0 * PI; float VAngle = UV.y * PI - PI / 2; float CosVAngle = cos(VAngle); float3 DirFromLatLong = float3(cos(HAngle) * CosVAngle, sin(HAngle) * CosVAngle, -sin(VAngle)); float4 DebugVis = TextureCubeSampleLevel(CubeTexture, CubeSampler, DirFromLatLong, 0); return DebugVis; } float4 SampleCubeAsSphere(TextureCube CubeTexture, SamplerState CubeSampler, float2 UV) { float2 Dir2D = (UV - 0.5) * 2.0; float4 DebugVis = float4(0.0, 0.0, 0.0, 1.0); if (length(Dir2D) <= 1.0) { float3 N = 0; const float3 RayO = float3(UV, -1); const float3 RayD = float3(0.0f, 0.0f, 1.0f); const float4 Sphere = float4(0.5f, 0.5f, 0.f, 0.5); const float2 Hit = RayIntersectSphere(RayO, RayD, Sphere); if (Hit.x >= 0) { const float3 P = RayO + RayD * Hit.x; N = normalize(P - Sphere.xyz); N *= float3(1.0, -1.0, 1.0); } DebugVis = TextureCubeSampleLevel(CubeTexture, CubeSampler, mul(N, (float3x3)ResolvedView.ViewToTranslatedWorld), 0); } return DebugVis; } void VisualizeIlluminanceMeterPS( float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0) { ResolvedView = ResolveView(); const uint2 PixelPosDynRes = uint2(float2(SVPos.xy) * ResolvedView.ViewResolutionFraction); float2 BufferUV = SvPositionToBufferUV(float4(PixelPosDynRes, SVPos.zw)); OutColor = 0.0f; const uint2 IlluMapPreviewOrigin = uint2(100, 110); const uint2 IlluMapPreviewSize = uint2(500, 250); const uint2 SkyMapPreviewOrigin = uint2(100, 370); const uint2 SkyMapPreviewSize = uint2(500, 250); const uint2 SphereIlluMapPreviewOrigin = IlluMapPreviewOrigin + uint2(IlluMapPreviewSize.x + 10, 0); const uint2 SphereIlluMapPreviewSize = uint2(250, 250); const uint2 SphereSkyMapPreviewOrigin = SkyMapPreviewOrigin + uint2(SkyMapPreviewSize.x + 10, 0); const uint2 SphereSkyMapPreviewSize = uint2(250, 250); if (all(uint2(SVPos.xy) == uint2(1, 1))) { float3 SkyLightIlluminance = GetIlluminance(); #if PROJECT_SUPPORT_SKY_ATMOSPHERE const float3 PlanetCenterToTranslatedWorldPos = (DFFastAddDemote(SkyLightCaptureWorlPos, ResolvedView.PreViewTranslation) - ResolvedView.SkyPlanetTranslatedWorldCenterAndViewHeight.xyz) * CM_TO_SKY_UNIT; // GetAtmosphereTransmittance does a shadow test against the virtual planet. const float3 TransmittanceToLight = GetAtmosphereTransmittance( PlanetCenterToTranslatedWorldPos, ResolvedView.AtmosphereLightDirection[0].xyz, ResolvedView.SkyAtmosphereBottomRadiusKm, ResolvedView.SkyAtmosphereTopRadiusKm, View.TransmittanceLutTexture, View.TransmittanceLutTextureSampler); const float3 SunLightIlluminance = ResolvedView.AtmosphereLightIlluminanceOuterSpace[0].rgb * TransmittanceToLight; #else const float3 SunLightIlluminance = ResolvedView.AtmosphereLightIlluminanceOuterSpace[0].rgb; #endif float SkyIlluminance = max3(SkyLightIlluminance.r, SkyLightIlluminance.g, SkyLightIlluminance.b); float SunIlluminance = max3(SunLightIlluminance.r, SunLightIlluminance.g, SunLightIlluminance.b); //float SkyIlluminance = dot(SkyLightIlluminance.rgb, (1.0/3.0).xxx); //float SunIlluminance = dot(SunLightIlluminance.rgb, (1.0/3.0).xxx); float TotalIlluminance = SkyIlluminance + SunIlluminance; FShaderPrintContext Context = InitShaderPrintContext(true, uint2(110, 70)); Print(Context, TEXT("Sky light illuminance = "), InitFontColor(0.8, 0.8, 0.8)); Print(Context, SkyLightIlluminance.r, InitFontColor(1.0, 0.8, 0.8)); Print(Context, SkyLightIlluminance.g, InitFontColor(0.8, 1.0, 0.8)); Print(Context, SkyLightIlluminance.b, InitFontColor(0.8, 0.8, 1.0)); Print(Context, 100.0 * SkyIlluminance / TotalIlluminance, InitFontColor(0.7, 0.7, 0.7)); Print(Context, TEXT("%"), InitFontColor(0.7, 0.7, 0.7)); Newline(Context); Print(Context, TEXT("Sun light illuminance = "), InitFontColor(0.8, 0.8, 0.8)); Print(Context, SunLightIlluminance.r, InitFontColor(1.0, 0.8, 0.8)); Print(Context, SunLightIlluminance.g, InitFontColor(0.8, 1.0, 0.8)); Print(Context, SunLightIlluminance.b, InitFontColor(0.8, 0.8, 1.0)); Print(Context, 100.0 * SunIlluminance / TotalIlluminance, InitFontColor(0.7, 0.7, 0.7)); Print(Context, TEXT("%"), InitFontColor(0.7, 0.7, 0.7)); Newline(Context); Print(Context, TEXT("Total illuminance = "), InitFontColor(0.8, 0.8, 0.8)); Print(Context, SkyLightIlluminance.r + SunLightIlluminance.r, InitFontColor(1.0, 0.8, 0.8)); Print(Context, SkyLightIlluminance.g + SunLightIlluminance.g, InitFontColor(0.8, 1.0, 0.8)); Print(Context, SkyLightIlluminance.b + SunLightIlluminance.b, InitFontColor(0.8, 0.8, 1.0)); Print(Context, 100.0 * (SkyIlluminance + SunIlluminance) / TotalIlluminance, InitFontColor(0.7, 0.7, 0.7)); Print(Context, TEXT("%"), InitFontColor(0.7, 0.7, 0.7)); Context = InitShaderPrintContext(true, SphereIlluMapPreviewOrigin + uint2(SphereIlluMapPreviewSize.x + 10, SphereIlluMapPreviewSize.y / 2)); Print(Context, TEXT("Illuminance meter view"), InitFontColor(0.9, 0.9, 0.9)); Context = InitShaderPrintContext(true, SphereSkyMapPreviewOrigin + uint2(SphereSkyMapPreviewSize.x + 10, SphereSkyMapPreviewSize.y / 2)); Print(Context, TEXT("Sky light cube map view"), InitFontColor(0.9, 0.9, 0.9)); } else if (all(uint2(SVPos.xy) >= IlluMapPreviewOrigin) && all(uint2(SVPos.xy) <= (IlluMapPreviewOrigin + IlluMapPreviewSize))) { float2 UV = (SVPos.xy - float2(IlluMapPreviewOrigin)) / float2(IlluMapPreviewSize); float4 DebugVis = SampleCubeAsLatLon(SourceCubemapTexture, SourceCubemapSampler, UV); DebugVis.rgb *= 1.0 / DebugVis.a; // This is a way to remove the texel dArea factor to show what luminance is integrated as a unction of NoL. OutColor = float4(DebugVis.rgb * ResolvedView.PreExposure, 0.0); } else if (all(uint2(SVPos.xy) >= SkyMapPreviewOrigin) && all(uint2(SVPos.xy) <= (SkyMapPreviewOrigin + SkyMapPreviewSize))) { float2 UV = (SVPos.xy - float2(SkyMapPreviewOrigin)) / float2(SkyMapPreviewSize); float4 DebugVis = SampleCubeAsLatLon(SkyLightCubeTexture, SkyLightCubeSampler, UV); DebugVis.rgb *= ResolvedView.SkyLightColor.rgb; OutColor = float4(DebugVis.rgb * ResolvedView.PreExposure, 0.0); } else if (all(uint2(SVPos.xy) >= SphereIlluMapPreviewOrigin) && all(uint2(SVPos.xy) <= (SphereIlluMapPreviewOrigin + SphereIlluMapPreviewSize))) { float2 UV = (SVPos.xy - float2(SphereIlluMapPreviewOrigin)) / float2(SphereIlluMapPreviewSize); float4 DebugVis = SampleCubeAsSphere(SourceCubemapTexture, SourceCubemapSampler, UV); DebugVis.rgb *= 1.0 / DebugVis.a; // This is a way to remove the texel dArea factor to show what luminance is integrated as a unction of NoL. OutColor = float4(DebugVis.rgb * ResolvedView.PreExposure, 0.0); } else if (all(uint2(SVPos.xy) >= SphereSkyMapPreviewOrigin) && all(uint2(SVPos.xy) <= (SphereSkyMapPreviewOrigin + SphereSkyMapPreviewSize))) { float2 UV = (SVPos.xy - float2(SphereSkyMapPreviewOrigin)) / float2(SphereSkyMapPreviewSize); float4 DebugVis = SampleCubeAsSphere(SkyLightCubeTexture, SkyLightCubeSampler, UV); DebugVis.rgb *= ResolvedView.SkyLightColor.rgb; OutColor = float4(DebugVis.rgb * ResolvedView.PreExposure, 0.0); } else { clip(-1.0); } // TODO print sky as a sphere } #endif // DEBUG_ILLUMINANCE_METER