I'm trying to figure out how to make a shader to render an infinite grid in the game world. This requires me to cover the entire screen with a quad, and then use a fragment shader to determine the color of each fragment based on where a ray that extends out from the camera and through the fragment intersects the world XZ plane.

Here's the fragment shader code (vertex shader is empty at this point):

void fragment()
{
vec3 cam_pos = (CAMERA_MATRIX * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
vec3 frag_dir = (CAMERA_MATRIX * vec4(VIEW, 0.0)).xyz;

float t = -cam_pos.y / frag_dir.y;
vec3 frag_intersect = cam_pos + (t * frag_dir);

ALBEDO = vec3(float((fract(abs(frag_intersect.x)) < 0.05 || fract(abs(frag_intersect.z)) < 0.05) && t < 0.0));
}

On a simple (non-fullscreen) quad, everything works nicely:

However, once I try to make the quad fullscreen as per the advanced post-processing tutorial by putting POSITION = vec4(VERTEX, 1.0); in the vertex shader, things get wonky:

Both screenshots are taken from the exact same camera position, but once I make the quad fullscreen, changing the camera position or zooming in/out causes the grid to get stretched and sheared to infinity and back. Why is this happening?

Godot version 3.2.3
in Engine

Well, I figured it out. I had to set render_mode skip_vertex_transform; and then do VERTEX = (PROJECTION_MATRIX * vec4(VERTEX, 1.0)).xyz; in the vertex shader instead.

But I still don't understand why this works and the other method does not? What's the difference between VERTEX and POSITION?

Out of curiosity, why not just use a large PlaneMesh instead? You could even enable Triplanar + World Triplanar on its SpatialMaterial and parent the PlaneMesh to the camera so that it always follows the camera while keeping its texture coordinates "fixed" to the world.