435 lines
16 KiB
HLSL
435 lines
16 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "../Common.ush"
|
|
|
|
#include "../SHCommon.ush"
|
|
#include "../ParticipatingMediaCommon.ush"
|
|
#include "../ReflectionEnvironmentShared.ush"
|
|
#include "../FastMath.ush"
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Local Fog Volume Data
|
|
|
|
struct FLocalFogVolumeGPUInstanceData
|
|
{
|
|
float3x3 PreTranslatedInvTransform;
|
|
float3 TranslatedWorlPos;
|
|
float UniformScale;
|
|
float UniformScaleInv; // Derived from UniformScale
|
|
|
|
float RadialFogExtinction;
|
|
float HeightFogExtinction;
|
|
float HeightFogFalloff;
|
|
float HeightFogOffset;
|
|
|
|
float3 Emissive;
|
|
float3 Albedo;
|
|
float PhaseG;
|
|
};
|
|
|
|
|
|
// Structured buffers cannot be sampled from the vertex shader on some mobile platforms.
|
|
// That is why simple Buffers are used for LocalFogVolumeInstances, because this tech needs to run on mobile platforms.
|
|
|
|
#if defined(LFVStruct)
|
|
|
|
#define LFVInsts LFVStruct.LFV.LocalFogVolumeInstances
|
|
#define LFVTileDataTex LFVStruct.LFV.LocalFogVolumeTileDataTexture
|
|
|
|
#define LFVTileDataResolution LFVStruct.LFV.LocalFogVolumeTileDataTextureResolution
|
|
#define LFVInstcount LFVStruct.LFV.LocalFogVolumeInstanceCount
|
|
#define LFVTilePixelSize LFVStruct.LFV.LocalFogVolumeTilePixelSize
|
|
#define LFVMaxDensityIntoVolumetricFog LFVStruct.LFV.LocalFogVolumeMaxDensityIntoVolumetricFog
|
|
#define LFVRenderInVolumetricFog LFVStruct.LFV.ShouldRenderLocalFogVolumeInVolumetricFog
|
|
#define LFVGlobalStartDistance LFVStruct.LFV.GlobalStartDistance
|
|
#define LFVHalfResTextureSizeAndInvSize LFVStruct.LFV.HalfResTextureSizeAndInvSize
|
|
|
|
#define LFVDirectionalLightColor LFVStruct.LFV.DirectionalLightColor
|
|
#define LFVDirectionalLightDirection LFVStruct.LFV.DirectionalLightDirection
|
|
|
|
#else
|
|
|
|
uint2 LFV_LocalFogVolumeTileDataTextureResolution;
|
|
uint LFV_LocalFogVolumeInstanceCount;
|
|
uint LFV_LocalFogVolumeTilePixelSize;
|
|
float LFV_LocalFogVolumeMaxDensityIntoVolumetricFog;
|
|
uint LFV_ShouldRenderLocalFogVolumeInVolumetricFog;
|
|
float LFV_GlobalStartDistance;
|
|
float4 LFV_HalfResTextureSizeAndInvSize;
|
|
|
|
float3 LFV_DirectionalLightColor;
|
|
float3 LFV_DirectionalLightDirection;
|
|
|
|
Buffer<float4> LFV_LocalFogVolumeInstances;
|
|
Texture2DArray<uint> LFV_LocalFogVolumeTileDataTexture;
|
|
|
|
#define LFVInsts LFV_LocalFogVolumeInstances
|
|
#define LFVTileDataTex LFV_LocalFogVolumeTileDataTexture
|
|
|
|
#define LFVTileDataResolution LFV_LocalFogVolumeTileDataTextureResolution
|
|
#define LFVInstcount LFV_LocalFogVolumeInstanceCount
|
|
#define LFVTilePixelSize LFV_LocalFogVolumeTilePixelSize
|
|
#define LFVMaxDensityIntoVolumetricFog LFV_LocalFogVolumeMaxDensityIntoVolumetricFog
|
|
#define LFVRenderInVolumetricFog LFV_ShouldRenderLocalFogVolumeInVolumetricFog
|
|
#define LFVGlobalStartDistance LFV_GlobalStartDistance
|
|
#define LFVHalfResTextureSizeAndInvSize LFV_HalfResTextureSizeAndInvSize
|
|
|
|
#define LFVDirectionalLightColor LFV_DirectionalLightColor
|
|
#define LFVDirectionalLightDirection LFV_DirectionalLightDirection
|
|
|
|
#endif
|
|
|
|
|
|
FLocalFogVolumeGPUInstanceData GetLocalFogVolumeGPUInstanceData(uint Index)
|
|
{
|
|
FLocalFogVolumeGPUInstanceData Data;
|
|
uint Offset = Index * 3;
|
|
|
|
float4 Data0 = LFVInsts[Offset + 0];
|
|
float4 Data1 = LFVInsts[Offset + 1];
|
|
float4 Data2 = LFVInsts[Offset + 2];
|
|
// See GetLocalFogVolumeViewSortingData for the packing mathod.
|
|
|
|
// Data0
|
|
Data.TranslatedWorlPos = Data0.xyz;
|
|
Data.UniformScale = Data0.w;
|
|
Data.UniformScaleInv = 1.0f / Data.UniformScale;
|
|
|
|
// Data1
|
|
float3 XVec;
|
|
float3 YVec;
|
|
XVec.xy = UnpackFloat2FromUInt(asuint(Data1.x));
|
|
float2 Temp = UnpackFloat2FromUInt(asuint(Data1.y));
|
|
XVec.z = Temp.x;
|
|
YVec.x = Temp.y;
|
|
YVec.yz = UnpackFloat2FromUInt(asuint(Data1.z));
|
|
|
|
float3 ZVec = cross(XVec, YVec);
|
|
|
|
Data.PreTranslatedInvTransform = float3x3(float3(XVec * Data.UniformScaleInv), float3(YVec * Data.UniformScaleInv), float3(ZVec * Data.UniformScaleInv));
|
|
|
|
// Data2
|
|
float3 Data2X = UnpackR11G11B10F(asuint(Data2.x));
|
|
Data.RadialFogExtinction= Data2X.x;
|
|
Data.HeightFogExtinction= Data2X.y;
|
|
Data.HeightFogFalloff = Data2X.z;
|
|
|
|
uint PackEmissiveData = asuint(Data2.y);
|
|
if (PackEmissiveData > 0)
|
|
{
|
|
Data.Emissive = UnpackR11G11B10F(PackEmissiveData);
|
|
}
|
|
else
|
|
{
|
|
Data.Emissive = 0.0f.xxx;
|
|
}
|
|
|
|
float4 Data2Z = UnpackRGBA8(asuint(Data2.z));
|
|
Data.Albedo = Data2Z.rgb;
|
|
Data.PhaseG = Data2Z.a;
|
|
Data.HeightFogOffset = Data2.w;
|
|
|
|
return Data;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Local Fog Volume Tile Data
|
|
|
|
uint2 LFVUnpackTile(uint In)
|
|
{
|
|
return uint2(In & 0xFFFF, In >> 16);
|
|
}
|
|
|
|
uint LFVPackTile(uint2 TileCoord)
|
|
{
|
|
#if COMPILER_SUPPORTS_PACK_INTRINSICS
|
|
return PackUInt2ToUInt(TileCoord.x, TileCoord.y);
|
|
#else
|
|
return TileCoord.x | (TileCoord.y << 16); // assumes 16bit is enough to represent a tiled resolution up to 65,535 :)
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Local Fog Volume Integrals
|
|
|
|
struct FFogData
|
|
{
|
|
float IntegratedLuminanceFactor;
|
|
float Coverage;
|
|
};
|
|
|
|
FFogData LocalFogVolumeEvaluateAnalyticalIntegral(FLocalFogVolumeGPUInstanceData FogInstance, float3 RayStartU, float3 RayDirU, float RayLengthU)
|
|
{
|
|
FFogData FogData;
|
|
|
|
float RadialOpticalDepth = 0.0f;
|
|
float HeightOpticalDepth = 0.0f;
|
|
|
|
if (FogInstance.RadialFogExtinction > 0.0f)
|
|
{
|
|
//////////
|
|
// Radial fog is evaluated according to spatially varying density defined as "Density * (1 - (d*d)/(r*r))" in the unint sphere space, where density is in fact Extinction.
|
|
// An explanation of it can be found in https://iquilezles.org/articles/spheredensity/
|
|
|
|
float3 SphereCenter = 0.0f;
|
|
float3 VolumeCenterToRayO = (RayStartU - SphereCenter);
|
|
|
|
float b = dot(RayDirU, VolumeCenterToRayO);
|
|
float c = dot(VolumeCenterToRayO, VolumeCenterToRayO) - 1.0f;
|
|
float h = b * b - c;
|
|
|
|
if (h >= 0.0)
|
|
{
|
|
h = sqrt(h);
|
|
float Length0 = -b - h;
|
|
float Length1 = -b + h;
|
|
|
|
Length0 = max(Length0, 0.0);
|
|
Length1 = max(Length1, 0.0);
|
|
Length1 = min(Length1, RayLengthU);
|
|
|
|
// Integral of a Extinction that descreases according to square distance from the center of the volume.
|
|
const float Length0Sqr = Length0 * Length0;
|
|
const float Length1Sqr = Length1 * Length1;
|
|
float Integral0 = -(c * Length0 + b * Length0Sqr + Length0Sqr * Length0 / 3.0f);
|
|
float Integral1 = -(c * Length1 + b * Length1Sqr + Length1Sqr * Length1 / 3.0f);
|
|
RadialOpticalDepth = max(0.0, FogInstance.RadialFogExtinction * (Integral1 - Integral0) * (3.0f / 4.0f));
|
|
}
|
|
}
|
|
|
|
if (FogInstance.HeightFogExtinction > 0.0f)
|
|
{
|
|
//////////
|
|
// Height fog is evaluated according to spatially varying density defined as "Density * Exp[-HeightFogFalloff * z]", where density is in fact Extinction.
|
|
// An explanation of it can be found in https://iquilezles.org/articles/fog/.
|
|
|
|
float StartHeight = RayStartU.z - FogInstance.HeightFogOffset; // account for the fog offset.
|
|
|
|
// We are going to divide by RayDirU.z so we need it to be never be 0.
|
|
const float SafeDirThreshold = 0.0001;
|
|
const float SignRayDirUZ = RayDirU.z >= 0.0f ? 1.0f : -1.0f;
|
|
const float SafeRayDirUZ = (abs(RayDirU.z) < SafeDirThreshold) ? SafeDirThreshold * SignRayDirUZ : RayDirU.z;
|
|
|
|
#if 0
|
|
// Original integral, less artefact
|
|
HeightOpticalDepth = (FogInstance.HeightFogExtinction / FogInstance.HeightFogFalloff) * exp(-StartHeight * FogInstance.HeightFogFalloff) * (1.0 - exp(-SafeRayDirUZ * RayLengthU * FogInstance.HeightFogFalloff)) / SafeRayDirUZ;
|
|
#elif 1
|
|
// Reworked to avoid large value as input to exp
|
|
//OpticalDepth = (FogInstance.HeightFogExtinction / FogInstance.HeightFogFalloff) * (exp(-StartHeight * FogInstance.HeightFogFalloff) - exp(-StartHeight * FogInstance.HeightFogFalloff - RayDir.z * RayLength * FogInstance.HeightFogFalloff)) / RayDir.z;
|
|
|
|
// But we use an updated formulation to avoid large input to exp leading to inf and nan.
|
|
float Factor0 = StartHeight * FogInstance.HeightFogFalloff;
|
|
float Factor1 = SafeRayDirUZ * RayLengthU * FogInstance.HeightFogFalloff;
|
|
Factor0 = max(-80.0f, Factor0); // Simply clamp Factor0 to a valid range in order to not have exp explode.
|
|
|
|
HeightOpticalDepth += (FogInstance.HeightFogExtinction / (FogInstance.HeightFogFalloff * SafeRayDirUZ)) * (exp(-Factor0) - exp(-(Factor0 + Factor1)));
|
|
#endif
|
|
|
|
/*
|
|
Proof of the height fog integral equations using Mathematica
|
|
This show that IntegratedLuminanceFactor is really equivalent to coverage in this simple case.
|
|
|
|
(*Density of matter according to height Y*)
|
|
Density [y_] := A * Exp[-B * y]
|
|
|
|
(*Ray position*)
|
|
Ray[t_] := Oz + t * Rz
|
|
|
|
(*Integrate Optical Depth for a distance T*)
|
|
Fog[T_] := Integrate[Density[Ray[t]], {t, 0, T}]
|
|
|
|
(*Optical Depth integration equation*)
|
|
Fog[T]
|
|
|
|
(*Transmittance from Optical Depth*)
|
|
Exp[-Integrate[Density[Ray[t]], {t, 0, T}]]
|
|
|
|
(*Transmittance from a position to the origin of tracing*)
|
|
Trans[T_] := Exp[-Integrate[Density[Ray[t]], {t, 0, T}]]
|
|
|
|
(*Integrate emissive and scattering from height fog, assuming density \
|
|
is extinction and extinction=scattering, thus albedo=1*)
|
|
Integrate[Density[Ray[t]]*EmSc *Trans[t], {t, 0, T}]
|
|
|
|
=> this leads to EmSc - Transmittance * EmSc
|
|
EmSc * (1 - Transmittance)
|
|
EmSc * Coverage
|
|
*/
|
|
}
|
|
|
|
#if 1
|
|
// => Reference path
|
|
// We now combine the two optical depth for the "opacity"=1-transmittance to be combined together.
|
|
// This helps to have soft edges when using height fog. We also account for that when voxelising in the volumetric fog.
|
|
float TR = exp(-RadialOpticalDepth);
|
|
float TH = exp(-HeightOpticalDepth);
|
|
float T = 1 - (1 - TR) * (1 - TH);
|
|
float OpticalDepth = -log(T);
|
|
|
|
// Evaluate transmittance, accounting for the volume scale converted to local unit space meter.
|
|
float Transmittance = exp(-OpticalDepth * FogInstance.UniformScale * CENTIMETER_TO_METER);
|
|
#else
|
|
// => Optimised path, but does not match due to CommonFactor not being applied at the same place
|
|
const float CommonFactor = FogInstance.UniformScale * CENTIMETER_TO_METER;
|
|
float TR = exp(-RadialOpticalDepth);
|
|
float TH = exp(-HeightOpticalDepth);
|
|
float Transmittance = 1 - (1 - TR) * (1 - TH);
|
|
#endif
|
|
|
|
// Assuming extinction is grey scale, we can compute a single coverage value from transmittance
|
|
FogData.Coverage = (1.0 - Transmittance);
|
|
// Assuming extinction==scattering, i.e. albedo=1, we can compute the integrale for each point on a ray while accouting for transmittance back to the view.
|
|
// It turns out that, under these conditions, the luminance and emissive factor is simply coverage. => see Mathematica details at the bottom of this page.
|
|
FogData.IntegratedLuminanceFactor = FogData.Coverage;
|
|
|
|
return FogData;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Local Fog Volume Evaluation
|
|
|
|
float3 LocalFogVolumeEvaluateInScattering(in FLocalFogVolumeGPUInstanceData FogInstance, in FFogData FogData, in float3 RayDirWorld)
|
|
{
|
|
half3 InScattering = 0.0f;
|
|
|
|
// Lighting equation here matches the volumetric fog.
|
|
|
|
// Main forward directional light intensity
|
|
InScattering += LFVDirectionalLightColor * HenyeyGreensteinPhase(-FogInstance.PhaseG, dot(RayDirWorld, LFVDirectionalLightDirection));
|
|
|
|
// Sky light
|
|
if (View.SkyLightVolumetricScatteringIntensity > 0)
|
|
{
|
|
float3 SkyLighting = View.SkyLightColor.rgb * GetSkySHDiffuseSimple(RayDirWorld * -FogInstance.PhaseG);
|
|
|
|
const float SkyVisibility = 1.0f; // Not possible to use AO with analytical integration
|
|
InScattering += (SkyVisibility * View.SkyLightVolumetricScatteringIntensity) * SkyLighting;
|
|
}
|
|
|
|
// Apply the medium uniform single scattering albedo
|
|
InScattering *= FogInstance.Albedo;
|
|
|
|
// Now account for emissive luminance after albedo and heighfog contribution has been accounted for.
|
|
// This do mean that the emissive color follows the medium density variation.
|
|
InScattering += FogInstance.Emissive;
|
|
|
|
// Then scale the contribution according to the normalised pre intergrated scattering.
|
|
return InScattering * FogData.IntegratedLuminanceFactor;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Local Fog Volume Instance Evaluation
|
|
|
|
// Returns color transmittance
|
|
float4 GetLFVInstanceContribution(
|
|
in uint InstanceIndex,
|
|
in float3 CamRayTranslatedWorldOrigin,
|
|
in float3 CamRayTranslatedWorldDir,
|
|
in float3 DepthBufferTranslatedWorldPos,
|
|
in bool bPlatformSupportsVolumetricFogOntranslucent)
|
|
{
|
|
FLocalFogVolumeGPUInstanceData FogInstance = GetLocalFogVolumeGPUInstanceData(InstanceIndex);
|
|
|
|
float3 LuminanceColor = float3(0.0f, 0.0f, 0.0f);
|
|
float Transmittance = 1.0f;
|
|
|
|
// The "U" prefix is for all computation done in the Unit Sphere space.
|
|
float3 DepthBufferPosU = mul(DepthBufferTranslatedWorldPos - FogInstance.TranslatedWorlPos, FogInstance.PreTranslatedInvTransform);
|
|
float3 RayPosU = mul(CamRayTranslatedWorldOrigin - FogInstance.TranslatedWorlPos, FogInstance.PreTranslatedInvTransform);
|
|
float3 RayDirU = mul(CamRayTranslatedWorldDir, FogInstance.PreTranslatedInvTransform);
|
|
RayDirU.xyz = normalize(RayDirU);
|
|
|
|
float2 TsU = RayIntersectSphere(RayPosU, RayDirU, float4(0.0, 0.0, 0.0, 1.0));
|
|
|
|
//Clamp to make sure we only consider intersection in front of the camera
|
|
TsU = max(LFVGlobalStartDistance.xx * FogInstance.UniformScaleInv, TsU);
|
|
|
|
// Clamp the traced distance to the depth buffer and compute world length.
|
|
float3 ViewToDepthVector = DepthBufferPosU - RayPosU;
|
|
float ViewToDepthVectorSqrLength = dot(ViewToDepthVector, ViewToDepthVector);
|
|
float LengthDepthBufferU = sqrt(ViewToDepthVectorSqrLength);
|
|
TsU = min(TsU, LengthDepthBufferU);
|
|
|
|
if (any(TsU > 0.0))
|
|
{
|
|
// Make the ray start after the volumetric fog far distance
|
|
if (LFVRenderInVolumetricFog && bPlatformSupportsVolumetricFogOntranslucent)
|
|
{
|
|
// Volumetric fog covers up to MaxDistance along ViewZ, exclude analytical fog from this range
|
|
float CosAngle = dot(CamRayTranslatedWorldDir, View.ViewForward);
|
|
float InvCosAngle = (CosAngle > 0.0001) ? rcp(CosAngle) : 0;
|
|
float ExcludeDistance = View.VolumetricFogMaxDistance * InvCosAngle * FogInstance.UniformScaleInv;
|
|
TsU = max(TsU, ExcludeDistance);
|
|
}
|
|
|
|
float RayTracedLengthU = max(0.0, abs(TsU.y - TsU.x));
|
|
|
|
if (RayTracedLengthU > 0.0)
|
|
{
|
|
FFogData FogData = LocalFogVolumeEvaluateAnalyticalIntegral(FogInstance, RayPosU + RayDirU * TsU.x, RayDirU, RayTracedLengthU);
|
|
|
|
LuminanceColor = LocalFogVolumeEvaluateInScattering(FogInstance, FogData, CamRayTranslatedWorldDir);
|
|
Transmittance = 1.0 - FogData.Coverage;
|
|
}
|
|
}
|
|
|
|
return float4(LuminanceColor, Transmittance);
|
|
}
|
|
|
|
|
|
|
|
// Evaluate the local fog volume contribution for a world position.
|
|
// Returns a float4 with Luminance Color in RGB and Transmittance in A.
|
|
float4 GetLFVContribution(
|
|
in ViewState ResolvedView,
|
|
in uint2 TilePos,
|
|
in float3 DepthBufferTranslatedWorldPos,
|
|
in bool bPlatformSupportsVolumetricFogOntranslucent,
|
|
inout uint OutLFVCount)
|
|
{
|
|
const float3 CamRayTranslatedWorldOrigin= ResolvedView.TranslatedWorldCameraOrigin;
|
|
const float3 CamRayWorldDir = normalize(DepthBufferTranslatedWorldPos - CamRayTranslatedWorldOrigin);
|
|
|
|
OutLFVCount = LFVTileDataTex[uint3(TilePos, 0)];
|
|
|
|
float3 LFVLuminanceColor = 0.0;
|
|
float LFVTransmittance = 1.0;
|
|
for (int LFVIndex = 0; LFVIndex < OutLFVCount; ++LFVIndex)
|
|
{
|
|
uint InstanceIndex = LFVTileDataTex[uint3(TilePos, 1 + LFVIndex)];
|
|
|
|
float4 InstanceContribution = GetLFVInstanceContribution(
|
|
InstanceIndex,
|
|
CamRayTranslatedWorldOrigin,
|
|
CamRayWorldDir,
|
|
DepthBufferTranslatedWorldPos,
|
|
bPlatformSupportsVolumetricFogOntranslucent);
|
|
|
|
LFVLuminanceColor = LFVLuminanceColor * InstanceContribution.a + InstanceContribution.rgb;
|
|
LFVTransmittance = LFVTransmittance * InstanceContribution.a;
|
|
}
|
|
|
|
return float4(LFVLuminanceColor, LFVTransmittance);
|
|
}
|
|
|
|
float4 GetLFVContribution(
|
|
in ViewState ResolvedView,
|
|
in uint2 TilePos,
|
|
in float3 TranslatedWorldPosition,
|
|
in bool bPlatformSupportsVolumetricFogOntranslucent = true)
|
|
{
|
|
uint OutLFVCount = 0;
|
|
return GetLFVContribution(ResolvedView, TilePos, TranslatedWorldPosition, bPlatformSupportsVolumetricFogOntranslucent, OutLFVCount);
|
|
}
|
|
|
|
|