Rendering edges
Table of Contents
Suppose you want to render the edges of a mesh like in the in the figure:
There are many ways to do this. Here I will use barycentric coordinates. Barycentric coordinates provide a very useful way to represent points relatively to the vertices of a convex polygon, giving us a tool to do all sorts of calculations easily without the need to perform geometric computations.
Barycentric Coordinates⌗
Given a triangle , with vertices , , and - and an arbitrary point . The barycentric coordinates of have three components , each relative to a different vertex. This new coordinate system is constructed from the normalized areas of the sub-triangles , , and .
The barycentric coordinates or are defined as
Notice that, since the areas are normalized by , we have . This way we can save some computations by using
Thus, the barycentric coordinates () represent . The cartesian coordinates of can be retrieved from the relation of each and their respective vertex position , or . In other words, is the result of a linear combination of the vertices , , and .
Always remember the sub-triangles you form with . Making coincide one of the vertices is the same as having one of those triangles equal and the other two vanish. You may also realize that each is associated to an edge and an opposite vertex . The value of is the signed distance of to the line formed by , and this distance is at . The following figure may help :)
Here are some observations:
- If , then the barycentric coordinates are ();
- If , then the barycentric coordinates are ();
- If , then the barycentric coordinates are ();
- Any point inside the triangle will have coordinates ;
- If lies on edge , then ;
- If lies on edge , then ;
- If lies on edge , then ;
As you may expect now, we will determine how close is from any edge using barycentric coordinates! But before that, here is a simple code to compute the barycentric coodinates of :
Detecting Edges in Fragment Shader (GLSL)⌗
The trick now is to determine the barycentric coordinates of our fragment. Because with those coordinates, we can determine if it belongs to an edge or not.
Since we know that the barycentric coordinates of the three vertices of are we can just assign those values as vertex attributes and let the hardware interpolate them throughout the fragments.
Now that we have the barycentric coordinates of our fragment, we need to get the distance to the closest edge. A fragment will be considered an edge if it is closer than the edge width . In which space the is defined is up to the method we use. Perhaps the most naive one is to define in the barycentric coordinates space, this way an fragment can be easily classified as edge using
Depending on the angle of the triangle and the camera view plane, you may also get edges to desapear due to interpolation issues. A way to get around this problem is to weight with the total change in barycentric coordinate values on the fragment:
In GLSL
, there is a function called fwidth
that computes the sum of the absolute
value of derivatives for a given variable accross fragments. The above test
can be coded in GLSL
like this:
Using OSL (Open Shading Language)⌗
Here I’ll take the chance to give an example using OSL
. In order to compute the
barycentric coordinates of our point , we can use some variables that OSL provides for us:
Variable | Description |
---|---|
P |
position of the point being shaded |
u and v |
2D parametric coordinates - for the current primitive - of P |
dPdu and dPdv |
Partial derivatives tangent to the surface ate P |
The vertices of our primitive (assuming a triangle here) can then be calculated like this:
Now, we again have the 3 vertices , , and , and a point . The rest goes as usual.
Compensating Triangle Shapes⌗
Until now, we’ve been defining on barycentric coordinates space. This method works fine for fairly regular triangles (all sides with equal size). However, streched triangles will show edges with varying width. See the edges on the bunny ears for example:
We might improve our results a little bit by trying to normalize things. Lets think about the actual sizes of our triangle: the side vectors , , and , and in particular, the heights , , and :
In barycentric coordinates terms, all these heights measure (that is why things get weird in streched triangles). However, we can bring our distances back to the space where our vertices live in. This can be done by scaling each to its respective height . Remember that each vertex is associated to an :
So, the distance to edge is , and the other two are where is the distance to edge , and is the distance to edge . The heights can be computed using simple vector projection:
Now, our fragment test becomes: Shader-Based Wireframe Drawing