←back to thread

209 points eliasylonen | 3 comments | | HN request time: 0.672s | source
Show context
exDM69 ◴[] No.41901911[source]
I have an even better darn grid shader that I use in my graphics projects.

The shader in this article wants to emulate the look of a sampled texture so that it blurs to medium gray at distance while avoiding moire patterns. And it does indeed look quite good at what it's aiming for.

I on the other hand wanted an "infinitely zoomable grid paper" look with constant-ish pixel width lines, such that the density of the grid changes. If applied to a "ground" plane, the grid gets sparser near the horizon. When you zoom in, the grid gets denser with a new "decade" of grid fading in with the old grid fading out.

I generally apply this to a "full screen triangle", and do a raycast against the ground plane (e.g. the y = 0 plane) and extract the "uv" coordinates from the raycast result (that would be the x,z coordinate of the ray hit). I've also applied this technique to a skybox, where I take the view ray direction unit vector and convert it to spherical coordinates (latitude, longitude) for the UV.

This shader gives a very clean looking grid at almost any viewing angle and distance. If your world space units are meters, this can be zoomed in from nanometers to gigameters while looking crisp and clean. Unfortunately, floating point issues take over after that point and there are some pixel artifacts when the camera is very close to the ground plane and the viewing angle is extreme. This could be fixed by adding some clamping to the the `log_width` variable, but I haven't bothered with that as I don't work in nanometers in computer graphics projects. There's also some flickering at the horizon line, which I've solved with a trivial fade out factor (not visible in the source code below).

As it is shown below, it'll show a grid with a subdivision factor of 10, like millimeter paper with the primary grid in centimeters and secondary grid in millimeters. The subdivision can be changed by adjusting the `base` and `N` variables. See the comments for explanation.

Here's the thing in all its glory. Apologies that I don't have a screenshot hosted where I could share. Please let me know if you're trying this out in your projects.

    float grid(vec2 uv, float linewidth) {
        vec2 width = linewidth * fwidth(uv);
        vec2 grid = abs(fract(uv - 0.5) - 0.5) / width;
        float line = min(1.0, min(grid.x, grid.y));
        return 1.0 - line;
    }

    float decade_grid(vec2 uv) {
        // grid subdivision factor (logarithm base)
        float base = 10.0;
        // grid density, primary grid is approximately base^N pixels wide
        // with N = 3, primary grid is 100 pixels, secondary grid 10 px
        float N = 3.0; // 3.0 is the densest grid that does not have moire patterns

        // approximate grid cell size using screen space uv partial derivatives
        vec2 width = fwidth(uv);
        //vec2 width = vec2(length(dFdx(uv)), length(dFdy(uv)));
        float w = max(width.x, width.y);
        //float w = length(width.xy);

        // take logarithm of grid cell size to find zoom factor
        float log_width = log2(w) / log2(base); // logarithm change of base

        // round down to find grid zoom factor (power of ten)
        float exponent = -floor(log_width) - N;
        float blend = 1.0 - fract(log_width); // blend between powers of ten

        // primary grid with wider lines
        float grid1 = grid(uv * pow(base, exponent), 1.0 + blend);
        // secondary grid with narrow lines
        float grid2 = grid(uv * pow(base, exponent + 1.0), 1.0);

        // mix primary and secondary grid with linear interpolation
        return mix(grid1, grid2, blend);
    }
replies(1): >>41903937 #
1. indigoabstract ◴[] No.41903937[source]
I've tried this in Shadertoy and it looks pretty good.

Can this be adapted to work with classic line segments made from quads or is it just for drawing grids in screen space?

replies(1): >>41905438 #
2. exDM69 ◴[] No.41905438[source]
> Can this be adapted to work with classic line segments made from quads or is it just for drawing grids in screen space?

It can be applied like a texture in the fragment shader using UV coordinates and their partial derivatives.

You can't really use this technique with line segments because the grid density up close to the camera is higher than far away. You'd need to do a lot of math to figure out where to draw your line segments. If you calculate all the line segment geometry, a fancy shader isn't required any more.

replies(1): >>41908866 #
3. indigoabstract ◴[] No.41908866[source]
Aha, I see. Line segments from quads suffer from all the issues mentioned in the article as well, especially in the distance, but I didn't quite understand in what context is the grid shader applicable. Thank you for clearing this up.