4

How do I draw a line, or a curved line using a fragment shader?

I have a program that calculates a bezier curve given a set of vertices specified by the user. Early on, it was pretty straightforward where I simply process each vertex on the application side to generate a set of interpolated points based on this cubic Bezier curve equation:

Cubic Bezier curve

... and then store all the processed vertices into a GL_VERTEX_ARRAY for drawing with glDrawArrays(GL_LINE_STRIP, 0, myArraySize). While the solution is simple, the time complexity for this implementation is O(n^2). The issue arises where I start increasing my step count, such as incrementing t by 0.01 each iteration of my loop, compounded by the user generating as many vertices they want.

So that's when I started looking into shaders, especially the fragment shader. As far as I understand, our fragment shader program assigns a color to the current fragment being processed by the GPU. I haven't gotten serious into shader programming, but my current line shader implementation is as follows:

#version 120

uniform vec2 resolution;
uniform vec2 ptA;
uniform vec2 ptB;
uniform vec2 ptC;
uniform vec2 ptD;
uniform float stepTotal;

void main()
{
    vec2 uv = gl_FragCoord.xy / resolution.xy;
    vec2 curvePts[int(stepTotal)];

    float t = stepTotal;

    for(float i = 0.; i < 1.0; i += 1./t)
    {
        # Courtesy to: https://yalantis.com/blog/how-we-created-visualization-for-horizon-our-open-source-library-for-sound-visualization/
        vec2 q0 = mix(ptA, ptB, i);
        vec2 q1 = mix(ptB, ptC, i);
        vec2 q2 = mix(ptC, ptD, i);

        vec2 r0 = mix(q0, q1, i);
        vec2 r1 = mix(q1, q2, i);

        vec2 p_int = mix(r0, r1, i);
        curvePts[int(i) * int(stepTotal)] = p_int;
    }

    // TO DO:
    // Check if current fragment is within the curve. Not sure how
    // to proceed from here...
}

As you can see, I am currently stuck on how to check if the current fragment is within the curve, as well as how do I assign a color to that specific fragment that eventually becomes a curved line when displayed.

JDBones
  • 495
  • 1
  • 7
  • 18
  • 1
    Two good learning sites: [The Book of Shaders](https://thebookofshaders.com/05/) and with [Tessellation](https://computeranimations.wordpress.com/2015/03/16/rasterization-of-parametric-curves-using-tessellation-shaders-in-glsl/) – Ripi2 Mar 27 '18 at 16:31
  • This is not a good solution, since you will calculate this for every pixel (fragment). Of course, you can add logic to decide if a pixel is "inside" curve, but the logic itself would be quite complicated. – Martin Perry Mar 27 '18 at 16:58
  • @Ripi2 - Thank you. That is a good site, reading it as we speak. – JDBones Mar 27 '18 at 17:00
  • @Martin Perry - True. This is a shift of programming gears for me, I'm so used to working in immediate mode where simply specifying some vertices and have it be drawn via GL_LINES, GL_LINE_STRIP, etc. – JDBones Mar 27 '18 at 17:02
  • 1
    Possible duplicate of [Draw Quadratic Curve on GPU](https://stackoverflow.com/questions/31336454/draw-quadratic-curve-on-gpu) – Spektre Mar 27 '18 at 17:45
  • see [GLSL rendering 2D cubic bezier curves](https://stackoverflow.com/a/60113617/2521214) its fully implemented partially based on link from previous comment – Spektre Feb 10 '20 at 15:34

1 Answers1

3

You might draw line segments using distance function:

float DistanceToLineSegment(vec3 p, vec3 a, vec3 b)
{
    vec3 pa = p - a, ba = b - a;
    float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
    return length( pa - ba*h );
}

The fragment will be "inside" of a segment if result of distance function is lesser than certain threshold.

You might also replace threshold test with smooth step function to draw anti-aliased line.

  • Yep. That's it! Thank you! – JDBones Apr 20 '18 at 23:01
  • Would you mind elaborating on semantics of `p`, `a`, `b`, at least, and, optionally, `pa`, `ba` and `h`? I find this answer useful but it's hard to make out the method with the chosen variable names (although I imagine one can't do much better with a number of vectors and for a three line procedure). – Armen Michaeli Jul 29 '21 at 11:48
  • "p" is the coordinates of an arbitrary point; "a" is the coordinates of the start point of the line segment; "b" is the coordinates of the end point of the line segment; "pa" is the vector from point "p" to point "a"; "pb" is the vector from point "p" to point "b"; – game development germ Jul 29 '21 at 17:20