Easy wireframe display with barycentric coordinates

Aug. 02, 2012

Sometimes it's required to display a wireframe of your model. The following post looks into how to do that in a flexible way.

If you debug your WebGL program or if you have a need to display a wireframe of your model in WebGL, there is one traditional choice: gl.LINES. However this requires you to supply a separate mesh to the GPU independently of your model. This can be inconvenient and wasteful. An alternative to do this is using barycentric coordinates to figure out how close you are to an edge during fragment shading.

A more in depth look at such techniques is also presented by this journal entry by cgg-journal.com. Jonas Wagner is also experimenting with the technique.


You can try the live demo for yourself. Note that the rendering is using a standard triangle mesh, no extra geometry is created to display the lines.



You'll find the sourcecode for this library on github

How it works

Each vertex in the mesh gets an additional attribute which I call barycentric. For the first vertex this is (1,0,0) for the second vertex it is (0,1,0) and for the third it is (0,0,1).

These coordinates get sent unmodified trough from the vertex shader to the fragment shader as varying vBC.

varying vec3 vBC;
attribute vec3 position, barycentric;
uniform mat4 proj, view;
void main(){
    vBC = barycentric;
    gl_Position = proj * view * vec4(position, 1.0);

When this is interpolated to shade each fragment, vBC is specifying the coordinate of the fragment inside the triangle.

We can then use this coordinate to shade differently depending on edge closeness.

if(any(lessThan(vBC, vec3(0.02)))){
    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);

Constant width and anti-aliasing

The shading from above gives ugly aliasing depending on the size of the triangle in relation to the screen. What we really want is to scale the distance depending on how quickly vBC changes on screen. Luckily, there is a way to do that just with the standard derivatives extension.

#extension GL_OES_standard_derivatives : enable
float edgeFactor(){
    vec3 d = fwidth(vBC);
    vec3 a3 = smoothstep(vec3(0.0), d*1.5, vBC);
    return min(min(a3.x, a3.y), a3.z);

The fwidth function computes abs(dFdx(vBC)) + abs(dFdy(vBC) which is a sort of average of change. Most importantly, using this, we can relate change in vBC to the screen. The smoothstep softly steps vBC from 0 to 1 between 0.0 and its respective fwidth. We return the smallest step (as we're working with 3 coordinates at the same time).

This can then be used to provide opaque shading

gl_FragColor.rgb = mix(vec3(0.0), vec3(0.5), edgeFactor());

Or in conjunction with alpha blending and alpha to coverage for transparent shading (the inside faces are not filled)

gl_FragColor = vec4(0.0, 0.0, 0.0, (1.0-edgeFactor())*0.95);