Wide line endcaps in a fragment shader?

I’ve been looking into various ways to draw wide lines. A couple of times I have run across references to rendering rounded endcaps in the fragment shader, instead of with geometry (by adding a little fan of triangles forming a half-circle). But I haven’t seen any concrete examples of how to do this. I get that you would measure the distance away from the line endpoint, and anything beyond that is discarded, which would form a round shape. But how do you do that in practice? I’ve only used fragment shaders to deal with color and lighting, not anything using distance or position.


Are you converting the lines to triangles (either in client code or a geometry shader), or just using glLineWidth() to render thick lines?

Either way, you need to set appropriate attributes. One option is to simply store the start and end coordinates as attributes, then find the distance between gl_FragCoord and the line segment (after conversion to window space; but note that with a perspective projection, endpoints might not have window-space coordinates).

Yes, converting to triangles. And yes I just want to know how to find the distance between the fragment coordinate and the segment. Basically, how do I get the fragment coordinate?

Add appropriate attributes to each vertex, containing “line space” coordinates. These will be interpolated during rasterisation, so the fragment shader knows the fragment’s location relative to the line.

Typically, the attribute would be a vec3, where the values are:
[li] The perpendicular distance from the line.
[/li][li] The distance along the line from one endpoint.
[/li][li] The distance along the line from the other endpoint.

If either of the last two values are negative, then it means that you’re inside the line cap, and should use the smaller value and the perpendicular distance to calculate the distance from the endpoint. If that’s less than half the width of the line then the fragment is inside the semicircle, otherwise it’s outside.

if (pos.y < 0 && length(pos.xy) < 0.5*line_width)
if (pos.z < 0 && length(pos.xz) < 0.5*line_width)

Here’s a diagram to explain the coordinate system:

If you want polylines with rounded or mitred joins, you have to calculate the distance along the edge of the stroke. If you just want caps, you can measure the distance along the centre line.

The parallel and perpendicular distances must have the same scale to get circular (rather than elliptical) caps.