7
\$\begingroup\$

I use a variance shadow mapping with a "standard" light bleeding fix in my graphics engine which is based on deferred rendering.. I have a single shadow map for a directional light beacause a test scene is relatively small. Saving depth looks like this:

float moment1 = gl_FragCoord.z;
float moment2 = moment1 * moment1;
outColor = vec2(moment1, moment2);

Then I perform a separable gaussian blur on that texture.

Shadow test:

float Chebyshev(vec2 moments, float mean, float minVariance)
{
    float shadow = 1.0f;
    if(mean <= moments.x)
    {
        shadow = 1.0f;
        return shadow;
    }
    else
    {
        float variance = moments.y - (moments.x * moments.x);
        variance = max(variance, minVariance);
        float d = mean - moments.x;
        shadow = variance / (variance + (d * d));
        float amount = 0.3f;
        shadow =  clamp((shadow - amount) / (1.0f - amount), 0.0f, 1.0f); // standard light bleeding fix
        return shadow;
    }
}

vec4 shadowCoord = shadowMatrix * vec4(viewSpacePosition, 1.0f); 
vec2 moments = texture(ShadowMap, shadowCoord.xy).xy;
float minVariance = 0.000001f;
float shadow = Chebyshev(moments, shadowCoord.z, minVariance);

I found a interesting technique - Exponential Variance Shadow Mapping which should provides better light bleeding fix. Here is saving depth for that technique:

float positiveExponent = 40.0f;
float negativeExponent = 5.0f
float depth = gl_FragCoord.z;
vec2 exponents = vec2(positiveExponent, negativeExponent);
depth = 2.0f * depth - 1.0f;
float pos = exp(exponents.x * depth);
float neg = -exp(-exponents.y * depth);
vec2 warpDepth = (pos, neg);
outColor = vec4(warpDepth, warpDepth * warpDepth);

How to implement a shadow test for that technique ? Here is my attempt:

float positiveExponent = 40.0f;
float negativeExponent = 5.0f;
vec2 exponents = vec2(positiveExponent, negativeExponent);

vec2 warpDepth(float depth)
{
    depth = 2.0f * depth - 1.0f;
    float pos = exp(exponents.x * depth);
    float neg = -exp(-exponents.y * depth);
    vec2 wDepth = vec2(pos, neg);
    return wDepth;
}

float Chebyshev(vec2 moments, float mean, float minVariance)
{
    float shadow = 1.0f;
    if(mean <= moments.x)
    {
        shadow = 1.0f;
        return shadow;
    }
    else
    {
        float variance = moments.y - (moments.x * moments.x);
        variance = max(variance, minVariance);
        float d = mean - moments.x;
        shadow = variance / (variance + (d * d));
        return shadow;
    }
}

vec4 shadowCoord = shadowMatrix * vec4(viewSpacePosition, 1.0f);
vec4 moments = texture(ShadowMap, shadowCoord.xy).xyzw;
vec2 posMoments = vec2(moments.x, moments.z);
vec2 negMoments = vec2(moments.y, moments.w);
vec2 wDepth = warpDepth(shadowCoord.z);
//float minVariance = 0.000001f;
//Edit
vec2 depthScale = 0.0001f * exponents * wDepth;
vec2 minVariance = depthScale * depthScale;
float posResult = Chebyshev(posMoments, wDepth.x, minVariance.x);
float negResult = Chebyshev(negMoments, wDepth.y, minVariance.y);
shadow = min(posResult, negResult);

Unfortunately it doesn't remove light bleeding. Did I do something wrong ? Maybe I should calculate two different minVariance based on the positive and negative exponents ?

Edit1: Calculating minVariance this way:

vec2 depthScale = 0.0001f * exponents * wDepth;
vec2 minVariance = depthScale * depthScale;
float posResult = Chebyshev(posMoments, wDepth.x, minVariance.x);
float negResult = Chebyshev(negMoments, wDepth.y, minVariance.y);

gives better results but light bleeding is still quite visible.

Edit2: Light bleeding: EVSM with minVariance which is calculated as above gives better result than VSM without standard light bleeding fix, but to get the best result I also use standard light bleeding fix with EVSM. A light bleeding fix is performed in Chebyshev function but maybe I should add it after following calculation: shadow = min(posResult, negResult) ?

EVSM causes small artifacts on the illuminated side of the objects so I add some glPolygonOffset when saving EVSM depth.

\$\endgroup\$
  • \$\begingroup\$ Looks like you have a typo here: vec2 warpDepth = (pos, neg) - should be vec2(pos, neg) \$\endgroup\$ – Nathan Reed Nov 19 '13 at 1:46
  • \$\begingroup\$ Thanks, but I immediately fixed that because I got glsl compiler errors. A problem lies somewhere else. \$\endgroup\$ – Irbis Nov 19 '13 at 13:06
  • \$\begingroup\$ I would be glad to see a screenshot, Irbis! \$\endgroup\$ – danijar Nov 21 '13 at 7:41
5
\$\begingroup\$

Variance shadow mapping, plainly put, just suffers from these light bleeding issues.

I personally prefer to implement ESM (check out page 257 of ShaderX6) as the memory pressure is half of the VSM map and the artifacts are much less abrasive to me: (The very beginning of the shadow is a bit too bright.)

With this said, here is a (rather old) PDF full of great techniques to get you thinking. (or just to show you the algorithm if you don't have ShaderX6) http://developer.download.nvidia.com/presentations/2008/GDC/GDC08_SoftShadowMapping.pdf

In my current engine, I have a hybrid which is basically ESM, but uses the 2-moment (or higher) shadow map to compute the variance and reduce the ESM artifacts at the places where the occluder is too close to the receiver.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.