Export (0) Print
Expand All

How to: Draw a Shadow

This example demonstrates how to draw a shadow using Matrix.CreateShadow and the stencil buffer.

To draw a shadow, you draw the original object without lighting using a shadow matrix. This sample demonstrates how to setup the shadow matrix and how to use the stencil buffer to avoid an uneven shadow.

Bb464050.note(en-US,XNAGameStudio.10).gifNote
To render the scene, this sample uses the BasicEffect class. For a discussion of BasicEffect see How to: Use BasicEffect. This sample also uses the Quad class introduced in How to: Draw a Textured Quad

To draw a shadow

  1. Use PreferredDepthStencilFormat to choose a depth buffer format that has some bits reserved for stencil buffering. You can do this by checking each depth stencil format with CheckDepthStencilMatch and picking the best depth/stencil format for your needs.

    GraphicsDeviceManager graphics;
    ContentManager content;
    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        content = new ContentManager( Services );
    
        graphics.PreferredDepthStencilFormat = SelectStencilMode();
    }
    private DepthFormat SelectStencilMode()
    {
        // Check stencil formats
        GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
        SurfaceFormat format = adapter.CurrentDisplayMode.Format;
        if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8 ))
            return DepthFormat.Depth24Stencil8;
        else if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8Single ))
            return DepthFormat.Depth24Stencil8Single;
        else if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil4 ))
            return DepthFormat.Depth24Stencil4;
        else if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth15Stencil1 ))
            return DepthFormat.Depth15Stencil1;
        else
            throw new ApplicationException( "Could Not Find Stencil Buffer for Default Adapter" );
    }
  2. Create a Plane that represents the surface the shadow falls upon. In this case, the shadow falls upon a textured quad drawn with the Quad class, so we create a plane using three vertices of the quad.

    Quad wall;
    Plane wallPlane;
    protected override void Initialize()
    {
        ...
        // Create a new Textured Quad to represent a wall
        wall = new Quad( Vector3.Zero, Vector3.Backward, Vector3.Up, 7, 7 );
        // Create a Plane using three points on the Quad
        wallPlane = new Plane( wall.UpperLeft, wall.UpperRight, wall.LowerLeft );
    
        base.Initialize();
    }
  3. Use CreateShadow to create a shadow matrix based on the direction of your key light and the plane the shadow falls upon - in this case, the Plane we created earlier from a textured quad. The shadow matrix also contains a translation to make sure it doesn't intersect the quad. If the shadow used exactly the same plane as an object, the two objects would both be displayed intermittently, an effect called z-fighting.

    Vector3 shadowLightDir;
    Matrix shadow;
    protected override void LoadGraphicsContent( bool loadAllContent )
    {
        if (loadAllContent)
        {
            ...
            shadowLightDir = quadEffect.DirectionalLight0.Direction;
            // Use the wall plane to create a shadow matrix, and make the shadow slightly
            // higher than the wall.  The shadow is based on the strongest light
            shadow = Matrix.CreateShadow( shadowLightDir, wallPlane ) *
                Matrix.CreateTranslation( wall.Normal / 100 );
        }
        ...
    }
  4. In your game's Draw method, first draw your scene as normal.

    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );
    
        // Draw floor, and draw model
        DrawQuad();
    
        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();
    
                effect.View = View;
                effect.Projection = Projection;
                effect.World = modelWorld;
            }
            mesh.Draw();
        }
  5. Next, we draw the shadow using a stencil buffer. The stencil buffer ensures that the shadow appears one-dimensional. Our first step is to set StencilEnable to true and Clear the stencil buffer.

    // Draw shadow, using the stencil buffer to prevent drawing overlapping
    // polygons
    
    // Clear stencil buffer to zero.
    graphics.GraphicsDevice.Clear( ClearOptions.Stencil, Color.Black, 0, 0 );
    graphics.GraphicsDevice.RenderState.StencilEnable = true;
  6. Setup the stencil buffer test for the shadow. Setting the StencilFunction to CompareFunction.Equal with a ReferenceStencil of 0 means we will only draw a pixel when the stencil buffer value is 0. Setting StencilPass to StencilOperation.Increment means that after a pixel is drawn, the stencil buffer value becomes 1, so no more pixels will be drawn.

    // Draw on screen if 0 is the stencil buffer value           
    graphics.GraphicsDevice.RenderState.ReferenceStencil = 0;
    graphics.GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
    // Increment the stencil buffer if we draw
    graphics.GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;
  7. Setup alpha blending for the shadow by setting AlphaBlendEnable to true, and setting the SourceBlend and DestinationBlend modes to use the alpha value.

    // Setup alpha blending to make the shadow semi-transparent
    graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
    graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
    graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
  8. Draw the model again, this time as a shadow. To make a shadow, we turn the ambient light color to black, disable other lights, and set an alpha transparency to make the ground below the shadow show through. The shape of the shadow is created by multiplying the model's world matrix with the shadow matrix.

    // Draw the shadow without lighting
    foreach (ModelMesh mesh in model.Meshes)
    {
        foreach (BasicEffect effect in mesh.Effects)
        {
            effect.AmbientLightColor = Vector3.Zero;
            effect.Alpha = 0.5f;
            effect.DirectionalLight0.Enabled = false;
            effect.DirectionalLight1.Enabled = false;
            effect.DirectionalLight2.Enabled = false;
            effect.View = View;
            effect.Projection = Projection;
            effect.World = modelWorld*shadow;
        }
        mesh.Draw();
    }
  9. Reset the render states to normal, disabling stencil testing and alpha blending

        // Return render states to normal            
    
        // turn stencilling off
        graphics.GraphicsDevice.RenderState.StencilEnable = false;
        // turn alpha blending off
        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;
    
        base.Draw( gameTime );
    }

The Complete Example

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;
    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        content = new ContentManager( Services );

        graphics.PreferredDepthStencilFormat = SelectStencilMode();
    }
    private DepthFormat SelectStencilMode()
    {
        // Check stencil formats
        GraphicsAdapter adapter = GraphicsAdapter.DefaultAdapter;
        SurfaceFormat format = adapter.CurrentDisplayMode.Format;
        if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8 ))
            return DepthFormat.Depth24Stencil8;
        else if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil8Single ))
            return DepthFormat.Depth24Stencil8Single;
        else if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth24Stencil4 ))
            return DepthFormat.Depth24Stencil4;
        else if (adapter.CheckDepthStencilMatch( DeviceType.Hardware, format, format, DepthFormat.Depth15Stencil1 ))
            return DepthFormat.Depth15Stencil1;
        else
            throw new ApplicationException( "Could Not Find Stencil Buffer for Default Adapter" );
    }
    Matrix View;
    Matrix Projection;

    Quad wall;
    Plane wallPlane;
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
        // Create the View and Projection matrices
        View = Matrix.CreateLookAt( new Vector3( 1, -2, 10 ), Vector3.Zero, Vector3.Up );
        Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3.0f, 1, 500 );
        // Create a new Textured Quad to represent a wall
        wall = new Quad( Vector3.Zero, Vector3.Backward, Vector3.Up, 7, 7 );
        // Create a Plane using three points on the Quad
        wallPlane = new Plane( wall.UpperLeft, wall.UpperRight, wall.LowerLeft );

        base.Initialize();
    }
    Model model;
    Matrix modelWorld;
    Texture2D texture;

    VertexDeclaration wallVertexDecl;
    BasicEffect quadEffect;

    Vector3 shadowLightDir;
    Matrix shadow;
    protected override void LoadGraphicsContent( bool loadAllContent )
    {
        if (loadAllContent)
        {
            model = content.Load<Model>( "model" );
            // Create the world Matrix for the model, placed to be near the wall:
            modelWorld = Matrix.CreateTranslation( new Vector3( 0, 0, 15 ) );
            modelWorld *= Matrix.CreateScale( .1f );
            texture = content.Load<Texture2D>( "wallpaper" );

            quadEffect = new BasicEffect( graphics.GraphicsDevice, null );
            quadEffect.EnableDefaultLighting();
            quadEffect.View = View;
            quadEffect.Projection = Projection;
            quadEffect.TextureEnabled = true;
            quadEffect.Texture = texture;

            shadowLightDir = quadEffect.DirectionalLight0.Direction;
            // Use the wall plane to create a shadow matrix, and make the shadow slightly
            // higher than the wall.  The shadow is based on the strongest light
            shadow = Matrix.CreateShadow( shadowLightDir, wallPlane ) *
                Matrix.CreateTranslation( wall.Normal / 100 );
        }
        // TODO: Load any ResourceManagementMode.Manual content
        wallVertexDecl = new VertexDeclaration( graphics.GraphicsDevice,
            VertexPositionNormalTexture.VertexElements );
    }
    protected override void UnloadGraphicsContent( bool unloadAllContent )
    {
        if (unloadAllContent)
        {
            // TODO: Unload any ResourceManagementMode.Automatic content
            content.Unload();
        }

        // TODO: Unload any ResourceManagementMode.Manual content
    }

    protected override void Update( GameTime gameTime )
    {
        // Allows the game to exit
        if (GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        // TODO: Add your update logic here

        base.Update( gameTime );
    }

    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );

        // Draw floor, and draw model
        DrawQuad();

        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();

                effect.View = View;
                effect.Projection = Projection;
                effect.World = modelWorld;
            }
            mesh.Draw();
        }


        // Draw shadow, using the stencil buffer to prevent drawing overlapping
        // polygons

        // Clear stencil buffer to zero.
        graphics.GraphicsDevice.Clear( ClearOptions.Stencil, Color.Black, 0, 0 );
        graphics.GraphicsDevice.RenderState.StencilEnable = true;
        // Draw on screen if 0 is the stencil buffer value           
        graphics.GraphicsDevice.RenderState.ReferenceStencil = 0;
        graphics.GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
        // Increment the stencil buffer if we draw
        graphics.GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;

        // Setup alpha blending to make the shadow semi-transparent
        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = true;
        graphics.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
        graphics.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

        // Draw the shadow without lighting
        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.AmbientLightColor = Vector3.Zero;
                effect.Alpha = 0.5f;
                effect.DirectionalLight0.Enabled = false;
                effect.DirectionalLight1.Enabled = false;
                effect.DirectionalLight2.Enabled = false;
                effect.View = View;
                effect.Projection = Projection;
                effect.World = modelWorld*shadow;
            }
            mesh.Draw();
        }
        // Return render states to normal            

        // turn stencilling off
        graphics.GraphicsDevice.RenderState.StencilEnable = false;
        // turn alpha blending off
        graphics.GraphicsDevice.RenderState.AlphaBlendEnable = false;

        base.Draw( gameTime );
    }
    private void DrawQuad()
    {
        graphics.GraphicsDevice.VertexDeclaration = wallVertexDecl;

        quadEffect.Begin();
        foreach (EffectPass pass in quadEffect.CurrentTechnique.Passes)
        {
            pass.Begin();

            graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
                PrimitiveType.TriangleList, wall.Vertices, 0, 4, wall.Indices, 0, 2 );

            pass.End();
        }
        quadEffect.End();
    }
}
Show:
© 2014 Microsoft