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

Original post that was rewritten for current MonoGame framework - https://gmjosack.github.io/posts/my-first-2d-pixel-shaders-part-1/

It has been about 7-8 years after the original post I linked above
and its pretty outdated, So I thought I might make a fresh one that works (What worked for me after I tried many things) so lets get into it.

SETTING UP:

Sprite used:

The first thing we’ll want to do add our texture to our project. Go ahead and add the texture you chose to the Content Pipeline (Double click on the Content.mgcb)

Texture2D texture;

Then in yout LoadContent method add the following line at the end substituting your assets name:

texture = Content.Load<Texture2D>("surge") // or whatever sprite you're using

Now lets go ahead and Draw it just to make sure everything is working as expected. Add the following to
your Draw method right above base.Draw();

spriteBatch.Begin();
spriteBatch.Draw(texture, new Vector2(0, 0), Color.White);
spriteBatch.End();

Now hit F5 and make sure everything compiles and looks okay. If you’re following along it should look something like this:

SETTING UP THE SHADER:

Open your Content Pipeline (like we previously did to add our sprite)
Follow the screenshot and create a “New Item…”

choose whatever name you’d like and be sure you’re selecting the Sprite Effect (.fx) option and then click OK.

Now lets go back to our Game1.cs (or whatever name you gave to your project/game)

Add a member variable of:

Effect effect;

And in your LoadContent method add the following line:

effect = Content.Load<Effect>("Effect1");

The rest of our changes will be in the Draw method. First we’re going to have to update our spriteBatch.Begin() call to use a new sort mode.

spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);

You can look up the details of these options in the MSDN Reference. SpriteSortMode.Immediate is required to apply the effect.
BlendState.AlphaBlend is the default. After your Begin call we’re going to add the following line which will Apply the pixel shader to the sprite.

effect.CurrentTechnique.Passes[0].Apply();

Now open up your freshly created shader and you’ll see somthing like this:

#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;
};

float4 MainPS(VertexShaderOutput input) : COLOR
{
	return tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color;
}

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

on the original tutorial it says to tear everything apart well DON’T DO IT EVER because you’ll
have lots and lots of errors and believe me you dont want them.

Our main function the we’re going to work with rightnow is:

float4 MainPS(VertexShaderOutput input) : COLOR
{
	return tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color;
}

It takes in as a parameter a struct VertexShaderOutput where we have our variables

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

Lets add a sampler2d you’ll be using these pretty often

right after your

Texture2D SpriteTexture;

add

sampler s0;

now lets change our float4 MainPS function to our needs
your new function should look as follows:

float4 MainPS(VertexShaderOutput input) : COLOR
{
	float4 color = tex2D(s0, input.TextureCoordinates);
	return color;
}

if you’ll compile it right now you’ll see no changes but lets add another line above the return color;

color.gb = color.r;

So your function in the end will look like:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
	float4 color = tex2D(s0, input.TextureCoordinates);
	color.gb = color.r;
    return color;
}

Hit F5 and see it in action

Hope I helped you starting with your first shader because I had hard time trying to setup something to work.

//If you saw some mistakes I made, please do let me now so I can fix it and make this tutorial as helpful as I can.

12 Likes

Nice one this is brill

1 Like

Great beginner tutorial, helpful for me.

1 Like

Really helpful for me. Thank you.

SpriteTextureSampler vs s0

You know i think he should be using the sampler he defined using s0 makes things a bit unclear.
The s0 here is independent now from the texture so if two textures were used later that s0 will confuse a new person.

These lines go together because SpriteTextureSampler is a texture sampler that is linked to SpriteTexture.

Texture2D SpriteTexture;

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


then later in the pixelshader

return tex2D( SpriteTextureSampler, input.TextureCoordinates) * input.Color;

so since people keep reading this i just wanted to point out when he says…

Lets add a sampler2d you’ll be using these pretty often

SpriteTextureSampler is a sampler that in this case is using s0 but doesn’t have to.
This can be confusing for shaders later on that may use more then one texture.
Since s0 isn’t linked to any texture in his definitions SpriteTextureSampler is.

3 Likes