How To: Apply a Pixel Shader to Sprites

This article demonstrates how to apply a pixel shader to sprites.

This example uses an offscreen render target to render a number of sprites, then applies a pixel shader to the full scene. This allows the pixel shader to shade the entire screen instead of each individual sprite separately.

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 Sprite Pixel Shader

  1. Create a sampler object with a Texture member. This is the texture that will be changed by the pixel shader. Although we declare the texture as an external parameter, in truth the SpriteBatch will populate the texture when it draws to the screen.

    uniform extern texture ScreenTexture;    
    sampler ScreenS = sampler_state
        Texture = <ScreenTexture>;    
  2. A pixel shader is only required to return a color for the current pixel. This pixel shader uses the tex2D fuction to pick a pixel on the sampler to display. Because this is a distortion shader, the pixel chosen is not necessarily the "current" pixel.

    float4 PixelShader(float2 texCoord: TEXCOORD0) : COLOR
        // pick a pixel on the screen for this pixel, based on
        // the calculated offset and direction
        float4 color = tex2D(ScreenS, texCoord+(sinoffset*sinsign));    
        return color;

To Apply a Pixel Shader to Sprites

  1. In LoadContent, create a SpriteBatch, load the Effect containing your pixel shader, and load any sprites you wish to draw.

  2. In the section of LoadContent for loading manual resources, create a RenderTarget2D for offscreen rendering, and a Texture2D using the same settings. The Texture2D will hold the scene that we later pass to the pixel shader.

    RenderTarget2D ShaderRenderTarget;
    Texture2D ShaderTexture;
    Effect ripple;
    protected override void LoadContent()
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        grid = Content.Load<Texture2D>("grid");
        ripple = Content.Load<Effect>("ripple");
        ShaderRenderTarget = CloneRenderTarget(GraphicsDevice, 1);
        ShaderTexture = new Texture2D(GraphicsDevice, ShaderRenderTarget.Width, ShaderRenderTarget.Height, 1,
            TextureUsage.None, ShaderRenderTarget.Format);
  3. In Draw, cache the current render target using GetRenderTarget, then switch to an offscreen render target using SetRenderTarget.

  4. Clear the offscreen render target, and then draw all the sprites that comprise the scene.

  5. Resolve the render target with ResolveRenderTarget, and retrieve the scene with GetTexture. Then reset the render target to the previous render target (normally the back buffer).

    // Change to our offscreen render target.
    RenderTarget2D temp = (RenderTarget2D)GraphicsDevice.GetRenderTarget(0);
    GraphicsDevice.SetRenderTarget(0, ShaderRenderTarget);
    // Render a simple scene.
    TileSprite(spriteBatch, grid);
    // Change back to the back buffer, and get our scene
    // as a texture.
    GraphicsDevice.SetRenderTarget(0, temp);
    ShaderTexture = ShaderRenderTarget.GetTexture();
  6. Using the Texture2D containing the scene to shade, call Begin on your SpriteBatch, specifying SpriteSortMode.Immediate.

  7. Call Begin on the Effect, and Begin on the pass containing your pixel shader.

  8. Call Draw on the SpriteBatch. The pixel shader you specified will be used to render the sprite.

  9. Call End on the SpriteBatch, the EffectPass, and the Effect.

    // Use Immediate mode and our effect to draw the scene
    // again, using our pixel shader.
    spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
    spriteBatch.Draw(ShaderTexture, Vector2.Zero, Color.White);

Community Additions