diff --git a/package/Shaders/ISApplyVolumetricLighting.hlsl b/package/Shaders/ISApplyVolumetricLighting.hlsl index 72492b198..146db73e8 100644 --- a/package/Shaders/ISApplyVolumetricLighting.hlsl +++ b/package/Shaders/ISApplyVolumetricLighting.hlsl @@ -38,10 +38,11 @@ PS_OUTPUT main(PS_INPUT input) float depth = DepthTex.Sample(DepthSampler, screenPosition).x; float repartition = clamp(RepartitionTex.SampleLevel(RepartitionSampler, depth, 0).x, 0, 0.9999); float vl = g_IntensityX_TemporalY.x * VLTex.SampleLevel(VLSampler, float3(input.TexCoord, repartition), 0).x; + float multipleScattering = vl * (1.0 + vl * (0.5 + vl * 0.25)); float noiseGrad = 0.03125 * NoiseGradSamplerTex.Sample(NoiseGradSamplerSampler, 0.125 * input.Position.xy).x; - float adjustedVl = noiseGrad + vl - 0.0078125; + float adjustedVl = noiseGrad + multipleScattering - 0.0078125; if (0.001 < g_IntensityX_TemporalY.y) { float2 motionVector = MotionVectorsTex.Sample(MotionVectorsSampler, screenPosition).xy; @@ -50,12 +51,11 @@ PS_OUTPUT main(PS_INPUT input) float previousVl = PreviousFrameTex.Sample(PreviousFrameSampler, previousScreenPosition).x; float previousDepth = PreviousDepthTex.Sample(PreviousDepthSampler, previousScreenPosition).x; - float temporalContribution = g_IntensityX_TemporalY.y * (1 - smoothstep(0, 1, min(1, 100 * abs(depth - previousDepth)))); + float depthDifference = abs(depth - previousDepth); + float temporalContribution = saturate(g_IntensityX_TemporalY.y * exp(-depthDifference * 100.0)); + temporalContribution *= saturate(1.0 - length(motionVector) * 10.0); - float isValid = 0; - if (previousTexCoord.x >= 0 && previousTexCoord.x < 1 && previousTexCoord.y >= 0 && previousTexCoord.y < 1) { - isValid = 1; - } + float isValid = (previousTexCoord.x >= 0 && previousTexCoord.x < 1 && previousTexCoord.y >= 0 && previousTexCoord.y < 1); psout.VL = lerp(adjustedVl, previousVl, temporalContribution * isValid); } else { psout.VL = adjustedVl; diff --git a/package/Shaders/ISVolumetricLightingGenerateCS.hlsl b/package/Shaders/ISVolumetricLightingGenerateCS.hlsl index 712dac5a5..30f95f97b 100644 --- a/package/Shaders/ISVolumetricLightingGenerateCS.hlsl +++ b/package/Shaders/ISVolumetricLightingGenerateCS.hlsl @@ -1,4 +1,5 @@ #include "Common/Constants.hlsli" +#include "Common/Random.hlsli" #if defined(CSHADER) SamplerState ShadowmapSampler : register(s0); @@ -33,6 +34,69 @@ cbuffer PerTechnique : register(b0) float DensityContribution : packoffset(c23.z); } +/** + * @brief Computes the Schlick phase function for approximating scattering. + * + * The Schlick phase function is a simplified approximation of the Henyey-Greenstein phase function. + * It provides a computationally efficient way to model anisotropic scattering effects in real-time applications. + * + * @param cosTheta The cosine of the scattering angle (cosine of the angle between the light and the view direction). + * @param g The anisotropy factor (-1 for full backward scattering, 1 for full forward scattering, and 0 for isotropic). + * @return The Schlick phase function value for the given parameters. + * + * @note Performance vs Quality: + * - Schlick is a faster approximation compared to Henyey-Greenstein because it avoids the expensive power operations. + * - It provides visually similar results but sacrifices some accuracy, especially for complex anisotropic scattering. + * - Ideal for real-time rendering where performance is critical, such as games (default for Skyrim). + */ +float SchlickPhase(float cosTheta, float g) +{ + float k = (1 - g * g) / (4 * M_PI * pow(1 + g * cosTheta, 2)); + return k; +} + +/** + * @brief Computes the Henyey-Greenstein phase function for scattering. + * + * The Henyey-Greenstein phase function is a widely used model for simulating light scattering in participating media. + * It accurately models the anisotropy of light scattering, providing good realism for both forward and backward scattering. + * + * @param cosTheta The cosine of the scattering angle (cosine of the angle between the light and the view direction). + * @param g The anisotropy factor (-1 for full backward scattering, 1 for full forward scattering, and 0 for isotropic). + * @return The Henyey-Greenstein phase function value for the given parameters. + * + * @note Performance vs Quality: + * - More computationally expensive than Schlick due to the use of power operations. + * - Offers a more physically accurate representation of scattering, particularly for media with pronounced forward or backward scattering behavior. + * - Suitable for applications where visual fidelity is more important than real-time performance. + */ +float HenyeyGreensteinPhase(float cosTheta, float g) +{ + float g2 = g * g; + return (1.0 - g2) / (4.0 * M_PI * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5)); +} + +/** + * @brief Computes the Mie phase function for light scattering. + * + * The Mie phase function models the scattering of light by larger particles, such as water droplets or fog. + * It is more complex than the Henyey-Greenstein phase function and is often used to model more realistic atmospheric effects. + * + * @param cosTheta The cosine of the scattering angle (cosine of the angle between the light and the view direction). + * @param g The anisotropy factor (-1 for full backward scattering, 1 for full forward scattering, and 0 for isotropic). + * @return The Mie phase function value for the given parameters. + * + * @note Performance vs Quality: + * - More computationally expensive than both Schlick and Henyey-Greenstein due to the complex denominator and power operations. + * - Produces the most physically accurate results, especially for scenes involving larger particles such as clouds or fog. + * - Best suited for high-fidelity rendering applications, but might be too slow for real-time gaming without optimization. + */ +float MiePhase(float cosTheta, float g) +{ + float denom = pow(1 + g * g - 2 * g * cosTheta, 1.5); + return (1.0 / (4.0 * M_PI)) * (1 + cosTheta * cosTheta) / denom; +} + [numthreads(32, 32, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { const float3 StepCoefficients[] = { { 0, 0, 0 }, @@ -46,7 +110,7 @@ cbuffer PerTechnique : register(b0) float3 depthUv = dispatchID.xyz / TextureDimensions.xyz + 0.001 * StepCoefficients[IterationIndex].xyz; float depth = InverseRepartitionTex.SampleLevel(InverseRepartitionSampler, depthUv.z, 0).x; - float4 positionCS = float4(2 * depthUv.x - 1, 1 - 2 * depthUv.y, depth, 1); + float4 positionCS = float4(2 * depthUv.x - 1, 1 - 2 * depthUv.y, depth, 1); // UVDepthToView(depthUv) float4 positionWS = mul(transpose(CameraViewProjInverse), positionCS); positionWS /= positionWS.w; @@ -97,11 +161,14 @@ cbuffer PerTechnique : register(b0) float3 noiseUv = 0.0125 * (InverseDensityScale * (positionWS.xyz + WindInput)); float noise = NoiseTex.SampleLevel(NoiseSampler, noiseUv, 0).x; + noise = lerp(noise, InterleavedGradientNoise(positionWS.xy), 0.5); float densityFactor = noise * (1 - 0.75 * smoothstep(0, 1, saturate(2 * positionWS.z / 300))); float densityContribution = lerp(1, densityFactor, DensityContribution); float LdotN = dot(normalize(-positionWS.xyz), normalize(DirLightDirection)); - float phaseFactor = (1 - PhaseScattering * PhaseScattering) / (4 * M_PI * (1 - LdotN * PhaseScattering)); + float forwardPhase = SchlickPhase(LdotN, PhaseScattering); + float backwardPhase = SchlickPhase(LdotN, -PhaseScattering * 0.5); + float phaseFactor = lerp(forwardPhase, backwardPhase, 0.5); float phaseContribution = lerp(1, phaseFactor, PhaseContribution); float vl = shadowContribution * densityContribution * phaseContribution; diff --git a/package/Shaders/ISVolumetricLightingRaymarchCS.hlsl b/package/Shaders/ISVolumetricLightingRaymarchCS.hlsl index 4faef9a15..da58cf022 100644 --- a/package/Shaders/ISVolumetricLightingRaymarchCS.hlsl +++ b/package/Shaders/ISVolumetricLightingRaymarchCS.hlsl @@ -19,6 +19,12 @@ cbuffer PerTechnique : register(b0) float3 position = (0.5 + float3(dispatchID.xy, StepIndex)) / TextureDimensions.xyz; float density = DensityTex.SampleLevel(DensitySampler, position, 0).x; - DensityRW[uint3(dispatchID.xy, StepIndex)] = previousDensity + density; + // Adaptive step size + float stepSize = 1.0 / TextureDimensions.z; + float gradient = abs(density - previousDensity); + float adaptiveStepSize = stepSize * lerp(1.0, 2.0, saturate(gradient * 10.0)); + + float transmittance = exp(-density * adaptiveStepSize); + DensityRW[uint3(dispatchID.xy, StepIndex)] = previousDensity + density * (1.0 - transmittance) / max(density, 1e-5); } #endif