Is there a way, in the XNA framework, to render your 2D scene using the typical SpriteBatch method and then, after that frame is rendered, apply effect to the whole image?
For example, blurring, sepia or even making the whole thing look like an old time movie film, with grain, dust, lines, etc?
Yes - what you'll do is set the render target to render to a texture instead of out your graphics hardware. Then, once your output is in a texture you'll apply your pixel shader effects, and send it off.
Some great basic sample effects are available here (I heartily recommend subscribing to this blog as well)
http://blogs.msdn.com/b/shawnhar/archive/2007/05/23/transitions-part-four-rendertargets.aspx
Here's my glowing code, I would be interested in comments on how to improve it. Basically I have objects divided into glowing and normal. I create two RenderTarget2D objects in my Initialize method, one of them with a depth buffer because I'm doing 3D:
GlowTarget = new RenderTarget2D(GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, true, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
GlowTarget2 = new RenderTarget2D(GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, true, SurfaceFormat.Color, DepthFormat.None);
In my draw method I render the normal objects to my GlowTarget then clear the color to black with zero Alpha and draw the glowing objects. I draw this to GlowTarget2 with a custom effect pixel shader applied to blur it in the X direction. Then I clear the render target and draw the normal objects. Lastly I draw the GlowTarget2 texture on top with another custom effect to blur in the Y direction.
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
// draw clear GlowTarget and draw normal, non-glowing objects to set depth buffer
GraphicsDevice.SetRenderTarget(GlowTarget);
GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Stencil | ClearOptions.Target, Color.FromNonPremultiplied(0, 0, 0, 0), 1f, 0);
MyScene.DrawNormal();
// clear target only and set color to black WITH ZERO ALPHA, then draw glowing objects
// so they will be the only ones that appear and they will be hidden by objects in front of them
GraphicsDevice.Clear(ClearOptions.Target, Color.FromNonPremultiplied(0, 0, 0, 0), 1f, 0);
MyScene.DrawGlow();
// blur objects horizontally into GlowTarget2
GraphicsDevice.SetRenderTarget(GlowTarget2);
GraphicsDevice.Clear(Color.FromNonPremultiplied(0, 0, 0, 0));
using (SpriteBatch sb = new SpriteBatch(GlowTarget2.GraphicsDevice))
{
blurXEffect.Parameters["PixelSize"].SetValue((float)(1.0f / (float)GlowTarget.Width));
sb.Begin(0, BlendState.Additive, SamplerState.PointWrap, DepthStencilState.Default, RasterizerState.CullNone, blurXEffect);
sb.Draw(GlowTarget, new Rectangle(GraphicsDevice.Viewport.X, GraphicsDevice.Viewport.Y, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
sb.End();
}
// now reset context and clear for actual drawing
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.Clear(Color.Black);
// TODO: Add your drawing code here
base.Draw(gameTime);
// draw scene to graphics card back buffer
MyScene.DrawNormal();
using (SpriteBatch sprite = new SpriteBatch(GraphicsDevice))
{
// draw glowing texture and blur Y this time
blurYEffect.Parameters["PixelSize"].SetValue((float)(1.0f / (float)GlowTarget.Height));
sprite.Begin(0, BlendState.Additive, SamplerState.PointWrap, DepthStencilState.Default, RasterizerState.CullNone, blurYEffect);
//sprite.Draw(GlowTarget, new Vector2(0, 0), Color.White);
sprite.Draw(GlowTarget2, new Rectangle(GraphicsDevice.Viewport.X, GraphicsDevice.Viewport.Y, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
sprite.End();
}
}
If you are doing something simple with 2D like applying a single effect then you could just set a custom effect in your SpriteBatch.Begin() call... Here's my shader for bluring X:
// texture we are rendering
sampler2D tex : register(S0);
float PixelSize;
// pixel shader function
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 c = 0; // will get max of each value for c
float alpha = 0; // alpha will be average
float2 myuv = uv;
for(int i = -7; i <= 7; i++)
{
myuv.x = uv.x + (i * PixelSize * 1.5);
float4 sample = tex2D(tex, myuv);
c = max(c, sample);
alpha += sample.a;
}
c.a = saturate(pow(abs(alpha / 6), 0.4));
return(c);
}
technique Technique1
{
pass Pass1
{
PixelShader = compile ps_2_0 main();
}
}
Here's a sample image, red green and blue squares normal with glowing versions above them: