3
\$\begingroup\$

I would like to render a grid with many dots, all equally spaced and being exactly 1 pixel wide. What I was able to achieve so far is this :

enter image description here

What I would like is (simulated using image editing program):

enter image description here

Here is the shader I wrote (which give result of 1st image). This is rendered as a giant quad.

Shader "Custom/Grid"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct vertInput {
                float4 pos : POSITION;
                float4 uv : TEXCOORD1;
            };

            struct vertOutput {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
            };

            vertOutput vert (vertInput input)
            {
                vertOutput o;
                o.pos = mul(UNITY_MATRIX_MVP, input.pos);
                o.uv = input.uv;
                return o;
            }

            fixed4 frag (vertOutput output) : SV_Target
            {
                float4 uv = output.uv;
                float x = step((uv.x * 100.0 + 0.025) % 1.0, 0.05);
                float y = step((uv.y * 100.0 + 0.025) % 1.0, 0.05);
                float c = min(x, y);
                return float4(c, c, c, 1.0);
            }
            ENDCG
        }
    }
}

Is it possible to achieve what I want using a shader ? What is needed from first draft is to control the size of each dot. I have tried to use partial derivatives (ddx and ddy) but was not able to make it work.

\$\endgroup\$
1
  • \$\begingroup\$ Suggestions: draw dots in screen space, on a plane, aligned to your camera. Send to this shader dtos world positions as a vector3 array. Convert to screenspace, and then you only need to draw dot, if it position equals to one of array. \$\endgroup\$ Jun 27, 2020 at 7:02

1 Answer 1

3
\$\begingroup\$

This does pretty well, though I found I needed to add a small fudge factor to avoid dots occasionally winking out when the grid point exactly straddled the border between pixels, but this does create a rare doubled pixel.

Pixel dot grid

(The dots look inconsistent in the preview due to scaling - click to see it at native resolution)

Multiply the uv you provide as input to control the spacing between dots.

fixed4 frag (v2f i) : SV_Target
{
    // Partial derivatives of grid in x & y screenspace directions.
    float2 dx = ddx(i.uv);
    float2 dy = ddy(i.uv);

    // Think of these two vectors as a matrix,
    // |  dx.x    dy.x  |
    // |  dx.y    dy.y  |
    // and compute its matrix inverse.

    float invDet = 1.0f/(dx.x*dy.y - dx.y*dy.x);

    float2 toX = float2(dy.y, -dy.x) * invDet;
    float2 toY = float2(-dx.y, dx.x) * invDet;

    // Measure our signed distance from the nearest grid line in uv space.
    float2 fromZero = frac(i.uv + 0.5f) - 0.5f;

    // Transform this to a signed distance in pixel space, 
    // by multiplying by our inverse matrix.
    float2 transformed = float2(dot(fromZero, toX), dot(fromZero, toY));

    // Zero-out any pixels that are more than half a pixel stride from a grid line.
    float on = 1.0f;
    on *= (transformed.x > -0.5f) * (transformed.x <= 0.505f);
    on *= (transformed.y > -0.5f) * (transformed.y <= 0.505f);

    // What remains should be single-pixel dots at the lattice points.
    return on;
}
\$\endgroup\$
2
  • \$\begingroup\$ Thanks a lot for your answer. Now I need to take some time to understand the math behind it (this is more complex than what I thought). \$\endgroup\$
    – tigrou
    Jun 27, 2020 at 19:13
  • \$\begingroup\$ I'll confess I don't quite understand it myself. Multiplying by the inverse matrix "felt" right here, but my brain may be too deeply in weekend mode to explain precisely why. ;) \$\endgroup\$
    – DMGregory
    Jun 27, 2020 at 19:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .