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))

Infinite Sprite

Vaporwave Grid

Subscribe
Notify of
guest

11 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Manuel
Manuel
1 month 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
1 month ago

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

DevList
DevList
1 month ago

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

josh
josh
3 days 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
2 days 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.