In this tutorial, we will be adding a postprocessing system to our engine. A postprocessor is an effect (.fx file) that only contains a pixel shader. It takes the current frame buffer value and applies the pixel shader to it, applying effects such as blurs, bloom, and special effects. The post processor class is called PostProcessor and has methods for getting the current frame buffer and drawing with the post processor.
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace Innovation
{
// Applies a post processing effect to a texture or the frame buffer
public class PostProcessor : Component, I2DComponent
{
// The post processor fx file
public Effect Effect;
// Width and height the the render target/frame buffer
protected int Width;
protected int Height;
// Texture that will processed
Texture2D input;
public Texture2D Input
{
get { return input; }
set
{
input = value;
if (Effect.Parameters["InputTexture"] != null)
Effect.Parameters["InputTexture"].SetValue(value);
}
}
// Draw rectangle
public Rectangle Rectangle { get { return new Rectangle(0, 0, Width, Height); } set { this.Width = value.Width; this.Height = value.Height; } }
public PostProcessor()
: base()
{
Setup(null, Engine.GraphicsDevice.Viewport.Width, Engine.GraphicsDevice.Viewport.Height);
}
public PostProcessor(Effect Effect, int Width, int Height) : base()
{
Setup(Effect, Width, Height);
}
public PostProcessor(Effect Effect, int Width, int Height, GameScreen Parent)
: base(Parent)
{
Setup(Effect, Width, Height);
}
// Set the effect, width, and height, and inititalize the Input texture
void Setup(Effect Effect, int Width, int Height)
{
this.Effect = Effect;
Input = new Texture2D(Engine.GraphicsDevice, 1, 1);
Input.SetData<Color>(new Color[] { Color.White });
this.Width = Width;
this.Height = Height;
}
// Gets the current scene texture from the frame buffer
public void GetInputFromFrameBuffer()
{
// Make sure we are working with a resolve texture
if (!(Input is ResolveTexture2D))
Input = GraphicsUtil.CreateResolveTexture();
// Then resolve the frame buffer to the Input texture
Engine.GraphicsDevice.ResolveBackBuffer((ResolveTexture2D)Input);
}
public override void Draw()
{
Engine.GraphicsDevice.Clear(Color.Black);
// Begin in a mode that will overrite everything and draw immediately
Engine.SpriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
Effect.Begin();
// For each pass...
foreach (EffectPass pass in Effect.CurrentTechnique.Passes)
{
pass.Begin();
// Draw the input texture with the effect applied
Engine.SpriteBatch.Draw(Input, Rectangle, Color.White);
pass.End();
}
Effect.End();
Engine.SpriteBatch.End();
base.Draw();
}
}
}
Lets create an example post processor, a gaussian blur. The gaussian blur first blurs the pixels horizontally, then vertically, using weights and offsets generated based on the width and height of the texture:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Innovation
{
// Post processor that applies a Gaussian blur to an image or the frame buffer
public class GaussianBlur : PostProcessor
{
// Offsets and wieghts for horizontal and vertical blurs
Vector2[] sampleOffsetsH = new Vector2[15];
float[] sampleWeightsH = new float[15];
Vector2[] sampleOffsetsV = new Vector2[15];
float[] sampleWeightsV = new float[15];
// Constructors load Effect called GaussianBlur.fx
public GaussianBlur(int Width, int Height)
: base(Engine.Content.Load<Effect>("Content/GaussianBlur"), Width, Height)
{
Setup(Width, Height);
}
public GaussianBlur(int Width, int Height, GameScreen Parent)
: base(Engine.Content.Load<Effect>("Content/GaussianBlur"), Width, Height, Parent)
{
Setup(Width, Height);
}
// Calculate the weights and offsets based on width and height
void Setup(int Width, int Height)
{
Vector2 texelSize = new Vector2(1f / Width, 1f / Height);
SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH);
SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV);
}
// Borrowed from "Shadow Mapping" by Andrew Joli
// http://www.ziggyware.com/readarticle.php?article_id=161
void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights)
{
// The first sample always has a zero offset.
fSampleWeights[0] = ComputeGaussian(0);
vSampleOffsets[0] = new Vector2(0);
// Maintain a sum of all the weighting values.
float totalWeights = fSampleWeights[0];
// Add pairs of additional sample taps, positioned
// along a line in both directions from the center.
for (int i = 0; i < 15 / 2; i++)
{
// Store weights for the positive and negative taps.
float weight = ComputeGaussian(i + 1);
fSampleWeights[i * 2 + 1] = weight;
fSampleWeights[i * 2 + 2] = weight;
totalWeights += weight * 2;
// The 1.5 offset kicks things off by
// positioning us nicely in between two texels.
float sampleOffset = i * 2 + 1.5f;
Vector2 delta = new Vector2(dx, dy) * sampleOffset;
// Store texture coordinate offsets for the positive and negative taps.
vSampleOffsets[i * 2 + 1] = delta;
vSampleOffsets[i * 2 + 2] = -delta;
}
// Normalize the list of sample weightings, so they will always sum to one.
for (int i = 0; i < fSampleWeights.Length; i++)
fSampleWeights[i] /= totalWeights;
}
// Borrowed from "Shadow Mapping" by Andrew Joli
// http://www.ziggyware.com/readarticle.php?article_id=161
private float ComputeGaussian(float n)
{
float theta = 2.0f + float.Epsilon;
return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta)));
}
// Applies the post processor to the texture in the specified direction
public void Draw(GaussianBlurDirection Direction, Texture2D Input)
{
this.Input = Input;
SetParameters(Direction);
base.Draw();
}
// Applies Gaussian Blur to frame buffer
public override void Draw()
{
GetInputFromFrameBuffer(); // Set Input texture
Engine.GraphicsDevice.Clear(Color.Black);
SetParameters(GaussianBlurDirection.Horizontal); // Set horizontal parameters
base.Draw(); // Apply blur
GetInputFromFrameBuffer(); // Set Input texture again
Engine.GraphicsDevice.Clear(Color.Black);
SetParameters(GaussianBlurDirection.Vertical); // Set vertical parameters
base.Draw(); // Apply blur
}
// Set blur parameters to effect
void SetParameters(GaussianBlurDirection Direction)
{
if (Direction == GaussianBlurDirection.Horizontal)
{
Effect.Parameters["sampleWeights"].SetValue(sampleWeightsH);
Effect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);
}
else
{
Effect.Parameters["sampleWeights"].SetValue(sampleWeightsV);
Effect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);
}
}
}
// Direction of Gaussian Blur: Horizontal or Vertical
public enum GaussianBlurDirection { Horizontal, Vertical };
}
And here is the post processor effect file, GaussianBlur.fx:
float sampleWeights[15];
float2 sampleOffsets[15];
Texture InputTexture;
sampler inputTexture = sampler_state
{
texture = <InputTexture>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
};
struct VS_OUTPUT
{
float4 Position : POSITION;
float2 TexCoords : TEXCOORD0;
};
float4 GaussianBlur_PS (VS_OUTPUT Input) : COLOR0
{
float4 color = float4(0, 0, 0, 1);
for(int i = 0; i < 15; i++ )
color += tex2D(inputTexture, Input.TexCoords + sampleOffsets[i]) * sampleWeights[i];
return color;
}
technique Blur
{
pass P0
{
PixelShader = compile ps_2_0 GaussianBlur_PS();
}
}
And thats all there is to it! Now to implement a Gaussian Blur:
// Top of Game1 class: GaussianBlur blur; // End of LoadContent() method: blur = new GaussianBlur(Engine.GraphicsDevice.Viewport.Width, Engine.GraphicsDevice.Viewport.Height); blur.Visible = false; // This will keep the engine from drawing it before we want it to // End of Draw() method: blur.Draw();
Now, if you run the game, you should see everything is all blurry:

Thats all for now!
Download the Code for this Chapter
Back to Game Engine Tutorial Series
« XNA Game Engine Tutorial Series #9: ComponentPredicate and Materials SpriteBatch, Renderstates, and You »

Thanks a lot Sean.
Awesome, Thank you.
Very nice Sean loving the speed with which you are pumping out these articles. Seeing as the post processor inherits from Component it has an automatic call to Draw() so you are getting Blur called twice which as well as making it quite blurry is killing your frame rate. Any reason for this that I am overlooking?
Right, I forgot about that. Set blur.Visible = false; in the game class and it should only draw when told to.
I’m deeply impressed
))
I have downloaded the XNA tools from Microsoft just two days ago and haven’t had any experience with them yet. Your tutorial is great getting into the XNA stuff even if you just started doing some 3D work like me. Thank you!
This is me probably me being silly but couldnt the the virtical and horizontal blur be done as multiple passes in the shader rather than clearing the backbuffer then redrawing with another setting?
Yes, but its effectively the same thing as we already have. Plus, this way we save extra code in the shader by writing the same code for different directions or using ‘if’ statements.
This is amazing. I have spent at lease $300 on books for XNA (starting with GS 1.0) and have learned more from you in these articles than all of the books.
Not taking away from the books, I just seem to get what you are teaching here better than reading the books.
Thank You, again.
this tutorial rocks. Bring on the next chapter!
Hey there,
Just to say, a wonderful series of tutorials, good speed and the best way of structuring one’s project.
What I have experienced as I enabled the blur effect, there suddenly was a problem with the depthbuffer of the terrain.
As I searched really long for a solution I wanted to give it to people here:
In the terrain class after
Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
add
Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
Engine.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
This should work
Bye and another time, thank you for the tutorials
Does it support VertexShader too?
I want to apply a post process distortion effect to an area that is larger then the viewport. (distortion reduces the quality of the image so i want to make it bigger, apply the effect then scale it down)
So basically, I want to render everything pre-process to a large framebuffer, sample that, take a rectangle from that, downscale it a bit and pass that to the viewport. Can you show me how I do that?