Cascaded Shadow Maps (2)

So last time we saw how we can partition the view frustum into several subfrustums and how to compute a projection matrix for each. This time we’ll look at how the shadow maps are actually rendered.

For starters, I chose to render all shadow maps into a single texture atlas instead of assigning a unique texture to each shadow split. This avoids us to switch render targets for each shadow split and also simplifies the shadow mapping shader. In this sample I am using a shadow atlas of 1024×1024 pixels,

mShadowMap = new RenderTarget2D(mGraphicsDevice, 1024, 1024, 
                                false, SurfaceFormat.Single, DepthFormat.Depth24);

and each shadow split is rendered into a 512×512 subrectangle. Ideally we’d render into the depth buffer only (double speed!) and then either resolve to a texture (XBox 360) or directly bind it as texture (DirectX). Unfortunately this is not possible in XNA. So I use a 32 bit float render target for the shadow map and a separate depth buffer.

During the shadow map render pass I then bind the atlas as render target and, for each split, set up the viewport to only render into the corresponding shadow atlas partition. Once the viewport is set we can finally render the model using the shadow transform as combined view and projection matrix:

// bind shadow atlas as render target and clear
mGraphicsDevice.SetRenderTarget(mShadowMap);
mGraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1.0f, 0);

// get model bone transforms
Matrix[] transforms = new Matrix[arena.Bones.Count];
arena.CopyAbsoluteBoneTransformsTo(transforms);

for (int i = 0; i < mNumShadowSplits; ++i)
{
    // set up viewport
    {
        int x = i % 2;
        int y = i / 2;
        var viewPort = new Viewport(x * 512, y * 512, 512, 512);

        mGraphicsDevice.Viewport = viewPort;
    }

    // Draw the arena model
    foreach (ModelMesh mesh in arena.Meshes)
    {
        foreach (var effect in mesh.Effects)
        {
            effect.Parameters["ViewProjection"].SetValue(ShadowSplitProjections[i]);
            effect.Parameters["World"].SetValue(transforms[mesh.ParentBone.Index]);
        }

        mesh.Draw();
    }
}

As for the shader used during this render pass, it’s nothing special: It just transforms each vertex into shadow clip space and then writes out the depth value as color – like any other shadow mapping shader:

float4x4 World;
float4x4 ViewProjection;

struct VertexShaderInput
{
    float4 Position        : POSITION0;
};

struct VertexShaderOutput
{
    float4 Position        : POSITION0;
    float  Depth           : COLOR0;
};

VertexShaderOutput ShadowVS(VertexShaderInput input, float4x4 worldTransform)
{
    VertexShaderOutput output = (VertexShaderOutput)0;

    float4 worldPosition = mul(input.Position, worldTransform);
    output.Position = mul(worldPosition, ViewProjection);
    output.Depth = output.Position.z / output.Position.w;

    return output;
}

float4 ShadowPS(VertexShaderOutput input) : COLOR0
{
    return float4( input.Depth, 0, 0, 0 );
}

Check out the images below: On the left you can see the scene and the view frustum and on the right the corresponding shadow atlas.

Obviously, the split distances don’t match the arena dimensions very well, but I’ll get into that in another post.

Links

Leave a Reply

Your email address will not be published. Required fields are marked *