Infinite Ground Grid

A proper 3D infinite ground grid, with perspective independent line thickness and correct depth. Similar to grid in Godot’s editor viewport and most 3D modeling apps.

  • projected on camera facing quad or sprite, no need for ground geometry
  • renders grid, checkerboard or both combined
  • major and minor grid lines
  • adjustable line thickness, subdivisions, colors and transparency
  • adjustable distance fade zone
  • adjustable ground plane elevation and center offset

Usage: Assign a shader to a 3D sprite or a quad and put it directly in front of camera so that it covers the entire view.

Shader code
shader_type spatial;
render_mode blend_mix,depth_draw_opaque, unshaded;

uniform vec4 gridColor: source_color;
uniform vec4 checkerColor: source_color;
uniform float fadeStart = 0.0;
uniform float fadeEnd = 10.0;
uniform float unitSize = 1.0;
uniform int subdivisions: hint_range(1, 10) = 5;
uniform float majorLineThickness = 2.0;
uniform float minorLineThickness = 1.0;
uniform float minorLineAlpha: hint_range(0.0, 1.0) = .3;
uniform vec3 centerOffset = vec3(0.0, 0.0, 0.0);

// calculate line mask, usning a bit of fwidth() magic to make line width not affected by perspective
float grid(vec2 pos, float unit, float thickness){
	vec2 threshold = fwidth(pos) * thickness * .5 / unit;
	vec2 posWrapped = pos / unit;
	vec2 line = step(fract(-posWrapped), threshold) + step(fract(posWrapped), threshold);
	return max(line.x, line.y);
}

// calculate checkerboard mask
float checker(vec2 pos, float unit){
	float square1 = step(.5, fract(pos.x / unit *.5));
	float square2 = step(.5, fract(pos.y / unit *.5));
	return max(square1,square2) - square1 * square2;
}

void fragment() {
	// ray from camera to fragemnt in wrold space
	vec3 rayWorld = normalize(mat3(INV_VIEW_MATRIX) * VIEW) ;
	
	// calculate fragment position in world space
	vec3 posWorld;
	float t = -(CAMERA_POSITION_WORLD.y - centerOffset.y) / (rayWorld.y );
	posWorld.y = -centerOffset.y;
	posWorld.xz = CAMERA_POSITION_WORLD.xz + t * rayWorld.xz + centerOffset.xz;
	
	// calculate planar distance from camera to fragment (used for fading) 
	float distPlanar = distance(posWorld.xz, centerOffset.xz);
	
	// grid
	float line = grid(posWorld.xz, unitSize, majorLineThickness);
	line += grid(posWorld.xz, unitSize / float(subdivisions), minorLineThickness) * minorLineAlpha;
	line = clamp(line, 0.0, 1.0);

	// checkerboard
	float chec = checker(posWorld.xz, unitSize);
	
	// distance fade factor
	float fadeFactor = 1.0 - clamp((distPlanar - fadeStart) / (fadeEnd - fadeStart), 0.0, 1.0);
	
	// write ground plane depth into z buffer
	vec4 pp = (PROJECTION_MATRIX * (VIEW_MATRIX * vec4(posWorld, 1.0)));
	DEPTH = pp.z / pp.w;
	
	// final alpha
	float alphaGrid = line * gridColor.a;
	float alphaChec = chec * checkerColor.a;
	ALPHA = clamp(alphaGrid + alphaChec, 0.0, 1.0) * fadeFactor;
	// eliminate grid above the horizon
	ALPHA *= step(t, 0.0);

	// final color (premultiplied alpha blend)
	ALBEDO = (checkerColor.rgb * alphaChec) * (1.0 - alphaGrid) + (gridColor.rgb * alphaGrid);
	
}
Tags
3d, grid, ground, infinite, projected, Spatial
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

Dashed Grid (The Best Darn Grid Shader (Yet))

Raycasted Infinite Cylinder

Infinite custom texture scrolling with modifiers

Subscribe
Notify of
guest

15 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Manuel
Manuel
8 months ago

Wow it’s perfect for my project, thank you! In my project I have the ground starting from the y coordinate 0, how can I make the grid appear from the y coordinate -1 so that it can be seen below the ground?

From coordinate 1 no line is seen and it is perfect.

medv007
medv007
7 months ago

Great Shader, is there a way to use arbitary world position instead of camera position to center the grid?

DevList
DevList
7 months ago

This effect is so cool, just what I needed, thank you

josh
josh
6 months ago

experiencing a weird issue where the projection on the y axis is wrong if the camera is far from the origin. you can test by setting the camera’s z position in the demo project to something like 50 and you’ll see the grid appears much below y 0.

josh
josh
6 months ago
Reply to  xyz

fixed now thank you.

do you know how i could keep the fade consistent in screen space? when the camera is far away the perceived size of the grid shrinks.

KekLuck
KekLuck
2 months ago

Great shader, but i have a problem that sometimes it gets rendered behind meshes that are below the grid. Anyone else experienced this?

Neblina
Neblina
1 month ago

I managed to get it working and it looks really great! The only problem is that my camera is Orthographic and the shader is in Perspective. I wonder if there’s a way to work around that somehow? Thanks so much!

Neblina
Neblina
1 month ago
Reply to  Neblina

I realize this works as intended, but on an Orthogonal camera you just don’t have to align the quad to the camera orientation, and just leave it flat at world 0. Thanks so much!

Vathrik
Vathrik
1 month ago

Hi! Excellent shader, I downloaded the project and scaled up the cube in the demo scene with the workspace split between the viewport and the camera preview. As I rotated the camera I noticed the plane did not remain at the same vertical elevation.

When viewed at an angle, it looked like the plane was higher on the cube, when viewed from near top down or side it looked dead center, and when viewed from below it looked below center. I was curious if you saw this same behavior? Uploaded a screenshot at https://imgur.com/a/KPQRXHU