Direct3D 11 (and the hardware that supports it) includes a lot of new functionality for programmers, but one of the most interesting and powerful additions is Tessellation. This feature has been used in numerous high profile titles and is a great way to upgrade your visuals without an unreasonable amount of extra content effort.

Arguably the easiest way to leverage GPU-based tessellation is computing dynamic LODs for your geometry on the fly. For the most part this is pretty straightforward to implement (once you learn the API).  However there are a few tricks that are worth discussing such as the best way to determine tessellation factors

[Note: For brevity and simplicity, I'm going to talk specifically about triangle meshes in this post. There are other options for quad meshes, but the majority of current tools and engines today still use triangles. Additionally, all code examples will be given in D3D11 SM 5.0 HLSL, but the concepts should be readily applicable to OpenGL hardware tessellation extensions.]

Review

The graphics pipeline for hardware tessellation has been explained excellently in many locations and at many topics.  It is assumed readers are familiar with the pipeline and usage.  If not, check out the following link.  Don't worry I'll wait for you.

http://developer.download.nvidia.com/presentations/2009/GDC/GDC09_D3D11Tessellation.pdf

Why Use Tessellation?

 

 A close up shot of a tessellated head.  No displacement map here, just smoothing from PN triangles.  You’ve likely seen this type of shot many times before. Click to zoom.

Figure 1 : A close up shot of a tessellated head.  No displacement map here, just smoothing from PN triangles.  You’ve likely seen this type of shot many times before.

 

Dynamic tessellation allows you to easily control the visual fidelity of the meshes in your scene on a per primitive basis as well as remove obnoxious mesh popping from LoD mesh swaps.The nature of the tessellation hardware allows you to specify a subdivision factor per edge and interior of each primitive dynamically.  This is a powerful tool. 

Previously, LoDs had to be pre-calculated and swapped out procedurally on the host. This results in increased bandwidth costs to upload new models as well as visual artifacts like popping.  With dynamic tessellation we can implement a smooth LoD system per primitive which will allows you to more finely control the geometry rendered.

A smooth LoD system is something that is not possible without a dynamic hardware tessellation solution.  There are other benefits to using tessellation that provide increased performance over statically tessellated sources meshes, but dynamic tessellation provides an increase in visual dynamic visual fidelity not obtainable any other way. 

 

 Same scene, same session, just moved the camera back a bit.  Tessellation auto adjusts to reduce triangle density

Figure 2 : Same scene, same session, just moved the camera back a bit.  Tessellation auto adjusts to reduce triangle density. Click to zoom.

 

 Again, same scene, same session, just moved camera really far away.  The triangle density of tessellation is roughly equivalent to no tessellation at all.

Figure 3 : Again, same scene, same session, just moved camera really far away.  The triangle density of tessellation is roughly equivalent to no tessellation at all. (Image is scaled up to highlight density)

 

How do I implement it?

As mentioned, this article isn’t going to cover the basics of tessellation, but rather talk about how to use dynamic tessellation to implement a smooth LOD system.  To that end, the most important piece is determining the tessellation factor for each primitive being tessellated.

Remember that the Hull shader is responsible for deciding how much to tessellate each control primitive.

While the Hull Constant Shader actually sets the expansion factors for the patch; for best performance, the recommendation is to calculate the expansion factor in the Hull Shader proper and pass down as a control point constant (“opposite edge factor”).

Picking a proper tessellation factor is a matter of taste, but there are some key points to remember:

1.       Most likely distance based heuristic.

2.       Clip space is convenient as it handles distance implicitly, but if you require a non-linear falloff, may want view space.

3.       Don't forget silhouettes.  If you use normal or view/clip triangle areas you will probably under tessellate the silhouettes.

 

“Crack!!!”

When using hardware tessellation, cracks and holes are the largest area of bugs.  The nature of the system gives full control to the graphics programmer to break their mesh.  It is probably a full article in and of itself to talk about crack avoidance.

For this article we’ll just concern ourselves with cracks from bad tessellation factors.

Be careful when designing an algorithm to pick tessellation factors along edges.  If you are not careful to maintain the same factors for primitives on both sides of an edge you will create T-junctions that will very likely cause cracking.  One down side of the control you have over subdivision of each primitive is that you can break the C0 continuity of a mesh quite easily. 

In general, as long as you use non-divergent data as a source for tessellation factor calculation you should be safe.  Example of divergent data that could be dangerous would be normals, UV coords, vertex colors, etc.  Anything an artist can split per vertex of a shared primitive.

 

 

 Two abutting primitives.  Each will calculate its own tessellation expansion factor, so be careful to make sure they match!

Figure 4 : Two abutting primitives.  Each will calculate its own tessellation expansion factor, so be careful to make sure they match!

 

Choosing the tessellation factor

Let’s talk about a couple of example of picking the expansion factor for your primitives.  You’ll probably want to think about the nature of your content and the game you are making to decide how you want your expansion factors to change.  But I’ve got some good starting points.

Important considerations when choosing a factor

-          Have an expansion factor based on camera matrix

-          Smoothly adjust the expansion based on inputs

-          Extendable to account for complexity in any displacement maps on that primitive

-          Parameterized for easy artist control/tweaking

Simple Method: The “distance from camera” heuristic

A simple way to calculate a tessellation factor might be to convert the distance from the camera into the range [1..64], possibly scaling or modifying the value along the way and use that as the tessellation factor:

Advantage: Relatively simple to calculate, and you could try out various “SomeFunction()”s to get a transition that you like.  The easiest one would be:

SomeFunction(x) = Cx, where C is an artist driven constant that scales up the tessellation

Disadvantage: Not dependent on the actual size of the triangles, and so for unevenly triangulated meshes, you will either over-tessellate dense sections, or under-tessellate coarse sections.

Recommended method: The “sphere diameter in clip space” heuristic

One heuristic that meets all the general needs for a robust dynamic LOD system would be to use the diameter of a bounding sphere around the edge in clip space to determine the tessellation factors for each edge:

1.       [CPU]Define an artist/designer controlled parameter of the ideal geometry scale in terms of pixels per edge

2.       [CPU]Convert this to edges per screen height before sending into shader constant memory

3.       [GPU:Hull Shader ]Obtain the diameter of a bounding sphere on each edge transformed into clip space and multiple by the shader constant to obtain a tessellation factor for the edge.

Notes:

-          “Screen Height” might be more accurately labeled “Render Target Height”

-          ProjectionMatrix[1,1]is the [1,1] component of the projection matrix

-          Dividing by Posclip.Wwill perspective correct the diameter

Here’s some HLSL that does this:

float GetPostProjectionSphereExtent(float3 Origin, float Diameter)
{
    float4 ClipPos = mul( VIEWPROJ, float4( Origin, 1.0 ) );
    return abs(Diameter * g_mTessProj11 / ClipPos.w);
}

float CalculateTessellationFactor(float3 Control0, float3 Control1)
{
    float e0 = distance(Control0,Control1);
    float3 m0 = (Control0 + Control1)/2;
    return max(1,g_fDynamicTessFactor * GetPostProjectionSphereExtent(m0,e0));
}

This gets you an orientation independent parameter to use as a tessellation factor.  The units of the perspective correct diameter are [0..1] where 1 would be interpreted as “screen sized”.  Multiplying by the EdgesPerScreenHeight will convert the parameter into the unit of "desired # edges".

Wrap-up

Once you have your expansion factor calculation, that’s about all you need for the LOD system.  You could layer this on with an existing mesh swap LOD system to smoothly transition between distinct LODs or add in displacement maps for more control over mesh details using the domain shader

To sum up, dynamic tessellation is a relatively easy way to add standout geometric fidelity to your title without having to maintain separate mesh models or deal with mesh popping and resource swapping headaches.

As GPUs get more powerful, there will be more and more GPU resource to make use of and having a simple slider like “triangle density” allows you to take advantage of power that is not yet available.

Hopefully I was able to convince you one of the most crucial and often overlooked pieces of dynamic tessellation LoDs is picking the tessellation expansion factors.  Good luck!

Questions or Comments?

We'd love to hear your feedback on our DevTalk forums!  Please visit this forum topic to discuss this blog post.  Thanks!