26

I have some simple polygons (fewer than 20 vertices) rendering flat on a simple xy plane, using GL_TRIANGLES and a flat color, a 2d simulation.

I would like to add a border of variable thickness and a different color to these polygons. I have something implemented using the same vertices and glLineWidth/GL_LINE_LOOP, which works, but is another rendering pass and repeats all the vertex transforms.

I think I should be able to do this in the fragment shader using gl_FragCoord and the vertex data and/or texture coordinates, but I'm not sure, and my naive attempts have been obviously incorrect.

I imagine something like the following.

uniform vec2 scale;  // viewport h/w
uniform float line_width;
uniform vec4 fill_color;
uniform vec4 border_color;

varying vec4 vertex; // in world coords

void main()
{
    if (distance_from_edge(gl_FragCoord, vertex, scale) < line_width)
    {
        // we're close to the edge the polygon, so we're the border.
        gl_FragColor = border_color;
    }
    else
    {
        gl_FragColor = fill_color;
    }
}

The part I'm trying to figure out is the distance_from_edge function - how can that be calculated? Is using gl_FragCoord the wrong approach - should I be using some kind of texture mapping?

As an experiment I tried converting the vertex to pixel space with the scale, and then calculate the distance between that and gl_FragCoord in pixels, but that give strange results that I didn't really understand. Plus I need the distance to the edge, not the vertex, but I'm not sure how to get that.

Any ideas?

EDIT: based on Nicol's response, my question becomes:

Assuming I have a triangle with 3 corner vertices marked as edge vertices, and one vertex in the middle marked as not edge (so rendered with 3 triangles in total), then how do I interpolate in the fragment shader to draw a border of a given thickness? I am assuming I pass the edge flag to the fragment shader, as well as the desired line thickness, and it does some interpolation calculation to figure out the distance between the edge and not edge vertices, and thresholds the color as border/fill as appropriate?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
bloodearnest
  • 393
  • 1
  • 3
  • 7
  • 1
    `gl_FragCoord` is going to be massively unhelpful to you. That just tells you where the fragment is on the screen; it says nothing about where the fragment is relative to the triangle. The vertex position will similarly be massively unhelpful. You're going to need the help of the vertex shader/user-provided data that puts values on vertices based on whether it's an edge vertex or not, and interpolate the value. That means you can't detect the edge of a single triangle; you'd have to split the triangle into several triangles, so that interior vertices and edge vertices can be distinguished. – Nicol Bolas Aug 03 '13 at 18:29
  • Thanks, I suspected I was missing something. So if I have a triangle with 3 vertices marked as edge vertices, and one in the middle is not (so rendered with 4 triangles), then how do I interpolate to a given line width for the border? – bloodearnest Aug 04 '13 at 18:21

1 Answers1

31

All you need are the barycentric coordinates, since you are dealing with triangles. Assign each vertex of the triangle an identity, and then use the hardware's built-in interpolation between the vertex and fragment stages to figure out the relative distance from each of the vertices in your fragment shader.

You can think of the barycentric coordinates for each vertex as the distance from the opposite edge. In the diagram below, vertex P0's opposite edge is e1, and its distance is represented by h1; its barycentric coordinate is <0.0, h1, 0.0>. GPUs may use this coordinate space internally to interpolate vertex attributes for triangles when fragments are generated during rasterization, it makes quick work of weighting per-vertex properties based on location within a triangle.

Diagram illustrating the calculation of distance from each edge

Below are two tutorials that explain how to do this, typically this is used for rendering a wireframe overlay so you might have better luck searching for that. For your purposes, since this is effectively a specialization of wireframe rendering (with the addition that you want to throw out lines that do not belong to exterior polygon edges), you will need to identify edge vertices and perform additional processing.

For instance, if a vertex is not part of an exterior edge, then you will want to assign it a barycentric coordinate of something like <1,100,0> and the connected vertex <0,100,1> and the interior edge will be ignored (assuming it is an edge opposite the vertex designated <0,1,0>, as seen in the diagram below). The idea is that you never want a point along this edge to interpolate anywhere near 0.0 (or whatever your threshold you use for shading a fragment as part of the border), making it extremely distant from the center of the triangle in the direction of the opposite vertex will solve this.

Diagram showing how to exclude interior edges

Without Geometry Shaders (OpenGL ES friendly):

Here's a link explaining how to do this, if you are able to modify your vertex data to hold the barycentric coordinates. It has higher storage and pre-processing requirements (in particular, sharing of vertices between adjacent edges may no longer be possible since you need each triangle to consist of three vertices that each have a different input barycentric coordinate - which is why geometry shaders are a desirable solution). However, it will run on a lot more OpenGL ES class hardware than more general solutions that require geometry shaders.

https://web.archive.org/web/20190220052115/http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/

With Geometry Shaders (Not OpenGL ES friendly):

Alternatively, you can use a geometry shader to compute the barycentric coordinates for each triangle at render-time as seen in this tutorial. Chances are in OpenGL ES you will not have access to geometry shaders, so this can probably be ignored.

http://strattonbrazil.blogspot.com/2011/09/single-pass-wireframe-rendering_10.html http://strattonbrazil.blogspot.com/2011/09/single-pass-wireframe-rendering_11.html

The theoretical basis for this solution can be found here (courtesy of the Internet Archive Wayback Machine):

http://web.archive.org/web/*/http://cgg-journal.com/2008-2/06/index.html

mgear
  • 1,333
  • 2
  • 22
  • 39
Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • Thanks, this has me on the right path. I've implemented the non-geometry shader wireframing and it works nicely. Now I just need to add in the additional non-perimeter vertexes and mark appropriately. I had already partially gone down that route in my own experiments, any how, but with 1 coord, not 3. – bloodearnest Aug 06 '13 at 13:09
  • 3
    Theoretical basis link is down, here an [alternative link (web archive)](https://web.archive.org/web/20130607004602/http://cgg-journal.com/2008-2/06/index.html). Another paper from the same guys: [Two Methods for Antialiased Wireframe Drawing with Hidden Line Removal](http://orbit.dtu.dk/fedora/objects/orbit:55459/datastreams/file_3735323/content) – fedab Sep 15 '14 at 11:12
  • Unfortunately, this technique leads to artifacts where borders approach vertices. Interior triangle edges can "cut off" portions of the border: http://imgur.com/Btl6h2Y. – John Jul 13 '17 at 22:13
  • I like this approach and I tried it, but it works only under the condition that the vertices are unique to the triangles. If triangles share vertices, then the result is randomly unpredictable. Basically it works if you are generating your own geometry, but there are problems if you are importing vertex data from OBJ files and such. – Thomas An Nov 08 '17 at 02:58