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  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.

One thought on “MonoGame HLSL Shader Example for a Post-Processing Blur Effect

  1. Trevor Andersen

    Thanks so much for this post! Helped me get my feet wet with shaders in monogame. Many of the old XNA tutorials for shaders do not work correctly in monogame,.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *