Shadow mapping on Monogame

For sun? Spotlight? Point light?

Anyways, the shadow map creation is the same for all of them, so let’s start with that.

You can basically work with the riemer’s tutorial …

Somewhere in your initialize or load you wanto to create a rendertarget for the shadow map.
Something like this

shadowMapRenderTarget = new RenderTarget2D(graphicsDevice, 2048, 2048, false, SurfaceFormat.Single, DepthFormat.Depth24, 0, RenderTargetUsage.PlatformContents);

We also need to load our shader, which we saved as an fx file (I’ll come to that later)

_ShadowMapGenerate = content.Load<Effect>("Shaders/ShadowMapsGenerate");

Then for each frame (or when the shadow caster moves) you need to set up your shadow matrices like this

Matrix lightView = Matrix.CreateLookAt(lightPosition,
                        lightPosition + _lightDirection,
                        Vector3.Up);

Matrix  lightProjection = Matrix.CreateOrthographic(width, height, nearZ, farZ);

Note: Orthographic means it’s a directional light (for example sunlight). If you have point or spot lights you use a perspective projection.

Then we need to combine these two for our lightViewProjection.

Matrix lightViewProjection = lightView * lightProjection;

Ok. So we setup the basics - now onto drawing all our shadows into the shadowMapRenderTarget.

private void DrawShadows(... our entities)
{
      graphicsDevice.SetRenderTarget(shadowMapRenderTarget);

      ... foreach of our entities we need the model and their position
      ...
        foreach(entity...)
        {
         DrawShadowMap(model, modelworldmatrix)
         }

}


private void DrawShadowMap(Model model, Matrix world)
        {
            for (int index = 0; index < model.Meshes.Count; index++)
            {
                ModelMesh mesh = model.Meshes[index];
                    for (int i = 0; i < mesh.MeshParts.Count; i++)
                    {
                        ModelMeshPart meshpart = mesh.MeshParts[i];
                        _ShadowMapGenerate.Parameters["WorldViewProj"].SetValue(world * LightViewProjection);

                        _ShadowMapGenerate.CurrentTechnique.Passes[0].Apply();

                        _graphicsDevice.SetVertexBuffer(meshpart.VertexBuffer);
                        _graphicsDevice.Indices = (meshpart.IndexBuffer);
                        int primitiveCount = meshpart.PrimitiveCount;
                        int vertexOffset = meshpart.VertexOffset;
                        int vCount = meshpart.NumVertices;
                        int startIndex = meshpart.StartIndex;

                        _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, vertexOffset, startIndex,
                            primitiveCount);
                    }
            }
        }

Note that I use _ShadowMapGenerate here. I hope it’s somewhat clear so far. I think this should correspond to the Riemer’s tutorial.

Onto the shader.

matrix  LightViewProj;

struct CreateShadowMap_VSOut
{
    float4 Position : POSITION;
    float Depth : TEXCOORD0;
};

//  CREATE SHADOW MAP
CreateShadowMap_VSOut CreateShadowMap_VertexShader(float4 Position : SV_POSITION)
{
    CreateShadowMap_VSOut Out;
    Out.Position    = mul(Position, mul(World, LightViewProj));
    Out.Depth       = Out.Position.z / Out.Position.w;
    
    return Out;
}

float4 CreateShadowMap_PixelShader(CreateShadowMap_VSOut input) : COLOR
{
    return float4(input.Depth, 0, 0, 0);
}

// Technique for creating the shadow map
technique CreateShadowMap
{
    pass Pass1
    {
        VertexShader = compile vs_5_0 CreateShadowMap_VertexShader();
        PixelShader = compile ps_5_0 CreateShadowMap_PixelShader();
    }
}

That’s it!

If you want to draw the renderTarget in the output (for debug purposes, or just to check how it looks) you can put this at the end of your Draw() function

_graphicsDevice.SetRenderTarget(null);
            _spriteBatch.Begin(0, BlendState.Opaque, SamplerState.AnisotropicClamp);
            _spriteBatch.Draw(_shadowMapRenderTarget, new Rectangle(0, 0, width, height), Color.White);
            _spriteBatch.End();

width and height being the output size obviously.

I hope this helps a bit. If you managed to make this work the rest (actual shadowing) is not too far off. Report back!

6 Likes