Writing your own 2D Pixel Shader in Monogame for Absolute Beginners

Here is a full example with notes on how the shader and game1 relate to each other.

Instructions.

Make a dx project name it GreyScale
Copy save his rabbit image to your projects content folder name it rabbit.
Open the pipeline tool and add his rabbit image to the pipline.
Make a new effect called GreyScale.
Right click and open it so you see it in visual studio.
Copy this shader into it.
Copy the below Game1 code over the existing projects Game1 code.
Save Build and Run.

sorry im super lazy this isnt grey scale its more like averaged towards grey.

#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif

Texture2D SpriteTexture;

sampler2D SpriteTextureSampler = sampler_state
{
    Texture = <SpriteTexture>;
};

struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TextureCoordinates : TEXCOORD0;
};

// Ps stands for pixel shader this is were we can effect the change of colors to our images.
// When we passed our effect to spritebatch.Begin( .., ...,, effect,..) this is what effects all the Draw calls we now make.
float4 MainPS(VertexShaderOutput input) : COLOR
{
    float4 texelColorFromLoadedImage = tex2D(SpriteTextureSampler, input.TextureCoordinates);
     // we can clip low alpha pixels on the shader if we like directly .. we wont however here.
    // clip(texelColorFromLoadedImage.a - 0.01f);
    float4 theColorWeGaveToSpriteBatchDrawAsaParameter = input.Color;
    float4 blendedColor = texelColorFromLoadedImage * theColorWeGaveToSpriteBatchDrawAsaParameter;
    // Until now blendColor is just like a regular spritebatch draw you are used to.
    // So now we will weight the rgb color elements towards grey,
    // However in this case were not actually doing a greyscale but this is also kinda neat.
    blendedColor.rgb = blendedColor.rgb * 0.5f + 0.5f; 
    return blendedColor;
}

technique SpriteDrawing
{
    pass P0
    {
        PixelShader = compile PS_SHADERMODEL
        MainPS();
    }
};

Copy this to your game1.

Deffered works with it.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace GreyScale
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        Effect effect;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()      { base.Initialize();}

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = Content.Load<Texture2D>("rabbit");
            effect = Content.Load<Effect>("GreyScale");
        }

        protected override void UnloadContent()  {}

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // I used non premultiplied images so the this is set in begin and the alpha value is now relevant in the shader.
            // The alpha of a pixel in this context is the amount of transparency for the pixel in your image.
            // So if you edit a image in say paint.net to have a alpha value of zero it will be transparent under this pixel shader.
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, effect, null);
            spriteBatch.Draw(texture, new Rectangle(100, 100, 200, 200), Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

two textures work as well side by side one after the other in draw.


// the effect of alpha blend instead on non premultiplied textures with this shader when clip is not used.

You can use this texture which i generated programatically based on distance with set data.
Just right click on it and save as image to your drive.
generated_image

    spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, effect, null);
    spriteBatch.Draw(texture, new Rectangle(100, 100, 100, 100), Color.White);
    spriteBatch.Draw(texture2, new Rectangle(200, 100, 100, 100), Color.White);
    spriteBatch.End();

Once you run it and look it over try making a little change like below or.
Use the grey scale formula or make something up yourself.

//blendedColor.rgb = blendedColor.rgb * 0.5f + 0.5f; 
blendedColor.r = 1.0f; 

Test run on DX.

1 Like