Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I've implemented shadow maps in my simple block engine as an exercise. I'm using one directional light and using the view volume to create the shadow matrices. I'm experiencing some problems with the shadows shimmering when the camera moves and I'd like to know if it's an issue with my implementation or just an issue with basic/naive shadow mapping itself.

Here's a video:

http://www.youtube.com/watch?v=vyprATt5BBg&feature=youtu.be

Here's the code I use to create the shadow matrices. The commented out code is my original attempt to perfectly fit the view frustum. You can also see my attempt to try clamping movement to texels in the shadow map which didn't seem to make any difference. Then I tried using a bounding sphere instead, also to no apparent effect.

    public void CreateViewProjectionTransformsToFit(Camera camera, out Matrix viewTransform, out Matrix projectionTransform, out Vector3 position)
    {
        BoundingSphere cameraViewFrustumBoundingSphere = BoundingSphere.CreateFromFrustum(camera.ViewFrustum);

        float lightNearPlaneDistance = 1.0f;

        Vector3 lookAt = cameraViewFrustumBoundingSphere.Center;

        float distanceFromLookAt = cameraViewFrustumBoundingSphere.Radius + lightNearPlaneDistance;
        Vector3 directionFromLookAt = -Direction * distanceFromLookAt;
        position = lookAt + directionFromLookAt;
        viewTransform = Matrix.CreateLookAt(position, lookAt, Vector3.Up);

        float lightFarPlaneDistance = distanceFromLookAt + cameraViewFrustumBoundingSphere.Radius;

        float diameter = cameraViewFrustumBoundingSphere.Radius * 2.0f;

        Matrix.CreateOrthographic(diameter, diameter, lightNearPlaneDistance, lightFarPlaneDistance, out projectionTransform);

        //Vector3 cameraViewFrustumCentroid = camera.ViewFrustum.GetCentroid();

        //position = cameraViewFrustumCentroid - (Direction * (camera.FarPlaneDistance - camera.NearPlaneDistance));
        //viewTransform = Matrix.CreateLookAt(position, cameraViewFrustumCentroid, Up);

        //Vector3[] cameraViewFrustumCornersWS = camera.ViewFrustum.GetCorners();
        //Vector3[] cameraViewFrustumCornersLS = new Vector3[8];
        //Vector3.Transform(cameraViewFrustumCornersWS, ref viewTransform, cameraViewFrustumCornersLS);

        //Vector3 min = cameraViewFrustumCornersLS[0];
        //Vector3 max = cameraViewFrustumCornersLS[0];
        //for (int i = 1; i < 8; i++)
        //{
        //    min = Vector3.Min(min, cameraViewFrustumCornersLS[i]);
        //    max = Vector3.Max(max, cameraViewFrustumCornersLS[i]);
        //}

        //// Clamp to nearest texel
        //float texelSize = 1.0f / Renderer.ShadowMapSize;
        //min.X -= min.X % texelSize;
        //min.Y -= min.Y % texelSize;
        //min.Z -= min.Z % texelSize;
        //max.X -= max.X % texelSize;
        //max.Y -= max.Y % texelSize;
        //max.Z -= max.Z % texelSize;

        //// We just use an orthographic projection matrix. The sun is so far away that it's rays are essentially parallel.
        //Matrix.CreateOrthographicOffCenter(min.X, max.X, min.Y, max.Y, -max.Z, -min.Z, out projectionTransform);
    }

And here's the relevant part of the shader:

if (CastShadows)
{
    float4 positionLightCS = mul(float4(position, 1.0f), LightViewProj);

    float2 texCoord = clipSpaceToScreen(positionLightCS) + 0.5f / ShadowMapSize;

    float shadowMapDepth = tex2D(ShadowMapSampler, texCoord).r;

    float distanceToLight = length(LightPosition - position);

    float bias = 0.2f;

    if (shadowMapDepth < (distanceToLight - bias))
    {
        return float4(0.0f, 0.0f, 0.0f, 0.0f);
    }
}

The shimmer is slightly better if I drastically reduce the view volume but I think that's mostly just because the texels become smaller and it's harder to notice them flickering back and forth.

I'd appreciate any insight, I'd very much like to understand what's going on before I try other techniques.

share|improve this question
add comment

1 Answer 1

Have you ever noticed how when your shadow map camera moves far away from your geometry, your shadows get less accurate and more blocky around the edges? Or have you ever halved the resolution of your shadow map and noticed a similar effect? That's called a magnification artifact, and it happens when multiple pixels in screen space look up the same texel in the shadow map.

In practice, you will probably always have some magnification artifacts, it's just a matter of minimizing how obvious they are, which is what I imagine you're trying to do here. The shimmer that you see in your video comes from how you're changing the distribution of magnification artifacts every frame. If you rendered your shadow map in the corner of your screen, you would see how it's changing size and shape every frame, enlarging some areas while shrinking others.

Ideally, each pixel in screen space would have its own unique lookup value. There's been a fair amount of research into the subject, but the most straightforward solution is called Cascaded Shadow Maps. This is a technique that splits the light's frustum into several different depth groups and renders a different shadow map for each one. When your scene objects render, each pixel samples the appropriate shadow map based on the pixel's depth. With CSM you might see some artifacts around the seams where different shadow frusta intersect, but you won't have the shimmering problem.

The paper I linked discusses some other methods like Perspective Shadow Maps, which attempt to warp the shadow map such that areas that take up more screen space also take up more space in the shadow map. The advantage is that it uses less memory than CSM, but the math is pretty crunchy and supposedly some of the edge cases are tough to deal with. I haven't seen any shadow map warping schemes implemented in the wild. If you understand shadow maps well enough to implement them, CSM isn't a big step beyond that.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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