How to handle depth when using multiple shaders.

I’m wondering if anyone has any suggestions or articles that may give me a good idea of how to approach this. I’m looking for how to structure my draw calls when I want to handle multiple shaders, or only want to apply a shader to a single sprite.

From my understanding you can only apply a single shader to a spritebatch call, so I’m interested in how to ensure depth is maintained between different spritebatch calls.

Right now I’m using the build in depth parameter for spriteBatch.Draw

spriteBatch.Draw(Animation.Sprite, location, drawRect, Color.White, 0f, origin, 1f, SpriteEffects.None, Layer);
and everything is being drawn in one spriteBatch call to maintain depth between all objects.

Any assistance or direction would be appreciated. :slight_smile:

Do you have any indication that depth is not being preserved between draw calls? I don’t think you need to do anything.

        spriteBatch.Begin();
        {
            spriteBatch.Draw(Animation.Sprite, location, drawRect, Color.White, 0f, origin, 1f, SpriteEffects.None, Layer);
        }
        spriteBatch.End();

        spriteBatch.Begin();
        {
            spriteBatch.Draw(Animation.Sprite2, location, drawRect, Color.White, 0f, origin, 1f, SpriteEffects.None, Layer);
        }
        spriteBatch.End();

Animation.Sprite2 overlap Animation.Sprite no matter what value of Layer

.
.
.
use SpriteSortMode.Immediate
for ex:

        spriteBatch.Begin(SpriteSortMode.Immediate);
        {
            effect.CurrentTechnique.Passes[0].Apply();
            spriteBatch.Draw(A)
            effect2.CurrentTechnique.Passes[0].Apply();
            spriteBatch.Draw(B)
        }
        spriteBatch.End();

but then… with SpriteSortMode.Immediate, B always overlap A

1 Like

I see, the depth values are not written to the depth buffer, they just determine render order. At least that’s the default behavior. SpriteBatch uses DepthStencilState.None, which completely disables the depth buffer. What if you set it to DepthStencilState.Default instead, will that work?

3 Likes

oh yes, with DepthStencilState.Default it work (just be careful with alpha blending)

This is correct. The depth buffer will only be cleared when you request it. In the templates this happens in the GraphicsDevice.Clear call in your Game1.Draw method. Note that the GraphicsDevice.Clear method optionally takes flags that indicate if the depth and stencil buffer should be cleared.

Apologies in advance. I’m still fairly new to this, so this may have already been answered and just gone over my head. :slight_smile:

So let’s say right now, I’m handling the drawing like this:

spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, null, DepthStencilState.DepthRead, null, null, camera.CameraMatrix);

Map.Draw(spriteBatch);
Characters.Draw(spriteBatch);

spriteBatch.End();

Chracters.Draw just goes through a List of characters, and calls their call Draw function

spriteBatch.Draw(Animation.Sprite, location, drawRect, Color.White, 0f, origin, 1f, SpriteEffects.None, Layer);

I don’t sort the chracter List, it’s just whatever order they happen to be added.

So let’s say I have two characters in that list, each going to have their own shader applied to it.

spriteBatch.Begin(SpriteSortMode.FrontToBack…effect1);
spriteBatch.Draw(Animation.Sprite, location, drawRect, Color.White, 0f, origin, 1f, SpriteEffects.None, 0.9);
spriteBatch.End

spriteBatch.Begin(SpriteSortMode.FrontToBack…effect2);
spriteBatch.Draw(Animation.Sprite, location, drawRect, Color.White, 0f, origin, 1f, SpriteEffects.None, 0.3);
spriteBatch.End

Is there any way to ensure that the first one I drew(with 0.9 depth) is drawn on top, despite being called first? Or is it a matter of needing to order the list of things being drawn before drawing them, and then just have them draw in order?

I’ve heard about the depth buffer before, although I’m having a big of difficulty finding anything that clicks with me when reading up on it, so if you have any recommended reading on that, that would be great.

I think I might be going in circles a bit, but any advice or direction is much appreciated!

Passing DepthStencilState.Default to the SpriteBatch.Begin calls should do the trick. The depth buffer stores how far away from the camera each pixel was drawn if you enable depth writing. If you enable depth reading, a pixel is drawn only when it is closer to the camera than whatever pixels were already drawn there. DepthStencilState.Default enables both reading and writing.

If you have transparent pixels in your sprites, you should be careful with the order you draw the sprites in. Unless you call clip in the shader, transparent pixels depth will still be written to the depth buffer, so you’ll want to render sprites with transparency in back-to-front order. Otherwise transparent pixels might block drawing of pixels from other sprites that are behind it.

EDIT:

With how the depth buffer and SpriteBatch rendering works, you’ll want to invert your depth values. Higher depth value means further away from the camera.

1 Like

You can swap the depthstencil state so you don’t have to invert them.

            public static DepthStencilState ds_depthtest_less = new DepthStencilState()
            {
                DepthBufferEnable = true,
                DepthBufferFunction = CompareFunction.Less
            };
            public static DepthStencilState ds_depthtest_greater = new DepthStencilState()
            {
                DepthBufferEnable = true,
                DepthBufferFunction = CompareFunction.Greater
            };
3 Likes

Thank you for the responses! That makes a lot more sense! :slight_smile:

willmotil, a question on the code you posted.

I tried both of those options out, with the following code:

         spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, ds_depthtest_Greater, null, null, null);
        spriteBatch.Draw(one, new Vector2(100, 100), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.9f);
        spriteBatch.End();
        spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, ds_depthtest_Greater, null, null, null);
        spriteBatch.Draw(two, new Vector2(400, 400), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.3f);
        spriteBatch.End();

This ends up drawing neither of the textures on screen.ds_depthtest_less seems to work fine.
Any ideas?

That’s odd i would think that should work as is …maybe its flipping the culling between cullmode clockwise or counterclockwise. Try setting the rasterizer state to cull None. I doubt that would work though.

public static RasterizerState rs_solid_nocull = new RasterizerState()
            {
                FillMode = FillMode.Solid,
                CullMode = CullMode.None
            };

I dunno maybe the call is messed up ? Or i forgot some quirk to it, been a long time since i needed to use it.
Try setting it directly to the graphics device after spriteBatch Begin(…).

        spriteBatch.Begin(SpriteSortMode.Defered, null, null, null, null, null, null);

        // flip the states yourself to change what begin did. 
        // to check its not doing something weird.
        graphics.GraphicsDevice.DepthStencilState = ds_depthtest_greater;
        graphics.GraphicsDevice.RasterizerState = rs_solid_nocull;

        spriteBatch.Draw(two, new Vector2(400, 400), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.3f);

        spriteBatch.End();

I’ve done this with quads outside of spritebatch were it works dunno why it doesn’t work by defaut in Begin(…). If the above doesn’t work, would probably need to make a test to figure it out.

Maybe front to back actually sorts it and turns off the depth buffer ? i dunno.

I tried out the above and still nothing. Something I did notice: I set it up so I can move the second texture around. I then set the first spriteBatch to:

      >   spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, DepthStencilOverride.ds_depthtest_lesser, DepthStencilOverride.rs_solid_nocull, null, null);

and the second to:

spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, DepthStencilOverride.ds_depthtest_greater, null, null, null);

I could see the first texture, as I expected based on earlier tests, but I could also see the second one, but only when I specifically moved it so it was overlapping the first one. Anywhere else and I couldn’t see it.

If this was just a quad you would have to invert the depths clear flag
GraphicsDevice.Clear(Color.Black);
GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 0f, 0);

Edit:

Ok… here we go…

// to make sure drawstring works right use greater equal.
public static DepthStencilState ds_depthtest_greater = new DepthStencilState()
        {
            DepthBufferEnable = true,
            DepthBufferFunction = CompareFunction.GreaterEqual
        };

// clear the back buffer
GraphicsDevice.Clear(Color.Black);
DepthStencilState ds;
if(flipDepth)
{
    // clear the depth buffer to near clipping 0 instead of far 1.
    GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 0f, 0);
    ds = ds_depthtest_greater_equal;
}
else
{
    ds = ds_depthtest_less_equal;
}

var rA = new Rectangle(100, 100, 100, 100);
var rB = new Rectangle(120, 100, 100, 100);

spriteBatch.Begin(SpriteSortMode.Texture, null, null, ds, null, null, null);
spriteBatch.Draw(texture, rA, texture.Bounds, Color.Blue, 0f, Vector2.Zero, SpriteEffects.None, .9f);
spriteBatch.End();

spriteBatch.Begin(SpriteSortMode.Texture, null, null, ds, null, null, null);
spriteBatch.Draw(texture, rB, texture.Bounds, Color.Red, 0f, Vector2.Zero, SpriteEffects.None, .3f);
spriteBatch.End();

Result : Blue sprite is drawn because it has a higher depth.

Worked for Immediate and Deferred mode. i deleted the test before i checked if it worked for texture but that is probably what you should use should be fine.

commenting out the clear option and setting the state to less than equal gives the reverse, the Red sprite is drawn in front. Both sprites are visible.
You don’t need sprite sort mode (either front to back or back to front), as the depth buffer is sorting by layer depth (alpha blend should work normally as well now). You can pick SpriteSortMode.Texture or immediate to batch on textures if you have manually batched them by texture. Then seperate spriteBatch begin end calls for different effects.

1 Like

Awesome, that works! Thank you so much for your help in understanding this. I really appreciate it!

I was doing a bit more testing, trying to implement some basic effects into the mix, and ran into an issue.

It works fine when I use DepthStencilState.Default, and set it to BackToFront. However, when I use this code, it looks less then ideal.

greyScale just changes the gb value, and alphaTest is to clip the pixel if alpha < 0.9.

        DepthStencilState ds = ds_depthtest_greater;
        GraphicsDevice.SetRenderTarget(renderTarget1);
        GraphicsDevice.Clear(Color.CornflowerBlue)
         spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, ds, null, greyScale, null);
        spriteBatch.Draw(character, new Vector2(0, 0), null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.5f);
        spriteBatch.End();
        GraphicsDevice.SetRenderTarget(null);
        GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Black, 0f, 0);
        spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, ds, null, alphaEffect, null);
        spriteBatch.Draw(renderTarget1, position, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.5f);
        spriteBatch.End();
        spriteBatch.Begin(SpriteSortMode.FrontToBack, null, null, ds, null, alphaEffect, null);
        spriteBatch.Draw(fount, new Vector2(100, 100), topHalf1, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.7f);
        
        spriteBatch.Draw(fount, new Vector2(100, 100 + bottomY1 ), bottomHalf1, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0.3f);
        spriteBatch.End();

The following is what is outputted.

The following is what I get when I use Default and BackToFront

Any direction would be appreciated!