352 lines
14 KiB
HLSL
352 lines
14 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
SkyAtmosphereCommon.usf: Sky and atmosphere common functions,
|
|
e.g. shared with mesh rendering or path tracing.
|
|
=============================================================================*/
|
|
|
|
#pragma once
|
|
|
|
#include "ParticipatingMediaCommon.ush"
|
|
#include "/Engine/Shared/EnvironmentComponentsFlags.h"
|
|
|
|
// Disabled for all mobile platforms, see USkyLightComponent::IsRealTimeCaptureEnabled
|
|
#define AP_SUPPORT_REALTIME_REFLECTION_CAPTURE (FEATURE_LEVEL > FEATURE_LEVEL_ES3_1)
|
|
|
|
// Controlls Atmosphere. struct access as not all shaders have access to thi
|
|
#ifndef AP_SUPPORT_SAMPLE_ATMOSHPERE
|
|
#define AP_SUPPORT_SAMPLE_ATMOSHPERE 1
|
|
#endif
|
|
|
|
// The constants below should match the one in SceneRendering.cpp
|
|
// Kilometers as unit for computations related to the sky and its atmosphere
|
|
#define CM_TO_SKY_UNIT 0.00001f
|
|
#define SKY_UNIT_TO_CM (1.0f/CM_TO_SKY_UNIT)
|
|
// Float accuracy offset in Sky unit (km, so this is 1m). Should match the one in FAtmosphereSetup::ComputeViewData
|
|
#define PLANET_RADIUS_OFFSET 0.001f
|
|
|
|
// Planet radius safe edge to make sure ray does intersect with the atmosphere, for it to traverse the atmosphere. Must match the one in FSceneRenderer::RenderSkyAtmosphereInternal.
|
|
// This is (0.01km/6420km).
|
|
#define PLANET_RADIUS_RATIO_SAFE_EDGE 1.00000155763f
|
|
|
|
|
|
// The number of killometer per slice in the aerial pespective camera volume texture. (assuming a uniform depth distribution)
|
|
#define AP_KM_PER_SLICE 4.0f
|
|
#define AP_KM_PER_SLICE_INV (1.0f / AP_KM_PER_SLICE)
|
|
|
|
|
|
float2 FromUnitToSubUvs(float2 uv, float4 SizeAndInvSize) { return (uv + 0.5f * SizeAndInvSize.zw) * (SizeAndInvSize.xy / (SizeAndInvSize.xy + 1.0f)); }
|
|
float2 FromSubUvsToUnit(float2 uv, float4 SizeAndInvSize) { return (uv - 0.5f * SizeAndInvSize.zw) * (SizeAndInvSize.xy / (SizeAndInvSize.xy - 1.0f)); }
|
|
|
|
|
|
float4 GetAerialPerspectiveLuminanceTransmittance(
|
|
bool ViewIsRealTimeReflectionCapture, float4 CameraAerialPerspectiveVolumeSizeAndInvSize,
|
|
float4 NDC, float3 WorldPositionRelativeToCamera,
|
|
Texture3D<float4> AerialPerspectiveVolumeTexture, SamplerState AerialPerspectiveVolumeTextureSampler,
|
|
float AerialPerspectiveVolumeDepthResolutionInv,
|
|
float AerialPerspectiveVolumeDepthResolution,
|
|
float AerialPerspectiveVolumeStartDepth,
|
|
float AerialPerspectiveVolumeDepthSliceLengthKm,
|
|
float AerialPerspectiveVolumeDepthSliceLengthKmInv,
|
|
float OneOverExposure,
|
|
float NearFadeOutRangeInvDepthKm)
|
|
{
|
|
if (View.RenderingReflectionCaptureMask == 0.0f && !IsSkyAtmosphereRenderedInMain(View.EnvironmentComponentsFlags))
|
|
{
|
|
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
|
}
|
|
|
|
if (IsOrthoProjection())
|
|
{
|
|
float2 SvPosXY = (((NDC.xy * float2(0.5f, -0.5f)) + 0.5f) * View.ViewSizeAndInvSize.xy) + ResolvedView.ViewRectMin.xy;
|
|
WorldPositionRelativeToCamera += (GetTranslatedWorldCameraPosFromView(SvPosXY, true) * CM_TO_SKY_UNIT) + WorldPositionRelativeToCamera;
|
|
}
|
|
|
|
float2 ScreenUv = (NDC.xy / NDC.ww) * float2(0.5f, -0.5f) + 0.5f;
|
|
|
|
float tDepth = max(0.0f, length(WorldPositionRelativeToCamera) - AerialPerspectiveVolumeStartDepth);
|
|
|
|
float LinearSlice = tDepth * AerialPerspectiveVolumeDepthSliceLengthKmInv;
|
|
float LinearW = LinearSlice * AerialPerspectiveVolumeDepthResolutionInv; // Depth slice coordinate in [0,1]
|
|
float NonLinW = sqrt(LinearW); // Squared distribution
|
|
float NonLinSlice = NonLinW * AerialPerspectiveVolumeDepthResolution;
|
|
|
|
const float HalfSliceDepth = 0.70710678118654752440084436210485f; // sqrt(0.5f)
|
|
float Weight = 1.0f;
|
|
if (NonLinSlice < HalfSliceDepth)
|
|
{
|
|
// We multiply by weight to fade to 0 at depth 0. It works for luminance and opacity.
|
|
Weight = saturate(NonLinSlice*NonLinSlice * 2.0f); // Square to have a linear falloff from the change of distribution above
|
|
}
|
|
Weight *= saturate(tDepth * NearFadeOutRangeInvDepthKm);
|
|
|
|
#if AP_SUPPORT_REALTIME_REFLECTION_CAPTURE
|
|
if (ViewIsRealTimeReflectionCapture)
|
|
{
|
|
// We modify ScreenUv to sample the correct AP accordign to 360 reflection AP LUT.
|
|
// This is the inverse of what is in RenderCameraAerialPerspectiveVolumeCS.
|
|
float3 WorldDir = normalize(WorldPositionRelativeToCamera);
|
|
|
|
float SinPhi = WorldDir.z;
|
|
float CosPhi = sqrt(1.0f - SinPhi * SinPhi);
|
|
ScreenUv.y = WorldDir.z * 0.5f + 0.5f;
|
|
|
|
float CosTheta = WorldDir.x / CosPhi;
|
|
float SinTheta = WorldDir.y / CosPhi;
|
|
float Theta = acos(CosTheta);
|
|
Theta = SinTheta < 0.0f ? (PI-Theta) + PI : Theta;
|
|
ScreenUv.x = Theta / (2.0 * PI);
|
|
|
|
ScreenUv = FromUnitToSubUvs(ScreenUv, CameraAerialPerspectiveVolumeSizeAndInvSize);
|
|
}
|
|
#endif
|
|
|
|
float4 AP = Texture3DSampleLevel(AerialPerspectiveVolumeTexture, AerialPerspectiveVolumeTextureSampler, float3(ScreenUv, NonLinW), 0.0f);
|
|
|
|
// Lerp to no contribution near the camera (careful as AP contains transmittance)
|
|
AP.rgb *= Weight;
|
|
AP.a = 1.0 - (Weight * (1.0f - AP.a));
|
|
|
|
// Debug Slices
|
|
#if 0
|
|
AP.rgba *= frac(clamp(NonLinSlice, 0, AerialPerspectiveVolumeDepthResolution));
|
|
AP.r += LinearW <= 0.0f ? 0.5f : 0.0f;
|
|
AP.g += LinearW >= 1.0f ? 0.5f : 0.0f;
|
|
AP.b += Weight < 1.0f ? 0.2f+0.2f*Weight : 0.0f;
|
|
#endif
|
|
|
|
AP.rgb *= OneOverExposure;
|
|
|
|
return AP;
|
|
}
|
|
|
|
float4 GetAerialPerspectiveLuminanceTransmittanceWithFogOver(
|
|
bool ViewIsRealTimeReflectionCapture, float4 CameraAerialPerspectiveVolumeSizeAndInvSize,
|
|
float4 NDC, float3 WorldPositionRelativeToCamera,
|
|
Texture3D<float4> AerialPerspectiveVolumeTexture, SamplerState AerialPerspectiveVolumeTextureSampler,
|
|
float AerialPerspectiveVolumeDepthResolutionInv,
|
|
float AerialPerspectiveVolumeDepthResolution,
|
|
float AerialPerspectiveVolumeStartDepth,
|
|
float AerialPerspectiveVolumeDepthSliceLengthKm,
|
|
float AerialPerspectiveVolumeDepthSliceLengthKmInv,
|
|
float OneOverExposure, float4 FogToApplyOver)
|
|
{
|
|
const float NearFadeOutRangeInvDepthKm = 1.0 / 0.00001f; // 1 centimeter fade region
|
|
float4 AP = GetAerialPerspectiveLuminanceTransmittance(
|
|
ViewIsRealTimeReflectionCapture, CameraAerialPerspectiveVolumeSizeAndInvSize,
|
|
NDC, WorldPositionRelativeToCamera,
|
|
AerialPerspectiveVolumeTexture, AerialPerspectiveVolumeTextureSampler,
|
|
AerialPerspectiveVolumeDepthResolutionInv,
|
|
AerialPerspectiveVolumeDepthResolution,
|
|
AerialPerspectiveVolumeStartDepth,
|
|
AerialPerspectiveVolumeDepthSliceLengthKm,
|
|
AerialPerspectiveVolumeDepthSliceLengthKmInv,
|
|
OneOverExposure,
|
|
NearFadeOutRangeInvDepthKm);
|
|
|
|
float4 FinalFog;
|
|
// Apply any other fog OVER aerial perspective because AP is usually optically thiner.
|
|
FinalFog.rgb = FogToApplyOver.rgb + AP.rgb * FogToApplyOver.a;
|
|
// And combine both transmittance.
|
|
FinalFog.a = FogToApplyOver.a * AP.a;
|
|
|
|
return FinalFog;
|
|
}
|
|
|
|
void fromTransmittanceLutUVs(
|
|
out float ViewHeight, out float ViewZenithCosAngle,
|
|
in float BottomRadius, in float TopRadius, in float2 UV)
|
|
{
|
|
float Xmu = UV.x;
|
|
float Xr = UV.y;
|
|
|
|
float H = sqrt(TopRadius * TopRadius - BottomRadius * BottomRadius);
|
|
float Rho = H * Xr;
|
|
ViewHeight = sqrt(Rho * Rho + BottomRadius * BottomRadius);
|
|
|
|
float Dmin = TopRadius - ViewHeight;
|
|
float Dmax = Rho + H;
|
|
float D = Dmin + Xmu * (Dmax - Dmin);
|
|
ViewZenithCosAngle = D == 0.0f ? 1.0f : (H * H - Rho * Rho - D * D) / (2.0f * ViewHeight * D);
|
|
ViewZenithCosAngle = clamp(ViewZenithCosAngle, -1.0f, 1.0f);
|
|
}
|
|
|
|
|
|
void getTransmittanceLutUvs(
|
|
in float viewHeight, in float viewZenithCosAngle, in float BottomRadius, in float TopRadius,
|
|
out float2 UV)
|
|
{
|
|
float H = sqrt(max(0.0f, TopRadius * TopRadius - BottomRadius * BottomRadius));
|
|
float Rho = sqrt(max(0.0f, viewHeight * viewHeight - BottomRadius * BottomRadius));
|
|
|
|
float Discriminant = viewHeight * viewHeight * (viewZenithCosAngle * viewZenithCosAngle - 1.0f) + TopRadius * TopRadius;
|
|
float D = max(0.0f, (-viewHeight * viewZenithCosAngle + sqrt(Discriminant))); // Distance to atmosphere boundary
|
|
|
|
float Dmin = TopRadius - viewHeight;
|
|
float Dmax = Rho + H;
|
|
float Xmu = (D - Dmin) / (Dmax - Dmin);
|
|
float Xr = Rho / H;
|
|
|
|
UV = float2(Xmu, Xr);
|
|
//UV = float2(fromUnitToSubUvs(UV.x, TRANSMITTANCE_TEXTURE_WIDTH), fromUnitToSubUvs(UV.y, TRANSMITTANCE_TEXTURE_HEIGHT)); // No real impact so off
|
|
}
|
|
|
|
void SkyViewLutParamsToUv(
|
|
in bool IntersectGround, in float ViewZenithCosAngle, in float3 ViewDir, in float ViewHeight, in float BottomRadius, in float4 SkyViewLutSizeAndInvSize,
|
|
out float2 UV)
|
|
{
|
|
float Vhorizon = sqrt(ViewHeight * ViewHeight - BottomRadius * BottomRadius);
|
|
float CosBeta = Vhorizon / ViewHeight; // GroundToHorizonCos
|
|
float Beta = acosFast4(CosBeta);
|
|
float ZenithHorizonAngle = PI - Beta;
|
|
float ViewZenithAngle = acosFast4(ViewZenithCosAngle);
|
|
|
|
if (!IntersectGround)
|
|
{
|
|
float Coord = ViewZenithAngle / ZenithHorizonAngle;
|
|
Coord = 1.0f - Coord;
|
|
Coord = sqrt(Coord);
|
|
Coord = 1.0f - Coord;
|
|
UV.y = Coord * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
float Coord = (ViewZenithAngle - ZenithHorizonAngle) / Beta;
|
|
Coord = sqrt(Coord);
|
|
UV.y = Coord * 0.5f + 0.5f;
|
|
}
|
|
|
|
{
|
|
UV.x = (atan2Fast(-ViewDir.y, -ViewDir.x) + PI) / (2.0f * PI);
|
|
}
|
|
|
|
// Constrain uvs to valid sub texel range (avoid zenith derivative issue making LUT usage visible)
|
|
UV = FromUnitToSubUvs(UV, SkyViewLutSizeAndInvSize);
|
|
}
|
|
|
|
float3x3 GetSkyViewLutReferential(in float4x4 ViewSkyViewLutReferential)
|
|
{
|
|
float3x3 SkyViewLutReferential = (float3x3) ViewSkyViewLutReferential;
|
|
return SkyViewLutReferential;
|
|
}
|
|
|
|
float3 GetAtmosphereTransmittance(
|
|
float3 PlanetCenterToWorldPos, float3 WorldDir, float BottomRadius, float TopRadius,
|
|
Texture2D<float4> TransmittanceLutTexture, SamplerState TransmittanceLutTextureSampler)
|
|
{
|
|
// For each view height entry, transmittance is only stored from zenith to horizon. Earth shadow is not accounted for.
|
|
// It does not contain earth shadow in order to avoid texel linear interpolation artefact when LUT is low resolution.
|
|
// As such, at the most shadowed point of the LUT when close to horizon, pure black with earth shadow is never hit.
|
|
// That is why we analytically compute the virtual planet shadow here.
|
|
const float2 Sol = RayIntersectSphere(PlanetCenterToWorldPos, WorldDir, float4(float3(0.0f, 0.0f, 0.0f), BottomRadius));
|
|
if (Sol.x > 0.0f || Sol.y > 0.0f)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
const float PHeight = length(PlanetCenterToWorldPos);
|
|
const float3 UpVector = PlanetCenterToWorldPos / PHeight;
|
|
const float LightZenithCosAngle = dot(WorldDir, UpVector);
|
|
float2 TransmittanceLutUv;
|
|
getTransmittanceLutUvs(PHeight, LightZenithCosAngle, BottomRadius, TopRadius, TransmittanceLutUv);
|
|
const float3 TransmittanceToLight = Texture2DSampleLevel(TransmittanceLutTexture, TransmittanceLutTextureSampler, TransmittanceLutUv, 0.0f).rgb;
|
|
return TransmittanceToLight;
|
|
}
|
|
|
|
float3 GetLightDiskLuminance(
|
|
float3 PlanetCenterToWorldPos, float3 WorldDir, float BottomRadius, float TopRadius,
|
|
Texture2D<float4> TransmittanceLutTexture, SamplerState TransmittanceLutTextureSampler,
|
|
float3 AtmosphereLightDirection, float AtmosphereLightDiscCosHalfApexAngle, float3 AtmosphereLightDiscLuminance)
|
|
{
|
|
const float ViewDotLight = dot(WorldDir, AtmosphereLightDirection);
|
|
const float CosHalfApex = AtmosphereLightDiscCosHalfApexAngle;
|
|
if (ViewDotLight > CosHalfApex)
|
|
{
|
|
const float3 TransmittanceToLight = GetAtmosphereTransmittance(
|
|
PlanetCenterToWorldPos, WorldDir, BottomRadius, TopRadius, TransmittanceLutTexture, TransmittanceLutTextureSampler);
|
|
|
|
// Soften out the sun disk to avoid bloom flickering at edge. The soften is applied on the outer part of the disk.
|
|
const float SoftEdge = saturate(2.0f * (ViewDotLight - CosHalfApex) / (1.0f - CosHalfApex));
|
|
|
|
return TransmittanceToLight * AtmosphereLightDiscLuminance * SoftEdge;
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
#if AP_SUPPORT_SAMPLE_ATMOSHPERE
|
|
////////////////////////////////////////////////////////////
|
|
// Participating medium properties
|
|
////////////////////////////////////////////////////////////
|
|
|
|
float3 GetAlbedo(float3 Scattering, float3 Extinction)
|
|
{
|
|
return Scattering / max(0.001f, Extinction);
|
|
}
|
|
|
|
struct MediumSampleRGB
|
|
{
|
|
float3 Scattering;
|
|
float3 Absorption;
|
|
float3 Extinction;
|
|
|
|
float3 ScatteringMie;
|
|
float3 AbsorptionMie;
|
|
float3 ExtinctionMie;
|
|
|
|
float3 ScatteringRay;
|
|
float3 AbsorptionRay;
|
|
float3 ExtinctionRay;
|
|
|
|
float3 ScatteringOzo;
|
|
float3 AbsorptionOzo;
|
|
float3 ExtinctionOzo;
|
|
|
|
float3 Albedo;
|
|
};
|
|
|
|
// If this is changed, please also update USkyAtmosphereComponent::GetTransmittance
|
|
MediumSampleRGB SampleAtmosphereMediumRGB(in float3 WorldPos)
|
|
{
|
|
const float SampleHeight = max(0.0, (length(WorldPos) - Atmosphere.BottomRadiusKm));
|
|
|
|
const float DensityMie = exp(Atmosphere.MieDensityExpScale * SampleHeight);
|
|
|
|
const float DensityRay = exp(Atmosphere.RayleighDensityExpScale * SampleHeight);
|
|
|
|
const float DensityOzo = SampleHeight < Atmosphere.AbsorptionDensity0LayerWidth ?
|
|
saturate(Atmosphere.AbsorptionDensity0LinearTerm * SampleHeight + Atmosphere.AbsorptionDensity0ConstantTerm) : // We use saturate to allow the user to create plateau, and it is free on GCN.
|
|
saturate(Atmosphere.AbsorptionDensity1LinearTerm * SampleHeight + Atmosphere.AbsorptionDensity1ConstantTerm);
|
|
|
|
MediumSampleRGB s;
|
|
|
|
s.ScatteringMie = DensityMie * Atmosphere.MieScattering.rgb;
|
|
s.AbsorptionMie = DensityMie * Atmosphere.MieAbsorption.rgb;
|
|
s.ExtinctionMie = DensityMie * Atmosphere.MieExtinction.rgb;
|
|
|
|
s.ScatteringRay = DensityRay * Atmosphere.RayleighScattering.rgb;
|
|
s.AbsorptionRay = 0.0f;
|
|
s.ExtinctionRay = s.ScatteringRay + s.AbsorptionRay;
|
|
|
|
s.ScatteringOzo = 0.0f;
|
|
s.AbsorptionOzo = DensityOzo * Atmosphere.AbsorptionExtinction.rgb;
|
|
s.ExtinctionOzo = s.ScatteringOzo + s.AbsorptionOzo;
|
|
|
|
s.Scattering = s.ScatteringMie + s.ScatteringRay + s.ScatteringOzo;
|
|
s.Absorption = s.AbsorptionMie + s.AbsorptionRay + s.AbsorptionOzo;
|
|
s.Extinction = s.ExtinctionMie + s.ExtinctionRay + s.ExtinctionOzo;
|
|
s.Albedo = GetAlbedo(s.Scattering, s.Extinction);
|
|
|
|
return s;
|
|
}
|
|
#endif //AP_SUPPORT_SAMPLE_ATMOSHPERE
|
|
|
|
float3 GetViewDistanceSkyLightColor()
|
|
{
|
|
#if SHADING_PATH_MOBILE && VERTEXSHADER
|
|
// Some mobile platforms cannot sample structured buffer in vertex shaders (height fog) so in this case a regular buffer is used.
|
|
return View.MobileDistantSkyLightLutBufferSRV[0].rgb;
|
|
#else
|
|
// Use a structured buffer for scalar loads.
|
|
return View.DistantSkyLightLutBufferSRV[0].rgb;
|
|
#endif
|
|
} |