[HR][/HR]
There had been quite a lot of questions about wireframe and edge highlighting materials on discord and apparently some users are interested in easy ways to render acceptable wireframe materials, so I decided to sketch a quick tut. [HR][/HR]
There are several widespread and proven approaches to wireframe rendering:
-
Line rasterization. Instead of drawing triangles, we actually tell GPU to draw lines between vertices. This is what is used for wireframe materials in UE4. Unfortunately it is suffering from several drawbacks. In particular, it lacks ability to adjust thickness in arbitrary way and shows severe aliasing.
-
Using dedicated mesh for wireframe. This one is pretty simple. We would construct a mesh, that would resemble all edges of our mesh. Regrettably, it would rely on certain automation tools to create this kind of mesh in your content creation package. Likewise, this method would noticeably increase mesh memory consumption and triangle counts.
-
Using geometry shader to create surfaces for wireframe rendering. This is the most balanced approach overall, brings out best visuals and is my personal favorite. However it requires somewhat advanced understanding of graphics to integrate in UE4. We will cover this approach in second part of this tutorial
-
Using partial screen-space derivatives. This approach relies on using DDX and DDY shader instructions to measure rate of change of the surface. It can be used to highlight the edges, but it is suffering from a problem, where we would have a hard time picking up an edge between two nearly co-planar triangles.
-
Using a wireframe texture. This way is dead simple. We would render out UV layout in our content creation package and use it as a texture in UE4 material. Needless to say, that it will suffer from resolution issues and will terribly inflate project’s texture memory.
-
Using a wieframe texture with dedicated unwrap for wireframe rendering. This method is a bit more interesting. We would need to create a dedicated UV unwrap, where all faces would be stacked on top of each other in a such way, that they would form a single triangle that occupies exactly half of the zero to one UV coordinate space. As with previous method, we would render UV layout into a texture. But this time, we can use only 1 texture for all out meshes. One particular downside is that it requires some additional work on the meshes and space to store extra UV channel. Luckily, almost all content creation package have an ability to unwrap each face into 0-1 UV range automatically.
-
Using dedicated unwrap for wireframe rendering and procedural mask. Essentially, this approach is the same as above, but instead of using a texture, we would generate our mask procedurally, eliminating issues caused by texture resolution from previous method. It is the method we will be covering in this tutorial.
[HR][/HR]The “not so manly” way to make wireframe materials in UE4.
The first step would be making a dedicated unwrap for out mesh. You can use UV channel 0(if You are not intending to use texture for the mesh) or UV channel 1(If You are not intending to use lightmapping). Otherwise You should be using UV channel 2. This step will vary between different content creation packages and I will not cover it in defail, but a common approach would be to:
- Triangulate the mesh
- Use face mapping(polygon mapping) on the mesh. This step will map every quad onto full 0-1 UV space.
- Detach, rotate and stack triangles as required.
- Your unwrap should look like this:
Every face of the mesh should be stacked on top of each other, forming a triangle with UV coordinates (0,0),(0,1) and (1,0) respectively.
After doing so, export your mesh and import it in UE4 as usual.
- When viewing UV in static mesh editor inside UE4, you should be getting the following on your UV channel of choice:
Next step would be creating material for the wireframe.
The key part of material graph is a set of material expressions, that would calculate the wireframe mask for us.
Since we now store dedicated UV channel for the purpose, doing so is trivial.
We would need to check, if the current pixel’s UV coordinates lie close to the edge of the triangle.
It can be done by converting pixel’s UV coordinates to barycentric coordinates, taking minimum between first and second barycentric coordinate component, and then taking minimum between result and inverse sum of first and second coordinates. The result would be a procedural map, that would show a distance to triangle edges. We can use IF conditional expression to check, if the distance lies within certain threshold. If so, we set the mask value to 1, otherwise we set mask value to 0. For a better mask, we can use SmoothStep material function.
- Material graph section for Wireframe Mask generation:
!Note, that TexCoord[0] material expression needs to be set to corresponding UV channel, which stores unwrap in question!.
- Just copy paste the code into the custom node and set its output to Float3 and inputs as depicted above:
//////////////////////////////////////////////////////////////////////
//Calculates barycentric coordinates of a point on a 2d triangle
//Inputs
//float2 vPointA
//float2 vPointB
//float2 vPointC
//float2 vPointP
///////////////////////////////////////////////
float2 v0 = vPointB - vPointA;
float2 v1 = vPointC - vPointA;
float2 v2 = vPointP - vPointA;
float v, w, u;
float fInvDenominator = 1 / (v0.x*v1.y - v1.x*v0.y);
v = (v2.x*v1.y - v1.x*v2.y) * fInvDenominator;
w = (v0.x*v2.y - v2.x*v0.y) * fInvDenominator;
float4 result;
result.x=v;
result.y=w;
result.z=v+w;
result.w=( (v > 0) && (w > 0) && ((v + w) < 1) );
return float3(v,w,v+w);//Outputs float3
- If so far, you performed everything correctly, you should be getting the following result:
We can add some simple eye candy to our material with flat shading:
-
This is basically it. You’ve obtained a basic wireframe mat.
-
You can set your material blending mode to Masked, enable TwoSided checkbox and connect Wireframe Mask generation section to Opacity Mask material input pin to get a transparent wireframe material:
- You can expand the idea on your own and experiment with things like adaptive edge threshold value, based on camera distance, etc.
The pros of this method are:
- Simplicity. Anyone can do it.
- Low performance costs.
- Virtually no platform or hardware restrictions
- Good and smooth visuals
The cons:
- Additional unwrapping work for meshes.
- The wireframe thickness depends on the face elongation. The shorter face side will be thicker then the longer one.
[HR][/HR]
When time permits, I will post a second part of the tutorial, involving implementation of geometry shader fins for wireframe rendering.