SpriteBatch and custom shaders in XNA Game Studio 4.0

SpriteBatch and custom shaders in XNA Game Studio 4.0

  • Comments 20

Improvement #1:

Our Sprite Effects sample uses SpriteSortMode.Immediate to draw sprites with a custom pixel shader:

    // Begin the sprite batch, then activate our custom effect.
    spriteBatch.Begin(SpriteBlendMode.None,
                      SpriteSortMode.Immediate,
                      SaveStateMode.None);

    desaturateEffect.Begin();
    desaturateEffect.CurrentTechnique.Passes[0].Begin();

    // Draw the sprite.
    spriteBatch.Draw(...);

    // End the sprite batch, then end our custom effect.
    spriteBatch.End();

    desaturateEffect.CurrentTechnique.Passes[0].End();
    desaturateEffect.End();

It works, but...   UGLY!

Game Studio 4.0 provides this cleaner alternative:

    spriteBatch.Begin(0, BlendState.Opaque, null, null, null, desaturateEffect);
    spriteBatch.Draw(...);
    spriteBatch.End();

Improvement #2:

If you look at the HLSL shader from previous versions of SpriteBatch, you will notice the Xbox implementation used a complex vertex shader. This meant that, while it was common to use SpriteBatch with a custom pixel shader, customizing the vertex shader was excessively difficult.

As of 4.0, the SpriteBatch vertex shader is much simpler:

    void SpriteVertexShader(inout float4 color    : COLOR0,
                            inout float2 texCoord : TEXCOORD0,
                            inout float4 position : POSITION0)
    {
    }

This makes it trivial to use SpriteBatch with custom vertex shaders. You can even combine SpriteBatch with BasicEffect! This code configures BasicEffect to replicate the default SpriteBatch coordinate system:

    Matrix projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 1);
    Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);

    basicEffect.World = Matrix.Identity;
    basicEffect.View = Matrix.Identity;
    basicEffect.Projection = halfPixelOffset * projection;

    basicEffect.TextureEnabled = true;
    basicEffect.VertexColorEnabled = true;

    spriteBatch.Begin(0, null, null, null, null, basicEffect);

By changing the projection and view matrices, it is now possible to position SpriteBatch drawing (including text) wherever you like within a 3D scene.

  • When using custom shaders for the sprite batch, do we have to take care of the half pixel/texel centering? I remember reading that the SpriteBatch took care of this automatically, but not sure if it was in the application or in the vertex shader.

  • > When using custom shaders for the sprite batch, do we have to take care of the half pixel/texel centering?

    It's up to you to apply that offset if you want exact SpriteBatch style screen texel alignment, or not if you want some other kind of projection (eg. placing sprites in a 3D scene).

    In the example of using BasicEffect that I posted above, I just baked this offset into the projection matrix.

  • Does this mean SpriteBatch has lost its 1 vertex per sprite (in the vertex buffer) optimisation for the 360? Does rotation/flipping/scaling now occur on the CPU?

  • Can I update custom effect parameter after calling effect.begin?

  • Nice shawn,,

    does this meen that we can pass depthbuffer and recontruct the depthbuffer in a spriteeffect

    and do some magic here like light, paraticles collision

    Best Regrads

    Michael Hansen

  • > Does this mean SpriteBatch has lost its 1 vertex per sprite (in the vertex buffer) optimisation for the 360? Does rotation/flipping/scaling now occur on the CPU?

    We're no longer using that vfetch shader trickery on Xbox, but don't worry, we applied some alternative cunning optimizations to keep sprite drawing nice and fast!

  • > Can I update custom effect parameter after calling effect.begin?

    There is no Effect.Begin API in Game Studio 4.0. Instead, you use EffectPass.Apply to set effect state onto the graphics device.

    You can change effect parameter values at any time, but this will not affect the device until you call Apply.

  • >There is no Effect.Begin

    It's my mistake, it should be "update parameter after SpriteBatch.Begin" :)

    Is the following code valid:

    spriteBatch.begin(Immediate)

    set effect params..

    apply params

    draw..

    set new params

    apply params

    draw..

    spriteBatch.end()

    What's the difference between

    Effect.CommitChange and EffectPass.Apply. Is there somthing like SetShaderConstant() in 4.0 so i can updte parameter directly?

  • > Is the following code valid:

    Yes.

    > What's the difference between Effect.CommitChange and EffectPass.Apply. Is there somthing like SetShaderConstant()

    There is no Effect.CommitChanges or SetShaderConstant API in Game Studio 4.0.

    EffectPass.Apply is always used any time you want to set shader parameters onto the device.

  • Thank you Shawn and team. The XNA font system is pretty locked down, so this finally gives a relatively simple way to draw text billboards. Speaking of fonts, any chance you guys could bake in support for loading non-installed fonts off the disk within the Font Descriptor processor using the PrivateFontCollection API? Or at least provide a way to inject a system Font object (that we can load ourselves) into the processor? In a lab environment (like at school), it can be difficult/impossible to use non-standard fonts because we don't have the privs to install them. The XNA font system in the pipeline is all internal sealed classes, so short of duplicating the XNA internals out using reflector there is no easy way to do this.

  • Hello there,

    could anyone point me to a example off how too use spritebatch with own effect (basiceffect) to draw in 3d scene?

    tried and searched but cant get it running.

    many thanks

  • Markus: have you tried the code example I gave at the end of this article?

  • This code is not working. What's wrong?

           protected void Render2TextureNormalCompute()

           {

               RenderTarget2D oldRT = (graphics.GraphicsDevice.GetRenderTargets().Length > 0) ?

                   graphics.GraphicsDevice.GetRenderTargets().GetValue(0) as RenderTarget2D : null;

               graphics.GraphicsDevice.SetRenderTarget(normalRenderTarget);

               graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.White, 1, 0);

               DstSpriteBatch.Begin(

                   0,

                   BlendState.Opaque,

                   null,

                   GraphicsDevice.DepthStencilState,

                   null,

                   gridEffect

               );

               gridEffect.CurrentTechnique = gridEffect.Techniques["ComputeNormals"];

               gridEffect.Parameters["textureMap1"].SetValue(DstRenderTarget);

               gridEffect.CurrentTechnique.Passes[0].Apply();

               DstSpriteBatch.Draw(DstRenderTarget,

                                     new Rectangle(0, 0, 256, 256),

                                     Color.White);

               DstSpriteBatch.End();

               graphics.GraphicsDevice.SetRenderTarget(oldRT);

           }

  • Zenhipster: I would recommend the creators.xna.com forums for this question. Blog comments aren't really a good place to do tech support especially if you need to post code examples!

  • But this code example matches the theme of the blog, and does not work.

    I relied on your help, but probably should ask someone else yet))

Page 1 of 2 (20 items) 12
Leave a Comment
  • Please add 3 and 7 and type the answer here:
  • Post