Files
UnrealEngine/Engine/Shaders/Private/PathTracing/Volume/PathTracingFog.ush
2025-05-18 13:04:45 +08:00

117 lines
3.8 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// FogParameters
#ifndef FogDensity
float2 FogDensity;
float2 FogHeight;
float2 FogFalloff;
float4 FogAlbedo;
float FogPhaseG;
float2 FogCenter;
float FogMinZ;
float FogMaxZ;
float FogRadius;
float FogFalloffClamp;
#endif
FVolumeIntersection FogIntersect(float3 Origin, float3 Direction, float TMin, float TMax)
{
// adapted from https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
// extended/simplified because we only need the entrance/exit distances
float3 oc = Origin - float3(FogCenter, FogMinZ);
float ba = FogMaxZ - FogMinZ;
float baba = ba * ba;
float bard = ba * Direction.z;
float baoc = ba * oc.z;
float k2 = baba - bard * bard;
float k1 = baba * dot(oc, Direction) - baoc * bard;
float k0 = baba * dot(oc, oc) - baoc * baoc - FogRadius * FogRadius * baba;
float h = k1 * k1 - k2 * k0;
if (h > 0.0)
{
float s = sign(k2);
float2 TCyl = (-k1 + float2(-s, s) * sqrt(h)) / k2;
float2 TCap = ((bard > 0.0 ? float2(0, baba) : float2(baba, 0)) - baoc) / bard;
const float THitMin = max3(TCap.x, TCyl.x, TMin);
const float THitMax = min3(TCap.y, TCyl.y, TMax);
return CreateVolumeIntersection(THitMin, THitMax);
}
return CreateEmptyVolumeIntersection();
}
// returns the density for each bank of fog
float2 FogGetDualDensity(float Z)
{
return FogDensity * exp2(-max((Z - FogHeight) * FogFalloff, FogFalloffClamp));
}
FVolumeDensityBounds FogGetDensityBounds(float3 Origin, float3 Direction, float TMin, float TMax)
{
#if 0
// conservative default (reference)
return CreateVolumeDensityBound(0, FogDensity.x + FogDensity.y);
#else
// get bounds at each endpoint of the ray (density is a monotonic function, so just eval at both endpoints and sort)
float2 DensityA = FogGetDualDensity(Origin.z + TMin * Direction.z);
float2 DensityB = FogGetDualDensity(Origin.z + TMax * Direction.z);
float2 DensityBounds = float2(
DensityA.x + DensityA.y,
DensityB.x + DensityB.y
);
// flip if in the wrong order
if (DensityBounds.x > DensityBounds.y)
DensityBounds = DensityBounds.yx;
return CreateVolumeDensityBound(DensityBounds.x, DensityBounds.y);
#endif
}
// Given a point in world space, return the amount of volume and its scattering properties
FVolumeShadedResult FogGetDensity(float3 TranslatedWorldPos)
{
FVolumeShadedResult Result = (FVolumeShadedResult)0;
float2 Density = FogGetDualDensity(TranslatedWorldPos.z);
Result.SigmaT = Density.x + Density.y;
Result.SigmaSHG = FogAlbedo.xyz * Result.SigmaT;
Result.PhaseG = FogPhaseG;
return Result;
}
// Return the transmittance along a ray segment
float3 FogGetTransmittance(float3 Origin, float3 Direction, float TMin, float TMax)
{
// Solve for intersection where (Z - FogHeight) * Falloff == FogFalloffClamp (or nearest point within slab)
float2 FogClipZ = FogFalloffClamp / FogFalloff + FogHeight;
float2 OH = Origin.z - FogHeight;
float Dz = Direction.z;
float2 Tau = 0.0;
if (abs(Dz) < 1e-3)
{
// perfectly horizontal ray -- either go through all uniform or all varying, but either way density will be constant
Tau = (TMax - TMin) * exp2(-max(FogFalloff * OH, FogFalloffClamp));
}
else
{
// figure out optical density through the uniform portion and varying portion respectively
float2 TMid = clamp(-(Origin.z - FogClipZ) / Dz, TMin, TMax);
float2 Ta = Dz > 0 ? TMid : TMin;
float2 Tb = Dz > 0 ? TMax : TMid;
// add varying portion (will be 0.0 from clamp of TMid if we don't actually enter the varying portion)
Tau = exp2(-max(FogFalloff * (OH + Dz * Ta), FogFalloffClamp)) - exp2(-max(FogFalloff * (OH + Dz * Tb), FogFalloffClamp));
Tau *= rcp(Dz * FogFalloff * log(2.0));
// add uniform portion (will be 0.0 from clamp of TMid if we don't actually enter the uniform portion)
Tau += (Dz > 0 ? TMid - TMin : TMax - TMid) * exp2(-FogFalloffClamp);
}
return exp(-dot(FogDensity, Tau));
}