Export (0) Print
Expand All

How To: Create a Depth Texture

This example demonstrates how to create a texture that contains depth information for a scene using a customized RenderTarget2D, DepthStencilBuffer, and a simple Effect.

To render the depth to a texture, you create a new RenderTarget2D and a DepthStencilBuffer with your desired depth format, then render your scene using a shader that draws each pixel in the render target as a depth value instead of a normal color. Then you use GetTexture on the RenderTarget2D to save that information to a Texture2D.

Bb975265.note(en-US,XNAGameStudio.20).gifNote
To render the scene, this sample uses a technique from a customized Effect file. For more information, see How To: Use EffectParameters and EffectTechniques.

The Complete Sample

The code in this tutorial illustrates the technique described in the text. A complete code sample for this tutorial is available for you to download, including full source code and any additional supporting files required by the sample.

To Create a Depth Texture

  1. In your game's LoadContent method, create a new RenderTarget2D for rendering the depth in your scene. You may want to choose a surface format that gives you the most depth information. In this example, SurfaceFormat.Single is chosen if the game machine supports it. (Use CheckDeviceFormat on PC to see if your chosen SurfaceFormat is supported on your game machine, or consult Xbox 360 Surface Formats for supported SurfaceFormats on Xbox). SurfaceFormat.Single creates a 32-bit floating point value for each pixel, representing the red channel. No bits are used for the green, blue, or alpha channels. Using floating point values for depth allows more precision for the shadow calculations, resulting in smoother shadows. Using a large render target and enabling antialiasing for the render target also improves the quality of the shadow rendering.

    shadowRenderTarget = GfxComponent.CreateRenderTarget(GraphicsDevice,
        1, SurfaceFormat.Single);
    
    public static RenderTarget2D CreateRenderTarget(GraphicsDevice device, int numberLevels, 
        SurfaceFormat surface)
    {
    
        if (!GraphicsAdapter.DefaultAdapter.CheckDeviceFormat(DeviceType.Hardware,
            GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format, TextureUsage.None,
            QueryUsages.None, ResourceType.RenderTarget, surface))
        {
            // Fall back to current display format
            surface = device.DisplayMode.Format;
        }
        return new RenderTarget2D(device,
            device.PresentationParameters.BackBufferWidth,
            device.PresentationParameters.BackBufferHeight,
            numberLevels, surface,
            device.PresentationParameters.MultiSampleType,
            device.PresentationParameters.MultiSampleQuality);
    
    }
    
  2. Next in your LoadContent method, create a new DepthStencilBuffer for your custom RenderTarget2D. The DepthStencilBuffer settings for width, height, and multisample quality should be the same as the values chosen for your render target. You may want to choose a depth format that gives you the most depth information. In this example, DepthFormat.Depth24Stencil8Single is chosen if the game machine supports it. (Use CheckDepthStencilMatch on Windows-based computers to see if your chosen DepthFormat is supported, or consult Xbox 360 Surface Formats for supported DepthFormats on Xbox. DepthFormat.Depth24Stencil8Single creates a 24-bit floating-point value for depth. This allows more depth precision than the normal 24-bit fixed-point buffer, but care must be taken with floating-point values to guard against floating-point errors for depth values that are nearly identical.

    shadowDepthBuffer = GfxComponent.CreateDepthStencil(shadowRenderTarget,
        DepthFormat.Depth24Stencil8Single);
    
    public static DepthStencilBuffer CreateDepthStencil(RenderTarget2D target)
    {
        return new DepthStencilBuffer(target.GraphicsDevice, target.Width,
            target.Height, target.GraphicsDevice.DepthStencilBuffer.Format,
            target.MultiSampleType, target.MultiSampleQuality);
    }
    public static DepthStencilBuffer CreateDepthStencil(RenderTarget2D target, DepthFormat depth)
    {
        if (GraphicsAdapter.DefaultAdapter.CheckDepthStencilMatch(DeviceType.Hardware,
           GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format, target.Format,
            depth))
        {
            return new DepthStencilBuffer(target.GraphicsDevice, target.Width,
                target.Height, depth, target.MultiSampleType, target.MultiSampleQuality);
        }
        else
            return CreateDepthStencil(target);
    }
    
  3. In your game's Update method, calculate a projection matrix from the point of view of the light source. Include as many objects in the scene as possible, if they are visible to the camera. In this example, both objects are always visible to the camera and do not move, so the bounding sphere used is the bounding sphere of both objects combined. Set the near and far plane of the projection matrix as close as possible to the objects in the scene - this will result in more accurate depth values for the depth map. The projection matrix is passed to the effect.

    protected override void Update(GameTime gameTime)
    {
        ...
        Matrix proj = CalcLightProjection(LightPos, bounds, defaultViewport);
        MyEffect.mLightProj.SetValue(proj);
        base.Update(gameTime);
    }
    
  4. In your Draw method, set the appropriate DepthBufferFunction for your depth texture effect. In this case, we use CompareFunction.LessEqual. With CompareFunction.LessEqual, when a pixel is depth tested, lower depth values are preserved while any pixels with a depth value higher than the current lowest value are discarded.

    GraphicsDevice.RenderState.DepthBufferFunction = CompareFunction.LessEqual;
    
  5. In your Draw method, call GraphicsDevice.SetRenderTarget to set the current render target (target 0) to the render target you created in Step 1. Then make a copy of the current DepthStencilBuffer on the GraphicsDevice before assigning the DepthStencilBuffer you created in Step 2 to the DepthStencilBuffer property on GraphicsDevice.

    GraphicsDevice.SetRenderTarget(0, shadowRenderTarget);
    // Cache the current depth buffer
    DepthStencilBuffer old = GraphicsDevice.DepthStencilBuffer;
    // Set our custom depth buffer
    GraphicsDevice.DepthStencilBuffer = shadowDepthBuffer;
    
  6. Render your scene using an effect that will draw the depth value of each pixel to your render target. A simple vertex and pixel shader for such an effect is illustrated below:

    // Render the shadow map
    GraphicsDevice.Clear(Color.Black);
    DrawScene(MyEffect.shadowMap);
    
  7. After you have rendered the depth values to the render target, call SetRenderTarget again, setting the current render target (target 0) to null. That will reset the current render target to the display buffer. Also set the DepthStencilBuffer on the GraphicsDevice to its former value.

    // Set render target back to the back buffer
    GraphicsDevice.SetRenderTarget(0, null);
    // Reset the depth buffer
    GraphicsDevice.DepthStencilBuffer = old;
    
  8. Lastly, call GetTexture on your RenderTarget2D to get a Texture2D containing the depth values for your scene. This is your depth texture.

    // Return the shadow map as a texture
    return shadowRenderTarget.GetTexture();
    

Rendering Depth in HLSL

  1. To render depth values in HLSL requires a custom vertex and a custom pixel shader. The vertex shader returns two values to the pixel shader. The first value is a POSITION that transforms the incoming POSITION into the view and projection space of the light source. The second value is the depth value of the transformed POSITION. The depth is calculated by dividing the z coordinate by the w coordinate. Dividing by w gives us a depth between 0 and 1. The depth is subtracted from 1 to get more precision from the floating point format. The depth is packed into a TEXCOORD semantic (in this case, TEXCOORD0) to be returned by the pixel shader.

    struct VS_SHADOW_OUTPUT
    {
        float4 Position : POSITION;
        float Depth : TEXCOORD0;
    };
    float4 GetPositionFromLight(float4 position)
    {
        float4x4 WorldViewProjection = mul(mul(g_mWorld, g_mLightView), g_mLightProj);
        return mul(position, WorldViewProjection);  
    }
    VS_SHADOW_OUTPUT RenderShadowMapVS(float4 vPos: POSITION)
    {
        VS_SHADOW_OUTPUT Out;
        Out.Position = GetPositionFromLight(vPos); 
        // Depth is Z/W.  This is returned by the pixel shader.
        // Subtracting from 1 gives us more precision in floating point.
        Out.Depth.x = 1-(Out.Position.z/Out.Position.w);    
        return Out;
    }
    
  2. The pixel shader returns one value, the depth of the pixel. This is calculated by the pixel shader and passed in the TEXCOORD0 semantic.

    The depth is returned as the red value by the pixel shader. If you are using SurfaceFormat.Single, this will hold a 32-bit floating point value. More bits will give us smoother shadows, and floating point will create smoother shadows than fixed point. However, this shader will create shadows using almost any SurfaceFormat value.

    float4 RenderShadowMapPS( VS_SHADOW_OUTPUT In ) : COLOR
    { 
        // The depth is Z divided by W. We return
        // this value entirely in a 32-bit red channel
        // using SurfaceFormat.Single.  This preserves the
        // floating-point data for finer detail.
        return float4(In.Depth.x,0,0,1);
    }
    

Community Additions

ADD
Show:
© 2014 Microsoft