create an infinite grid from a shader

how can I create a grid from a shader?

I’m trying to use this shader
https://github.com/manbeardgames/hlsl-shaders/blob/master/project-specific/ophidian/backgroundgrid.fx

but I don’t understand what to do in Draw the code

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

        _firstShader.CurrentTechnique.Passes[0].Apply();//doesn't draw anything
        base.Draw(gameTime);
    }

there are no errors on loading content
_firstShader = Content.Load(“backgroundgrid”);

It’s not drawing anything because you aren’t actually drawing anything. From the documention on that shader…

//--------------------------------------------------------
//	Background Grid
//	
//	Draws the background grid for ophidian based on two
//	colors.  The colors used fror the cells are based
//	on _basecolor and _secondarycolor. Every cell is color
//	_basecolor, except for those that are on an odd row
//	and column paring.
//
//	Note:
//	In the game, to draw this grid, I render a 1x1 pixel
//	texture that is then stretched to the size of the viewport
//	The shader is applied to this stretched texture.
//--------------------------------------------------------

Especially the Note section.

You’ll also need to set those parameters on your shader before you make the Apply call. Look into this and give things a try. Post back if you need some additional help :slight_smile:

thanks for your reply.
unfortunately this doesn’t help me. I can’t figure out what I’m doing wrong.
this code doesn’t draw anything
_tex = new Texture2D( GraphicsDevice, 1, 1 )
and Draw section:

_spriteBatch.Begin(effect: _firstShader);
_spriteBatch.Draw(_tex, new Rectangle( 0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
_spriteBatch.End();

Untested, but this is the basics of what you need.

// Game class vars
static Texture2D plainTexture;

// LoadContent
plainTexture = new Texture2D(graphics.GraphicsDevice, 1, 1);
plainTexture.SetData(new Color[] { Color.White });

// Draw
_firstShader.Parameters["_viewport_width"].SetValue(GraphicsDevice.Viewport.Width);
_firstShader.Parameters["_viewport_height"].SetValue(GraphicsDevice.Viewport.Height);
spriteBatch.Begin(effect: _firstShader);
spriteBatch.Draw(plainTexture, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
spriteBatch.End();
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace shader_test;

public class Game1 : Game
{
    private GraphicsDeviceManager _graphics;
    private SpriteBatch _spriteBatch;
    private Effect _firstShader;
    private Texture2D _tex;

    public Game1()
    {
        Window.AllowUserResizing = true;
        _graphics = new GraphicsDeviceManager(this);
        //_graphics.GraphicsProfile = GraphicsProfile.HiDef;
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
    }

    protected override void Initialize()
    {
        // TODO: Add your initialization logic here

        base.Initialize();
    }

    protected override void LoadContent()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        _firstShader = Content.Load<Effect>("backgroundgrid");
        _tex = new Texture2D( GraphicsDevice, 1, 1 );
        _tex.SetData(new Color[] { Color.White });

        // TODO: use this.Content to load your game content here
    }

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

        // TODO: Add your update logic here

        base.Update(gameTime);
    }

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

_firstShader.Parameters["_viewport_width"].SetValue(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width);
        _firstShader.Parameters["_viewport_height"].SetValue(GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height);
        _spriteBatch.Begin(effect: _firstShader);//_spriteBatch.Begin(); - draw black screen
        _spriteBatch.Draw(_tex, Vector2.Zero, 
                          new Rectangle(0, 0, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width, GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height),
                          Color.Black);
        _spriteBatch.End();
        
        base.Draw(gameTime);
    }
}

this code doesn’t draw anything
project: mgdesktopgl
possibly a problem with the shader…

I got curious so I decided to make a test project. I’m not sure why, but the only way I could get it to work was by manually setting values for each var.

i.e.:

_firstShader.Parameters["_basecolor"].SetValue(Color.Black.ToVector4());
_firstShader.Parameters["_secondarycolor"].SetValue(Color.Red.ToVector4());
_firstShader.Parameters["_linecolor"].SetValue(Color.White.ToVector4());
_firstShader.Parameters["_linethickness"].SetValue(0.03125f);
_firstShader.Parameters["_cell_width"].SetValue(32f);
_firstShader.Parameters["_cell_height"].SetValue(32f);
_firstShader.Parameters["_viewport_width"].SetValue(GraphicsDevice.Viewport.Width);
_firstShader.Parameters["_viewport_height"].SetValue(GraphicsDevice.Viewport.Height);

Output:

Full DesktopGL Example:

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


namespace GridShaderTest
{
	public class Game1 : Game
	{
		private GraphicsDeviceManager _graphics;
		private SpriteBatch _spriteBatch;
		static Texture2D plainTexture;
		static Effect backgroundGridShader;


		public Game1()
		{
			_graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
			IsMouseVisible = true;
		}


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


		protected override void LoadContent()
		{
			_spriteBatch = new SpriteBatch(GraphicsDevice);

			plainTexture = new Texture2D(GraphicsDevice, 1, 1);
			plainTexture.SetData(new Color[] { Color.White });

			backgroundGridShader = Content.Load<Effect>("backgroundgrid");
		}


		protected override void Update(GameTime gameTime)
		{
			base.Update(gameTime);
		}


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

			backgroundGridShader.Parameters["_basecolor"].SetValue(Color.Black.ToVector4());
			backgroundGridShader.Parameters["_secondarycolor"].SetValue(Color.Red.ToVector4());
			backgroundGridShader.Parameters["_linecolor"].SetValue(Color.White.ToVector4());
			backgroundGridShader.Parameters["_linethickness"].SetValue(0.03125f);
			backgroundGridShader.Parameters["_cell_width"].SetValue(32f);
			backgroundGridShader.Parameters["_cell_height"].SetValue(32f);
			backgroundGridShader.Parameters["_viewport_width"].SetValue(GraphicsDevice.Viewport.Width);
			backgroundGridShader.Parameters["_viewport_height"].SetValue(GraphicsDevice.Viewport.Height);
			_spriteBatch.Begin(effect: backgroundGridShader);
			_spriteBatch.Draw(plainTexture, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
			_spriteBatch.End();

			base.Draw(gameTime);
		}
	}
}

it worked. Thank you.
it’s strange that all external variables are specified in the shader…

If you needed to do this, it would maybe suggest that the initialization of parameter values in the shader isn’t working. Is it supposed to?

Not in OpenGL, never did, works only in DX project. Since OpenGL project doesn’t pull those values to CPU to store them in Effect and set them later. Approach of that example is hell anyway as it is forcing texture bind to never use is, not to mention initializing and flushing entire spritebatch for single drawcall… come on.

2 Likes

is there a good example of using a shader together with MG?

It’s best to understand basics, all you need for this kind of effect is to understand how to put triangle on screen with custom effect. This will set you on the right track: Drawing Triangles - RB Whitaker's Wiki

1 Like

thanks, I’ll take a look

That’s good to know, thanks for clarifying.

1 Like

Good to know! Thanks for the insight.

I’m relatively new to 3D-like stuff (vertex buffers, models, vertex shaders, etc). I have created this example using the method you suggested (using VertexBuffer instead of SpriteBatch/texture). I’d be interested in your feedback on this. Namely, is there anything I’m doing wrong/inefficiently/etc?

Game class

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


namespace GridShaderTest
{
	public class Game1 : Game
	{
		GraphicsDeviceManager _graphics;
		Effect backgroundGridShader;
		VertexBuffer vertexBuffer;
		Matrix world = Matrix.Identity;
		Matrix view = Matrix.CreateLookAt(new Vector3(0.5f, 0.5f, 1), new Vector3(0.5f, 0.5f, 0), Vector3.Up);
		Matrix projection = Matrix.CreateOrthographic(1, 1, 0.01f, 100f);


		public Game1()
		{
			_graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
			IsMouseVisible = true;
		}


		protected override void LoadContent()
		{
			backgroundGridShader = Content.Load<Effect>("backgroundgrid");
			backgroundGridShader.Parameters["_world"].SetValue(world);
			backgroundGridShader.Parameters["_view"].SetValue(view);
			backgroundGridShader.Parameters["_projection"].SetValue(projection);
			backgroundGridShader.Parameters["_basecolor"].SetValue(Color.Black.ToVector4());
			backgroundGridShader.Parameters["_secondarycolor"].SetValue(Color.Red.ToVector4());
			backgroundGridShader.Parameters["_linecolor"].SetValue(Color.White.ToVector4());
			backgroundGridShader.Parameters["_linethickness"].SetValue(0.03125f);
			backgroundGridShader.Parameters["_cell_width"].SetValue(32f);
			backgroundGridShader.Parameters["_cell_height"].SetValue(32f);
			backgroundGridShader.Parameters["_viewport_width"].SetValue(1);
			backgroundGridShader.Parameters["_viewport_height"].SetValue(1);

			VertexPosition[] vertices = new VertexPosition[] {
				new VertexPosition(new Vector3(0, 0, 0)),
				new VertexPosition(new Vector3(0, 1, 0)),
				new VertexPosition(new Vector3(1, 0, 0)),
				new VertexPosition(new Vector3(1, 1, 0)),
			};
			vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPosition), vertices.Length, BufferUsage.WriteOnly);
			vertexBuffer.SetData<VertexPosition>(vertices);
		}


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

			GraphicsDevice.SetVertexBuffer(vertexBuffer);

			backgroundGridShader.CurrentTechnique = backgroundGridShader.Techniques["DrawGrid"];
			foreach (EffectPass pass in backgroundGridShader.CurrentTechnique.Passes)
			{
				pass.Apply();
				GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
			}

			base.Draw(gameTime);
		}
	}
}

backgroundgrid.fx (essentially the same as the one provided at start of this thread, but needed a VertexShader and therefore uses position instead of texcoord)

#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


float4x4 _world;
float4x4 _view;
float4x4 _projection;

float4 _basecolor;
float4 _secondarycolor;
float4 _linecolor;
float _linethickness;
float _viewport_width;
float _viewport_height;
float _cell_width;
float _cell_height;


struct VsInput
{
    float4 position : POSITION0;
};


struct PsInput
{
    float4 position : SV_POSITION0;
};


PsInput VSFunction(VsInput input)
{
	PsInput output;
    float4 worldPos = mul(input.position, _world);
    float4 viewPos = mul(worldPos, _view);
    output.position = mul(viewPos, _projection);
    return output;
}


float4 PSFunction(PsInput input) : COLOR0
{
	float tileCountWide = _viewport_width / _cell_width;
	float tileCountTall = _viewport_height / _cell_height;
	float translated_cell_width = 1.0 / tileCountWide;
	float translated_cell_height = 1.0 / tileCountTall;
    if (frac(input.position.x / translated_cell_width) < _linethickness || frac(input.position.y / translated_cell_height) < _linethickness)
		return _linecolor;
	else
	{
		if (fmod(floor(input.position.x / translated_cell_width) , 2.0) == 0.0 && fmod(floor(input.position.y / translated_cell_height), 2.0) == 0.0)
			return _secondarycolor;
		else
			return _basecolor;
	}
}


technique DrawGrid
{
	pass Grid
	{
		VertexShader = compile VS_SHADERMODEL VSFunction();
		PixelShader = compile PS_SHADERMODEL PSFunction();
	}
};

Result:

C# code is fine, not 100% optimized but on modern HW it is insignificant.

Shader is meh, again, modern HW will chew for it without issue however it simply points out to bad practice. We will have to dive deeper in there tho, I will give you super short, super simplified version:

Branching in shader is FINE if branch results have large spatial coherency and/or prevents expensive instructions/operations to be executed. In this case compiler will probably think “wtf” and ensure that this is flattened (or replace it with few arithmetic instructions) but still it just looks meh, doesn’t it. Default MG VS doesn’t exactly help either for several reasons.

So again, in this case, in something this simple, what you did is PRACTICALLY fine. But if you want to push further, I will be talking modern Shader Models, SM3.0 is dead and should stay dead:

Replace screen space quad with screen space triangle. Drop vertex buffer, you don’t need it. All you need is draw call with information you are drawing three vertices with any triangle topology, non indexed, that’s it, that simple, that cheap.

In vertex shader use SV_VertexID to expand, as simply as this:

 VSOUTPosUv main(uint vertexId : SV_VertexID)
 {
    VSOUTPosUv output = (VSOUTPosUv) 0;
 
    float2 uv = float2((vertexId << 1) & 2, vertexId & 2); //On some HW static array will be slightly faster, just fun fact, irrelevant difference
    output.Position = float4(uv * float2(2, -2) + float2(-1, 1), 0, 1);
    output.Uv = uv;
 
    return output;
 }

This will prevent taps into vertex buffer, constant buffer (in fact, no CB has to be bind to VS stage at all, big plus) and save few lanes when dealing with diagonal split. For pixel shader, I would like to leave it as exercise to reader, I would have to test it and I sorta don’t wanna do that right now, I am sure there are multiple elegant methods how to do it, I would start by unifying input size for grid and width line :slight_smile:

1 Like

Oh hi, thats my shader that i wrote a long time ago when learning shaders and monogame.

Its probably a terrible shader, i havent looked at it in a long time, im so sorry.

3 Likes

Oh god it has nested branching.

Im so ashamed of past me. Please forgive me

1 Like

I would be very interested if someone can post an example of this.

graphics.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 1);

This should do, I don’t use MG anymore tho. What you need to happen on lower level is

ImmediateContext.Draw(3, 0);

Nothing else, no bound vertex buffer or index buffer, just this