EVSM Exponent Evaluation Formulas in GLSL
This article provides an overview and technical breakdown of the positive and negative exponent evaluation formulas used in Exponential Variance Shadow Maps (EVSM). It covers the mathematical transformation of depth values during shadow map generation, the GLSL implementation of these exponential depth warps, and the dual Chebyshev upper-bound evaluation used at runtime to resolve light-bleeding artifacts while maintaining pre-filterability.
Depth Warping Function
Standard Variance Shadow Maps (VSM) suffer from light bleeding because Chebyshev's inequality only provides an upper bound on probability. EVSM reduces this artifact by applying exponential warping to the depth values, drastically steepening the distribution curve.
To avoid false shadow-receivers behind occluders and false occluders in front, EVSM uses both a positive and a negative exponential basis. Given a linear depth \(d \in [0, 1]\) and positive warp constants \(c_1\) and \(c_2\):
- Positive Exponent Warp: \[f(d) = \exp(c_1 \cdot d)\]
- Negative Exponent Warp: \[g(d) = -\exp(-c_2 \cdot d)\]
The negative sign in front of the second exponential preserves monotonicity so that depth ordering remains consistent.
Moment Generation in GLSL
When rendering the shadow map, four moments are typically computed and stored across a 4-channel texture (RGBA32F or RGBA16F):
// Shadow Map Generation Fragment Shader
uniform vec2 u_Exponents; // vec2(c1, c2), commonly vec2(40.0, 40.0) or vec2(80.0, 40.0)
vec4 WarpDepth(float depth, vec2 exponents) {
// Rescale depth to [-1, 1] optionally, or keep in [0, 1]
float pos = exp(exponents.x * depth);
float neg = -exp(-exponents.y * depth);
return vec4(pos, pos * pos, neg, neg * neg);
}
void main() {
float depth = gl_FragCoord.z;
gl_FragColor = WarpDepth(depth, u_Exponents);
}Visibility Evaluation Formula in GLSL
During the lighting pass, visibility is computed by evaluating the one-tailed Chebyshev upper bound independently for both the positive and negative warped components, then taking the minimum of the two results.
Chebyshev Upper Bound Function
Given the first moment \(M_1 = E[x]\), the second moment \(M_2 = E[x^2]\), and the warped fragment depth \(t\):
\[\sigma^2 = \max(M_2 - M_1^2, \, \epsilon)\] \[p_{\max}(t) = \begin{cases} 1.0, & \text{if } t \le M_1 \\ \frac{\sigma^2}{\sigma^2 + (t - M_1)^2}, & \text{if } t > M_1 \end{cases}\]
GLSL Evaluation Function
// Lighting Pass / Shadow Evaluation
uniform sampler2D u_ShadowMap;
uniform vec2 u_Exponents; // Must match the exponents used during shadow map generation
uniform float u_BleedingReduction; // e.g., clamp or smoothstep parameter
float ChebyshevUpperBound(vec2 moments, float t, float minVariance) {
// If receiver is in front of the occluder, it is fully lit
if (t <= moments.x) {
return 1.0;
}
// Variance calculation
float variance = moments.y - (moments.x * moments.x);
variance = max(variance, minVariance);
// Compute probabilistic upper bound
float d = t - moments.x;
float p_max = variance / (variance + d * d);
return p_max;
}
float EvaluateEVSM(vec4 moments, float fragmentDepth, vec2 exponents) {
// 1. Warp the receiver depth
float posWarp = exp(exponents.x * fragmentDepth);
float negWarp = -exp(-exponents.y * fragmentDepth);
// Minimum variance threshold to prevent floating-point precision issues
const float minVariance = 0.00001;
// 2. Evaluate positive bound
vec2 posMoments = moments.xy;
float pPos = ChebyshevUpperBound(posMoments, posWarp, minVariance);
// 3. Evaluate negative bound
vec2 negMoments = moments.zw;
float pNeg = ChebyshevUpperBound(negMoments, negWarp, minVariance);
// 4. Combined visibility is the minimum of both bounds
return min(pPos, pNeg);
}Numerical Precision Considerations
The exponent values (\(c_1, c_2\)) determine the effectiveness of light-bleed reduction versus floating-point stability:
- For a 32-bit floating-point texture (
RGBA32F), exponent values between40.0and80.0provide strong light-bleed rejection without overflowingfloatbounds (\(e^{80} \approx 5.54 \times 10^{34}\)). - For a 16-bit floating-point texture (
RGBA16F), exponents must be scaled down to approximately5.0to10.0to prevent immediate overflow at \(65504.0\).