Not long ago I wrote about custom shaders on Windows Phone 8 using MonoGame, see here.
In this blog post a custom shader is used for a post-processing effect. For such effects 2D only shaders are used.
The most common scenario:
- 3D scene is constructed the usual way,
- but the 3D scene is rendered into a RenderTarget instead, and
- then the RenderTarget is rendered to screen using a post-processing effect
To create a really simple example we use a quad as our 3D scene and a very simple diagonal blur shader.
First we take a look at the resulting screenshot on a Windows Phone 8 device without and with the post-processing effect applied.
First the code:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace PostProcessMini1 { public class Game1 : Game { private GraphicsDeviceManager _graphics; private SpriteBatch _spriteBatch; private Matrix _view, _projection; private Texture2D _tex2d; private BasicEffect _basicEffect; private Effect _blurEffect; private VertexPositionNormalTexture[] _vertices; private int[] _indices; private RenderTarget2D _renderTarget; public Game1() { _graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { base.Initialize(); } protected override void LoadContent() { _spriteBatch = new SpriteBatch(GraphicsDevice); CreateQuad(); _tex2d = Content.Load("s7"); _view = Matrix.CreateLookAt(new Vector3(0, 0, 2), Vector3.Zero, Vector3.Up); _projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 4.0f / 3.0f, 1, 500); _basicEffect = new BasicEffect(GraphicsDevice); SetupBasicEffect(); _blurEffect = Content.Load("BlurEffect"); _renderTarget = new RenderTarget2D(GraphicsDevice, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight, false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24); } private void SetupBasicEffect() { _basicEffect.World = Matrix.Identity; _basicEffect.View = _view; _basicEffect.Projection = _projection; _basicEffect.TextureEnabled = true; _basicEffect.Texture = _tex2d; } private void CreateQuad() { _vertices = new VertexPositionNormalTexture[4]; _vertices[0].Position = new Vector3(-0.5f, -0.5f, 0f); _vertices[0].TextureCoordinate = new Vector2(0f, 1f); _vertices[0].Normal = Vector3.Backward; _vertices[1].Position = new Vector3(-0.5f, 0.5f, 0f); _vertices[1].TextureCoordinate = new Vector2(0f, 0f); _vertices[1].Normal = Vector3.Backward; _vertices[2].Position = new Vector3(0.5f, -0.5f, 0f); _vertices[2].TextureCoordinate = new Vector2(1f, 1f); _vertices[2].Normal = Vector3.Backward; _vertices[3].Position = new Vector3(0.5f, 0.5f, 0f); _vertices[3].TextureCoordinate = new Vector2(1f, 0f); _vertices[3].Normal = Vector3.Backward; _indices = new int[6]; _indices[0] = 0; _indices[1] = 1; _indices[2] = 2; _indices[3] = 2; _indices[4] = 1; _indices[5] = 3; } protected override void Update(GameTime gameTime) { base.Update(gameTime); } bool _useBlur = true; protected override void Draw(GameTime gameTime) { if(_useBlur) GraphicsDevice.SetRenderTarget(_renderTarget); GraphicsDevice.Clear(Color.CornflowerBlue); foreach (EffectPass pass in _basicEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, _vertices, 0, 4, _indices, 0, 2); } if (_useBlur) { GraphicsDevice.SetRenderTarget(null); GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue); _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); _blurEffect.CurrentTechnique.Passes[0].Apply(); _spriteBatch.Draw(_renderTarget, new Vector2(0, 0), Color.White); _spriteBatch.End(); } base.Draw(gameTime); } } }
Last but no least the HLSL shader:
sampler TextureSampler : register(s0); Texture2DmyTex2D; float4 PixelShaderFunction(float4 pos : SV_POSITION, float4 color1 : COLOR0, float2 texCoord : TEXCOORD0) : SV_TARGET0 { float4 tex; tex = myTex2D.Sample(TextureSampler, texCoord.xy) * .6f; tex += myTex2D.Sample(TextureSampler, texCoord.xy + (0.005)) * .2f; return tex; } technique Technique1 { pass Pass1 { PixelShader = compile ps_4_0_level_9_3 PixelShaderFunction(); } }
Please leave a comment if it helped you getting started with your post-processing effects.