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);
}
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.
Hi,
Glad you find the shader useful. Per your suggestion, I now updated the shader so it has the additional elevation uniform that controls the elevation of the ground plane.
Great Shader, is there a way to use arbitary world position instead of camera position to center the grid?
The center is at world origin. You can easily offset it in xz plane by adding a vec2 to posWorld.xz in line 38
I updated the shader code. The elevation is nowreplaced with centerOffset uniform that lets you offset the grid on all 3 axes.
This effect is so cool, just what I needed, thank you
Thanks. Glad you found it useful.
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.
Fixed the shader code. Thanks for spotting this.
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.
You can update the fade range paramters, depending on camera distance from the grid origin.
Great shader, but i have a problem that sometimes it gets rendered behind meshes that are below the grid. Anyone else experienced this?
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!
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!
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
Can you post the exact steps to replicate it? Also the version of Godot you are using.
Works almost perfect. Only issue I have is that it’s centered to the world orgin not the player (camera) position. Is there a quick way to change that?
You can update the centerOffset parameter at runtime.
This doesn’t seem to work the way I’d expect? It does offset the grid, but it doesn’t change the point where the fade is centered on. I’d like to change that location to match an object location, but leave the grid offset alone.