Grid Background Question

Hello everyone,

I was wondering, would anyone know how to make a dynamically moving background as seen in the link below?

Gif animation of provided picture

1 Like

Sure, if you want to do it with spritebatch here you go

In this code my background tile is called tex, so thatā€™s hwat i draw.
Itā€™s important that in the spritebatch.begin() i set the sampler to wrap.

This way we can wrap around multiple times for the same texture.

In our spritebatch.Draw( ā€¦ ) we have 2 rectangles.
The first one is the destination rectangle. In my case I just draw to the whole screen resolution, which i store in my GameSettings class, but just plug in the widht/ height of your game window.

The second one is the source rectangle. If our original tile texture has letā€™s say 100 x 100 pixels and our source rectangle is 50, 50, 50, 50 only the bottom right quarter of the texture is drawn.

However if we set our source rectangle to 0, 0 , 200, 200 we draw a block of 2x2 tiles, since our source is 2 times the size. I hope you get the idea.

We can then translate this source rectangle read position with the timer we get from GameTime, which is provided by draw function (or update for that matter)

public void Draw(GameTime gameTime)
        {
           
           spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.LinearWrap);

            int width = tex.Width;
            int height = tex.Height;

            const int rows = 20;
            const int columns = 30;

            //make this once per frame

            //frametimer == 1 when our game runs at 60 hz
            float frametimer = (float) (gameTime.TotalGameTime.TotalMilliseconds*60/1000);

            const float scrollDirectionX = 4;
            const float scrollDirectionY = 0.5f;

            
            int startx = (int) (scrollDirectionX*frametimer);
            int starty = (int)(scrollDirectionY * frametimer);

            Rectangle readRectangle = new Rectangle(startx, 
                starty,
                width * columns,
                height * rows);

            spriteBatch.Draw(tex, new Rectangle(0,0,GameSettings.g_ScreenWidth, GameSettings.g_ScreenHeight), readRectangle, Color.White);
           spriteBatch.End();
}
1 Like

Thanks for the explanation and help, greatly appreciated! :slight_smile:

Awesome! Thatā€™s definitely smarter than the way I did it. Iā€™m actually drawing the texture multiple times to fill the background (the UI resolution is only 480x270), and then just randomizing a vector2 as velocity and diff-ing to it along with a timer.

Results :slight_smile:

http://puu.sh/uxdZb.gif

Looks a bit funky without anti-aliasing because of the line thickness being unstable. You might want to try enabling AA as mentioned in this post.

A bug made it into 3.6 that can cause a crash when setting GraphicsDeviceManager.PreferMultiSampling to true because MonoGame internally tries to set the MS count too high, so you need to set the MultiSampleCount to a supported value like in the linked post for now. A fix will be in for 3.6.1.

that canā€™t help though, msaa only works on geometry

EDIT:

It should be noted that doing this with Spritebatch is inherently flawed, since you have to round to int, since the default Rectangle Class only accepts integer values, but what we really want are floats for smooth movement.
Since spritebatch is the most easy to employ I posted the sample above, but for a more suitable effect you would have to write a short custom shader (or modify your monogame to have floating point rectangles)

Yep line thickness there is another solution to that.

When you draw lines next to each other in these loops you will get floating point errors on plot positions due to casting. This can make the lines add a extra pixel here and there. It gives off a flickering jerky look on lines randomly and at the speed the screen is drawn its too fast to see it, unless you drop your fps to like 1 and enlarge a portion of the drawn area but it makes things appear just like that.

You can try this its what i used to do with overlapping hex tiling.
Its extra work though.

Save the Right or Top or Bottom position to be the start x y of the next plot as needed.

Then recalculate the current plot as it it were done from scratch as you do already like from the loop directly. Ignore that xy as you already will be using the previous Right Top or Bottom.

(you only need that to keep the actual alignment because those float errors if you donā€™t do that will shrink your entire map and expand it by a few pixels depending on the zoom if you zoom in or out or if you dont)
There is more.

Save the Right and Bottom though again for the next plot.
Take the previous right bottom bottom as the xy for this plot. Take the current Right Bottom - previous Right Bottom which becomes the width height of this plot. Rinse and repeat.
All these number now explicitly align edges will be exactly on top of each other and the total map tile width height wont shrink and expand a few pixels here and there. the ultimate effect is your map looks smooth. Hope that makes sense.
Might be more trouble then your willing to deal with.

Recently I started implementing a shader based grid.
Lines are drawn with AA by the shader, no need for multisampling.
The video demonstrates what I was going for.

I am still not happy with the results. Itā€™s not really infinite, and the bigger the quad the more floating errors on small scale. There are a couple of magic numbers in the shader and doesnā€™t work for small scales>1.0. Also the shader requires PS4 or PS4_9_3 because of fwidth() (MG doesnā€™t complain,but technically it should run on Reach).
The next step is to draw a 2D quad on the screen/viewport and perform a ray-to-plane collision within the shader.

#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_3
#endif

float4x4 World;
float4x4 View;
float4x4 Projection;
float QuadSize;

struct VertexShaderInput
{
	float4 Position : POSITION0;
float3 Normal : NORMAL;
	float4 Texture : TEXCOORD;
};

struct VertexShaderOutput
{
float4 Position   : SV_POSITION;
float4 WSPosition : POSITION1;
float4 VSPosition : POSITION2;
float3 Normal  : NORMAL;
float4 Texture : TEXCOORD;
};


VertexShaderOutput MainVS(in VertexShaderInput input)
{
	VertexShaderOutput output = (VertexShaderOutput)0;

//Position View
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);

output.WSPosition = worldPosition;
output.VSPosition = viewPosition;

output.Normal = input.Normal;
output.Texture = input.Texture;


	return output;
}

float GridPS(float2 pos, float mag, float lgfrc)
{    
float size = pow(10, mag);
pos = pos / size;

float fracx = frac(pos.x);
float fracy = frac(pos.y);
float ifracx = 1.0 - fracx;
float ifracy = 1.0 - fracy;
float fracx_ = max(fracx, ifracx);
float fracy_ = max(fracy, ifracy);    
float fracxy = max(fracx_, fracy_);
fracxy = (fracxy - 0.5) * 2;

float sz = lgfrc;

fracxy = max(0,((fracxy - 1) * sz) + 1);

float specular = fracxy;
return specular;
}

float4 MainPS(VertexShaderOutput input) : COLOR
{
float specular = 1.0;
float2 pos = input.WSPosition.xy;
float2 fw2 = fwidth(input.VSPosition.xy);
float fw = min(fw2.x,fw2.y);
float logfw = log10(fw)+2;
float logfwfrac = frac(logfw);
float ilogfw = logfw - logfwfrac;

float specular1 = GridPS(pos, ilogfw + 1, 10 + 90 * (1-logfwfrac));
float specular2 = GridPS(pos, ilogfw, 10 + (1 - logfwfrac)) * (1 - logfwfrac);
//specular2 = 0;
specular = max(specular1, specular2);

float3 baseColor = float3(1.0, 0.64, 0.0);    
return float4(baseColor * specular, 1.0);
}

technique BasicColorDrawing
{
	pass P0
	{
		VertexShader = compile VS_SHADERMODEL MainVS();
		PixelShader = compile PS_SHADERMODEL MainPS();
	}
};

ā€¦

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using tainicom.Aether.Elementary.Cameras;

namespace tainicom.Aether.GridDebugDraw.Components
{
    public class GridComponent : IDrawable, IGameComponent
    {   
        private readonly GraphicsDevice GraphicsDevice;

        VertexBuffer _quadVertexBuffer;
        IndexBuffer _quadIndexBuffer;
        Effect _gridEffect;

        float quadSize = 2048;

        internal ICamera Camera;

        public int DrawOrder { get; set; }
        public bool Visible { get; set; }
        public event EventHandler<EventArgs> DrawOrderChanged;
        public event EventHandler<EventArgs> VisibleChanged;

        public GridComponent(GraphicsDevice graphicsDevice, ContentManager content)
        {
            this.GraphicsDevice = graphicsDevice;

            var testVertices = new VertexPositionNormalTexture[4];
            testVertices[0].Position = new Vector3(-1f, -1f, 0) * quadSize;
            testVertices[1].Position = new Vector3(+1f, -1f, 0) * quadSize;
            testVertices[2].Position = new Vector3(+1f, +1f, 0) * quadSize;
            testVertices[3].Position = new Vector3(-1f, +1f, 0) * quadSize;
            testVertices[0].Normal = Vector3.Forward;
            testVertices[1].Normal = Vector3.Forward;
            testVertices[2].Normal = Vector3.Forward;
            testVertices[3].Normal = Vector3.Forward;
            testVertices[0].TextureCoordinate = new Vector2(0,0);
            testVertices[1].TextureCoordinate = new Vector2(0,1);
            testVertices[2].TextureCoordinate = new Vector2(1,0);
            testVertices[3].TextureCoordinate = new Vector2(1,1);
            short[] textIndices = new short[] { 0, 1, 2, 2, 3, 0 };
            _quadVertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionNormalTexture.VertexDeclaration, 4, BufferUsage.None);
            _quadIndexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, textIndices.Length, BufferUsage.None);
            _quadVertexBuffer.SetData(testVertices);
            _quadIndexBuffer.SetData(textIndices);
                 
            _gridEffect = content.Load<Effect>("GridEffect");
        }

        public virtual void Draw(GameTime gameTime)
        {
            this.GraphicsDevice.BlendState = BlendState.AlphaBlend;
            this.GraphicsDevice.DepthStencilState = DepthStencilState.None;
            this.GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            this.GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;

            var vp = this.GraphicsDevice.Viewport;
            var vpSize = new Vector2(vp.Width,vp.Height);

            var editMatrix = TableEditor.EditorSettings.EditMatrix;
            var scale = Matrix.CreateScale(this.Scale);
            var world = editMatrix;
            
            ICamera camera = this.Camera;

            var worldViewProjection = world * Camera.View * camera.Projection;
            this._gridEffect.Parameters["World"].SetValue(world);
            this._gridEffect.Parameters["View"].SetValue(Camera.View);
            this._gridEffect.Parameters["Projection"].SetValue(camera.Projection);
            this._gridEffect.Parameters["QuadSize"].SetValue(quadSize);

            this._gridEffect.CurrentTechnique.Passes[0].Apply();

            this.GraphicsDevice.SetVertexBuffer(_quadVertexBuffer);
            this.GraphicsDevice.Indices = _quadIndexBuffer;
            this.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2);
        }

        public void Initialize()
        {
            this.LoadContent();
        }

        protected void LoadContent()
        {
            Color baseColor = Color.DarkOrange;         
        }

        protected void UnloadContent()
        {
        }
        
    }
}

ā€¦

void IDebugDraw.BeforeRenderScene(AetherEngineViewModel aetherEngineViewModel, GameTime gameTime, ICamera camera)
{
    if (!_isDebugViewEnabled) return;

    var model = aetherEngineViewModel.Model as TableEditor.Models.MainModel;

    var engine = aetherEngineViewModel.AetherEngine;
    var device = AetherContextMG.GetDevice(engine);

    device.BlendState = BlendState.AlphaBlend;
    device.DepthStencilState = DepthStencilState.None;
    device.SamplerStates[0] = SamplerState.PointClamp;

    _gridComponent.Camera = camera;
    _gridComponent.Draw(gameTime);
}

Made some changes to the Grid shader.

I now draw a quad on the entire viewport with points from -1,-1 to 1,1 and unproject each pixel to the edit plane.
This way itā€™s now infinite (well, itā€™s much better than before which was limited to 1024x1024 and with no artifacts) and runs on Reach too (I split it to 4 sub techniques and project 2 neighbor points to substitute frac()).

Thatā€™s cool.
Can you post the new code?

Sure.

I will post the complete project on gitHub.

Hereā€¦


3 Likes

And advanced question for embedding MonoGameControl in WinForms? How did you get successful on WinForms with MonoGameControl ( GraphicsDeviceControl )

I really miss that - I am using MonoGame + OpenTK ( OpenGL ) in WinForms because I want develop like your editor with 4 viewports

Check my thread of MonoGame Community.

Thanks!

// EDIT: OPS 7 months later

// EDIT: I have checked your github that you are using only SharpDX?
I thought you have tested successful with DesktopGL version.

I build the shaders for both DX and OpenGL.
You can see how to use the Grid in the animation sample.

The video with the 4 controls is from my editor. I use WindowsFormsHost+SwapChainRenderTarget to render on a WPF element but I will probably migrate to something like D3DImage. Letā€™s move this discussion to your other thread about SwapChainRenderTarget. (I donā€™t use openGL so I canā€™t actually help with that.)

1 Like

Okay no problem that is why only SharpDX works fine for embedding in WinForms.

But it doesnā€™t work for OpenTK. Like this

private GraphicsDevice _device
ā€¦

_device = new GraphicsDevice(_apdater, _profile, _pp);

Than I run after compilation and Visual Studio throws common exception reference.

I am really surprise because I found MonoGame + OpenGL

Than It explains simple and I have tried and it aloways throw exception. But my compilation doesnā€™t throw error just success. And I open explorer myeditor.exe from Debug directory / Release directory it shows error exception dialog like Close, Continue or Show Log and I continue my successfull compiled myeditor.exe but OpenTK.GLControl viewport shows like died viewport.

Thanks is why I am disappointed. But I donā€™
t like SharpDX. I want only OpenTK/OpenGL.net Because my display card has OpenGL 4.5+ and VulkanGL 1.1+ That is why - Thanks for explanation about your work. Nice work But I really like your grid if move out than grid change 10 into 1 grid quad - It looks like Unity 3D or Xenko GameStudio.

I really donā€™t believe that WinForms-members use only SharpDX for WinForms. Thanks! Possible with GTK++ 2.1.x? I knew GLWidget for GraphicsDeviceWidget?

I will try againā€¦