2

I'm trying to draw pixel-perfect lines in OpenGL using a fragment shader. I have the following fragment shader which I got from this question. However the result is 2-pixels in size rather than 1 pixel.

#version 330

layout(location=0) out vec4 frag_colour;

in vec4 color;

uniform vec2 positionA;
uniform vec2 positionB;
uniform int thickness;

float lineSegment(vec2 p, vec2 a, vec2 b, float thickness)
{
    vec2 pa = p - a;
    vec2 ba = b - a;

    float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );

    float k = length(pa - ba*h);

    return smoothstep(0.0, thickness, k);
}

void main()
{
    vec2 uv = gl_FragCoord.xy;

    frag_colour = mix(color,vec4(0),lineSegment(uv,positionA,positionB,thickness));
}

As I understand it, this is because wherever the "line" is, the pixels to either side are technically the same distance away, so if I try to remove any kind of anti-aliasing, they are both "on the line" and so my 1-pixel wide line ends up being 2 pixels.

enter image description here You can see how the yellow horizontal line is 2-pixels wide (compare it to the individual pixels in the text).

I've tried some other fragment code here and there from the internet and they all feature this same problem.

How can I draw a pixel-perfect, aliased (not anti-aliased), line in GLSL?

EDIT 1 After adjusting the positions and thickness by 0.5f, so move to the centre of the pixels, the diagonal lines have anti-aliasing on each sort of "step".

Diagonal Line:

enter image description here

genpfault
  • 51,148
  • 11
  • 85
  • 139
NeomerArcana
  • 1,978
  • 3
  • 23
  • 50
  • 1
    That `thickness` is like a radius around the line. If you specify 1 pixel, you will get 1 pixel above and 1 pixel below, i.e. 2 pixels in total. If you use 0.5px radius, make sure to specify your line with endpoints at pixel centers to avoid numerical instabilities. – Nico Schertler Jun 07 '19 at 23:09
  • Thanks, @Nico, that did it for a horizontal/vertical line. But a line that goes at an angle has antialiasing added to it. – NeomerArcana Jun 07 '19 at 23:26
  • 2
    Then replace the `smoothstep` with something like `return k <= thickness ? 0 : 1`. – Nico Schertler Jun 07 '19 at 23:32
  • That's it @NicoSchertler. It's always obvious after someone explains it. If you put it into an answer I'll accept it. – NeomerArcana Jun 07 '19 at 23:35

1 Answers1

1

Perhaps this would be helpful (tested in shadertoy):

bool lineSegment(vec2 p, vec2 a, vec2 b, float thickness)
{
    vec2 pa = p - a;
    vec2 ba = b - a;
    float len = length(ba);
    vec2 dir = ba / len;
    float t = dot(pa, dir);
    vec2 n = vec2(-dir.y, dir.x);
    float factor = max(abs(n.x), abs(n.y));
    float distThreshold = (thickness - 1.0 + factor)*0.5;
    float proj = dot(n, pa);
    
    return (t > 0.0) && (t < len) && (proj <= distThreshold) && (proj > -distThreshold);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 positionA = iResolution.xy * 0.5;
    vec2 positionB = iMouse.xy;
    float thickness = 1.0;
    
    fragColor = mix(vec4(0), vec4(1), float(lineSegment(fragCoord,positionA,positionB,thickness)));
}

Just in case, here's a related question on math.stackexchange.

dairin0d
  • 11
  • 4