28

Is it possible for me to add line thickness in the fragment shader considering that I draw the line with GL_LINES? Most of the examples I saw seem to access only the texels within the primitive in the fragment shader and a line thickness shader would need to write to texels outside the line primitive to obtain the thickness. If it is possible however, a very small, basic, example, would be great.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Meda
  • 2,776
  • 4
  • 20
  • 31
  • Despite the accepted answer, the more practical answer is "you don't want to do that" (in a fragment shader) - its not a technique that will scale well to many lines. See [itjak's answer](https://stackoverflow.com/a/15276648/199364). – ToolmakerSteve Jul 04 '19 at 18:32

5 Answers5

30

Quite a lot is possible with fragment shaders. Just look what some guys are doing. I'm far away from that level myself but this code can give you an idea:

#define resolution vec2(500.0, 500.0)
#define Thickness 0.003

float drawLine(vec2 p1, vec2 p2) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;

  float a = abs(distance(p1, uv));
  float b = abs(distance(p2, uv));
  float c = abs(distance(p1, p2));

  if ( a >= c || b >=  c ) return 0.0;

  float p = (a + b + c) * 0.5;

  // median to (p1, p2) vector
  float h = 2 / c * sqrt( p * ( p - a) * ( p - b) * ( p - c));

  return mix(1.0, 0.0, smoothstep(0.5 * Thickness, 1.5 * Thickness, h));
}

void main()
{
  gl_FragColor = vec4(
      max(
        max(
          drawLine(vec2(0.1, 0.1), vec2(0.1, 0.9)),
          drawLine(vec2(0.1, 0.9), vec2(0.7, 0.5))),
        drawLine(vec2(0.1, 0.1), vec2(0.7, 0.5))));
}

enter image description here

Another alternative is to check with texture2D for the color of nearby pixel - that way you can make you image glow or thicken (e.g. if any of the adjustment pixels are white - make current pixel white, if next to nearby pixel is white - make current pixel grey).

defhlt
  • 1,775
  • 2
  • 17
  • 24
  • Man, that website is grand! – Meda Oct 12 '13 at 21:34
  • 15
    While this "works", it should be noted that this is probably the most inefficient way in th world to draw three lines. 99% of the fragments that are processed are thrown away during blending. – Damon Oct 21 '13 at 18:56
  • 1
    +Damon sure it was not intended as best practices and my example is somewhat silly; but just to demonstrate the principle and possibilities of shaders - the similar way you could do various glow or particle effects or render the whole terrains from heightmaps or complex geometrical fractals from distance fields not using a single vertex. Surprisingly shaders can be pretty fast. – defhlt Oct 21 '13 at 20:49
  • 1
    +Damon, btw, more inefficient way than this would be calculating pixels with CPU, which is widely used nevertheless. – defhlt Oct 21 '13 at 20:59
  • 1
    +1 to what Damon said. Using a geometry shader is likely to be more than 1000x quicker than this method, and allow you to use normal fragment shaders to apply a design to the line when you want. I would agree that this answer is great to solve the question to the letter, but perhaps not in spirit. – Elliot Woods Mar 10 '16 at 10:18
  • CAUTION: This approach doesn't scale well. Calculating pixels with CPU - done well - would do much better than this, given many lines. This isn't a technique anyone should copy. OTOH, it is the question that is fundamentally flawed - this is a valid demonstration of how to do something one almost certainly doesn't want to do. – ToolmakerSteve Jul 04 '19 at 18:26
6

No, it is not possible in the fragment shader using only GL_LINES. This is because GL restricts you to draw only on the geometry you submit to the rasterizer, so you need to use geometry that encompasses the jagged original line plus any smoothing vertices. E.g., you can use a geometry shader to expand your line to a quad around the ideal line (or, actually two triangles) which can pose as a thick line.

In general, if you generate bigger geometry (including a full screen quad), you can use the fragment shader to draw smooth lines.

Here's a nice discussion on that subject (with code samples).

ltjax
  • 15,837
  • 3
  • 39
  • 62
  • so in the fragment shader you can only access/modify the fragments within the drawing primitives? That would make sense. As for your proposed solution, thanks but in opengl es I can't use geometry shaders :( – Meda Mar 07 '13 at 16:55
  • Not access - you can read from any source, but you can only write to one location and that's given to you by the rasterizer. You can use the same method on the CPU if you don't have geometry shaders. – ltjax Mar 07 '13 at 17:05
  • 5
    never say no unless you can prove it – GottZ Apr 21 '16 at 08:54
  • 1
    @GottZ: Well, I was answering under the conditions in the question, namely drawing the lines with GL_LINES. Sure, if you render fullscreen quad, you can draw anything in the fragment shader. But that was not what was asked for. – ltjax Aug 18 '17 at 14:13
  • sounds silly but yes i was refering to a fullscreen fragment shader where you know resolution (inside the shader). why do i say that? http://i.imgur.com/dw6y7Ko.png cause i did it. (yes this is just two polygons you see there). this was mainly a proof of concept in raw glsl plus some c++ winapi magic though. i needed pixel size information to get perfect anti aliasing without post processing – GottZ Aug 18 '17 at 15:48
  • Thanks for the link. – Robinson Sep 23 '17 at 17:02
4

Here's my approach. Let p1 and p2 be the two points defining the line, and let point be the point whose distance to the line you wish to measure. Point is most likely gl_FragCoord.xy / resolution;

Here's the function.

float distanceToLine(vec2 p1, vec2 p2, vec2 point) {
    float a = p1.y-p2.y;
    float b = p2.x-p1.x;
    return abs(a*point.x+b*point.y+p1.x*p2.y-p2.x*p1.y) / sqrt(a*a+b*b);
}

Then use that in your mix and smoothstep functions.

Also check out this answer: https://stackoverflow.com/a/9246451/911207

Community
  • 1
  • 1
David Braun
  • 782
  • 1
  • 9
  • 18
0

A simple hack is to just add a jitter in the vertex shader: gl_Position += vec4(delta, delta, delta, 0.0); where delta is the pixelsize i.e. 1.0/viewsize

Do the line-draw pass twice using zero, and then the delta as jitter (passed in as a uniform).

KBVIS
  • 1
0

To draw a line in Fragment Shader, we should check that the current pixel (UV) is on the line position. (is not efficient using only the Fragment shader code! this is just for the test with glslsandbox) An acceptable UV point should have these two conditions:

1- The maximum permissible distance between (uv, pt1) should be smaller than the distance between (pt1, pt2). With this condition we create a assumed circle with the center of pt2 and radious = distance(pt2, pt1) and also prevent the drawing of line that is longer than the distance(pt2, pt1).

2- For each UV we assume a hypothetical circle with a connection point on ptc position of the line(pt2,pt1). If the distance between UV and PTC is less than the line tickness, we select this UV as the line point.

in our code: r = distance (uv, pt1) / distance (pt1, pt2) give us a value between 0 and 1. we interpolate a point (ptc) between pt1 and pt2 with value of r

code:

#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

float line(vec2 uv, vec2 pt1, vec2 pt2,vec2 resolution)
{
    
    float clrFactor = 0.0;
    float tickness = 3.0 / max(resolution.x, resolution.y);  //only used for tickness
    
    float r  = distance(uv, pt1) / distance(pt1, pt2);
    
    if(r <= 1.0) // if desired Hypothetical circle in range of vector(pt2,pt1)
    {
        vec2 ptc = mix(pt1, pt2, r); // ptc = connection point of Hypothetical circle and line calculated with interpolation
        float dist = distance(ptc, uv);  // distance betwenn current pixel (uv) and ptc
        if(dist < tickness / 2.0)
        {
            clrFactor = 1.0;
        }
    }
    return clrFactor;
}



void main()
{
    vec2 uv = gl_FragCoord.xy / resolution.xy; //current point
    //uv = current pixel
    //      0 < uv.x < 1 , 0 < uv.x < 1
    //      left-down= (0,0)
    //      right-top= (1,1)
    
    vec2 pt1 = vec2(0.1, 0.1);  //line point1 
    vec2 pt2 = vec2(0.8, 0.7);  //line point2 
       
    
    float lineFactor = line(uv, pt1, pt2, resolution.xy);
    vec3 color = vec3(.5, 0.7 , 1.0);
    
    gl_FragColor = vec4(color * lineFactor , 1.);
}