Stitching and LoD Using Tessellation Shaders for Terrain Rendering

In this article I will only concentrate on explaining the means by which you can implement stitching to a terrain with LoD (level of detail). I am not going to touch upon the subjects of textures, lighting etc. to keep it as compact as possible.
So, let’s assume you already have your terrain as a mesh of triangles and have passed them successfully to the vertex shader. Well done. Now you have a terrain with equal level of detail across the board. That may suit other programmers fine, but not you. You have ambition. You have the drive to go beyond the accomplishments of mere mortals. And so you decide to implement LoD. In case you already know what that means, then you can skip the next section.

LEVEL OF DETAIL (LoD)

Let’s say you have a complex model of a building or an object. Up close, it’s wonderfully detailed, it’s a marvel to look at it. However, when you’re far away from it and can barely distinguish it, do you really need all that detail? Of course not. Don’t force the GPU to do extra work for a far model which will occupy only a few pixels in screenspace. And that’s where LoD comes in. The farther the observer is from a visible object, the less detailed it must be, lest it consumes too much rendering power. As such, both the geometry and textures need to be simplified, through vertex elimination and resolution changes respectively.

Levels of detail

Levels of detail

In the case of our simple terrain, for each triangle the LoD will be determined inside the tessellation shader, explained in the next section.

THE TESSELLATION SHADER

Tessellation is the process of breaking down high order primitives (patches) into smaller components, such as triangles for easier rendering.

pipeline

Rendering Pipeline (OpenGL Superbible 6th ed.)

This is an optional shader, which is placed right after the vertex shader. It’s an entity encompassing 3 separate parts:

  • Tessellation control shader – Here you can set the level of inner and outer tessellation and you can pass information to the TES
  • Tessellation Engine – It is responsible for dividing the patch into smaller primitives
  • Tessellation evaluation shader – Each invocation receives as input a newly created vertex inside the patch, which must be given its position within the world space.

Further on, I will refer to edges which are opposite to vertices. To clarify what I mean, here’s an example:

Edge BC is opposite vertex A

Edge BC is opposite vertex A

The most important part of this process is determining the desired tessellation levels, both inner and outer (we’ll be looking just at triangle tessellation examples). The inner level is set by just one variable and as can be seen in the image below, it determines the number of inner triangles which will be created inside the patch. Observe the difference between an odd and an even level. With every level increase, the shader will attempt to create another triangle inside the last created triangle, followed by creating additional triangles between the last 2 created in relation to the outer levels set. In the case of an odd level, the center of the patch will be a triangle, while in an even level, it will be the triangle’s center of gravity (intersection of all the medians).

The outer level determines the number of segments each edge will be divided into. Here, the order in which you pass the vertices to the vertex shader matters, because the first outer level pertains to the opposite edge of the first vertex of the triangle, the second level with the second vertex, etc. For example, out3 is the level of vertex 3 and its effects are seen on edge 1-2, opposite to vertex 3.

Various inner and outer levels

Various inner and outer levels

The code for a standard TCS and TES is the following:

if (gl_InvocationID == 0){
 gl_TessLevelInner[0] = 7.0;
 gl_TessLevelOuter[0] = 2.0;
 gl_TessLevelOuter[1] = 3.0;
 gl_TessLevelOuter[2] = 7.0;
 }
 gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
 gl_Position = (gl_TessCoord.x*gl_in[0].gl_Position +gl_TessCoord.y * gl_in[1].gl_Position + gl_TessCoord.z * gl_in[2].gl_Position);

STITCHING

Now, knowing what LoD and tessellation are, let’s talk about stitching in relation to these concepts. There may be cases in which 2 triangles which share an edge have two different outer level values for the opposite vertices to that edge, resulting in something like this:

A poor and good example of stitching

A poor and good example of stitching

If you would place a texture over this mesh, it would look wrong, and that’s why stitching is important.

LOD AND STITCHING USING THE TESSELLATION SHADER

The LoD of a patch depends, of course on the distance between it and the observer, which means you will need to pass on the eye position to the shaders. Also, if you’d prefer not to hardcode values inside the CS (control shader), you can also pass on a vector with distance values where the LoD changes.

Well, now that we’ve gotten that out of the way, here’s some code:

//the LoD function
float level (vec4 poz1, vec4 poz2){
    float lod=1;
	float d=distance(poz1, poz2);
	if(d<10) lod=10;
	if(10<=d && d<30) lod=5;
	if(30<=d && d<50) lod=2;
	if(50<=d) lod=1;

	return lod;
}




gl_InvocationID == 0){
vec3 d1=gl_in[1].gl_Position.xyz+(gl_in[2].gl_Position.xyz-gl_in[1].gl_Position.xyz)/2;
vec3 d2=gl_in[0].gl_Position.xyz+(gl_in[2].gl_Position.xyz-gl_in[0].gl_Position.xyz)/2;
vec3 d3=gl_in[0].gl_Position.xyz+(gl_in[1].gl_Position.xyz-gl_in[0].gl_Position.xyz)/2;

float e0=level(vec4(d1,1.0),vec4(eye_position,1.0));
float e1=level(vec4(d2,1.0),vec4(eye_position,1.0));
float e2=level(vec4(d3,1.0),vec4(eye_position,1.0));
float m=min(e0,min(e1,e2));

gl_TessLevelInner[0] = floor((min(e0,min(e1,e2))+max(e0,max(e1,e2)))/2);
gl_TessLevelOuter[0] = e0;
gl_TessLevelOuter[1] = e1;
gl_TessLevelOuter[2] = e2;
}

Now, for a bit of explaining. To implement stitching, you will not use the distance between each vertex and the observer in your LoD determination, you will use a point from the edge opposite it (for convenience, I used the middle of the edge). I store each middle in the variables d1,d2,d3 (again, pay attention to the order of the vertices). Then in the 3 “e”s I store the LoDs of each edge (the level function is easy to write, you just check between what values each distance is situated and return the proper LoD). Then you set a proper inner level and the outer levels.

And that’s it. You needn’t modify the Evaluation Shader at all. That’s all it takes to implement LoD and stitching. A lot of build-up for little payoff, you say? Bulls**t. You enjoyed it. Oh, and here’s my final result, complete with lighting model.

Final Tessellation Terrain

Tessellation Terrain Result

 


Tagged under:

I'm an engineer, currently employed at a financial software company. My interests include gaming, LPing and, of course, reviewing, but also game dev and graphics. Also, in the past I've dabbled in amateur photography, reviewing movies and writing short stories and blog posts. I am also a huge Song of Ice and Fire fan, but that's beside the point. Youtube Channel, Deviantart , Google + , Twitter

blog comments powered by Disqus