Export (0) Print
Expand All

How To: Draw a Shadow

Demonstrates how to draw a shadow using Matrix.CreateShadow and the stencil buffer.

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

Bb464050.note(en-US,XNAGameStudio.30).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.

The Complete Sample

The code in this topic shows you the technique. You can download a complete code sample for this topic, including full source code and any additional supporting files required by the sample.

Drawing a Shadow

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;
    SpriteBatch spriteBatch;
    
    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        graphics.PreferredDepthStencilFormat = SelectStencilMode();
    }
    private static 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 InvalidOperationException("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. Thus, 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 on which the shadow falls.

    In this case, it is the Plane you created previously from a textured quad. The shadow matrix also contains a translation to make sure it does not intersect the quad. If the shadow used exactly the same plane as an object, the two objects would both be displayed intermittently. This is called the z-fighting effect.

    Vector3 shadowLightDir;
    Matrix shadow;
    protected override void LoadContent()
    {
        ...
        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)
    {
        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. 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.
    GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 0);
    GraphicsDevice.RenderState.StencilEnable = true;
    
  6. Set up the stencil buffer test for the shadow by setting the StencilFunction to CompareFunction.Equal with a ReferenceStencil of 0.

    This means you 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           
    GraphicsDevice.RenderState.ReferenceStencil = 0;
    GraphicsDevice.RenderState.StencilFunction = CompareFunction.Equal;
    // Increment the stencil buffer if we draw
    GraphicsDevice.RenderState.StencilPass = StencilOperation.Increment;
    
  7. Set up the 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
    GraphicsDevice.RenderState.AlphaBlendEnable = true;
    GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
    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
    GraphicsDevice.RenderState.StencilEnable = false;
    // turn alpha blending off
    GraphicsDevice.RenderState.AlphaBlendEnable = false;
    

Community Additions

ADD
Show:
© 2014 Microsoft