// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= SkyAtmosphere.usf: Sky and atmosphere rendering functions. =============================================================================*/ // Change this to force recompilation of all volumetric cloud material shaders #pragma message("UESHADERMETADATA_VERSION 849A6B91-7442-4D77-AA37-77C896193CD5") #include "Common.ush" #include "SceneTexturesCommon.ush" #include "/Engine/Shared/EnvironmentComponentsFlags.h" #ifndef COLORED_TRANSMITTANCE_ENABLED // Never used, UE4 does not supports dual blending #define COLORED_TRANSMITTANCE_ENABLED 0 #endif #ifndef MULTISCATTERING_APPROX_SAMPLING_ENABLED #define MULTISCATTERING_APPROX_SAMPLING_ENABLED 0 #endif #ifndef HIGHQUALITY_MULTISCATTERING_APPROX_ENABLED #define HIGHQUALITY_MULTISCATTERING_APPROX_ENABLED 0 #endif #ifndef FASTSKY_ENABLED #define FASTSKY_ENABLED 0 #endif #ifndef FASTAERIALPERSPECTIVE_ENABLED #define FASTAERIALPERSPECTIVE_ENABLED 0 #endif #ifndef SOURCE_DISK_ENABLED #define SOURCE_DISK_ENABLED 0 #endif #ifndef PER_PIXEL_NOISE #define PER_PIXEL_NOISE 0 #endif #ifndef SECOND_ATMOSPHERE_LIGHT_ENABLED #define SECOND_ATMOSPHERE_LIGHT_ENABLED 0 #endif #ifndef RENDERSKY_ENABLED #define RENDERSKY_ENABLED 0 #endif #ifndef TRANSMITTANCE_PASS #define TRANSMITTANCE_PASS 0 #endif #ifndef MULTISCATT_PASS #define MULTISCATT_PASS 0 #endif #ifndef SKYLIGHT_PASS #define SKYLIGHT_PASS 0 #endif #ifndef SKYVIEWLUT_PASS #define SKYVIEWLUT_PASS 0 #endif #ifndef SAMPLE_OPAQUE_SHADOW #define SAMPLE_OPAQUE_SHADOW 0 #endif #ifndef SAMPLE_CLOUD_SHADOW #define SAMPLE_CLOUD_SHADOW 0 #endif #ifndef SAMPLE_CLOUD_SKYAO #define SAMPLE_CLOUD_SKYAO 0 #endif #ifndef SAMPLE_ATMOSPHERE_ON_CLOUDS #define SAMPLE_ATMOSPHERE_ON_CLOUDS 0 #endif #ifndef VIRTUAL_SHADOW_MAP #define VIRTUAL_SHADOW_MAP 0 #endif #ifndef SEPARATE_MIE_RAYLEIGH_SCATTERING #define SEPARATE_MIE_RAYLEIGH_SCATTERING 0 #endif #if SAMPLE_OPAQUE_SHADOW #define DYNAMICALLY_SHADOWED 1 #define TREAT_MAXDEPTH_UNSHADOWED 1 #define SHADOW_QUALITY 2 #define NO_TRANSLUCENCY_AVAILABLE #include "ShadowProjectionCommon.ush" #include "ShadowFilteringCommon.ush" #define VOLUME_SHADOW_SAMPLING_INPUT 0 #include "VolumeLightingCommonSampling.ush" #undef VOLUME_SHADOW_SAMPLING_INPUT #if SECOND_ATMOSPHERE_LIGHT_ENABLED #define VOLUME_SHADOW_SAMPLING_INPUT 1 #include "VolumeLightingCommonSampling.ush" #undef VOLUME_SHADOW_SAMPLING_INPUT #endif #if VIRTUAL_SHADOW_MAP #include "VirtualShadowMaps/VirtualShadowMapProjectionCommon.ush" #endif #endif // SAMPLE_OPAQUE_SHADOW #if (SAMPLE_CLOUD_SHADOW || SAMPLE_CLOUD_SKYAO) #include "VolumetricCloudCommon.ush" #endif #include "SkyAtmosphereCommon.ush" #if SAMPLE_ATMOSPHERE_ON_CLOUDS Texture2D VolumetricCloudDepthTexture; Texture2D InputCloudLuminanceTransmittanceTexture; #endif #if MSAA_SAMPLE_COUNT > 1 Texture2DMS MSAADepthTexture; #endif // View data is not available for passes running once per scene (and not once per view). #define VIEWDATA_AVAILABLE (TRANSMITTANCE_PASS!=1 && MULTISCATT_PASS!=1 && SKYLIGHT_PASS!=1) // FASTSKY mapping is done based on Light0 as main light #define FASTSKY_LIGHT_INDEX 0 // Only available for shaders ran per view, so this constant should not be accessed in common code such as IntegrateSingleScatteredLuminance for instance. float AerialPerspectiveStartDepthKm; // Propagate alpha with (View.RenderingReflectionCaptureMask == 0.0f) guarantee uint bPropagateAlphaNonReflection; // - RayOrigin: ray origin // - RayDir: normalized ray direction // - SphereCenter: sphere center // - SphereRadius: sphere radius // - Returns distance from RayOrigin to closest intersecion with sphere, // or -1.0 if no intersection. float RaySphereIntersectNearest(float3 RayOrigin, float3 RayDir, float3 SphereCenter, float SphereRadius) { float2 Sol = RayIntersectSphere(RayOrigin, RayDir, float4(SphereCenter, SphereRadius)); float Sol0 = Sol.x; float Sol1 = Sol.y; if (Sol0 < 0.0f && Sol1 < 0.0f) { return -1.0f; } if (Sol0 < 0.0f) { return max(0.0f, Sol1); } else if (Sol1 < 0.0f) { return max(0.0f, Sol0); } return max(0.0f, min(Sol0, Sol1)); } float4 GetScreenTranslatedWorldPos(float4 SVPos, float DeviceZ) { #if HAS_INVERTED_Z_BUFFER DeviceZ = max(0.000000000001, DeviceZ); // TODO: investigate why SvPositionToWorld returns bad values when DeviceZ is far=0 when using inverted z #endif return float4(SvPositionToTranslatedWorld(float4(SVPos.xy, DeviceZ, 1.0)), 1.0); } bool MoveToTopAtmosphere(inout float3 WorldPos, in float3 WorldDir, in float AtmosphereTopRadius) { float ViewHeight = length(WorldPos); if (ViewHeight > AtmosphereTopRadius) { float TTop = RaySphereIntersectNearest(WorldPos, WorldDir, float3(0.0f, 0.0f, 0.0f), AtmosphereTopRadius); if (TTop >= 0.0f) { float3 UpVector = WorldPos / ViewHeight; float3 UpOffset = UpVector * -PLANET_RADIUS_OFFSET; WorldPos = WorldPos + WorldDir * TTop + UpOffset; } else { // Ray is not intersecting the atmosphere return false; } } return true; // ok to start tracing } //////////////////////////////////////////////////////////// // LUT functions //////////////////////////////////////////////////////////// // Transmittance LUT function parameterisation from Bruneton 2017 https://github.com/ebruneton/precomputed_atmospheric_scattering // uv in [0,1] // ViewZenithCosAngle in [-1,1] // ViewHeight in [bottomRAdius, topRadius] void UvToLutTransmittanceParams(out float ViewHeight, out float ViewZenithCosAngle, in float2 UV) { //UV = FromSubUvsToUnit(UV, SkyAtmosphere.TransmittanceLutSizeAndInvSize); // No real impact so off fromTransmittanceLutUVs(ViewHeight, ViewZenithCosAngle, Atmosphere.BottomRadiusKm, Atmosphere.TopRadiusKm, UV); } void LutTransmittanceParamsToUv(in float ViewHeight, in float ViewZenithCosAngle, out float2 UV) { getTransmittanceLutUvs(ViewHeight, ViewZenithCosAngle, Atmosphere.BottomRadiusKm, Atmosphere.TopRadiusKm, UV); } // SkyViewLut is a new texture used for fast sky rendering. // It is low resolution of the sky rendering around the camera, // basically a lat/long parameterisation with more texel close to the horizon for more accuracy during sun set. void UvToSkyViewLutParams(out float3 ViewDir, in float ViewHeight, in float2 UV) { // Constrain uvs to valid sub texel range (avoid zenith derivative issue making LUT usage visible) UV = FromSubUvsToUnit(UV, SkyAtmosphere.SkyViewLutSizeAndInvSize); float Vhorizon = sqrt(ViewHeight * ViewHeight - Atmosphere.BottomRadiusKm * Atmosphere.BottomRadiusKm); float CosBeta = Vhorizon / ViewHeight; // cos of zenith angle from horizon to zeniht float Beta = acosFast4(CosBeta); float ZenithHorizonAngle = PI - Beta; float ViewZenithAngle; if (UV.y < 0.5f) { float Coord = 2.0f * UV.y; Coord = 1.0f - Coord; Coord *= Coord; Coord = 1.0f - Coord; ViewZenithAngle = ZenithHorizonAngle * Coord; } else { float Coord = UV.y * 2.0f - 1.0f; Coord *= Coord; ViewZenithAngle = ZenithHorizonAngle + Beta * Coord; } float CosViewZenithAngle = cos(ViewZenithAngle); float SinViewZenithAngle = sqrt(1.0 - CosViewZenithAngle * CosViewZenithAngle) * (ViewZenithAngle > 0.0f ? 1.0f : -1.0f); // Equivalent to sin(ViewZenithAngle) float LongitudeViewCosAngle = UV.x * 2.0f * PI; // Make sure those values are in range as it could disrupt other math done later such as sqrt(1.0-c*c) float CosLongitudeViewCosAngle = cos(LongitudeViewCosAngle); float SinLongitudeViewCosAngle = sqrt(1.0 - CosLongitudeViewCosAngle * CosLongitudeViewCosAngle) * (LongitudeViewCosAngle <= PI ? 1.0f : -1.0f); // Equivalent to sin(LongitudeViewCosAngle) ViewDir = float3( SinViewZenithAngle * CosLongitudeViewCosAngle, SinViewZenithAngle * SinLongitudeViewCosAngle, CosViewZenithAngle ); } //////////////////////////////////////////////////////////// // Utilities //////////////////////////////////////////////////////////// #if VIEWDATA_AVAILABLE // Exposure used for regular views by the FastSky and AP LUTs. #define ViewPreExposure View.PreExposure #define ViewOneOverPreExposure View.OneOverPreExposure // When rendering a real time reflection capture (sky envmap) whe use a different output exposure #define OutputPreExposure (View.RealTimeReflectionCapture ? View.RealTimeReflectionCapturePreExposure : View.PreExposure) #else #define ViewPreExposure 1.0f #define ViewOneOverPreExposure 1.0f #define OutputPreExposure 1.0f #endif //////////////////////////////////////////////////////////// // Real time reflection capture overriden parameters //////////////////////////////////////////////////////////// #if PERMUTATION_REALTIME_REFLECTION_LUT #undef OutputPreExposure #define OutputPreExposure View.PreExposure float3x3 GetSkyViewLutReferentialParameter() { return GetSkyViewLutReferential(SkyAtmosphereRealTimeReflectionLUTParameters.SkyViewLutReferential); } float3 GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter() { return SkyAtmosphereRealTimeReflectionLUTParameters.SkyPlanetTranslatedWorldCenterAndViewHeight.xyz; } float3 GetSkyCameraTranslatedWorldOriginParameter() { return SkyAtmosphereRealTimeReflectionLUTParameters.SkyCameraTranslatedWorldOrigin; } #else // PERMUTATION_REALTIME_REFLECTION_LUT float3x3 GetSkyViewLutReferentialParameter() { return GetSkyViewLutReferential(View.SkyViewLutReferential); } float3 GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter() { return View.SkyPlanetTranslatedWorldCenterAndViewHeight.xyz; } float3 GetSkyCameraTranslatedWorldOriginParameter() { return View.SkyCameraTranslatedWorldOrigin; } #endif // PERMUTATION_REALTIME_REFLECTION_LUT // This is the world position of the camera. It is also force to be at the top of the virutal planet surface. // This is to always see the sky even when the camera is buried into the virtual planet. float3 GetCameraTranslatedWorldPos() { return GetSkyCameraTranslatedWorldOriginParameter(); } // This is the camera position relative to the virtual planet center. // This is convenient because for all the math in this file using world position relative to the virtual planet center. float3 GetTranslatedCameraPlanetPos() { return (GetCameraTranslatedWorldPos() - GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter()) * CM_TO_SKY_UNIT; } SamplerState TransmittanceLutTextureSampler; SamplerState MultiScatteredLuminanceLutTextureSampler; SamplerState SkyViewLutTextureSampler; SamplerState CameraAerialPerspectiveVolumeTextureSampler; SamplerState VolumetricCloudShadowMapTexture0Sampler; SamplerState VolumetricCloudShadowMapTexture1Sampler; SamplerState VolumetricCloudSkyAOTextureSampler; Texture2D TransmittanceLutTexture; Texture2D MultiScatteredLuminanceLutTexture; Texture2D SkyViewLutTexture; Texture3D CameraAerialPerspectiveVolumeTexture; Texture2D VolumetricCloudShadowMapTexture0; Texture2D VolumetricCloudShadowMapTexture1; Texture2D VolumetricCloudSkyAOTexture; float VolumetricCloudShadowStrength0; float VolumetricCloudShadowStrength1; int VirtualShadowMapId0; int VirtualShadowMapId1; #if SOURCE_DISK_ENABLED uint SourceDiskEnabled; float3 GetLightDiskLuminance(float3 PlanetCenterToCamera, float3 WorldDir, uint LightIndex) { float t = RaySphereIntersectNearest(PlanetCenterToCamera, WorldDir, float3(0.0f, 0.0f, 0.0f), Atmosphere.BottomRadiusKm); if (t < 0.0f // No intersection with the planet && View.RenderingReflectionCaptureMask==0.0f) // Do not render light disk when in reflection capture in order to avoid double specular. The sun contribution is already computed analyticaly. { // GetLightDiskLuminance contains a tiny soft edge effect float3 LightDiskLuminance = GetLightDiskLuminance( PlanetCenterToCamera, WorldDir, Atmosphere.BottomRadiusKm, Atmosphere.TopRadiusKm, TransmittanceLutTexture, TransmittanceLutTextureSampler, View.AtmosphereLightDirection[LightIndex].xyz, View.AtmosphereLightDiscCosHalfApexAngle_PPTrans[LightIndex].x, View.AtmosphereLightDiscLuminance[LightIndex].xyz); // Clamp to avoid crazy high values (and exposed 64000.0f luminance is already crazy high, solar system sun is 1.6x10^9). Also this removes +inf float and helps TAA. const float3 MaxLightLuminance = 64000.0f; float3 ExposedLightLuminance = LightDiskLuminance * OutputPreExposure; ExposedLightLuminance = min(ExposedLightLuminance, MaxLightLuminance); return ExposedLightLuminance; } return 0.0f; } #endif float3 GetMultipleScattering(float3 WorlPos, float ViewZenithCosAngle) { float2 UV = saturate(float2(ViewZenithCosAngle*0.5f + 0.5f, (length(WorlPos) - Atmosphere.BottomRadiusKm) / (Atmosphere.TopRadiusKm - Atmosphere.BottomRadiusKm))); // We do no apply UV transform to sub range here as it has minimal impact. float3 MultiScatteredLuminance = MultiScatteredLuminanceLutTexture.SampleLevel(MultiScatteredLuminanceLutTextureSampler, UV, 0).rgb; return MultiScatteredLuminance; } float3 GetTransmittance(in float LightZenithCosAngle, in float PHeight) { float2 UV; LutTransmittanceParamsToUv(PHeight, LightZenithCosAngle, UV); #ifdef WHITE_TRANSMITTANCE float3 TransmittanceToLight = 1.0f; #else float3 TransmittanceToLight = TransmittanceLutTexture.SampleLevel(TransmittanceLutTextureSampler, UV, 0).rgb; #endif return TransmittanceToLight; } #define DEFAULT_SAMPLE_OFFSET 0.3f float SkyAtmosphereNoise(float2 UV) { // return DEFAULT_SAMPLE_OFFSET; // return float(Rand3DPCG32(int3(UV.x, UV.y, S)).x) / 4294967296.0f; #if VIEWDATA_AVAILABLE && PER_PIXEL_NOISE return View.RealTimeReflectionCapture ? DEFAULT_SAMPLE_OFFSET : InterleavedGradientNoise(UV.xy, float(View.StateFrameIndexMod8)); #else return DEFAULT_SAMPLE_OFFSET; #endif } //////////////////////////////////////////////////////////// // Main scattering/transmitance integration function //////////////////////////////////////////////////////////// struct SingleScatteringResult { float3 L; // Scattered light (luminance) float3 LMieOnly; // L but Mie scattering only float3 LRayOnly; // L but Rayleigh scattering only float3 OpticalDepth; // Optical depth (1/m) float3 Transmittance; // Transmittance in [0,1] (unitless) float3 TransmittanceMieOnly; // Transmittance in [0,1] (unitless) but Mie scattering only float3 TransmittanceRayOnly; // Transmittance in [0,1] (unitless) but Rayleigh scattering only float3 MultiScatAs1; }; struct SamplingSetup { bool VariableSampleCount; float SampleCountIni; // Used when VariableSampleCount is false float MinSampleCount; float MaxSampleCount; float DistanceToSampleCountMaxInv; }; // In this function, all world position are relative to the planet center (itself expressed within translated world space) SingleScatteringResult IntegrateSingleScatteredLuminance( in float4 SVPos, in float3 WorldPos, in float3 WorldDir, in bool Ground, in SamplingSetup Sampling, in float DeviceZ, in bool MieRayPhase, in float3 Light0Dir, in float3 Light1Dir, in float3 Light0Illuminance, in float3 Light1Illuminance, in float AerialPespectiveViewDistanceScale, in float tMaxMax = 9000000.0f) { SingleScatteringResult Result; Result.L = 0; Result.LMieOnly = 0; Result.LRayOnly = 0; Result.OpticalDepth = 0; Result.Transmittance = 1.0f; Result.TransmittanceMieOnly = 1.0f; Result.TransmittanceRayOnly = 1.0f; Result.MultiScatAs1 = 0; if (dot(WorldPos, WorldPos) <= Atmosphere.BottomRadiusKm * Atmosphere.BottomRadiusKm) { return Result; // Camera is inside the planet ground } float2 PixPos = SVPos.xy; // Compute next intersection with atmosphere or ground float3 PlanetO = float3(0.0f, 0.0f, 0.0f); float tMax = 0.0f; #if 0 // The bottom code causes the skyview lut to flicker when view from space afar. // Remove that code when the else section is proven. float tBottom = RaySphereIntersectNearest(WorldPos, WorldDir, PlanetO, Atmosphere.BottomRadiusKm); float tTop = RaySphereIntersectNearest(WorldPos, WorldDir, PlanetO, Atmosphere.TopRadiusKm); if (tBottom < 0.0f) { if (tTop < 0.0f) { tMax = 0.0f; // No intersection with planet nor its atmosphere: stop right away return Result; } else { tMax = tTop; } } else { if (tTop > 0.0f) { tMax = min(tTop, tBottom); } } #else float tBottom = 0.0f; float2 SolB = RayIntersectSphere(WorldPos, WorldDir, float4(PlanetO, Atmosphere.BottomRadiusKm)); float2 SolT = RayIntersectSphere(WorldPos, WorldDir, float4(PlanetO, Atmosphere.TopRadiusKm)); const bool bNoBotIntersection = all(SolB < 0.0f); const bool bNoTopIntersection = all(SolT < 0.0f); if (bNoTopIntersection) { // No intersection with planet or its atmosphere. tMax = 0.0f; return Result; } else if (bNoBotIntersection) { // No intersection with planet, so we trace up to the far end of the top atmosphere // (looking up from ground or edges when see from afar in space). tMax = max(SolT.x, SolT.y); } else { // Interesection with planet and atmospehre: we simply trace up to the planet ground. // We know there is at least one intersection thanks to bNoBotIntersection. // If one of the solution is invalid=-1, that means we are inside the planet: we stop tracing by setting tBottom=0. tBottom = max(0.0f, min(SolB.x, SolB.y)); tMax = tBottom; } #endif float PlanetOnOpaque = 1.0f; // This is used to hide opaque meshes under the planet ground #if VIEWDATA_AVAILABLE #if SAMPLE_ATMOSPHERE_ON_CLOUDS if (true) { float tDepth = DeviceZ; // When SAMPLE_ATMOSPHERE_ON_CLOUDS, DeviceZ is world distance in kilometer. if (tDepth < tMax) { tMax = tDepth; } } #else // SAMPLE_ATMOSPHERE_ON_CLOUDS if (DeviceZ != FarDepthValue) { const float3 DepthBufferTranslatedWorldPosKm = GetScreenTranslatedWorldPos(SVPos, DeviceZ).xyz * CM_TO_SKY_UNIT; const float3 TraceStartTranslatedWorldPosKm = WorldPos + GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter() * CM_TO_SKY_UNIT; // apply planet offset to go back to world from planet local referencial. const float3 TraceStartToSurfaceWorldKm = DepthBufferTranslatedWorldPosKm - TraceStartTranslatedWorldPosKm; float tDepth = length(TraceStartToSurfaceWorldKm); if (tDepth < tMax) { tMax = tDepth; } else { // Artists did not like that we handle automatic hiding of opaque element behind the planet. // Now, pixel under the surface of earht will receive aerial perspective as if they were on the ground. //PlanetOnOpaque = 0.0; } //if the ray intersects with the atmosphere boundary, make sure we do not apply atmosphere on surfaces are front of it. if (dot(WorldDir, TraceStartToSurfaceWorldKm) < 0.0) { return Result; } } #endif // SAMPLE_ATMOSPHERE_ON_CLOUDS #endif tMax = min(tMax, tMaxMax); // Sample count float SampleCount = Sampling.SampleCountIni; float SampleCountFloor = Sampling.SampleCountIni; float tMaxFloor = tMax; if (Sampling.VariableSampleCount) { SampleCount = lerp(Sampling.MinSampleCount, Sampling.MaxSampleCount, saturate(tMax*Sampling.DistanceToSampleCountMaxInv)); SampleCountFloor = floor(SampleCount); tMaxFloor = tMax * SampleCountFloor / SampleCount; // rescale tMax to map to the last entire step segment. } float dt = tMax / SampleCount; // Phase functions const float uniformPhase = 1.0f / (4.0f * PI); const float3 wi = Light0Dir; const float3 wo = WorldDir; float cosTheta = dot(wi, wo); float MiePhaseValueLight0 = HenyeyGreensteinPhase(Atmosphere.MiePhaseG, -cosTheta); // negate cosTheta because due to WorldDir being a "in" direction. float RayleighPhaseValueLight0 = RayleighPhase(cosTheta); #if SECOND_ATMOSPHERE_LIGHT_ENABLED cosTheta = dot(Light1Dir, wo); float MiePhaseValueLight1 = HenyeyGreensteinPhase(Atmosphere.MiePhaseG, -cosTheta); // negate cosTheta because due to WorldDir being a "in" direction. float RayleighPhaseValueLight1 = RayleighPhase(cosTheta); #endif // Ray march the atmosphere to integrate optical depth float3 L = 0.0f; float3 LMieOnly = 0.0f; float3 LRayOnly = 0.0f; float3 Throughput = 1.0f; float3 ThroughputMieOnly = 1.0f; float3 ThroughputRayOnly = 1.0f; float3 OpticalDepth = 0.0f; float t = 0.0f; float tPrev = 0.0f; float3 ExposedLight0Illuminance = Light0Illuminance * OutputPreExposure; #if SECOND_ATMOSPHERE_LIGHT_ENABLED float3 ExposedLight1Illuminance = Light1Illuminance * OutputPreExposure; #endif //#if SAMPLE_OPAQUE_SHADOW // Get the referencial when rendering the SkyView lut being in a special Z-top space #if SKYVIEWLUT_PASS float3x3 LocalReferencial = GetSkyViewLutReferentialParameter(); #endif //#endif float PixelNoise = PER_PIXEL_NOISE ? SkyAtmosphereNoise(PixPos.xy) : DEFAULT_SAMPLE_OFFSET; for (float SampleI = 0.0f; SampleI < SampleCount; SampleI += 1.0f) { // Compute current ray t and sample point P if (Sampling.VariableSampleCount) { // More expenssive but artefact free float t0 = (SampleI) / SampleCountFloor; float t1 = (SampleI + 1.0f) / SampleCountFloor;; // Non linear distribution of samples within the range. t0 = t0 * t0; t1 = t1 * t1; // Make t0 and t1 world space distances. t0 = tMaxFloor * t0; if (t1 > 1.0f) { t1 = tMax; //t1 = tMaxFloor; // this reveal depth slices } else { t1 = tMaxFloor * t1; } t = t0 + (t1 - t0) * PixelNoise; dt = t1 - t0; } else { t = tMax * (SampleI + PixelNoise) / SampleCount; } float3 P = WorldPos + t * WorldDir; float PHeight = length(P); // Sample the medium MediumSampleRGB Medium = SampleAtmosphereMediumRGB(P); const float3 SampleOpticalDepth = Medium.Extinction * dt * AerialPespectiveViewDistanceScale; const float3 SampleTransmittance = exp(-SampleOpticalDepth); OpticalDepth += SampleOpticalDepth; // Transmittance Ray only and Mie only set half of ozone in rayleigh and half of ozone in mie parts. // This is not great but I do not have any better solution. // Also most of the time Ozone is high in the atmosphere so it should be fine this way. ThroughputMieOnly *= exp(-(Medium.ExtinctionMie + Medium.ExtinctionOzo) * dt * AerialPespectiveViewDistanceScale); ThroughputRayOnly *= exp(-(Medium.ExtinctionRay + Medium.ExtinctionOzo) * dt * AerialPespectiveViewDistanceScale); // Phase and transmittance for light 0 const float3 UpVector = P / PHeight; float Light0ZenithCosAngle = dot(Light0Dir, UpVector); float3 TransmittanceToLight0 = GetTransmittance(Light0ZenithCosAngle, PHeight); float3 PhaseTimesScattering0; float3 PhaseTimesScattering0MieOnly; float3 PhaseTimesScattering0RayOnly; if (MieRayPhase) { PhaseTimesScattering0MieOnly= Medium.ScatteringMie * MiePhaseValueLight0; PhaseTimesScattering0RayOnly= Medium.ScatteringRay * RayleighPhaseValueLight0; PhaseTimesScattering0 = PhaseTimesScattering0MieOnly + PhaseTimesScattering0RayOnly; } else { PhaseTimesScattering0MieOnly= Medium.ScatteringMie * uniformPhase; PhaseTimesScattering0RayOnly= Medium.ScatteringRay * uniformPhase; PhaseTimesScattering0 = Medium.Scattering * uniformPhase; } #if SECOND_ATMOSPHERE_LIGHT_ENABLED // Phase and transmittance for light 1 float Light1ZenithCosAngle = dot(Light1Dir, UpVector); float3 TransmittanceToLight1 = GetTransmittance(Light1ZenithCosAngle, PHeight); float3 PhaseTimesScattering1; float3 PhaseTimesScattering1MieOnly; float3 PhaseTimesScattering1RayOnly; if (MieRayPhase) { PhaseTimesScattering1MieOnly= Medium.ScatteringMie * MiePhaseValueLight1; PhaseTimesScattering1RayOnly= Medium.ScatteringRay * RayleighPhaseValueLight1; PhaseTimesScattering1 = PhaseTimesScattering1MieOnly + PhaseTimesScattering1RayOnly; } else { PhaseTimesScattering1MieOnly= Medium.ScatteringMie * uniformPhase; PhaseTimesScattering1RayOnly= Medium.ScatteringRay * uniformPhase; PhaseTimesScattering1 = Medium.Scattering * uniformPhase; } #endif // Multiple scattering approximation float3 MultiScatteredLuminance0 = 0.0f; #if MULTISCATTERING_APPROX_SAMPLING_ENABLED MultiScatteredLuminance0 = GetMultipleScattering(P, Light0ZenithCosAngle); #endif #if SECOND_ATMOSPHERE_LIGHT_ENABLED float3 MultiScatteredLuminance1 = 0.0f; #if MULTISCATTERING_APPROX_SAMPLING_ENABLED MultiScatteredLuminance1 = GetMultipleScattering(P, Light1ZenithCosAngle); #endif #endif // Planet shadow float tPlanet0 = RaySphereIntersectNearest(P, Light0Dir, PlanetO + PLANET_RADIUS_OFFSET * UpVector, Atmosphere.BottomRadiusKm); float PlanetShadow0 = tPlanet0 >= 0.0f ? 0.0f : 1.0f; float3 ShadowP0 = P; bool bUnused = false; #if SKYVIEWLUT_PASS ShadowP0 = GetTranslatedCameraPlanetPos() + t * mul(LocalReferencial, WorldDir); // Inverse of the local SkyViewLUT referencial transform #endif #if SAMPLE_OPAQUE_SHADOW { float3 ShadowSampleWorldPosition0 = ShadowP0 * SKY_UNIT_TO_CM + GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter(); PlanetShadow0 *= ComputeLight0VolumeShadowing(ShadowSampleWorldPosition0 /* - DFHackToFloat(PrimaryView.PreViewTranslation)*/, false, false, bUnused); #if VIRTUAL_SHADOW_MAP if (VirtualShadowMapId0 != INDEX_NONE) { FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapDirectional(VirtualShadowMapId0, ShadowSampleWorldPosition0); PlanetShadow0 *= VirtualShadowMapSample.ShadowFactor; } #endif // VIRTUALSHADOW_MAP } #endif #if SAMPLE_CLOUD_SKYAO float OutOpticalDepth = 0.0f; MultiScatteredLuminance0 *= GetCloudVolumetricShadow(ShadowP0 * SKY_UNIT_TO_CM + GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter(), VolumetricCloudCommonParameters.CloudSkyAOTranslatedWorldToLightClipMatrix, VolumetricCloudCommonParameters.CloudSkyAOFarDepthKm, VolumetricCloudSkyAOTexture, VolumetricCloudSkyAOTextureSampler, OutOpticalDepth); #endif #if SAMPLE_CLOUD_SHADOW float OutOpticalDepth2 = 0.0f; PlanetShadow0 *= saturate(lerp(1.0f, GetCloudVolumetricShadow(ShadowP0 * SKY_UNIT_TO_CM + GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter(), VolumetricCloudCommonParameters.CloudShadowmapTranslatedWorldToLightClipMatrix[0], VolumetricCloudCommonParameters.CloudShadowmapFarDepthKm[0].x, VolumetricCloudShadowMapTexture0, VolumetricCloudShadowMapTexture0Sampler, OutOpticalDepth2), VolumetricCloudShadowStrength0)); #endif // MultiScatteredLuminance is already pre-exposed, atmospheric light contribution needs to be pre exposed // Multi-scattering is also not affected by PlanetShadow or TransmittanceToLight because it contains diffuse light after single scattering. float3 S = ExposedLight0Illuminance * (PlanetShadow0 * TransmittanceToLight0 * PhaseTimesScattering0 + MultiScatteredLuminance0 * Medium.Scattering); float3 SMieOnly = ExposedLight0Illuminance * (PlanetShadow0 * TransmittanceToLight0 * PhaseTimesScattering0MieOnly + MultiScatteredLuminance0 * Medium.ScatteringMie); float3 SRayOnly = ExposedLight0Illuminance * (PlanetShadow0 * TransmittanceToLight0 * PhaseTimesScattering0RayOnly + MultiScatteredLuminance0 * Medium.ScatteringRay); #if SECOND_ATMOSPHERE_LIGHT_ENABLED float tPlanet1 = RaySphereIntersectNearest(P, Light1Dir, PlanetO + PLANET_RADIUS_OFFSET * UpVector, Atmosphere.BottomRadiusKm); float PlanetShadow1 = tPlanet1 >= 0.0f ? 0.0f : 1.0f; float3 ShadowP1 = P; #if SAMPLE_OPAQUE_SHADOW #if SKYVIEWLUT_PASS ShadowP1 = GetTranslatedCameraPlanetPos() + t * mul(LocalReferencial, WorldDir); // Inverse of the local SkyViewLUT referencial transform #endif { float3 ShadowSampleWorldPosition1 = ShadowP1 * SKY_UNIT_TO_CM + GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter(); PlanetShadow1 *= ComputeLight1VolumeShadowing(ShadowSampleWorldPosition1/* - DFHackToFloat(PrimaryView.PreViewTranslation)*/, false, false, bUnused); #if VIRTUAL_SHADOW_MAP if (VirtualShadowMapId1 != INDEX_NONE) { FVirtualShadowMapSampleResult VirtualShadowMapSample = SampleVirtualShadowMapDirectional(VirtualShadowMapId1, ShadowSampleWorldPosition1); PlanetShadow1 *= VirtualShadowMapSample.ShadowFactor; } #endif // VIRTUALSHADOW_MAP } #endif // SAMPLE_OPAQUE_SHADOW #if SAMPLE_CLOUD_SHADOW float OutOpticalDepth3 = 0.0f; PlanetShadow1 *= saturate(lerp(1.0f, GetCloudVolumetricShadow(ShadowP1 * SKY_UNIT_TO_CM + GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter(), VolumetricCloudCommonParameters.CloudShadowmapTranslatedWorldToLightClipMatrix[1], VolumetricCloudCommonParameters.CloudShadowmapFarDepthKm[1].x, VolumetricCloudShadowMapTexture1, VolumetricCloudShadowMapTexture1Sampler, OutOpticalDepth3), VolumetricCloudShadowStrength1)); #endif // Multi-scattering can work for the second light but it is disabled for the sake of performance. S += ExposedLight1Illuminance * (PlanetShadow1 * TransmittanceToLight1 * PhaseTimesScattering1 + MultiScatteredLuminance1 * Medium.Scattering); SMieOnly += ExposedLight1Illuminance * (PlanetShadow1 * TransmittanceToLight1 * PhaseTimesScattering1MieOnly + MultiScatteredLuminance1 * Medium.ScatteringMie); SRayOnly += ExposedLight1Illuminance * (PlanetShadow1 * TransmittanceToLight1 * PhaseTimesScattering1RayOnly + MultiScatteredLuminance1 * Medium.ScatteringRay); #endif // When using the power serie to accumulate all sattering order, serie r must be <1 for a serie to converge. // Under extreme coefficient, MultiScatAs1 can grow larger and thus results in broken visuals. // The way to fix that is to use a proper analytical integration as porposed in slide 28 of http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/ // However, it is possible to disable as it can also work using simple power serie sum unroll up to 5th order. The rest of the orders has a really low contribution. #define MULTI_SCATTERING_POWER_SERIE 0 const float3 SafeMediumExtinction = max(Medium.Extinction, 1.e-9); #if MULTI_SCATTERING_POWER_SERIE==0 // 1 is the integration of luminance over the 4pi of a sphere, and assuming an isotropic phase function of 1.0/(4*PI) Result.MultiScatAs1 += Throughput * Medium.Scattering * 1.0f * dt; #else float3 MS = Medium.Scattering * 1; float3 MSint = (MS - MS * SampleTransmittance) / SafeMediumExtinction; Result.MultiScatAs1 += Throughput * MSint; #endif #if 0 L += Throughput * S * dt; LMieOnly += Throughput * SMieOnly * dt; LRayOnly += Throughput * SRayOnly * dt; Throughput *= SampleTransmittance; #else // See slide 28 at http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/ float3 Sint = (S - S * SampleTransmittance) / SafeMediumExtinction; // integrate along the current step segment float3 SintMieOnly = (SMieOnly - SMieOnly * SampleTransmittance) / SafeMediumExtinction; float3 SintRayOnly = (SRayOnly - SRayOnly * SampleTransmittance) / SafeMediumExtinction; L += Throughput * Sint; // accumulate and also take into account the transmittance from previous steps LMieOnly += Throughput * SintMieOnly; LRayOnly += Throughput * SintRayOnly; Throughput *= SampleTransmittance; #endif tPrev = t; } if (Ground && tMax == tBottom) { // Account for bounced light off the planet float3 P = WorldPos + tBottom * WorldDir; float PHeight = length(P); const float3 UpVector = P / PHeight; float Light0ZenithCosAngle = dot(Light0Dir, UpVector); float3 TransmittanceToLight0 = GetTransmittance(Light0ZenithCosAngle, PHeight); const float NdotL0 = saturate(dot(UpVector, Light0Dir)); L += Light0Illuminance * TransmittanceToLight0 * Throughput * NdotL0 * Atmosphere.GroundAlbedo.rgb / PI; #if SECOND_ATMOSPHERE_LIGHT_ENABLED { const float NdotL1 = saturate(dot(UpVector, Light1Dir)); float Light1ZenithCosAngle = dot(UpVector, Light1Dir); float3 TransmittanceToLight1 = GetTransmittance(Light1ZenithCosAngle, PHeight); L += Light1Illuminance * TransmittanceToLight1 * Throughput * NdotL1 * Atmosphere.GroundAlbedo.rgb / PI; } #endif } Result.L = L; Result.LMieOnly = LMieOnly; Result.LRayOnly = LRayOnly; Result.OpticalDepth = OpticalDepth; Result.Transmittance = Throughput * PlanetOnOpaque; Result.TransmittanceMieOnly = ThroughputMieOnly * PlanetOnOpaque; Result.TransmittanceRayOnly = ThroughputRayOnly * PlanetOnOpaque; return Result; } //////////////////////////////////////////////////////////// // Main sky and atmosphere on opaque ray marching shaders //////////////////////////////////////////////////////////// float StartDepthZ; void SkyAtmosphereVS( in uint VertexId : SV_VertexID, out float4 Position : SV_POSITION) { 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, StartDepthZ, 1.0f); } float4 PrepareOutput(float3 PreExposedLuminance, float3 Transmittance = float3(1.0f, 1.0f, 1.0f)) { // Sky materials can result in high luminance values, e.g. the sun disk. // This is so we make sure to at least stay within the boundaries of fp10 and not cause NaN on some platforms. // We also half that range to also make sure we have room for other additive elements such as bloom, clouds or particle visual effects. const float3 SafePreExposedLuminance = min(PreExposedLuminance, Max10BitsFloat.xxx * 0.5f); const float GreyScaleTransmittance = dot(Transmittance, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); float4 LuminanceAlpha = float4(SafePreExposedLuminance, GreyScaleTransmittance); FLATTEN if(bPropagateAlphaNonReflection > 0) { #if SUPPORT_PRIMITIVE_ALPHA_HOLDOUT LuminanceAlpha.rgb = (IsSkyAtmosphereHoldout(View.EnvironmentComponentsFlags) && View.bPrimitiveAlphaHoldoutEnabled) ? 0.0f : LuminanceAlpha.rgb; #endif LuminanceAlpha.a = 1 - GreyScaleTransmittance; } return LuminanceAlpha; } uint DepthReadDisabled; void UpdateVisibleSkyAlpha(in float DeviceZ, inout float4 OutLuminance) { // This is for parity with the sky dome mesh behavior. // Output fully opaque alpha if looking through outer space, if atmosphere is not in holdout. bool bOutputFullyOpaque = (bPropagateAlphaNonReflection > 0) && (DeviceZ == FarDepthValue); #if SUPPORT_PRIMITIVE_ALPHA_HOLDOUT bOutputFullyOpaque &= !IsSkyAtmosphereHoldout(View.EnvironmentComponentsFlags) || !View.bPrimitiveAlphaHoldoutEnabled; #endif FLATTEN if (bOutputFullyOpaque) { OutLuminance.a = 1.0; } } void RenderSkyAtmosphereRayMarchingPS( in float4 SVPos : SV_POSITION, out float4 OutLuminance : SV_Target0 #if COLORED_TRANSMITTANCE_ENABLED , float4 Transmittance : SV_TARGET1; // For dual source blending when available #endif #if MSAA_SAMPLE_COUNT > 1 , in uint SampleIndex : SV_SampleIndex #endif ) { OutLuminance = 0; float2 PixPos = SVPos.xy; float2 UvBuffer = PixPos * View.BufferSizeAndInvSize.zw; // Uv for depth buffer read (size can be larger than viewport) // Debug print a texture //const float displaySize = 256.0f; //if(all(PixPos 0.999) { OutLuminance = float4(0.0f, 0.0f, 0.0f, 1.0f); return; } const float CloudDepthKm = VolumetricCloudDepthTexture.Load(int3(PixPos, 0)).r; float DeviceZ = CloudDepthKm; // Warning: for simplicity, we use DeviceZ as world distance in kilometer when SAMPLE_ATMOSPHERE_ON_CLOUDS. See special case in IntegrateSingleScatteredLuminance. #else // SAMPLE_ATMOSPHERE_ON_CLOUDS #if MSAA_SAMPLE_COUNT > 1 float DeviceZ = DepthReadDisabled ? FarDepthValue : MSAADepthTexture.Load(int2(PixPos), SampleIndex).x; #else float DeviceZ = DepthReadDisabled ? FarDepthValue : LookupDeviceZ(UvBuffer); #endif if (DeviceZ == FarDepthValue) { // Get the light disk luminance to draw LuminanceScale = SkyAtmosphere.SkyLuminanceFactor; #if SOURCE_DISK_ENABLED if (SourceDiskEnabled > 0) { PreExposedL += GetLightDiskLuminance(WorldPos, WorldDir, 0); #if SECOND_ATMOSPHERE_LIGHT_ENABLED PreExposedL += GetLightDiskLuminance(WorldPos, WorldDir, 1); #endif } #endif #if RENDERSKY_ENABLED==0 // We should not render the sky and the current pixels are at far depth, so simply early exit. // We enable depth bound when supported to not have to even process those pixels. OutLuminance = PrepareOutput(float3(0.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 1.0f)); //Now the sky pass can ignore the pixel with depth == far but it will need to alpha clip because not all RHI backend support depthbound tests. // And the depthtest is already setup to avoid writing all the pixel closer than to the camera than the start distance (very good optimisation). // Since this shader does not write to depth or stencil it should still benefit from EArlyZ even with the clip (See AMD depth-in-depth documentation) clip(-1.0f); return; #endif } else if (SkyAtmosphere.FogShowFlagFactor <= 0.0f) { OutLuminance = PrepareOutput(float3(0.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 1.0f)); clip(-1.0f); return; } #endif // SAMPLE_ATMOSPHERE_ON_CLOUDS float ViewHeight = length(WorldPos); #if FASTSKY_ENABLED && RENDERSKY_ENABLED if (ViewHeight < (Atmosphere.TopRadiusKm * PLANET_RADIUS_RATIO_SAFE_EDGE) && DeviceZ == FarDepthValue && (View.RenderingReflectionCaptureMask > 0.0f || IsSkyAtmosphereRenderedInMain(View.EnvironmentComponentsFlags))) { float2 UV; // The referencial used to build the Sky View lut float3x3 LocalReferencial = GetSkyViewLutReferentialParameter(); // Input vectors expressed in this referencial: Up is always Z. Also note that ViewHeight is unchanged in this referencial. float3 WorldPosLocal = float3(0.0, 0.0, ViewHeight); float3 UpVectorLocal = float3(0.0, 0.0, 1.0); float3 WorldDirLocal = mul(LocalReferencial, WorldDir); float ViewZenithCosAngle = dot(WorldDirLocal, UpVectorLocal); // Now evaluate inputs in the referential bool IntersectGround = RaySphereIntersectNearest(WorldPosLocal, WorldDirLocal, float3(0, 0, 0), Atmosphere.BottomRadiusKm) >= 0.0f; SkyViewLutParamsToUv(IntersectGround, ViewZenithCosAngle, WorldDirLocal, ViewHeight, Atmosphere.BottomRadiusKm, SkyAtmosphere.SkyViewLutSizeAndInvSize, UV); float4 SkyLuminanceTransmittance = SkyViewLutTexture.SampleLevel(SkyViewLutTextureSampler, UV, 0); float3 SkyLuminance = SkyLuminanceTransmittance.rgb; float3 SkyGreyTransmittance = 1.0f; FLATTEN if(bPropagateAlphaNonReflection > 0) { SkyGreyTransmittance = SkyLuminanceTransmittance.aaa; } PreExposedL += SkyLuminance * LuminanceScale * (ViewOneOverPreExposure * OutputPreExposure); OutLuminance = PrepareOutput(PreExposedL, SkyGreyTransmittance); UpdateVisibleSkyAlpha(DeviceZ, OutLuminance); return; } #endif #if FASTAERIALPERSPECTIVE_ENABLED #if COLORED_TRANSMITTANCE_ENABLED #error The FASTAERIALPERSPECTIVE_ENABLED path does not support COLORED_TRANSMITTANCE_ENABLED. #else float3 DepthBufferTranslatedWorldPos = GetScreenTranslatedWorldPos(SVPos, DeviceZ).xyz; float4 NDCPosition = mul(float4(DepthBufferTranslatedWorldPos.xyz, 1), View.TranslatedWorldToClip); const float NearFadeOutRangeInvDepthKm = 1.0 / 0.00001f; // 1 centimeter fade region float4 AP = GetAerialPerspectiveLuminanceTransmittance( ResolvedView.RealTimeReflectionCapture, ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeSizeAndInvSize, NDCPosition, (DepthBufferTranslatedWorldPos - GetCameraTranslatedWorldPos()) * CM_TO_SKY_UNIT, CameraAerialPerspectiveVolumeTexture, CameraAerialPerspectiveVolumeTextureSampler, SkyAtmosphere.CameraAerialPerspectiveVolumeDepthResolutionInv, SkyAtmosphere.CameraAerialPerspectiveVolumeDepthResolution, AerialPerspectiveStartDepthKm, SkyAtmosphere.CameraAerialPerspectiveVolumeDepthSliceLengthKm, SkyAtmosphere.CameraAerialPerspectiveVolumeDepthSliceLengthKmInv, ViewOneOverPreExposure * OutputPreExposure, NearFadeOutRangeInvDepthKm); PreExposedL += AP.rgb * LuminanceScale; float Transmittance = AP.a; OutLuminance = PrepareOutput(PreExposedL, float3(Transmittance, Transmittance, Transmittance)); UpdateVisibleSkyAlpha(DeviceZ, OutLuminance); return; #endif #else // FASTAERIALPERSPECTIVE_ENABLED // Move to top atmosphere as the starting point for ray marching. // This is critical to be after the above to not disrupt above atmosphere tests and voxel selection. if (!MoveToTopAtmosphere(WorldPos, WorldDir, Atmosphere.TopRadiusKm)) { // Ray is not intersecting the atmosphere OutLuminance = PrepareOutput(PreExposedL); return; } // Apply the start depth offset after moving to the top of atmosphere for consistency (and to avoid wrong out-of-atmosphere test resulting in black pixels). WorldPos += WorldDir * AerialPerspectiveStartDepthKm; SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = true; Sampling.MinSampleCount = SkyAtmosphere.SampleCountMin; Sampling.MaxSampleCount = SkyAtmosphere.SampleCountMax; Sampling.DistanceToSampleCountMaxInv = SkyAtmosphere.DistanceToSampleCountMaxInv; } const bool Ground = false; const bool MieRayPhase = true; const float AerialPespectiveViewDistanceScale = DeviceZ == FarDepthValue ? 1.0f : SkyAtmosphere.AerialPespectiveViewDistanceScale; SingleScatteringResult ss = IntegrateSingleScatteredLuminance( SVPos, WorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, View.AtmosphereLightDirection[0].xyz, View.AtmosphereLightDirection[1].xyz, View.AtmosphereLightIlluminanceOuterSpace[0].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, View.AtmosphereLightIlluminanceOuterSpace[1].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, AerialPespectiveViewDistanceScale); PreExposedL += ss.L * LuminanceScale; if (View.RenderingReflectionCaptureMask == 0.0f && !IsSkyAtmosphereRenderedInMain(View.EnvironmentComponentsFlags)) { PreExposedL = 0.0f; } #if SAMPLE_ATMOSPHERE_ON_CLOUDS // We use gray scale transmittance to match the rendering when applying the AerialPerspective texture const float GreyScaleAtmosphereTransmittance = dot(ss.Transmittance, float3(1.0 / 3.0f, 1.0 / 3.0f, 1.0 / 3.0f)); // Reduce cloud luminance according to the atmosphere transmittance and add the atmosphere in scattred luminance according to the cloud coverage. PreExposedL = CloudLuminanceTransmittance.rgb * GreyScaleAtmosphereTransmittance + CloudCoverage * PreExposedL; // Coverage of the cloud layer itself does not change. ss.Transmittance = CloudLuminanceTransmittance.a; #endif #if COLORED_TRANSMITTANCE_ENABLED #error Requires support for dual source blending. output.Luminance = float4(PreExposedL, 1.0f); output.Transmittance = float4(ss.Transmittance, 1.0f); #else OutLuminance = PrepareOutput(PreExposedL, ss.Transmittance); UpdateVisibleSkyAlpha(DeviceZ, OutLuminance); return; #endif #endif // FASTAERIALPERSPECTIVE_ENABLED } //////////////////////////////////////////////////////////// // Transmittance LUT //////////////////////////////////////////////////////////// #ifndef THREADGROUP_SIZE #define THREADGROUP_SIZE 1 #endif // see SkyAtmosphereRendering.cpp GetSkyLutTextureFormat #if SHADING_PATH_MOBILE RWTexture2D TransmittanceLutUAV; #else RWTexture2D TransmittanceLutUAV; #endif [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void RenderTransmittanceLutCS(uint3 ThreadId : SV_DispatchThreadID) { float2 PixPos = float2(ThreadId.xy) + 0.5f; // Compute camera position from LUT coords float2 UV = (PixPos) * SkyAtmosphere.TransmittanceLutSizeAndInvSize.zw; float ViewHeight; float ViewZenithCosAngle; UvToLutTransmittanceParams(ViewHeight, ViewZenithCosAngle, UV); // A few extra needed constants float3 WorldPos = float3(0.0f, 0.0f, ViewHeight); float3 WorldDir = float3(0.0f, sqrt(1.0f - ViewZenithCosAngle * ViewZenithCosAngle), ViewZenithCosAngle); SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = false; Sampling.SampleCountIni = SkyAtmosphere.TransmittanceSampleCount; } const bool Ground = false; const float DeviceZ = FarDepthValue; const bool MieRayPhase = false; const float3 NullLightDirection = float3(0.0f, 0.0f, 1.0f); const float3 NullLightIlluminance = float3(0.0f, 0.0f, 0.0f); const float AerialPespectiveViewDistanceScale = 1.0f; SingleScatteringResult ss = IntegrateSingleScatteredLuminance( float4(PixPos,0.0f,1.0f), WorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, NullLightDirection, NullLightDirection, NullLightIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); float3 transmittance = exp(-ss.OpticalDepth); #if SHADING_PATH_MOBILE TransmittanceLutUAV[int2(PixPos)] = float4(transmittance, 0.0f); #else TransmittanceLutUAV[int2(PixPos)] = transmittance; #endif } //////////////////////////////////////////////////////////// // Multi-scattering LUT //////////////////////////////////////////////////////////// // see SkyAtmosphereRendering.cpp GetSkyLutTextureFormat #if SHADING_PATH_MOBILE RWTexture2D MultiScatteredLuminanceLutUAV; #else RWTexture2D MultiScatteredLuminanceLutUAV; #endif Buffer UniformSphereSamplesBuffer; uint UniformSphereSamplesBufferSampleCount; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void RenderMultiScatteredLuminanceLutCS(uint3 ThreadId : SV_DispatchThreadID) { float2 PixPos = float2(ThreadId.xy) + 0.5f; // We do no apply UV transform from sub range here as it has minimal impact. float CosLightZenithAngle = (PixPos.x * SkyAtmosphere.MultiScatteredLuminanceLutSizeAndInvSize.z) * 2.0f - 1.0f; float3 LightDir = float3(0.0f, sqrt(saturate(1.0f - CosLightZenithAngle * CosLightZenithAngle)), CosLightZenithAngle); const float3 NullLightDirection = float3(0.0f, 0.0f, 1.0f); const float3 NullLightIlluminance = float3(0.0f, 0.0f, 0.0f); const float3 OneIlluminance = float3(1.0f, 1.0f, 1.0f); // Assume a pure white light illuminance for the LUT to act as a transfer (be independent of the light, only dependent on the earth) float ViewHeight = Atmosphere.BottomRadiusKm + (PixPos.y * SkyAtmosphere.MultiScatteredLuminanceLutSizeAndInvSize.w) * (Atmosphere.TopRadiusKm - Atmosphere.BottomRadiusKm); float3 WorldPos = float3(0.0f, 0.0f, ViewHeight); float3 WorldDir = float3(0.0f, 0.0f, 1.0f); SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = false; Sampling.SampleCountIni = SkyAtmosphere.MultiScatteringSampleCount; } const bool Ground = true; const float DeviceZ = FarDepthValue; const bool MieRayPhase = false; const float AerialPespectiveViewDistanceScale = 1.0f; const float SphereSolidAngle = 4.0f * PI; const float IsotropicPhase = 1.0f / SphereSolidAngle; #if HIGHQUALITY_MULTISCATTERING_APPROX_ENABLED float3 IntegratedIlluminance = 0.0f; float3 MultiScatAs1 = 0.0f; for (int s = 0; s < UniformSphereSamplesBufferSampleCount; ++s) { SingleScatteringResult r0 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, UniformSphereSamplesBuffer[s].xyz, Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); IntegratedIlluminance += r0.L; MultiScatAs1 += r0.MultiScatAs1; } const float InvCount = 1.0f / float(UniformSphereSamplesBufferSampleCount); IntegratedIlluminance *= SphereSolidAngle * InvCount; MultiScatAs1 *= InvCount; float3 InScatteredLuminance = IntegratedIlluminance * IsotropicPhase; #elif 1 // Cheap and good enough approximation (but lose energy) SingleScatteringResult r0 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r1 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, -WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); float3 IntegratedIlluminance = (SphereSolidAngle / 2.0f) * (r0.L + r1.L); float3 MultiScatAs1 = (1.0f / 2.0f)*(r0.MultiScatAs1 + r1.MultiScatAs1); float3 InScatteredLuminance = IntegratedIlluminance * IsotropicPhase; #else // Less cheap but approximation closer to ground truth SingleScatteringResult r0 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(0.70710678118f, 0.0f, 0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r1 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(-0.70710678118f, 0.0f, 0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r2 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(0.0f, 0.70710678118f, 0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r3 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(0.0f, -0.70710678118f, 0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r4 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(0.70710678118f, 0.0f, -0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r5 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(-0.70710678118f, 0.0f, -0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r6 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(0.0f, 0.70710678118f, -0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); SingleScatteringResult r7 = IntegrateSingleScatteredLuminance(float4(PixPos, 0.0f, 1.0f), WorldPos, float3(0.0f, -0.70710678118f, -0.70710678118f), Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, OneIlluminance, NullLightIlluminance, AerialPespectiveViewDistanceScale); // Integral of in-scattered Luminance (Lumen/(m2.sr)) over the sphere gives illuminance (Lumen/m2). // This is done with equal importance for each samples over the sphere. float3 IntegratedIlluminance = (SphereSolidAngle / 8.0f) * (r0.L + r1.L + r2.L + r3.L + r4.L + r5.L + r6.L + r7.L); // MultiScatAs1 represents the contribution of a uniform environment light over a sphere of luminance 1 and assuming an isotropic phase function float3 MultiScatAs1 = (1.0f / 8.0f)*(r0.MultiScatAs1 + r1.MultiScatAs1 + r2.MultiScatAs1 + r3.MultiScatAs1 + r4.MultiScatAs1 + r5.MultiScatAs1 + r6.MultiScatAs1 + r7.MultiScatAs1); // Compute the InScatteredLuminance (Lumen/(m2.sr)) assuming a uniform IntegratedIlluminance, isotropic phase function (1.0/sr) // and the fact that this illumiance would be used for each path/raymarch samples of each path float3 InScatteredLuminance = IntegratedIlluminance * IsotropicPhase; #endif // MultiScatAs1 represents the amount of luminance scattered as if the integral of scattered luminance over the sphere would be 1. // - 1st order of scattering: one can ray-march a straight path as usual over the sphere. That is InScatteredLuminance. // - 2nd order of scattering: the inscattered luminance is InScatteredLuminance at each of samples of fist order integration. Assuming a uniform phase function that is represented by MultiScatAs1, // - 3nd order of scattering: the inscattered luminance is (InScatteredLuminance * MultiScatAs1 * MultiScatAs1) // - etc. #if MULTI_SCATTERING_POWER_SERIE==0 float3 MultiScatAs1SQR = MultiScatAs1 * MultiScatAs1; float3 L = InScatteredLuminance * (1.0f + MultiScatAs1 + MultiScatAs1SQR + MultiScatAs1 * MultiScatAs1SQR + MultiScatAs1SQR * MultiScatAs1SQR); #else // For a serie, sum_{n=0}^{n=+inf} = 1 + r + r^2 + r^3 + ... + r^n = 1 / (1.0 - r), see https://en.wikipedia.org/wiki/Geometric_series const float3 R = MultiScatAs1; const float3 SumOfAllMultiScatteringEventsContribution = 1.0f / (1.0f - R); float3 L = InScatteredLuminance * SumOfAllMultiScatteringEventsContribution; #endif // MultipleScatteringFactor can be applied here because the LUT is compute every frame // L is pre-exposed since InScatteredLuminance is computed from pre-exposed sun light. So multi-scattering contribution is pre-exposed. #if SHADING_PATH_MOBILE MultiScatteredLuminanceLutUAV[int2(PixPos)] = float4(L * Atmosphere.MultiScatteringFactor, 0.0f); #else MultiScatteredLuminanceLutUAV[int2(PixPos)] = L * Atmosphere.MultiScatteringFactor; #endif } //////////////////////////////////////////////////////////// // Sky View LUT //////////////////////////////////////////////////////////// // Even thought the texture can have 3 channels only (see SkyAtmosphereRendering.cpp GetSkyLutTextureFormat) // We always write 4 channels as the grey scale transmittance can be used when alpha propagation is enabled. RWTexture2D SkyViewLutUAV; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void RenderSkyViewLutCS(uint3 ThreadId : SV_DispatchThreadID) { float2 PixPos = float2(ThreadId.xy) + 0.5f; float2 UV = PixPos * SkyAtmosphere.SkyViewLutSizeAndInvSize.zw; float3 WorldPos = GetTranslatedCameraPlanetPos(); // For the sky view lut to work, and not be distorted, we need to transform the view and light directions // into a referential with UP being perpendicular to the ground. And with origin at the planet center. // This is the local referencial float3x3 LocalReferencial = GetSkyViewLutReferentialParameter(); // This is the LUT camera height and position in the local referential float ViewHeight = length(WorldPos); WorldPos = float3(0.0, 0.0, ViewHeight); // Get the view direction in this local referential float3 WorldDir; UvToSkyViewLutParams(WorldDir, ViewHeight, UV); // And also both light source direction float3 AtmosphereLightDirection0 = View.AtmosphereLightDirection[0].xyz; AtmosphereLightDirection0 = mul(LocalReferencial, AtmosphereLightDirection0); float3 AtmosphereLightDirection1 = View.AtmosphereLightDirection[1].xyz; AtmosphereLightDirection1 = mul(LocalReferencial, AtmosphereLightDirection1); // Move to top atmospehre if (!MoveToTopAtmosphere(WorldPos, WorldDir, Atmosphere.TopRadiusKm)) { // Ray is not intersecting the atmosphere SkyViewLutUAV[int2(PixPos)] = 0.0f; return; } SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = true; Sampling.MinSampleCount = SkyAtmosphere.FastSkySampleCountMin; Sampling.MaxSampleCount = SkyAtmosphere.FastSkySampleCountMax; Sampling.DistanceToSampleCountMaxInv = SkyAtmosphere.FastSkyDistanceToSampleCountMaxInv; } const bool Ground = false; const float DeviceZ = FarDepthValue; const bool MieRayPhase = true; const float AerialPespectiveViewDistanceScale = 1.0f; SingleScatteringResult ss = IntegrateSingleScatteredLuminance( float4(PixPos, 0.0f, 1.0f), WorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, AtmosphereLightDirection0, AtmosphereLightDirection1, View.AtmosphereLightIlluminanceOuterSpace[0].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, View.AtmosphereLightIlluminanceOuterSpace[1].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, AerialPespectiveViewDistanceScale); const float Transmittance = dot(ss.Transmittance, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); SkyViewLutUAV[int2(PixPos)] = float4(ss.L, Transmittance); } //////////////////////////////////////////////////////////// // Distant sky light LUT //////////////////////////////////////////////////////////// // see SkyAtmosphereRendering.cpp GetSkyLutTextureFormat RWStructuredBuffer DistantSkyLightLutBufferUAV; RWBuffer MobileDistantSkyLightLutBufferUAV; //Buffer UniformSphereSamplesBuffer; float4 AtmosphereLightDirection0; float4 AtmosphereLightDirection1; float4 AtmosphereLightIlluminanceOuterSpace0; float4 AtmosphereLightIlluminanceOuterSpace1; float DistantSkyLightSampleAltitude; groupshared float3 GroupSkyLuminanceSamples[THREADGROUP_SIZE*THREADGROUP_SIZE]; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)] void RenderDistantSkyLightLutCS(uint3 ThreadId : SV_DispatchThreadID) { const int LinearIndex = ThreadId.y*THREADGROUP_SIZE + ThreadId.x; float2 PixPos = float2(ThreadId.xy) + 0.5f; float2 UV = PixPos * SkyAtmosphere.SkyViewLutSizeAndInvSize.zw; // As of today, we assume the world is alway at the top of the planet along Z. // If it needs to change, we can transform all AtmosphereLightDirection into local basis onder the camera (it would then be view dependent). // Overall this is fine because this sky lighting ambient is used for clouds using a dome and it won't be used in such sky views. // IF needed later, we could compute illuminance for multiple position on earth in a lat/long texture float3 SamplePos = float3(0, 0, Atmosphere.BottomRadiusKm + DistantSkyLightSampleAltitude); float ViewHeight = length(SamplePos); // We are going to trace 64 times using 64 parallel threads. // Result are written in shared memory and prefix sum is applied to integrate the lighting in a single RGB value // that can then be used to lit clouds in the sky mesh shader graph. // Select a direction for this thread const float3 SampleDir = UniformSphereSamplesBuffer[LinearIndex].xyz; SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = false; Sampling.SampleCountIni = 10.0f; } const bool Ground = false; const float DeviceZ = FarDepthValue; const bool MieRayPhase = false; const float AerialPespectiveViewDistanceScale = 1.0f; SingleScatteringResult ss = IntegrateSingleScatteredLuminance( float4(PixPos, 0.0f, 1.0f), SamplePos, SampleDir, Ground, Sampling, DeviceZ, MieRayPhase, AtmosphereLightDirection0.xyz, AtmosphereLightDirection1.xyz, AtmosphereLightIlluminanceOuterSpace0.rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, AtmosphereLightIlluminanceOuterSpace1.rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, AerialPespectiveViewDistanceScale); GroupSkyLuminanceSamples[LinearIndex] = ss.L * SkyAtmosphere.SkyLuminanceFactor; // Wait for all group threads to be done GroupMemoryBarrierWithGroupSync(); // Now we manually apply prefix sum for a thread group size of 64 #if SKYLIGHT_PASS==1 && THREADGROUP_SIZE!=8 #error This shader only works for a thread group size of 8x8 #endif if (LinearIndex < 32) { GroupSkyLuminanceSamples[LinearIndex] += GroupSkyLuminanceSamples[LinearIndex + 32]; } GroupMemoryBarrierWithGroupSync(); if (LinearIndex < 16) { GroupSkyLuminanceSamples[LinearIndex] += GroupSkyLuminanceSamples[LinearIndex + 16]; } GroupMemoryBarrierWithGroupSync(); if (LinearIndex < 8) { GroupSkyLuminanceSamples[LinearIndex] += GroupSkyLuminanceSamples[LinearIndex + 8]; } GroupMemoryBarrierWithGroupSync(); if (LinearIndex < 4) { GroupSkyLuminanceSamples[LinearIndex] += GroupSkyLuminanceSamples[LinearIndex + 4]; } GroupMemoryBarrierWithGroupSync(); // The smallest wave size is 4 on Mali G-71 hardware. So now we can do simple math operations without group sync. if (LinearIndex < 2) { GroupSkyLuminanceSamples[LinearIndex] += GroupSkyLuminanceSamples[LinearIndex + 2]; } if (LinearIndex < 1) { const float3 AccumulatedLuminanceSamples = GroupSkyLuminanceSamples[LinearIndex] + GroupSkyLuminanceSamples[LinearIndex + 1]; const float SamplerSolidAngle = 4.0f * PI / float(THREADGROUP_SIZE * THREADGROUP_SIZE); const float3 Illuminance = AccumulatedLuminanceSamples * SamplerSolidAngle; const float3 UniformPhaseFunction = 1.0f / (4.0f * PI); // Luminance assuming scattering in a medium with a uniform phase function. const float4 OutputResult = float4(Illuminance * UniformPhaseFunction, 0.0f); DistantSkyLightLutBufferUAV[0] = OutputResult; MobileDistantSkyLightLutBufferUAV[0]= OutputResult; // Since this is ran once per scene, we do not have access to view data (VIEWDATA_AVAILABLE==0). // So this buffer is not pre-exposed today. } } //////////////////////////////////////////////////////////// // Camera aerial perspective volume LUT //////////////////////////////////////////////////////////// RWTexture3D CameraAerialPerspectiveVolumeUAV; #if SEPARATE_MIE_RAYLEIGH_SCATTERING RWTexture3D CameraAerialPerspectiveVolumeMieOnlyUAV; RWTexture3D CameraAerialPerspectiveVolumeRayOnlyUAV; #endif float RealTimeReflection360Mode; [numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, THREADGROUP_SIZE)] void RenderCameraAerialPerspectiveVolumeCS(uint3 ThreadId : SV_DispatchThreadID) { if (SkyAtmosphere.FogShowFlagFactor <= 0.0f) { CameraAerialPerspectiveVolumeUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); #if SEPARATE_MIE_RAYLEIGH_SCATTERING CameraAerialPerspectiveVolumeMieOnlyUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); CameraAerialPerspectiveVolumeRayOnlyUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); #endif return; } float2 PixPos = float2(ThreadId.xy) + 0.5f; float2 UV = PixPos * SkyAtmosphere.CameraAerialPerspectiveVolumeSizeAndInvSize.zw; float4 SVPos = float4(View.ViewRectMin.xy + UV * View.ViewSizeAndInvSize.xy, 0.0f, 1.0f);// SV_POS as if resolution was the one from the scene view. float3 WorldDir = GetScreenWorldDir(SVPos); float3 CamPos = GetTranslatedCameraPlanetPos(); if (IsOrthoProjection()) { CamPos += GetTranslatedWorldCameraPosFromView(SVPos.xy, true); } if (RealTimeReflection360Mode) { float2 UnitUV = FromSubUvsToUnit(UV, SkyAtmosphere.CameraAerialPerspectiveVolumeSizeAndInvSize); // Simple lat-long mapping with with UV.y=sin(ElevationAngle) float SinPhi = 2.0f * UnitUV.y - 1.0f; float CosPhi = sqrt(1.0f - SinPhi * SinPhi); float Theta = 2.0f * PI * UnitUV.x; float CosTheta = cos(Theta); float SinTheta = sqrt(1.0f - CosTheta * CosTheta) * (Theta > PI ? -1.0f : 1.0f); WorldDir = float3(CosTheta * CosPhi, SinTheta * CosPhi, SinPhi); WorldDir = normalize(WorldDir); } float Slice = ((float(ThreadId.z) + 0.5f) * SkyAtmosphere.CameraAerialPerspectiveVolumeDepthResolutionInv); // +0.5 to always have a distance to integrate over Slice *= Slice; // squared distribution Slice *= SkyAtmosphere.CameraAerialPerspectiveVolumeDepthResolution; float3 RayStartWorldPos = CamPos + AerialPerspectiveStartDepthKm * WorldDir; // Offset according to start depth float ViewHeight; // Compute position from froxel information float tMax = Slice * SkyAtmosphere.CameraAerialPerspectiveVolumeDepthSliceLengthKm; float3 VoxelWorldPos = RayStartWorldPos + tMax * WorldDir; float VoxelHeight = length(VoxelWorldPos); // Check if the voxel is under the horizon. const float UnderGround = VoxelHeight < Atmosphere.BottomRadiusKm; // Check if the voxel is beind the planet (to next check for below the horizon case) float3 CameraToVoxel = VoxelWorldPos - CamPos; float CameraToVoxelLen = length(CameraToVoxel); float3 CameraToVoxelDir = CameraToVoxel / CameraToVoxelLen; float PlanetNearT = RaySphereIntersectNearest(CamPos, CameraToVoxelDir, float3(0, 0, 0), Atmosphere.BottomRadiusKm); bool BelowHorizon = PlanetNearT > 0.0f && CameraToVoxelLen > PlanetNearT; if (BelowHorizon || UnderGround) { CamPos += normalize(CamPos) * 0.02f; // TODO: investigate why we need this workaround. Without it, we get some bad color and flickering on the ground only (floating point issue with sphere intersection code?). float3 VoxelWorldPosNorm = normalize(VoxelWorldPos); float3 CamProjOnGround = normalize(CamPos) * Atmosphere.BottomRadiusKm; float3 VoxProjOnGround = VoxelWorldPosNorm * Atmosphere.BottomRadiusKm; float3 VoxelGroundToRayStart = CamPos - VoxProjOnGround; if (BelowHorizon && dot(normalize(VoxelGroundToRayStart), VoxelWorldPosNorm) < 0.0001f) { // We are behind the sphere and the sphere normal is pointing away from V: we are below the horizon. float3 MiddlePoint = 0.5f * (CamProjOnGround + VoxProjOnGround); float MiddlePointHeight = length(MiddlePoint); // Compute the new position to evaluate and store the value in the voxel. // the position is the oposite side of the horizon point from the view point, // The offset of 1.001f is needed to get matching colors and for the ray to not hit the earth again later due to floating point accuracy float3 MiddlePointOnGround = normalize(MiddlePoint) * Atmosphere.BottomRadiusKm;// *1.001f; VoxelWorldPos = CamPos + 2.0f * (MiddlePointOnGround - CamPos); //CameraAerialPerspectiveVolumeUAV[ThreadId] = float4(1, 0, 0, 0); //#if SEPARATE_MIE_RAYLEIGH_SCATTERING // CameraAerialPerspectiveVolumeMieOnlyUAV[ThreadId] = float4(1, 0, 0, 0); // CameraAerialPerspectiveVolumeRayOnlyUAV[ThreadId] = float4(1, 0, 0, 0); //#endif //return; // debug } else if (UnderGround) { //No obstruction from the planet, so use the point on the ground VoxelWorldPos = normalize(VoxelWorldPos) * (Atmosphere.BottomRadiusKm); //VoxelWorldPos = CamPos + CameraToVoxelDir * PlanetNearT; // better match but gives visual artefact as visible voxels on a simple plane at altitude 0 //CameraAerialPerspectiveVolumeUAV[ThreadId] = float4(0, 1, 0, 0); //#if SEPARATE_MIE_RAYLEIGH_SCATTERING // CameraAerialPerspectiveVolumeMieOnlyUAV[ThreadId] = float4(0, 1, 0, 0); // CameraAerialPerspectiveVolumeRayOnlyUAV[ThreadId] = float4(0, 1, 0, 0); //#endif //return; // debug } WorldDir = normalize(VoxelWorldPos - CamPos); RayStartWorldPos = CamPos + AerialPerspectiveStartDepthKm * WorldDir; // Offset according to start depth tMax = length(VoxelWorldPos - RayStartWorldPos); } float tMaxMax = tMax; // Move ray marching start up to top atmosphere. ViewHeight = length(RayStartWorldPos); if (ViewHeight >= Atmosphere.TopRadiusKm) { float3 prevWorlPos = RayStartWorldPos; if (!MoveToTopAtmosphere(RayStartWorldPos, WorldDir, Atmosphere.TopRadiusKm)) { // Ray is not intersecting the atmosphere CameraAerialPerspectiveVolumeUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); #if SEPARATE_MIE_RAYLEIGH_SCATTERING CameraAerialPerspectiveVolumeMieOnlyUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); CameraAerialPerspectiveVolumeRayOnlyUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); #endif return; } float LengthToAtmosphere = length(prevWorlPos - RayStartWorldPos); if (tMaxMax < LengthToAtmosphere) { // tMaxMax for this voxel is not within the planet atmosphere CameraAerialPerspectiveVolumeUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); #if SEPARATE_MIE_RAYLEIGH_SCATTERING CameraAerialPerspectiveVolumeMieOnlyUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); CameraAerialPerspectiveVolumeRayOnlyUAV[ThreadId] = float4(0.0f, 0.0f, 0.0f, 1.0f); #endif return; } // Now world position has been moved to the atmosphere boundary: we need to reduce tMaxMax accordingly. tMaxMax = max(0.0, tMaxMax - LengthToAtmosphere); } SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = false; Sampling.SampleCountIni = max(1.0f, (float(ThreadId.z) + 1.0f) * SkyAtmosphere.CameraAerialPerspectiveSampleCountPerSlice); } const bool Ground = false; const float DeviceZ = FarDepthValue; const bool MieRayPhase = true; const float AerialPespectiveViewDistanceScale = SkyAtmosphere.AerialPespectiveViewDistanceScale; SingleScatteringResult ss = IntegrateSingleScatteredLuminance( float4(PixPos, 0.0f, 1.0f), RayStartWorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, View.AtmosphereLightDirection[0].xyz, View.AtmosphereLightDirection[1].xyz, View.AtmosphereLightIlluminanceOuterSpace[0].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, View.AtmosphereLightIlluminanceOuterSpace[1].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, AerialPespectiveViewDistanceScale, tMaxMax); #if SUPPORT_PRIMITIVE_ALPHA_HOLDOUT if (IsSkyAtmosphereHoldout(View.EnvironmentComponentsFlags) && !RealTimeReflection360Mode) { ss.L *= 0; ss.LMieOnly *= 0; ss.LRayOnly *= 0; } #endif const float Transmittance = dot(ss.Transmittance, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); CameraAerialPerspectiveVolumeUAV[ThreadId] = float4(ss.L, Transmittance); #if SEPARATE_MIE_RAYLEIGH_SCATTERING const float TransmittanceMieOnly = dot(ss.TransmittanceMieOnly, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); const float TransmittanceRayOnly = dot(ss.TransmittanceRayOnly, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); CameraAerialPerspectiveVolumeMieOnlyUAV[ThreadId] = float4(ss.LMieOnly, TransmittanceMieOnly); CameraAerialPerspectiveVolumeRayOnlyUAV[ThreadId] = float4(ss.LRayOnly, TransmittanceRayOnly); #endif } //////////////////////////////////////////////////////////// // Debug //////////////////////////////////////////////////////////// float ViewPortWidth; float ViewPortHeight; float Mean3(float3 v) { return dot(v, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); } float3 SimpleTonemap(float3 Luminance) { return pow(Luminance, 1.0 / 2.2); } void RenderSkyAtmosphereDebugPS( in float4 SVPos : SV_POSITION, out float4 OutLuminance : SV_Target0) { float2 PixPos = SVPos.xy; // Common ray marching input SamplingSetup Sampling = (SamplingSetup)0; { Sampling.VariableSampleCount = false; Sampling.SampleCountIni = 128.0f; } const bool Ground = false; const bool MieRayPhase = true; float DeviceZ = FarDepthValue; const float3 NullLightDirection = float3(0.0f, 0.0f, 1.0f); const float3 NullLightIlluminance = float3(0.0f, 0.0f, 0.0f); const float AerialPespectiveViewDistanceScale = 1.0f; // We position the camera at the same height as the view port camera, along z-up vector float3 WorldPos = float3(0.0f, 0.0f, length((View.TranslatedWorldCameraOrigin - GetSkyPlanetTranslatedWorldCenterAndViewHeightParameter()) * CM_TO_SKY_UNIT)); // Some organisation constants const float Margin = 2.0f; const float TimeOfDayHeight = 64.0f; const float TimeOfDayTop = TimeOfDayHeight + Margin*2.0f; // Hemisphere view, also show a bit of ground. float2 DebugSize = ViewPortWidth * 0.25f; float2 DebugPos = float2(2.0f, ViewPortHeight - DebugSize.y - TimeOfDayTop); if(all(PixPos < (DebugPos+DebugSize)) && all(PixPos > DebugPos)) { float2 UV = clamp(PixPos - DebugPos, 0.0f, DebugSize) / DebugSize; UV = UV * 2.0f - 1.0f; float UVLen = length(UV); if (UVLen < 1.0f) { float SinToHorizon = -2.0f * UVLen*UVLen + 1.0f; float CosXY = cos(asin(SinToHorizon)); float3 WorldDir = normalize(float3(CosXY*normalize(UV), SinToHorizon)); SingleScatteringResult SS = IntegrateSingleScatteredLuminance( SVPos, WorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, View.AtmosphereLightDirection[0].xyz, NullLightDirection, View.AtmosphereLightIlluminanceOuterSpace[0].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, NullLightIlluminance, AerialPespectiveViewDistanceScale); OutLuminance = float4(SimpleTonemap(SS.L), 0.0f); return; } OutLuminance = 0.0f; return; } // All-at-once time of day visualization. DebugSize = float2(1024.0f, TimeOfDayHeight); DebugPos = float2(70.0f, ViewPortHeight - DebugSize.y - Margin); // Adaptive TimeOfDay count const float DesiredTimeOfDayWidth = 128.0f; float NumTimeOfDay = floor((ViewPortWidth - DebugPos.x) / DesiredTimeOfDayWidth); DebugSize.x = NumTimeOfDay * DesiredTimeOfDayWidth; if (all(PixPos < (DebugPos + DebugSize)) && all(PixPos > DebugPos)) { float2 UV = clamp(PixPos - DebugPos, 0.0f, DebugSize) / DebugSize; float CurrentTimeOfDay = floor(UV.x*NumTimeOfDay) / NumTimeOfDay; UV.x = frac(UV.x*NumTimeOfDay); float LightHorizonAngle = -0.05f + CurrentTimeOfDay * CurrentTimeOfDay * PI * 0.5f; float3 LightDir = float3(cos(LightHorizonAngle), 0.0f, sin(LightHorizonAngle)); float ViewLightAngleCos = cos(PI * UV.x); float ViewLightAngleSin = sqrt(1.0f - ViewLightAngleCos * ViewLightAngleCos); float ViewHorizonCos = cos(0.5f * PI * (1.0f-UV.y)); float ViewHorizonSin = sqrt(1.0f - ViewHorizonCos * ViewHorizonCos); float3 WorldDir = float3( ViewHorizonCos * ViewLightAngleCos, ViewHorizonCos * ViewLightAngleSin, ViewHorizonSin ); SingleScatteringResult SS = IntegrateSingleScatteredLuminance( SVPos, WorldPos, WorldDir, Ground, Sampling, DeviceZ, MieRayPhase, LightDir, NullLightDirection, View.AtmosphereLightIlluminanceOuterSpace[0].rgb * SkyAtmosphere.SkyAndAerialPerspectiveLuminanceFactor, NullLightIlluminance, AerialPespectiveViewDistanceScale); OutLuminance = float4(SimpleTonemap(SS.L), 0.0f); return; } // Atmosphere density distribution. DebugSize = float2(ViewPortWidth * 0.2f, ViewPortHeight * 0.7f); DebugPos = float2(ViewPortWidth - DebugSize.x - 2.0f, ViewPortHeight * 0.1f); if (all(PixPos < (DebugPos + DebugSize)) && all(PixPos > DebugPos)) { float2 UV = clamp(PixPos - DebugPos, 0.0f, DebugSize) / DebugSize; UV.y = 1.0f - UV.y; UV = UV.yx; float3 Color = float3(pow(1.0f-UV, 200.0f), 0.0f); const float TotMax = 0.01f; // Cosntant max extinction is less confusing float3 SampleWorldPos = float3(0.0f, 0.0f, Atmosphere.BottomRadiusKm + UV.x * (Atmosphere.TopRadiusKm - Atmosphere.BottomRadiusKm)); MediumSampleRGB Sample = SampleAtmosphereMediumRGB(SampleWorldPos); // Curves represent the maximum contribution as extinction (no differenciation between scattering or absorption) float3 Curves = float3( Mean3(Sample.ExtinctionRay) / TotMax, Mean3(Sample.ExtinctionMie) / TotMax, Mean3(Sample.ExtinctionOzo) / TotMax ); Curves = saturate((Curves - UV.y)*100000.0f); OutLuminance = float4(Curves, 0.0f); // No exposure for constant visual return; } // Brown ground DebugSize.y += 15.0f; if (all(PixPos < (DebugPos + DebugSize)) && all(PixPos > DebugPos)) { OutLuminance = float4(float3(0.125f, 0.05f, 0.005f), 0.0f); // No exposure for constant visual return; } clip(-1.0f); OutLuminance = float4(0.0f, 0.0f, 0.0f, 1.0f); return; } //////////////////////////////////////////////////////////// // Editor //////////////////////////////////////////////////////////// #ifdef SHADER_EDITOR_HUD #include "MiniFontCommon.ush" void RenderSkyAtmosphereEditorHudPS( in float4 SVPos : SV_POSITION, out float4 OutLuminance : SV_Target0) { int2 PixelPos = SVPos.xy; PixelPos = PixelPos % int2(512, 256); if (!(PixelPos.x < 440 && PixelPos.y < 30)) { clip(-1.0); OutLuminance = float4(1,0,0, 1); return; } const int StartX = 10; int2 Cursor = int2(StartX, 10); float3 OutColor = 0.0f; const float3 TextColor = float3(1, 0.75, 0.25); #define P(x) PrintCharacter(PixelPos, OutColor, TextColor, Cursor, x); #define SPACE PrintCharacter(PixelPos, OutColor, TextColor, Cursor, _SPC_); P(_Y_)P(_O_)P(_U_)P(_R_) SPACE P(_S_)P(_C_)P(_E_)P(_N_)P(_E_) SPACE P(_C_)P(_O_)P(_N_)P(_T_)P(_A_)P(_I_)P(_N_)P(_S_) SPACE P(_A_) SPACE P(_S_)P(_K_)P(_Y_)P(_D_)P(_O_)P(_M_)P(_E_) SPACE P(_M_)P(_E_)P(_S_)P(_H_) SPACE P(_W_)P(_I_)P(_T_)P(_H_) SPACE P(_A_) SPACE P(_S_)P(_K_)P(_Y_) SPACE P(_M_)P(_A_)P(_T_)P(_E_)P(_R_)P(_I_)P(_A_)P(_L_)P(_COMMA_) Cursor.x = StartX; Cursor.y += 12; P(_B_)P(_U_)P(_T_) SPACE P(_I_)P(_T_) SPACE P(_D_)P(_O_)P(_E_)P(_S_) SPACE P(_N_)P(_O_)P(_T_) SPACE P(_C_)P(_O_)P(_V_)P(_E_)P(_R_) SPACE P(_T_)P(_H_)P(_A_)P(_T_) SPACE P(_P_)P(_A_)P(_R_)P(_T_) SPACE P(_O_)P(_F_) SPACE P(_T_)P(_H_)P(_E_) SPACE P(_S_)P(_C_)P(_R_)P(_E_)P(_E_)P(_N_)P(_DOT_)P(_DOT_)P(_DOT_) SPACE #undef P #undef SPACE OutLuminance = float4(OutColor,1); return; } #endif