Software7

Personal Developer Notebook

MonoGame HLSL Shader Example for a Post-Processing Blur Effect

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.

Scene Without Blur Effect

Scene without Blur Effect

Blurred Image

Blurred Image

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);
Texture2D <float4> myTex2D;
 
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.