Files
UnrealEngineUWP/Engine/Shaders/Private/HeightFogCommon.ush
jeremy moore 79397409f9 Fix potential nan in fog calculations.
Seen when doing offscreen tile mesh rendering with SM6.
#rb sebastien.hillaire
#preflight skip

#ROBOMERGE-AUTHOR: jeremy.moore
#ROBOMERGE-SOURCE: CL 20007294 via CL 20007312 via CL 20007318
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690)

[CL 20007642 by jeremy moore in ue5-main branch]
2022-05-02 09:18:56 -04:00

286 lines
14 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
HeightFogCommon.usf:
=============================================================================*/
#ifndef PERMUTATION_SUPPORT_FOG_INSCATTERING_TEXTURE
#define PERMUTATION_SUPPORT_FOG_INSCATTERING_TEXTURE (FEATURE_LEVEL >= FEATURE_LEVEL_SM4)
#endif
#ifndef PERMUTATION_SUPPORT_FOG_DIRECTIONAL_LIGHT_INSCATTERING
#define PERMUTATION_SUPPORT_FOG_DIRECTIONAL_LIGHT_INSCATTERING 1
#endif
#ifndef PERMUTATION_SUPPORT_VOLUMETRIC_FOG
#define PERMUTATION_SUPPORT_VOLUMETRIC_FOG (FEATURE_LEVEL >= FEATURE_LEVEL_SM4)
#endif
#ifndef PERMUTATION_SUPPORT_FOG_START_DISTANCE
#define PERMUTATION_SUPPORT_FOG_START_DISTANCE 1
#endif
#ifndef PERMUTATION_SUPPORT_FOG_SECOND_TERM
#define PERMUTATION_SUPPORT_FOG_SECOND_TERM 1
#endif
#if INSTANCED_STEREO
#if MATERIALBLENDING_ANY_TRANSLUCENT
#define FogStructISR TranslucentBasePass.Shared.FogISR
#else
#define FogStructISR OpaqueBasePass.Shared.FogISR
#endif
#endif
static const float FLT_EPSILON = 0.001f;
static const float FLT_EPSILON2 = 0.01f;
// FogStruct.ExponentialFogParameters: FogDensity * exp2(-FogHeightFalloff * (CameraWorldPosition.z - FogHeight)) in x, FogHeightFalloff in y, MaxWorldObserverHeight in z, StartDistance in w.
// FogStruct.ExponentialFogParameters2: FogDensitySecond * exp2(-FogHeightFalloffSecond * (CameraWorldPosition.z - FogHeightSecond)) in x, FogHeightFalloffSecond in y, FogDensitySecond in z, FogHeightSecond in w
// FogStruct.ExponentialFogParameters3: FogDensity in x, FogHeight in y, whether to use cubemap fog color in z, FogCutoffDistance in w.
// FogStruct.FogInscatteringTextureParameters: mip distance scale in x, bias in y, num mips in z
float3 ComputeInscatteringColor(float3 CameraToReceiver, float CameraToReceiverLength)
{
half3 Inscattering = FogStruct.ExponentialFogColorParameter.xyz;
#if PERMUTATION_SUPPORT_FOG_INSCATTERING_TEXTURE
BRANCH
if (FogStruct.ExponentialFogParameters3.z > 0)
{
float FadeAlpha = saturate(CameraToReceiverLength * FogStruct.FogInscatteringTextureParameters.x + FogStruct.FogInscatteringTextureParameters.y);
float3 CubemapLookupVector = CameraToReceiver;
// Rotate around Z axis
CubemapLookupVector.xy = float2(dot(CubemapLookupVector.xy, float2(FogStruct.SinCosInscatteringColorCubemapRotation.y, -FogStruct.SinCosInscatteringColorCubemapRotation.x)), dot(CubemapLookupVector.xy, FogStruct.SinCosInscatteringColorCubemapRotation.xy));
float3 DirectionalColor = TextureCubeSampleLevel(FogStruct.FogInscatteringColorCubemap, FogStruct.FogInscatteringColorSampler, CubemapLookupVector, 0).xyz;
float3 NonDirectionalColor = TextureCubeSampleLevel(FogStruct.FogInscatteringColorCubemap, FogStruct.FogInscatteringColorSampler, CubemapLookupVector, FogStruct.FogInscatteringTextureParameters.z).xyz;
Inscattering *= lerp(NonDirectionalColor, DirectionalColor, FadeAlpha);
}
#endif
#if PROJECT_SUPPORT_SKY_ATMOSPHERE_AFFECTS_HEIGHFOG
Inscattering += FogStruct.SkyAtmosphereAmbientContributionColorScale * View.SkyAtmosphereHeightFogContribution * Texture2DSampleLevel(View.DistantSkyLightLutTexture, View.DistantSkyLightLutTextureSampler, float2(0.5f, 0.5f), 0.0f).rgb;
#endif
return Inscattering;
}
// Calculate the line integral of the ray from the camera to the receiver position through the fog density function
// The exponential fog density function is d = GlobalDensity * exp(-HeightFalloff * z)
float CalculateLineIntegralShared(float FogHeightFalloff, float RayDirectionZ, float RayOriginTerms)
{
float Falloff = max(-127.0f, FogHeightFalloff * RayDirectionZ); // if it's lower than -127.0, then exp2() goes crazy in OpenGL's GLSL.
float LineIntegral = ( 1.0f - exp2(-Falloff) ) / Falloff;
float LineIntegralTaylor = log(2.0) - ( 0.5 * Pow2( log(2.0) ) ) * Falloff; // Taylor expansion around 0
return RayOriginTerms * ( abs(Falloff) > FLT_EPSILON2 ? LineIntegral : LineIntegralTaylor );
}
// @param WorldPositionRelativeToCamera = WorldPosition - InCameraPosition
half4 GetExponentialHeightFog(float3 WorldPositionRelativeToCamera, float ExcludeDistance)
{
const half MinFogOpacity = FogStruct.ExponentialFogColorParameter.w;
const float MaxWorldObserverHeight = FogStruct.ExponentialFogParameters.z;
const float3 WorldObserverOrigin = float3(LWCHackToFloat(PrimaryView.WorldCameraOrigin).xy, min(LWCHackToFloat(PrimaryView.WorldCameraOrigin).z, MaxWorldObserverHeight)); // Clamp z to max height
float3 CameraToReceiver = WorldPositionRelativeToCamera;
CameraToReceiver.z += LWCHackToFloat(PrimaryView.WorldCameraOrigin).z - WorldObserverOrigin.z; // Compensate this vector for clamping the observer height
float CameraToReceiverLengthSqr = dot(CameraToReceiver, CameraToReceiver);
float CameraToReceiverLengthInv = rsqrt(max(CameraToReceiverLengthSqr, 0.00000001f));
float CameraToReceiverLength = CameraToReceiverLengthSqr * CameraToReceiverLengthInv;
half3 CameraToReceiverNormalized = CameraToReceiver * CameraToReceiverLengthInv;
float RayOriginTerms = FogStruct.ExponentialFogParameters.x;
float RayOriginTermsSecond = FogStruct.ExponentialFogParameters2.x;
float RayLength = CameraToReceiverLength;
float RayDirectionZ = CameraToReceiver.z;
#if USE_GLOBAL_CLIP_PLANE
BRANCH
// While rendering a planar reflection with a clip plane, we must compute analytical fog using a camera path starting from the plane, rather than the virtual camera origin
if (dot(View.GlobalClippingPlane.xyz, 1) > 0.0f)
{
float CameraOriginPlaneDistance = dot(View.GlobalClippingPlane, float4(WorldObserverOrigin + LWCHackToFloat(PrimaryView.PreViewTranslation), 1));
float PlaneIntersectionTime = -CameraOriginPlaneDistance / dot(CameraToReceiver, View.GlobalClippingPlane.xyz);
// Only modify the start distance if the reflection plane is between the camera and receiver point
if (PlaneIntersectionTime > 0 && PlaneIntersectionTime < 1)
{
ExcludeDistance = max(ExcludeDistance, PlaneIntersectionTime * CameraToReceiverLength);
}
}
#endif
// Factor in StartDistance
#if PERMUTATION_SUPPORT_FOG_START_DISTANCE
ExcludeDistance = max(ExcludeDistance, FogStruct.ExponentialFogParameters.w);
if (ExcludeDistance > 0)
{
float ExcludeIntersectionTime = ExcludeDistance * CameraToReceiverLengthInv;
float CameraToExclusionIntersectionZ = ExcludeIntersectionTime * CameraToReceiver.z;
float ExclusionIntersectionZ = WorldObserverOrigin.z + CameraToExclusionIntersectionZ;
float ExclusionIntersectionToReceiverZ = CameraToReceiver.z - CameraToExclusionIntersectionZ;
// Calculate fog off of the ray starting from the exclusion distance, instead of starting from the camera
RayLength = (1.0f - ExcludeIntersectionTime) * CameraToReceiverLength;
RayDirectionZ = ExclusionIntersectionToReceiverZ;
float Exponent = max(-127.0f, FogStruct.ExponentialFogParameters.y * (ExclusionIntersectionZ - FogStruct.ExponentialFogParameters3.y));
RayOriginTerms = FogStruct.ExponentialFogParameters3.x * exp2(-Exponent);
float ExponentSecond = max(-127.0f, FogStruct.ExponentialFogParameters2.y * (ExclusionIntersectionZ - FogStruct.ExponentialFogParameters2.w));
RayOriginTermsSecond = FogStruct.ExponentialFogParameters2.z * exp2(-ExponentSecond);
}
#endif
// Calculate the "shared" line integral (this term is also used for the directional light inscattering) by adding the two line integrals together (from two different height falloffs and densities)
float ExponentialHeightLineIntegralShared = CalculateLineIntegralShared(FogStruct.ExponentialFogParameters.y, RayDirectionZ, RayOriginTerms);
#if PERMUTATION_SUPPORT_FOG_SECOND_TERM
ExponentialHeightLineIntegralShared+= CalculateLineIntegralShared(FogStruct.ExponentialFogParameters2.y, RayDirectionZ, RayOriginTermsSecond);
#endif
float ExponentialHeightLineIntegral = ExponentialHeightLineIntegralShared * RayLength;
half3 InscatteringColor = ComputeInscatteringColor(CameraToReceiver, CameraToReceiverLength);
half3 DirectionalInscattering = 0;
#if PERMUTATION_SUPPORT_FOG_DIRECTIONAL_LIGHT_INSCATTERING
// if InscatteringLightDirection.w is negative then it's disabled, otherwise it holds directional inscattering start distance
BRANCH
if (FogStruct.InscatteringLightDirection.w >= 0
#if PERMUTATION_SUPPORT_FOG_INSCATTERING_TEXTURE
&& FogStruct.ExponentialFogParameters3.z == 0
#endif
)
{
#if PROJECT_SUPPORT_SKY_ATMOSPHERE_AFFECTS_HEIGHFOG
const float UniformPhaseFunction = 1.0f / (4.0f*PI);
half3 DirectionalInscatteringColor;
half3 DirectionalLightInscattering;
// No need to test View.AtmosphereLightIlluminanceOnGroundPostTransmittance[0].a because InscatteringLightDirection.w above is doing the same test already.
DirectionalInscatteringColor = FogStruct.DirectionalInscatteringColor.xyz + View.SkyAtmosphereHeightFogContribution * View.AtmosphereLightIlluminanceOnGroundPostTransmittance[0].rgb * UniformPhaseFunction;
DirectionalLightInscattering = DirectionalInscatteringColor * pow(saturate(dot(CameraToReceiverNormalized, View.AtmosphereLightDirection[0].xyz)), FogStruct.DirectionalInscatteringColor.w);
if (View.AtmosphereLightIlluminanceOnGroundPostTransmittance[1].a > 0.0f) // Skip DirectionalInscatteringColor on the second light when disabled.
{
DirectionalInscatteringColor = FogStruct.DirectionalInscatteringColor.xyz + View.SkyAtmosphereHeightFogContribution * View.AtmosphereLightIlluminanceOnGroundPostTransmittance[1].rgb * UniformPhaseFunction;
DirectionalLightInscattering += DirectionalInscatteringColor * pow(saturate(dot(CameraToReceiverNormalized, View.AtmosphereLightDirection[1].xyz)), FogStruct.DirectionalInscatteringColor.w);
}
#else
// Setup a cosine lobe around the light direction to approximate inscattering from the directional light off of the ambient haze;
half3 DirectionalLightInscattering = FogStruct.DirectionalInscatteringColor.xyz * pow(saturate(dot(CameraToReceiverNormalized, FogStruct.InscatteringLightDirection.xyz)), FogStruct.DirectionalInscatteringColor.w);
#endif
// Calculate the line integral of the eye ray through the haze, using a special starting distance to limit the inscattering to the distance
float DirectionalInscatteringStartDistance = FogStruct.InscatteringLightDirection.w;
float DirExponentialHeightLineIntegral = ExponentialHeightLineIntegralShared * max(RayLength - DirectionalInscatteringStartDistance, 0.0f);
// Calculate the amount of light that made it through the fog using the transmission equation
half DirectionalInscatteringFogFactor = saturate(exp2(-DirExponentialHeightLineIntegral));
// Final inscattering from the light
DirectionalInscattering = DirectionalLightInscattering * (1 - DirectionalInscatteringFogFactor);
}
#endif
// Calculate the amount of light that made it through the fog using the transmission equation
half ExpFogFactor = max(saturate(exp2(-ExponentialHeightLineIntegral)), MinFogOpacity);
FLATTEN
if (FogStruct.ExponentialFogParameters3.w > 0 && CameraToReceiverLength > FogStruct.ExponentialFogParameters3.w)
{
ExpFogFactor = 1;
DirectionalInscattering = 0;
}
// Fog color is unused when additive / modulate blend modes are active.
#if (MATERIALBLENDING_ADDITIVE || MATERIALBLENDING_MODULATE)
half3 FogColor = 0.0;
#else
half3 FogColor = (InscatteringColor) * (1 - ExpFogFactor) + DirectionalInscattering;
#endif
return half4(FogColor, ExpFogFactor);
}
half4 CalculateHeightFog(float3 WorldPositionRelativeToCamera)
{
float ExcludeDistance = 0;
#if PERMUTATION_SUPPORT_VOLUMETRIC_FOG
// Volumetric fog covers up to MaxDistance along ViewZ, exclude analytical fog from this range
float CosAngle = dot(normalize(WorldPositionRelativeToCamera), View.ViewForward);
float InvCosAngle = (CosAngle > FLT_EPSILON) ? rcp(CosAngle) : 0;
ExcludeDistance = View.VolumetricFogMaxDistance * InvCosAngle;
#endif
float4 FogInscatteringAndOpacity = GetExponentialHeightFog(WorldPositionRelativeToCamera, ExcludeDistance);
return FogInscatteringAndOpacity;
}
#if SUPPORTS_INDEPENDENT_SAMPLERS
#define SharedIntegratedLightScatteringSampler View.SharedBilinearClampedSampler
#else
#define SharedIntegratedLightScatteringSampler FogStruct.IntegratedLightScatteringSampler
#endif
float4 CombineVolumetricFog(float4 GlobalFog, float3 VolumeUV, uint EyeIndex, float SceneDepth)
{
float4 VolumetricFogLookup = float4(0, 0, 0, 1);
#if PERMUTATION_SUPPORT_VOLUMETRIC_FOG
float VolFogStartDistance = 0.0f;
if (FogStruct.ApplyVolumetricFog > 0)
{
#if INSTANCED_STEREO
if (EyeIndex == 0)
{
VolFogStartDistance = FogStruct.VolumetricFogStartDistance;
VolumetricFogLookup = Texture3DSampleLevel(FogStruct.IntegratedLightScattering, SharedIntegratedLightScatteringSampler, VolumeUV, 0);
}
else
{
VolFogStartDistance = FogStructISR.VolumetricFogStartDistance;
VolumetricFogLookup = Texture3DSampleLevel(FogStructISR.IntegratedLightScattering, SharedIntegratedLightScatteringSampler, VolumeUV, 0);
}
#else
VolFogStartDistance = FogStruct.VolumetricFogStartDistance;
VolumetricFogLookup = Texture3DSampleLevel(FogStruct.IntegratedLightScattering, SharedIntegratedLightScatteringSampler, VolumeUV, 0);
#endif
}
// Do not apply the Froxel volumetric texture in front of the fog start distance. (the soft fading occur in FinalIntegrationCS).
// We go with a quickly increasing step function because the soft fade in from start distance occurs in FinalIntegrationCS.
VolumetricFogLookup = lerp(float4(0, 0, 0, 1), VolumetricFogLookup, saturate((SceneDepth - VolFogStartDistance) * 100000000.0f));
#endif
// Visualize depth distribution
//VolumetricFogLookup.rgb += .1f * frac(min(ZSlice, 1.0f) / View.VolumetricFogInvGridSize.z);
return float4(VolumetricFogLookup.rgb + GlobalFog.rgb * VolumetricFogLookup.a, VolumetricFogLookup.a * GlobalFog.a);
}
float ComputeNormalizedZSliceFromDepth(float SceneDepth)
{
return log2(SceneDepth * View.VolumetricFogGridZParams.x + View.VolumetricFogGridZParams.y) * View.VolumetricFogGridZParams.z * View.VolumetricFogInvGridSize.z;
}
float3 ComputeVolumeUVFromNDC(float4 NDCPosition)
{
NDCPosition.xy /= NDCPosition.w;
return float3(NDCPosition.xy * float2(.5f, -.5f) + .5f, ComputeNormalizedZSliceFromDepth(NDCPosition.w));
}
float3 ComputeVolumeUV_DEPRECATED(float3 WorldPosition, float4x4 WorldToClip)
{
float4 NDCPosition = mul(float4(WorldPosition, 1), WorldToClip);
return ComputeVolumeUVFromNDC(NDCPosition);
}
float3 ComputeVolumeUV(FLWCVector3 WorldPosition, FLWCInverseMatrix WorldToClip)
{
float4 NDCPosition = LWCMultiply(MakeLWCVector4(WorldPosition, 1.0f), WorldToClip);
return ComputeVolumeUVFromNDC(NDCPosition);
}