Why bother with it? Well, it can show geometry compexity - i.e. you can see number of triangles. For example you can show your amazing shader mesostructure effect and then show that there's only two triangles per plane!
The shader I implemented based on nvidia's paper (source pdf). And it's quite simple. The main idea is to calculate fragment position on triangle. If it near the edge we shade it and at the end we get triangle outline. The magic is in interpolation of vertex-edge distances in vertex shader. All we need to know - is the distances from vertices to opposite edges.
Notice also that for vertex a we write distances in vector form as (da, 0, 0), for vertex b as (0, db, 0) and for c as (0, 0, dc). Then we pass this distances form vertex shader to fragment shader. Next, in fragment shader we choose smallest from da, db and dc ant that's our corect distance of fragment to edge.
The main problem that we can't calculate this distances in geometry shader as nvidia's paper do (simply because we DON'T HAVE geometry shader). We must calculate them beforehand and supply as vertex attribute (see formula on picture above). This means that we need to duplicate a lot of data - each vertex must be unique - i.e. it must not be shared between several triangles.So, we have correct distance in fragment shader. What next? We simply check that distance with some value (line thickness) and shade this fragment only if it in correct position. Here's a demo:
As you can see - it works but wireframe is jaggy, not antialiased. To fix that I use smoothing function from paper. The smoothness applied on 2px range in range from [lineThickness - 1] to [lineThickness + 1]. The result (you can change line width with slider):
//attributes alias va0, pos; alias va1, distance; //constants alias vc0, TRANSFORM_MATRIX; //temps //varyings alias v0, distanceOut; //for vertex a Vector.
([da, 0, 0]) //shader op = mul4x4(pos, TRANSFORM_MATRIX); distanceOut = distance;
//constants alias fc0, LINE_COLOR; alias fc1.x, ONE; alias fc1.y, MINUS_TWO; alias fc1.z, TWO; alias fc1.w, POW; alias fc2.x, THICKNESS_MINUS_ONE; alias fc2.y, THICKNESS_PLUS_ONE; //textures //temps alias ft0, d; //correct fragment distance alias ft1, oneOrZero; alias ft2, color; alias ft3, smoothValue; alias ft4, temp; //varyings alias v0, distanceIn; //shader color = LINE_COLOR; min d.x, distanceIn.x, distanceIn.y; min d.x, d.x, distanceIn.z; //get smooth value - it's a value in range [+1] -> [~0] on 2px interval temp = THICKNESS_PLUS_ONE - d.x; temp = TWO - temp; pow temp temp POW; temp *= MINUS_TWO; exp smoothValue temp; //if fragment distance [d] < [thickess - 1], then 'temp' set to 1 otherwise set to smooth value slt oneOrZero d.x THICKNESS_MINUS_ONE; temp = ONE * oneOrZero; oneOrZero = ONE - oneOrZero; smoothValue *= oneOrZero; temp += smoothValue; color.w *= temp; //if fragment distance greater then max allowed thickness, then discard it (set alpha to 0) slt oneOrZero d.x THICKNESS_PLUS_ONE; temp = oneOrZero - ONE; //if fragment outside line thicknes + smooth range - discard it kill temp.x; oc = color;
And this is wireframe in action on complex geometry:
P.S.: you can rotate geometry with left and right mouse button and move it forward/backward wth +/-.