5
\$\begingroup\$

I would like to have in-game grid lines similar to ones found in Unity editor. There is simple solution(first listing) based on shaders found on unity forums(on right image). However, that solution does not take in account antialiasing and drawn lines looks terrible even few units away from camera.

float4 frag(vertexOutput input) : COLOR {
    if (frac(input.worldPos.x / _GridSpacing) < _GridThickness 
        || frac(input.worldPos.z / _GridSpacing) < _GridThickness) {
        return _GridColour;
    }
    else return _BaseColour;
}

overview In order to address that, I tried using typical fwidth/smoothstep combination:

float4 frag(vertexOutput input) : COLOR{
    float distX = min(frac(input.worldPos.x / _GridSpacing), 1.0 - frac(input.worldPos.x / _GridSpacing));
    float distZ = min(frac(input.worldPos.z / _GridSpacing), 1.0 - frac(input.worldPos.z / _GridSpacing));
    float dist = min(distX, distZ);
    float delta = fwidth(dist);
    float alpha = smoothstep(_GridThickness - delta, _GridThickness, dist);
    return lerp(_GridColour, _BaseColour, alpha);
}

Even though it has nice fade, the result is not quite near to perfect - not only the lines looks thinner(which can be somewhat fixed by increasing width), but also have various artifacts(e.g. in joints) and horizontal lines disappear much sooner than vertical ones. Detail image(anti aliased on left): detail I am interested in shader solutions, since according to answer to similar question relying textures there in can be no solution with textures. In addition I would like grid lines fade at reasonable distance(when closely inspecting aliased overview image you can see where plane ends) rather than continuing to infinity.
Full current version(for testing purposes) with signed distance.

\$\endgroup\$
3
  • \$\begingroup\$ I think this has to do with the way you're calculating your distX and distZ values. They're absolute distances - so if the left pixel is 0.1 to the left of the line, and the right pixel is 0.1 to the right of the line, the partial derivative detects 0 change between the two, failing to notice that you crossed the line in-between. If you use a signed distance instead, the left pixel will get a value of -0.1 and you'll get a correct partial derivative of 0.2 between them. \$\endgroup\$ – DMGregory Jun 1 '17 at 16:59
  • \$\begingroup\$ Also, I wouldn't read too much into the answer in the linked question claiming it's impossible to get a good result with textures, since the question itself was edited to include a pretty good result with textures - it just depends on how you set up the mipmaps and how you sample them. \$\endgroup\$ – DMGregory Jun 1 '17 at 17:03
  • \$\begingroup\$ @DMGregory Using singed distance in fwidth makes the lines even thinner, but some(the ones in joints remain) of the aliasing artifact are less apparent. The edited version from linked question seems like having problems with thinner lines. \$\endgroup\$ – wondra Jun 1 '17 at 17:21
7
\$\begingroup\$

Here's an antialiased grid shader that seems to work pretty well.

The main difference is calculating a screenspace distance to the nearest gridline in each direction separately, and then taking the minimum of these two distances.

Example grid shader outputSecond example of grid shader output

Shader "Unlit/Grid"
{
    Properties
    {
        _GridColour ("Grid Colour", color) = (1, 1, 1, 1)
        _BaseColour ("Base Colour", color) = (1, 1, 1, 0)
        _GridSpacing ("Grid Spacing", float) = 0.1
        _LineThickness ("Line Thickness", float) = 1        
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"}
        LOD 100

        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            fixed4 _GridColour;
            fixed4 _BaseColour;
            float _GridSpacing;
            float _LineThickness;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = mul(unity_ObjectToWorld, v.vertex).xz / _GridSpacing;

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {               
                float2 wrapped = frac(i.uv) - 0.5f;
                float2 range = abs(wrapped);

                float2 speeds;
                /* // Euclidean norm gives slightly more even thickness on diagonals
                float4 deltas = float4(ddx(i.uv), ddy(i.uv));
                speeds = sqrt(float2(
                            dot(deltas.xz, deltas.xz),
                            dot(deltas.yw, deltas.yw)
                         ));
                */  // Cheaper Manhattan norm in fwidth slightly exaggerates thickness of diagonals
                speeds = fwidth(i.uv);

                float2 pixelRange = range/speeds;
                float lineWeight = saturate(min(pixelRange.x, pixelRange.y) - _LineThickness);

                return lerp(_GridColour, _BaseColour, lineWeight);
            }
            ENDCG
        }
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ The problem with screen space is the line appears to be same width regardless distance, blending in flat color further away(as seen in one of the answers of linked question). I would like grid lines similar to those found in editor (which doesn't seem to suffer from same artifact as included aa solution does). \$\endgroup\$ – wondra Jun 1 '17 at 18:26
  • \$\begingroup\$ Ahhh. Because of the way you were using fwidth, I thought that was an effect you were deliberately trying to create. You can adjust this by varying _LineWidth with distance \$\endgroup\$ – DMGregory Jun 1 '17 at 18:55
  • \$\begingroup\$ Thinking about it, it might not been the best solution to use fwidth because it was used for anitaliasing implicit shapes rather than thin lines. I am not sure if varying _LineWidth would be the answer - with the original, aliased, solution the lines are drawn correctly if thick enough, there must be an error in antialiasing (as mentioned, signed distance helped but it is not perfect - especially the joints are problematic). \$\endgroup\$ – wondra Jun 1 '17 at 19:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.