Microsoft Developer Network > Forums Home > Archived Forums Forums > XNA Framework > Getting the DepthBuffer is possible?
Ask a questionAsk a question
 

AnswerGetting the DepthBuffer is possible?

  • Tuesday, January 16, 2007 1:47 AMImanol Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    How can i get the DepthBuffer and put it into a texture at any time while drawing meshes?

     Is that possible?

     

    And a quite peculiar question too:

    Is there any way to, for example, assign the Alpha channel in and RGBA texture (multiplied by a factor) as a value for the z-depth of a concrete part of a mesh while drawing it?

    This means... is there any way to assign the z-depth value per pixel inside the pixel shader function?

     

    Thanks.

Answers

  • Tuesday, January 16, 2007 3:31 AMnop Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    I'm not sure about the answer to the first question, since I'm at work and don't have the docs in front of me, but I'm pretty sure that the only way you can turn a depth buffer into a texture is to create it using a lockable floating point format (slow) and copy it using the CPU (also slow). You're better off rendering depth values into a second render target (or in a second rendering pass on the XBOX).

    Yes, you can set the output depth in a pixel shader. You do this by writing to a variable with the DEPTH (again - I might be wrong, so check the docs) semantic. Note that this will sap your fill rate as the GPU will no longer be able to skip running the vertex shader on occluded pixels (since it doesn't know if a pixel is going to be occluded until the pixel shader has run and given it the desired depth value).
  • Tuesday, January 16, 2007 9:29 AMthedo Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    I dont know if its possible to get th depth buffer directly, but its certainly possible to do it indirectly.

    in your Vertex shader you can do something like this :

    struct VS_OUTPUT
    {
        float4 position  : POSITION;
        float2 textureCoordinate1 : TEXCOORD0;
        float2 depth :TEXCOORD1;
    };
     
    VS_OUTPUT VertexShader(
        float4 Position  : POSITION,
        float3 Normal : NORMAL,
        float2 TextureCoordinate1 : TEXCOORD0)
    {
        VS_OUTPUT Out = (VS_OUTPUT)0;

        Out.position = mul(Position, WorldViewProj);
        Out.textureCoordinate1 = TextureCoordinate1; 

        Out.depth.x = Out.position.z;

        return Out;
    }

     

    Now what that is doing is taking the Z coordinate of your vertex and putting it into a texture coordinate (which we can access later in a pixel shader).

    Now heres the Pixel shader

    struct PS_OUTPUT
    {
     float4 color : COLOR0;
     float4 depth : COLOR1;
    };
     

    PS_OUTPUT PixelShader(float2 textureCoordinate1 : TEXCOORD0, float2 depth : TEXCOORD1 ) : COLOR
    {
     PS_OUTPUT Out = (PS_OUTPUT)0;
     
     Out.color = tex2D(textureSampler1, textureCoordinate1).rgba;
     Out.depth.x = depth.x;
         return (Out);
    }

    OK, so what we have done here is, rendered the color as usual with the tex2D part, and put the dpeth into the Output depth. because this is called for every pixel the depth is interpolated to an appropriate value for each call. Which is nice. Now we still dont really have anything, because if we render this direct to screen, then the COLOR1 is ignored (or at least it is on my system!), and COLOR0 is all that goes into that backbuffer. But - if we render to texture(s) then we are a go. Texture 0 is our COLOR0 part (the color) and Texture 1 is our COLOR1(the depth buffer part that we put in our self).

    So something liek this for the setup is good (in LoadGraphicsContent)-

    RenderTarget2D[] renderTarget2D = new RenderTarget2D[2];
            SpriteBatch screenSprite;

    if (loadAllContent)
                {
                   
                    screenSprite = new SpriteBatch(graphics.GraphicsDevice);
                }

              
               
                renderTarget2D[0] = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth , graphics.PreferredBackBufferHeight , 1, SurfaceFormat.Color);
                renderTarget2D[1] = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth , graphics.PreferredBackBufferHeight , 1, SurfaceFormat.Single);

     

    And then to render the stuff to the textures -

    #region Set Rendering to Render Target
                graphics.GraphicsDevice.SetRenderTarget(0, renderTarget2D[0]);
                graphics.GraphicsDevice.SetRenderTarget(1, renderTarget2D[1]);

                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;

                graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
                #endregion

                base.Draw(gameTime);

                #region Render Offscreen buffer to screen
                graphics.GraphicsDevice.ResolveRenderTarget(0);
                graphics.GraphicsDevice.ResolveRenderTarget(1);

                graphics.GraphicsDevice.SetRenderTarget(0, null);

                screenFX.Parameters["Texture"].SetValue(renderTarget2D[0].GetTexture());
                screenFX.Parameters["Depth"].SetValue(renderTarget2D[1].GetTexture());

    screenSprite.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
                screenFX.Begin();
                screenFX.CurrentTechnique.Passes[0].Begin();
                screenSprite.Draw(renderTarget2D[0].GetTexture(), new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), new Rectangle(0, 0, renderTarget2D[0].Width, renderTarget2D[0].Height), Color.White);

                screenSprite.End();
                screenFX.CurrentTechnique.Passes[0].End();
                screenFX.End();

                #endregion

     

    I use this technique for doing a depth of field effect - when i render my sprite to screen, i use the depth from the Texture 1 and use it as a multiplier to the tex2D lookup texcoords so that for near objects the value is smaller and therefore the blur factor is less.

     

    I hope this helps you. If you have any Q's just post and I'm sure I'll read them ;)

    Neil

All Replies

  • Tuesday, January 16, 2007 3:31 AMnop Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    I'm not sure about the answer to the first question, since I'm at work and don't have the docs in front of me, but I'm pretty sure that the only way you can turn a depth buffer into a texture is to create it using a lockable floating point format (slow) and copy it using the CPU (also slow). You're better off rendering depth values into a second render target (or in a second rendering pass on the XBOX).

    Yes, you can set the output depth in a pixel shader. You do this by writing to a variable with the DEPTH (again - I might be wrong, so check the docs) semantic. Note that this will sap your fill rate as the GPU will no longer be able to skip running the vertex shader on occluded pixels (since it doesn't know if a pixel is going to be occluded until the pixel shader has run and given it the desired depth value).
  • Tuesday, January 16, 2007 3:46 AMImanol Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Thanks, I'm going to try if the the DEPTH semantic works, because that's the most useful for me. I was so worried about the complexity my problem had that i didn't even think about the existence of that simple semantic ^^.

     

    Thanks again

  • Tuesday, January 16, 2007 9:29 AMthedo Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    I dont know if its possible to get th depth buffer directly, but its certainly possible to do it indirectly.

    in your Vertex shader you can do something like this :

    struct VS_OUTPUT
    {
        float4 position  : POSITION;
        float2 textureCoordinate1 : TEXCOORD0;
        float2 depth :TEXCOORD1;
    };
     
    VS_OUTPUT VertexShader(
        float4 Position  : POSITION,
        float3 Normal : NORMAL,
        float2 TextureCoordinate1 : TEXCOORD0)
    {
        VS_OUTPUT Out = (VS_OUTPUT)0;

        Out.position = mul(Position, WorldViewProj);
        Out.textureCoordinate1 = TextureCoordinate1; 

        Out.depth.x = Out.position.z;

        return Out;
    }

     

    Now what that is doing is taking the Z coordinate of your vertex and putting it into a texture coordinate (which we can access later in a pixel shader).

    Now heres the Pixel shader

    struct PS_OUTPUT
    {
     float4 color : COLOR0;
     float4 depth : COLOR1;
    };
     

    PS_OUTPUT PixelShader(float2 textureCoordinate1 : TEXCOORD0, float2 depth : TEXCOORD1 ) : COLOR
    {
     PS_OUTPUT Out = (PS_OUTPUT)0;
     
     Out.color = tex2D(textureSampler1, textureCoordinate1).rgba;
     Out.depth.x = depth.x;
         return (Out);
    }

    OK, so what we have done here is, rendered the color as usual with the tex2D part, and put the dpeth into the Output depth. because this is called for every pixel the depth is interpolated to an appropriate value for each call. Which is nice. Now we still dont really have anything, because if we render this direct to screen, then the COLOR1 is ignored (or at least it is on my system!), and COLOR0 is all that goes into that backbuffer. But - if we render to texture(s) then we are a go. Texture 0 is our COLOR0 part (the color) and Texture 1 is our COLOR1(the depth buffer part that we put in our self).

    So something liek this for the setup is good (in LoadGraphicsContent)-

    RenderTarget2D[] renderTarget2D = new RenderTarget2D[2];
            SpriteBatch screenSprite;

    if (loadAllContent)
                {
                   
                    screenSprite = new SpriteBatch(graphics.GraphicsDevice);
                }

              
               
                renderTarget2D[0] = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth , graphics.PreferredBackBufferHeight , 1, SurfaceFormat.Color);
                renderTarget2D[1] = new RenderTarget2D(graphics.GraphicsDevice, graphics.PreferredBackBufferWidth , graphics.PreferredBackBufferHeight , 1, SurfaceFormat.Single);

     

    And then to render the stuff to the textures -

    #region Set Rendering to Render Target
                graphics.GraphicsDevice.SetRenderTarget(0, renderTarget2D[0]);
                graphics.GraphicsDevice.SetRenderTarget(1, renderTarget2D[1]);

                graphics.GraphicsDevice.RenderState.DepthBufferEnable = true;

                graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
                #endregion

                base.Draw(gameTime);

                #region Render Offscreen buffer to screen
                graphics.GraphicsDevice.ResolveRenderTarget(0);
                graphics.GraphicsDevice.ResolveRenderTarget(1);

                graphics.GraphicsDevice.SetRenderTarget(0, null);

                screenFX.Parameters["Texture"].SetValue(renderTarget2D[0].GetTexture());
                screenFX.Parameters["Depth"].SetValue(renderTarget2D[1].GetTexture());

    screenSprite.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState);
                screenFX.Begin();
                screenFX.CurrentTechnique.Passes[0].Begin();
                screenSprite.Draw(renderTarget2D[0].GetTexture(), new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), new Rectangle(0, 0, renderTarget2D[0].Width, renderTarget2D[0].Height), Color.White);

                screenSprite.End();
                screenFX.CurrentTechnique.Passes[0].End();
                screenFX.End();

                #endregion

     

    I use this technique for doing a depth of field effect - when i render my sprite to screen, i use the depth from the Texture 1 and use it as a multiplier to the tex2D lookup texcoords so that for near objects the value is smaller and therefore the blur factor is less.

     

    I hope this helps you. If you have any Q's just post and I'm sure I'll read them ;)

    Neil

  • Tuesday, January 16, 2007 12:42 PMImanol Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    You are very welcome about your posts. I think they really clarify the hole thing.
  • Sunday, March 11, 2007 3:29 AMmoonwhite108 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Accessing depthbuffer and Z-depth embbed in the alpha channel are methods to gain real time screen space Z info which is most likely to be used on post effects like depth map or depth field.

    In the book ShaderX5, they pointed out that embedding Z-depth in alpha channel is less accurate (only 8bit) and expansive in both performance and line of codes. You literally have to write extra Z-depth output shader code for every object in the scene (or you have to construct an inheritable shader structure)

    They proposed an alternative which is to direct access depthbuffer and give examples for both OpenGL and D3D9 / 10.

    However, can XNA convert depthfield to a texture?

    If so, sudo or detailed code is going to be really appreciated. :)

  • Monday, March 12, 2007 4:57 PMShawn Hargreaves - MSFT Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    There is no way to convert the GPU depth buffer into a texture in XNA.

    That is possible on some hardware, but not on all, and works in quite different ways on different cards, so we decided not to expose it at least for this first release.

    Your best bet in XNA is just to render the depth information you need out directly, either to the alpha channel of your main rendertarget or to a separate float32 rendertarget if you need more precision.