I’m trying to create polygons with an n-pixel wide inset outline (outline is completely contained inside the drawn area of the original polygon) with a different color.

Presumably because I said the word “pixel” I have invoked the dread demon “fragment shader”. In addition, since I said “inset”, I have probably summoned his slightly less intimidating brother “stencil buffer” as well. And, presumably, “different color” means that the correct incantation will have multiple rendering passes. (I’m not wedded to any of this if there is a much better way …)

However, I can’t seem to put the pieces together in such a way as to get anything coherent. I can create stipple patterns with my fragment shader just fine, but I can’t things to stay confined to the outline pixels.

You can either do it geometrically (creating the outline as a strip of triangles) or using a fragment shader.

The former option probably doesn’t involve OpenGL (unless you’re going to use a geometry shader).

For the latter, each vertex needs an attribute which is 0.0 for an exterior vertex or 1.0 for an interior vertex (depending upon how the polygon is tessellated into triangles, this may require duplicating vertices). In the fragment shader, dividing the attribute’s value by its gradient (obtained using the dFdx and dFdy functions) gives you the distance of the current fragment’s centre from the edge in pixels, e.g.

This gives you an outline on one edge for each triangle. If you want an outline on more than one edge, you can add additional attributes (or use a vec2 or vec3 attribute), and perform the distance calculation and comparison for each edge, OR-ing the results. But if you want outlines on a varying number edges for the triangles in a single mesh (draw call), it gets more complex. One option is to always provide a vec3 attribute, but set the value to 1.0 for all three vertices if you don’t want a particular edge outlined. Again, this may require duplicate vertices depending upon how the polygon is tessellated. For radial tessellation (where a convex n-gon is tessellated into n triangles, each using two adjacent exterior vertices and a common interior vertex), you only need one attribute which is 1.0 for the interior vertex and 0.0 for the exterior vertices.

That trick with the derivative is neat. I have to remember that.

As for one edge per triangle … oof. Not all the geometry is going to be convex. So, I would have to either submit the same triangle 2 times with different vertex labels or triangulate suboptimally (ie 4 triangles to render a rectangle for example–your radial decomposition) in order to always keep 1 exterior and 2 interior edges. Got it.

As for rendering geometrically, how would I decompose the edge into a geometry of triangles that are then “pixels” in width? It seems like I would have to use my main CPU to unwind the transformation matrix in one of the directions (map “pixels” to “geometry width” or “geometry width” to “pixels”)

That seems like it’s not going to perform very well. I would probably have to do something in the geometry shader to make this not run like a dog. And I still probably have to triangulate suboptimally to make sure I can mark an “exterior” edge for the geometry shader to chew on.

I’ll give the fragment shader with the multiply labelled vertices and try and cogitate on the geometry shader for a while. I’ve got lots to chew on and think about.

You can use transform feedback to capture transformed vertices.

You can pass the polygon boundary to the geometry shader by rendering it as a GL_LINE_LOOP. The geometry shader would need an input layout of lines_adjacency so that you get the vertices for previous and successive edges. The geometry shader would emit two triangles for each invocation. Alternatively, you could just have it emit one point for each invocation (one of the two inset vertices), then use these to generate both the boundary triangles and the interior polygon. Although it would probably be simpler to just render the boundary over the original polygon; unless the boundary is particularly wide, the overdraw shouldn’t be significant.