the Fiddler's picture

[Solved] Help with Variance Shadow Map implementation (light leakage and directional lights)

Edit: light leakage is a known issue with VSM. I have managed to solve this by "overdarkening" the shadows by 10-70% depending on the type of the shadow. You lose some detail in the finer parts, but it works nicely in the general case. Note that this doesn't solve the issue of light leakage in overlapping shadows - there is no good solution to this issue yet.

I spent the better part of the last two days implementing Variance Shadow Maps (VSM). The implementation is now working with quite nice results, but I have encountered two issues that I haven't been able to solve yet.

[First issue]

The first issue is light leakage, namely lack of contact between the shadow caster and its shadow (see screenshot. Also, not to be confused with light bleeding, which appears on overlapping shadows of different casters). This becomes more pronounced the further the light is away.

My scene is relatively small (z-range is 1 - 128) and I am using Rgba32f textures - could this be linked to insufficient precision? If not, is this a known problem with VSM? (I have only seen one reference to light leakage which happened to be a user error). Can anyone think of a way to mask this artifact?

[Second issue]

The second issue is not specific to VSM and has to do with directional lights. My first approach was to simulate directional lighting by moving a point light far enough away. Unfortunately, this caused precision to suffer, causing shadows to become blocky and pronouncing the previous leakage issue.

As far as I know, the other recommended way is to use an orthographic projection for the light - which sounds good in theory, but with many details I haven't been able to work out: what do you use for left-right and top-bottom projection parameters in this case? What about the near-far planes? Is there anything else you need to do to get this to work? (because no matter what parameters I tried, the result was total shadow).

Also, is there any way to improve precision, without using cascaded / parallel-split shadow maps?

Here is how I set up the projection for rendering from the light's point of view:

     void ShadowPass(CubeFramebuffer fbo)
    {
        // Setting up an ortho projection like this doesn't seem to work (everything stays in shadow).
        // Matrix4 shadow_moon_projection = MathExtensions.CreateOrthoProjection(-128, 128, -128, 128, Moon.Radius);
        Matrix4 shadow_moon_projection = MathExtensions.CreateCubeProjection(1, Moon.Radius);
 
        using (depth_program.Use())
        {
            for (int face = 0; face < 6; face++)
            {
                using (fbo.Use(CubeMapFace.PositiveX + face))
                {
                    // Moon light
                    shadow_light_pos = Matrix4.Translation(-Moon.Position.Xyz);
                    shadow_light_pos.Transpose();
                    Matrix4.Mult(ref cube_modelview[face], ref shadow_light_pos, out shadow_mv);
                    Matrix4.Mult(ref shadow_moon_projection, ref shadow_mv, out shadow_mvp);
 
                    GL.UniformMatrix4(depth_program.Uniforms["ModelviewProjection"].Index, true, ref shadow_mvp);
                    GL.Uniform4(depth_program.Uniforms["Light.Position"].Index, Moon.Position);
                    GL.Uniform1(depth_program.Uniforms["Light.InverseRadius"].Index, Moon.InverseRadius);
 
                    GL.Clear(ClearBufferMask.DepthBufferBit);
                    GL.ColorMask(true, false, true, false);
                    // Render level geometry
 
                    // Set up second light as above
                    //GL.Clear(ClearBufferMask.DepthBufferBit);
                    //GL.ColorMask(false, true, false, true);
                    // Render level geometry
                }
            }
        }
    }
 
    public static class MathExtensions
    {
        public static Matrix4 CreateCubeProjection(float zNear, float zFar)
        {
	        return new Matrix4(
                     1,  0, 0, 0,
		     0, -1, 0, 0,
		     0,  0, (zFar + zNear) / (zFar - zNear), -(2 * zFar * zNear) / (zFar - zNear),
		     0,  0, 1, 0);
        }
 
        public static Matrix4 CreateOrthoProjection(float left, float right, float bottom, float top, float zNear, float zFar)
        {
            float rl = right - left;
            float tb = top - bottom;
            float fn = zFar - zNear;
 
            return new Matrix4(
                2.0f / rl, 0, 0, -(right + left) / rl,
                0, 2.0f / tb, 0, -(top + bottom) / tb,
                0, 0, -2.0f / fn, -(zFar + zNear) / fn,
                0, 0, 0, 1);
        }
    }

Afterwards, I simply render from the camera's point of view, passing the cubemap to the lighting shader (attached below). Note that I pack two lights in the cubemap, using GL.ColorMask to control which components they write to (first light: r and b, second light: g and a).

Inline Images
Screenshot which displays the light leakage problem
AttachmentSize
DepthVS.txt379 bytes
DepthFS.txt459 bytes
LightingVS.txt1.44 KB
LightingFS.txt1.79 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

After tinkering some more, it seems the light leakage is directly related to the light radius of the relevant light. The higher the radius, the more the leakage that occurs.

Edit: scratch that, I accidentaly bumped the lower-limit of the variance while changing the light radius.

kanato's picture

Do you have source code / an executable that we can download and play with? I would love to learn nice dynamic shadow mapping techniques like this, but I really have no experience with them.

As a bit of a tangent (har) but I've been wondering, do you think it is a better optimization to include the bitangent in the vertex data, or to calculate it in the vertex shader? I've seen examples that do both.

the Fiddler's picture

Unfortunately, no, I do not yet know if I can release the source for this project and the binaries are too large to distribute (something close to 100MB).

On the other hand, I plan to clean up and release a helper library I've been working on: it's a high-level wrapper of OpenGL 3.0 that simplifies working with textures, framebuffers, vertex buffers, shaders etc.

I can also post the relevant code for setting up and rendering with shadows, if you like. The actual rendering is quite simple - most of the code deals with setting up the framebuffers, shaders and necessary matrices.

Regarding bitangents, I usually calculate them in the vertex shader. I haven't measured which way is faster, but my shaders tend to be bandwidth limited (texture reads) rather than ALU limited and this might help a little.

meeper's picture
the Fiddler wrote:

On the other hand, I plan to clean up and release a helper library I've been working on: it's a high-level wrapper of OpenGL 3.0 that simplifies working with textures, framebuffers, vertex buffers, shaders etc.

I can also post the relevant code for setting up and rendering with shadows, if you like. The actual rendering is quite simple - most of the code deals with setting up the framebuffers, shaders and necessary matrices.

Sorry to resurrect an old post, but sample code for setting up/rendering with shadows would be killer :)

the Fiddler's picture

Please file a feature request and I'll look into adding a shadow mapping example to OpenTK. :)