Smart Terrain Rendering with Xna using Billod algorithm

Version Française ici.

If you’re a game developer you’ve already worked on terrain rendering. It is, in my opinion the critical point of a game. Not because conceptually speaking it is the most important visible thing on the screen but because it is the main factor playing on the performance and quality of your application. It is therefore important to use techniques designed to minimize the burden borne by your game to view the ground while preserving its quality display. This article shows a way that attempts to answer this question in XNA.

Conducting a study of game engine designed to help game developers in the Imagine Cup competition, I naturally preferred this important aspect in seeking a way to display a 3D world unlimited real size with an acceptable level of detail.

There are many techniques for displaying world explained by interesting articles. These are often based on a compromise uncomfortable wearing a choice between the size of land or quality level. In addition, they are often prohibitive algorithms.

This article does not appear as the only possible response to the display ground XNA but would just provide an effective solution running for this technology knowing that most of the examples existing on the Web are made in DirectX and OpenGL.

We will split our study in several phases:

·          First we describe the problems posed by the display of land and explain the need for optimization.

·          We then describe the method used to cut our land into a QuadTree.

·          We will explain how we reduce the retail field at distance while keeping up its appearance nearby.

·          We will describe a more technical implementation of this technique

·          Finally we will see possible optimizations offered to the reader.

The problem

Let’s say we wanted to model a land about 10 km by  10km with a degree of detail about 1 meter (1 kilometer = 1000 meters). So we have (without optimization)  a grid of 100000 * 100000 to show. We have to specify that that each vertex of the grid has a memory size equal to approximately 40 bytes. It reaches   about 4 gigabytes of data to display. The hardware of a lambda PC cannot handle such loads memory. Another problem, such a field modeled in a grid rendering static demand of 200 million triangles per frame. This is a disaster. From the moment the size of the field becomes important and where we add the ground ingredients of usual games, as 3D models, an user interface, animations,   performance of a game can no longer follow. This is here, that we have to manage a smart rendering.

It is therefore important that the CPU and / or GPU taken by the display field is as low as possible to keep processing time for other needs of the game. The virtual world offered by most modern games are huge and cannot be done with a simple static grid

The need for optimization is therefore evident. I invite readers to visit http://ww.vterrain.org to discover some presentations techniques other than that presented here.

If we had a specification to comply, we would have:

·          Keep the appearance of the ground up by reducing as much as possible the burden with its display.

·          Make the management of land by the code clean and accessible.

·          Using a technique efficient, simple and fast.

·          Respecting a QuadTree to optimize performance and meet the needs of treatment games.

Keep appearance with 100 times less resources, is a beautiful goal!.

The first step is to manage our future terrain through a QuadTree.

A QuadTree is a data type tree in which each node can have up to four children. The Quadtrees are most often used to split a two-dimensional (3D abscissa and depth) by recursively subdivided into four knots. Each node has 4 children and so on. The route of such a tree is simple and takes on average the same time : for each level, there is a maximum of 4 tests to be “allowed” to navigate into lower level.   This is important given the task before us here: “Show ground with maximum details using minimal resources.”. The goal is to minimize the course of such a tree through its hubs in making these tests smart and simple.

Principle

The QuadTree (often used for rendering the field) provides an intelligent answer to the problem of optimization. First it loads the data only when needed. Then, the burden caused by the representation of a node on the screen depends on the depth of the node in the tree. Therefore, the sooner it is estimated that a node not be displayed in the course of a tree, the greater the optimization of display is realized (we limit the same time the number of triangles to display ). We will explain it all over this article.

More on sinking into the tree, more precision displayed with refined detail.

The real question to be asked to optimize the display of our tree is “what tests should be performed to determine the appropriateness of displaying a node on the screen”. We will see this as soon as possible. For now let us concentrate on how we manage the tree in our code.

Display the tree

Initially our terrain is only rendered by a single node (see picture above). The node size will be equals to the size of the terrain. A node on the screen corresponds to a surface composed of 9 square vertices:

1. Nord North

2. Nord Est North East

3. Est East

4. Sud Est South East

5. Sud South

6. Sud Ouest South West

7. Ouest West

8. Nord Ouest North West

9. Centre Center

A node is composed of 9 vertices

The QuadTree will be based on a heightfield (a two-dimensional array containing all the heights of each vertex Y on the screen). Each vertex of each node in the QuadTree has a height whose value is drawn from the heightfield.

Initially, only the corner of   a node are visible, namely: North East, South East, North West, South West. Its rendering is similar to the following picture:

The shaded vertices are visible vertices

These four vertices displayed allow us to draw two triangles form the surface of square knot. For each update of the terrain, for each node currently visible, we calculate the visibility of each of the vertices. Each update of a node is accompanied by a redrawing of triangles that make it up. he following picture explicit some possible configurations for a node:

The algorithm for this part would be:

/ / For each of the vertices North, East, South, West

If ( !Vertex.Visible && VertexTest())

Vertex.Visible = true ;

Else if (Vertex.Visible && !VertexTest())

Vertex.Visible = false;

The test of this algorithm takes into account the distance of the vertex from the camera and the growing need for detail as the camera approached it. We will see the nature of this test later.

In a second step it is necessary to calculate the visibility of children of current node. A node consists of four children as the next picture shows:

Here we have shown the North East of child root node, then the child South East of it, then the child of the South East last child finally North West of it.

The more one penetrates deep into the QuadTree over the mesh tightens and will display a more detailed terrain.

The algorithm for this part at this stage of our study would be:

// For each of the four children North West, North East, South West, South East

If ( !Child.Visible && ChildTest())

Child.Visible = true ;

Else if (Child.Visible && ! ChildTest())

Child.Visible = false;

Once again, the test of this algorithm takes into account the distance to which the child and the growing need for detail as the camera approached it. We will see the nature of this test later.

When a child is exposed, its vertices and his own children are subject to two algorithms that we have just seen. We are therefore in a principle of recursion.

We compute the triangles that’s makes a node by not taking into account the parts overshadowed by children:

We note here that the vertices North and East of the parent shared with the vertices North West and South West of the child. We will see that later.

The basic principle of the Billod algorithm is relatively simple. Vertices et enfants à afficher sont soumis à chaque mise à jour du terrain à un ensemble de tests qui déterminent leur visibilité. Vertices and children are subjected, at each updated of the terrain, to a set of tests that determine their visibility. The display of a node node is a recursive set of instructions that  goes through the  QuadTree along the root node to each leaf node of each branch.

Improvements

We talked previously shared vertices between a child and a parent. The previous image illustrates this point. This may cause an unpleasant graphic bug illustrated by the image below:

Or, with wireframe:

This problem is due to the non-activation of a vertex in the neighboring node. The nodes affected by this bug are:

So in reality:

The West vertex of the child red is activated but not the vertex of the yellow child. There is therefore a delta between the two sides of the two children. To resolve this bug simply validate the vertex of the child is yellow:

Finally we get a perfect continuity:

Now imagine that the southern vertex of the yellow child becomes visible. The parent node has only two children north west and north east. Activating this vertex pose the same problem as before graph:

Unfortunately, unlike the previous case, there are no children in the South West. It is therefore necessary to create a child to this position (although the “ChildTest” of our algorithm for that child returns false) and to activate its North vertex:

The addition of southern vertex of the white child led to the creation of the Child South West and the activation of southern vertex parent node. The vertex South may itself cause the modification of neighboring nodes. There are, here, some cascading effects that are not really controllable but, fortunately, limited. The following image shows the impact of activation of several children deep into the QuadTree at the bottom right of the terrain.

We note that the nodes located at the opposite are impacted.

Some years ago I read an excellent article by a person named Stan Melax author of an ingenious technique for calculating a dynamic aspect of a 3D model at different levels of resolution. The articles on progressive meshes are subject to the same laws as those on land: either the reduction algorithm is too complicated or it is inappropriate. Stan Melax found a clever way to solve this problem. He studies the cost the disappearance of each vertex by studying their impact on the overall shape. He is then able to gradually reduce the resolution of the 3D model by removing  vertices in a logical order with the less impact.  Its strength lies in the calculation of the cost of a vertex which proves to be a simple and effective. We will proceed the same way.

The curve : Element to remember

To explain simply :  We measure a terrain to its forms. We measure a form to its curves. The question is then: how to define a curve ?. Answering this question would determine the locations of the terrain where we must keep an important retail level and those that can be degraded.

There are many techniques to transform a curve into a mathematical function. We return in this case to the beginning problem: Do not overload the processor too much. We have seen, it is necessary to execute the formula found at all vertices and all children nodes. A formula that generates a lot of instructions will deteriorate application performance.

The solution is simple. We have said that our land is splitted into a set of nodes belonging to a tree. Each curve of the land therefore consists of nodes that are actually surfaces square whose size varies

The terrain is a set of curves visible on the screen. Each curve is an assembly of square knots coast to coast glued.

The normal, order of magnitude of a curve

Plus la courbe est importante, plus les nœuds qui la composent forment un angle important. The greater the curve is, the greater the angles made by its nodes are important. Take for example a curve :

It shows all nodes glued side by side. At the place where the curve is really important we find nodes that form an angle. In contrast to where there is no curve, but a simple slope, nodes coast to coast appear to belong to the same plane. We noticed even more if we show each normals nodes:

We are now close to finding the formula! All the students know that the mathematical function that measures the angle between two vectors is the dot product.

The inner product of two vectors represented by non-zero vectors A and B is the actual number ABcos (θ) if the angle θ is the angle formed between the two lines whose direction are represented by the two vectors.

The maximum angle that can be found in a curve of land as we conceive it tends to 90 degrees (90 degrees value cannot be achieved because nodes can’t overlap). Cos 90 equal 0 so we can infer that when the curve formed by the vectors is important, their dot product approach 0 (A * B * Cos (90) = A * B * 0 = 0). On the contrary if the value of inner product increases, is that the curve formed by the two nodes tend toward the “right” (A * B * Cos (0) = A * B * 1 = AB).   See this picture:

The inner product between the normal parent and child will return here a value far away from 0 : we are on a surface, certainly inclined, but plane. Needless to cut the node.

Consider now a pronounced curve :

The angle between the three normals is more pronounced. The original node (symbolized by the black dotted line) should be divided into sub-nodes. We can found in this process a certain logic: a flat surface is easily modeled with few polygons. A curved surface ask for much. Here to complete the wireframe curve:

Where the curve is more pronounced, our technique uses more primitives

Activation

Child Test method should compare the normal of the children to test with the normal of the parent node to which it belongs. If the angle between the two normal exceeds a threshold when the method returns true.

Disabling

Disabling a child occurs when a series of factors are met:

·          The child has no longer visible vertices

·          The child has more sub-children

·          The test method returns false.

Enable/Disable switch node in the vertex

Interpolated to real

La gestion de la visibilité des vertices ne suit pas la même logique que celle décrite précédemment. The management of the vertices visibility does not follow the same logic as that described above. Here there is no curves but delta.   Only vertices on the sides of a node are to be tested. So the North, East, South, West vertices. When a node is created, this vertices are not visible. But they are located between two vertices on node edge and so, we can deduce their height easily. Here is an example of node viewed from the side:

It shows the vertices North West and South West enabled (visible)) and West   vertex disabled.

The West vertex seems to be on the right along the two summits. In real, it may not. For now we have interpolated (deducted) its position (If South West is at a height of 10 and North West at a height of 20, it is reasonable to assume that the West vertex will be at a height of 15). Yet in reality, it is may be at another height:

There is a distance between the delta position of vertex West interpolated its actual position. This difference in height measures the degree of reality of the node currently displayed with a more detailed version.

View node with only the 4 vertices summits displayed therefore does not adequately reflect the reality on the ground displayed by the node.

Way of activation

The activation method is very simple, we only need to determine the Delta value (a simple difference of heights) and determine if it exceeds a threshold too.

Test of disabling

Disabling a vertex is only when a series of factors are met:

·          The vertex is used by a child node

·          The test method returns false.

Progressive Details

It was one of the prerequisites for our technology, we must optimize the display according to the camera position. At this point, our two functions tests (for vertices and children nodes) do not take into account the camera position and the position of the element to test.

We have seen in the two algorithms dedicated to the vertices and nodes, that test is performed to determine if a vertex or child should be displayed or not. The land must be displayed according to the camera position for the comfort of the player. The more an area of land is close to the camera the more it have to be detailed. It also aims to preserve up to the appearance of the terrain away with a minimum of vertices and gradually detail when the camera comes.

For the time the testing method is based on the angle made between the normal of a child with its parent.  This procedure is not necessarily relevant distance.

If a few degrees angle is visible when you are close to the nodes that make up this angle is not necessarily detectable at a distance It is not necessary to activate the child.

Here is the implementation of this test method:

private bool ChildTest(Vector3 childNormal, BoundingBox boundingBox, Vector3 cameraPosition)

{

//by default, the four childs of the root node are visible.

if (this.Depth < 1)

return true;

//get the closest point to the camera and check the distance

float distanceCameraToPoint = Vector3.Distance(GetBoundingBoxClosestPointToPoint(boundingBox, cameraPosition), cameraPosition);

//compute the dot product between parent normal and child normal

float dotprod = 1 – Vector3.Dot(childNormal, this.CenterVertex.Value.Normal);

//check with the threshold

return (distanceCameraToPoint / this.ParentTree.QuadTreeDetail) < (dotprod);

}

By default nodes at the top of the tree are made visible. We then determine the distance between the camera position and the nearest point of it in the BoundingBox encompassing child. The dot product is then calculated. We test the division of this distance by an adjustable threshold is below the dot product.

Note that this method of testing may well be adapted to submit the QuadTree to another technique optimization.

Progressive detail for the vertex

Again, the testing method for vertices does not take into account the distance to the camera. We must determine if the distance between the actual position of the vertex and its position interpolated exceeds a threshold configurable.

Here is the testing method:

public bool VertexTest(Vector3 vertexPosition, Sides side, Vector3 cameraPosition)

{

//get the distance between interpolated height position and real height position

float lengthToTest = this._realToInterpolatedVertexHeight[(int)side];

//get the distance from the camera position to the vertex position

float distanceCameraToPoint = Vector3.Distance(vertexPosition, cameraPosition);

//check with the threshold

return lengthToTest * this.ParentTree.VertexDetail > distanceCameraToPoint;

}

The delta between the two vertices (interpolated and real) is stored in memory. We then determine the distance between the vertex and the camera. A test is then conducted to determine if the Delta exceeds the threshold distance to the camera. Like earlier this test method may well be adapted to submit the vertices of each node to another technique optimization.

A field at different level of detail, from left to right: 7500, 3000, 1500, 300 150 vertices

Improvements

Our algorithm at this stage works perfectly: it displays the details where they are relevant, frees up resources and uses a quadtree.

Remember that time, the relevance of viewing a node is measured using the dot product of its normal with normal direct its parent. This is rather good, but we can improve. The following pictures illustrate a graphic unpleasant bug that modifies the appearance of ground within walking distance:

In front of us: a slight bump on the ground

The hump has been truncated

Note that the bump on the first visible image is greatly reduced by the mere fact of having moved slightly camera. But our algorithm has worked here perfectly. We simply validated a threshold requiring a node to be cut. So we need to reduce the implementation of our algorithm close to the camera. We will therefore introduce a second threshold that will represent what we mean by “proximity”.

The new test function is thus as follows:

private bool ChildTest(Vector3 childNormal, BoundingBox childBoundingBox, Vector3 cameraPosition)

{

//by default, the four childs of the root node are visible.

if (this.Depth < this.ParentTree.MinimalDepth)

return true;

//get the closest point to the camera and check the distance

float distanceCameraToPoint = Vector3.Distance(GetBoundingBoxClosestPointToPoint(childBoundingBox, cameraPosition), cameraPosition);

//compute the dot product between parent normal and child normal

float dotprod = 1 – Vector3.Dot(childNormal, this.CenterVertex.Value.Normal);

//check with the threshold

}

We now have two thresholds, a threshold of visibility for “near displays” to maximize the details close to the camera and a threshold of “far display” that manages the details beyond the previous threshold according to a affine function as we did until now.

The advantage is undeniable. For proof, just take a look at the picture below:

I went to the center of the terrain, I stopped the implementation of the algorithm (similar to a pause in the Update of the QuadTree) and take some the altitude. We notice that the details seem to diminish when away from the center. This is indeed the case:

At the center of the screen near the crater, the details are at maximum. If we were on the ground, we do not see any ground near bump be changed by moving the camera. On the contrary, the far away details keeps the overall look of the land.

It’s not all, we are now able to generate a climate and geographical well known: a mountain line (sorry I do not know the English term for “ligne bleue”). Look at this picture:

It is the blue line Vosges clearly visible from the north of “Franche-Comte” in France. Plus on regarde. The more one looks far more mist arises in a bluish tint. By applying our algorithm and a modified fog we are able to generate a skyline purified up to cut a new notch resources taken by the displayed terrain. Return on our field, but this time by being near the ground:

We’ve added a blue fog to simulate this. The remote mountains are visible thanks to the difference in color the fog gives them. Now the same view but with a threshold of visibility at distance greatly reduced:

We get a display similar but with 1300 primitives less!

Code and optimization

The code given with this article is deliberately sketchy for the sake of understanding. Son but est de tester la technique Billod Its purpose is to test the Billod technique described here  with no optimization.

·          The terrain is naturally managed by a class called Terrain. We will return later on its possible usefulness. Terrain is a container for Quadtrees. It represents the whole ground.

·          The tree is represented by an instance of QuadTree class. QuadTree represents a part of Terrain. It is a container of QuadNodes.

·          The nodes of the tree are created using the QuadNode class. It is a container of TerrainVertex. QuadNode represents a node.

·          Finally the 9 vertices of a node are TerrainVertex.

Most treatment is within the class QuadNode. This has two important methods: Initialize and Update.   The Initialize method handles load the node with all information related to its position in the QuadTree. It makes visible its four edges, it determines the neighboring nodes (for sharing vertices). It also calculates the deltas, normals, BoundingBox. It is called once every creation of a node.

The Update method performs tests on the four vertices “sides” (North, East, South, West) and four children on the potential (North West, North East, South West, South East) to each update of the tree .

This is the class QuadTree which initializes the first node and start treatment on the tree. Every x seconds (adjustable) it launches an asynchronous update on all the nodes instantiated to update the tree depending on the position of the camera.

The instantiation of a QuadTree is achieved by specifying the size of it, its depth and its location:

The structure TerrainVertex contains the vertex to display. A vertex can be shared on multiple nodes.

The code of this four major classes has been simplified in the extreme and widely commented. If there are still black spots do not hesitate to contact me.

A quick and easy (dirty ?) solution has been  implemented to update the field. A BackgroundWorker is launched when you start the application and handle the updating of the field asynchronously.

This procedure avoids any slowdown of the game during the update. The data displays (VertexBuffer and IndexBuffer) are stored in a stack and used in the Render method. In the example provided with this article, we do an update that every 4 seconds.

Optimizations

Apart from improvements to the code, here are some ideas to increase the quality of reporting and the power of technology Billodes.

·          We do two courses of the shaft each Update (one to update the tree, the other to extract the list to display triangles). There ‘may be a point to improve here.

·          The updated every X seconds is not necessarily the best solution. There are several other possible avenues to update, for example when the camera is moved or predicting the movement of the player and update accordingly. Finally, it should be noted that the final status of a tree is not achieved in a single Update It may be preferable in some cases to await the final version of the tree by calling the Update method as many times as necessary. You shlould prefer update the tree by little part each frame.

·          Some thresholds for activation or deactivation of a child or a vertex are very low. We can consider that they will never be activated.

·          When a node is cut or merged, or when a vertex changes state (visible / hidden), the transition is visible.It may be useful to cut this transition more gradual steps to soften the visual changes.

·          Relying on the Terrain class. In this sample we render only a single tree. The Terrain class field can be a serious springboard to try to manage several trees that will be linked one to another.

·          We load the entire Heightfield from the start by analyzing a black and white. It is an effective way to proceed to achieve an example but not to make a  game. It might be wise to store the array of heights in a file and read what the QuadTree needs at each Update.

·          The source code has not been optimized for readability.

The example program

This program is just one example of a rapid implementation of the land Billodes. The business classes are quickly adaptable to individual needs. The project is XNA 3.0. Il est toutefois adaptable au 2.0. However, it is adaptable to 2.0.

Commands to interact with the world are:

·          Mouse: Click to turn the camera.

·          Arrow keys: moving the camera in the current direction.

·          Key W: Wireframe Mode On / Off.

·          Page Up / Page Down: Change the Far Threshold Test.

·          Key left Shift + Page Up / Page Down: Near Threshold Test.

·          Left Key Shift + arrow keys: fast move.

·          Left Key Alt + arrow keys: moving slow.

To test the Billod algorithm, simply move on the ground and studying the evolution of the field.

Note that it is possible to modify some parameters affecting the display using the configuration file of the application.

You can reuse the source code as you want. I just ask to refer me in your “credits”.

References / Credits

The Web site of Stan Melax: http://www.melax.com . A very pragmatic man at the root of a technical Progressive Mesh very smart on which I based my algorithm. I take this article to thank him for his kindness and his exchange at the time or I was working on progressive meshes in XNA.

“Continuous LOD Terrain Meshing Using Adaptive Quadtrees” by Thatcher Ulrich. A method quite similar to mine but more research-oriented to deltas. This is one of the few methods available on the web that can be used for a game. My algorithm has a QuadTree quite similar to his “Adaptive QuadTree” but his management heightfield is much more intelligent than mine. His details management is, in my opinion less  good than mine.

The page Hugues Hoppe: http://research.microsoft.com/ ~ hoppe. One of the masters of the terrain rendering. Everything is so great but illegible for novices and not necessarily adapted to the world of video gaming.

Virtual Terrain Project: http://www.vterrain.org/ . Website with a great set of publications around the management of terrain.

And on a more personal way Mathieu Laussel, a French friend 3D modeler (engineering) with whom I realize an editor of the world and who kindly wait as I write this article. And Boris Driss student that like game development and with who, talking is very interresting.

You can downalod the C# project here.

59 thoughts on “Smart Terrain Rendering with Xna using Billod algorithm”

1. Bostich says:

Wow, this seems to fit perfectly for my needs! This is awesome that you share your code here to the public! Big thank’s for your work ðŸ™‚

Bostich

2. valentin says:

you’re welcome ðŸ™‚

3. What an amazing explanation, anyone can understand it. Thank you so much. I have been looking for a long time on net as how to enable LOD in terrain rendering. Your article fills that gap for me. Thanks once again.

4. Armigus says:

I’m trying to make your algorithm work with a more involved effect like Reimer’s 4th series but I’m running into all manner of difficulty. Have you tried anything similar?

5. valentin says:

There are a lot of effects explicited in the reimers’s 4th series. Witch one gives you trouble ? I will try to help you

You can find here an implementation made by me on my own algorithm :

6. Armigus says:

Let’s start with a working version of Billod that uses a custom effect file. The next, and I believe breaking, stage is to replace VertexPositionNormalTexture with this:

public struct VertexMultitextured
{
public Vector3 Position;
public Vector3 Normal;
public Vector3 Tangent;
public Vector4 TextureCoordinate;
public Vector4 TexWeights;
public float Slope;

public static int SizeInBytes = (3 + 3 + 3 + 4 + 4 + 1) * sizeof(float);
public static VertexElement[] VertexElements = new VertexElement[]
{
new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Tangent, 0 ),
new VertexElement( 0, sizeof(float) * 9, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
new VertexElement( 0, sizeof(float) * 13, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
new VertexElement( 0, sizeof(float) * 17, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2 ),
};
}

The tangent vectors are not that hard to get:
// curl tangent of the current quad
Vector3 curl = Vector3.Cross(normal, Vector3.Up);
curl.Normalize();

I had to build a custom method for setting the vertices:
public VertexMultitextured SetVertex(float x, float z, Vector3 norm, Vector3 tan)
{
VertexMultitextured ans = new VertexMultitextured();
ans.Position = new Vector3(x,ParentTree.GetHeight(x, z),z);
ans.Normal = norm;
ans.Tangent = tan;
ans.TextureCoordinate = new Vector4(GetTextureCoordinates(x, z),0,0);

float x0, y0, z0, w0, p0;
p0 = ans.Position.Y / (_parentTree.Peak-_parentTree.Valley);
x0 = MathHelper.Clamp(1.0f – Math.Abs(p0 – 0.0f) * 6, 0, 1);
y0 = MathHelper.Clamp(1.0f – Math.Abs(p0 – 0.33f) * 5, 0, 1);
z0 = MathHelper.Clamp(1.0f – Math.Abs(p0 – 0.67f) * 5, 0, 1);
w0 = MathHelper.Clamp(1.0f – Math.Abs(p0 – 1.0f) * 5, 0, 1);
float isum = 1 / (x0 + y0 + z0 + w0);
ans.TexWeights = new Vector4(x0, y0, z0, w0);
ans.TexWeights *= isum;

float angle = Vector3.Dot(norm, Vector3.Up);
ans.Slope = MathHelper.Clamp((0.9f – angle) * 5.0f, 0.0f, 1.0f);

return ans;
}

Then it was a matter of replacing the inline vertex constructors, for example:

CenterVertex = new TerrainVertex(SetVertex(xHalf, zHalf, normal, gradient));

I’m getting a blank screen and I have yet to figure out why.

7. valentin says:

can’t figure why too ðŸ™‚
did you change all my VertexPositionNormalTexture by your own vertex structure type ?
A blank screen is a common answer to this kind of mistake

8. Armigus says:

If you want the terrain to render using a customized vertex format and a custom shader, isn’t it necessary to replace the vertex type project-wide? It’s not like you can have a generic vertex struct Vertex and have XNA recognize it because generics are not value types by default. C# has no equivalent of VB’s ByVal to force a value type either.

9. Armigus says:

If you need define a generic class so that the item is indeed a value type you could have

You would then have to convert all classes that refer to vertex structures to generics, a task in and of itself. I haven’t tested this theory yet so no guarantees.

BTW, check out what Dragoon did with Reimer’s material (http://dracocepheus.blogspot.com/). It’s a component-based project. Since I eventually want a full game engine I am trying to pull together GameComponent items that can be registered as services.

10. valentin says:

yes you just have tu replace in all the projet.
For the generic i dont use it beacause if you want to make a special operation on vertex you will have to cast or try generic operation that cost more cpu than direct operation.

In Xna, code optimization are not the best optimization…

Wow! You are man! This is exactly what I was looking for!

12. Armigus says:

I’m actually getting some results with improving the terrain rendering, to the point of wanting the camera to maintain a bounding frustum and check the node children against it.

The QuadNode class needs some refactoring first:
public TerrainVertex SetVertex(float x, float z, Vector3 n)
{
VertexPositionNormalTexture v0 = new VertexPositionNormalTexture ();
v0.Position = new Vector3(x, ParentTree.GetHeight(x, z), z);
v0.Normal = n;
v0.TextureCoordinate = GetTextureCoordinates(x, z);
return new TerrainVertex(v0);
}

public Vector3 GetNormal(Vector3 nw, Vector3 ne, Vector3 sw, Vector3 se)
{
Vector3 n = new Plane(nw, ne, se).Normal * new Plane(nw, se, sw).Normal;
n.Normalize();
return n;
}

public Vector3 GetNormal(float left, float right, float top, float bottom)
{
Vector3 nwp = new Vector3(left, ParentTree.GetHeight(left, top), top);
Vector3 nep = new Vector3(right, ParentTree.GetHeight(right, top), top);
Vector3 swp = new Vector3(left, ParentTree.GetHeight(left, bottom), bottom);
Vector3 sep = new Vector3(right, ParentTree.GetHeight(right, bottom), bottom);
return GetNormal(nwp, nep, swp, sep);
}

public Vector3 GetNormal(TerrainVertex nw, TerrainVertex ne, TerrainVertex sw, TerrainVertex se)
{
Vector3 nwp = nw.Value.Position();
Vector3 nep = ne.Value.Position();
Vector3 swp = sw.Value.Position();
Vector3 sep = se.Value.Position();
return GetNormal(nwp, nep, swp, sep);
}

This will simplify the Initialize method considerably:
public void Initialize()
{
float size = GetNodeSize();
float x = Location.X;
float z = Location.Y;
float xWhole = x + size;
float zWhole = z + size;
float zHalf = z + size / 2;
float xHalf = x + size / 2;

Vector3 normal = GetNormal(x, xWhole, z, zWhole);

//first compute the 4 egdes of the current square
//here we know all : position and height.
{
if (Parent != null)
{
//if we have a parent maybe we can use its vertices
switch (Position)
{
case NodeChild.NorthWest:
NorthWestVertex = Parent.NorthWestVertex;
NorthEastVertex = Parent.NorthVertex;
SouthEastVertex = Parent.CenterVertex;
SouthWestVertex = Parent.WestVertex;
break;
case NodeChild.NorthEast:
NorthWestVertex = Parent.NorthVertex;
NorthEastVertex = Parent.NorthEastVertex;
SouthWestVertex = Parent.CenterVertex;
SouthEastVertex = Parent.EastVertex;
break;
case NodeChild.SouthWest:
NorthWestVertex = Parent.WestVertex;
NorthEastVertex = Parent.CenterVertex;
SouthWestVertex = Parent.SouthWestVertex;
SouthEastVertex = Parent.SouthVertex;
break;
case NodeChild.SouthEast:
NorthWestVertex = Parent.CenterVertex;
NorthEastVertex = Parent.EastVertex;
SouthWestVertex = Parent.SouthVertex;
SouthEastVertex = Parent.SouthEastVertex;
break;
default:

13. valentin says:

that’s great ðŸ™‚ send me your code so that i can see ðŸ™‚

14. Bert says:

I am trying to use the code of this project in order to build an terrain with tree’s. However when I try to place a tree on a certain position on the quadtree it appears to be floating in the air.

I noticed that the QuadTree.GetHeight()-method is not that exact, therefore I try to calculate the height on my own with the following steps:

1. Locate the triangle which contains the point
2. Measure the altitude between the point in the triangle and 0

This almost gives me the correct height, because I can see the trees following the shape of the mountains, how ever they still appear to be floating….

Does anyone have an idea how I can solve this?

15. valentin says:

i will try to answer tomorrow ðŸ™‚

16. valentin says:

by tree you mean the objet model Tree (like flower, grass ect.) ?
I first think that you were speaking about quad tree so it was hard to understand your problem ðŸ™‚

you only have to substract a delta that you have to substract to the height of each tree.

I dont understand your point 2 “Measure the altitude between the point in the triangle and 0”.

17. Bert says:

Well maybe my question was not that clear…

But the problem is that I need to place tree’s( 3D model ) on the terrain. However I find it quite hard to get the height of a certain point on the quadtree.

Therefore I lookup the childNode that contains the point of which I need to get the height. When I have found the child node containing that point, I look up which triangle contains the point of which I want to know the height. And then I calculate the height of the point in that triangle.

Since the terrain is rendered with the help of the vertices it should give the exact height of the terrain on that point…

I’m sorry that I can not give a better description of the problem.

18. valentin says:

you have to know the exact position of your tree inside the triangle that contains the X, Z position of your tree,

right ?

19. Bert says:

Well I know that i have the right triangle from the quadtree, but when I try to get the height of the point in the triangle, I don’t get the right height…. All of the trees float above the terrain.

20. valentin says:

but the trees are following the shape of the mountains ?
so you just have to reduce the height maybe.

you can send me your code here : lord.asriel [@] free dot fr

21. Bert says:

I have solved the problem :). It appears I have made a mistake in my own code due which the tree’s where not correctly positioned.

Thanks for you help!

22. I found lots of interesting information on msmvps.com. The post was professionally written and I feel like the author has extensive knowledge in the subject. msmvps.com keep it that way.

23. valentin says:

Bert > I used to think it was that problem. Great good job
Send me pictures of your world ðŸ™‚

Payday Loans -> Thank you

24. raygeee says:

hi,
first of all thanks for sharing the code and the thorough explanation.
While integrating it into my project I found an ugly little bug:

Suppose your quadtree is located at (0,0,0) and the width of the heightmap is 256. Then the far out vertex defining the quadtree should be located at (255,y,255) – it’s not. It’s at (256,y,256) which results in having two bad shaped sides of the quadtree.

in Initialize() replace
float size = GetNodeSize();
with
float size = GetNodeSize() – 1;

and in AddChild(NodeChild position, NodeContent flag) replace
float size = node.GetNodeSize();
with
float size = node.GetNodeSize() – 1;

So far this worked for me although with 4 quadtrees I still have a little gap between the quadtrees because the vertex positions aren’t the same.

Cheers, raygeee

25. valentin says:

thx you for your great help ðŸ™‚
i will correct this bug so ðŸ™‚

26. I’m currently seeing a bug whenever I try and make the heightfield larger.

In Game.cs, inside of InitializeTerrain(), there is:
int landSize = (int)32768;

If I make landSize twice as large, the terrain does become much larger, however, I end up with large cliffs on what I assume is the edges of the quadtrees. Any ideas on how to fix this?

27. valentin says:

what is the depth of the quadtree ?
When you increment the thresold with page up it correct the problem ?

28. Nic says:

I’m not sure about the depth. It would be at whatever you left it at in your demo, all I did to the demo is change this:
int landSize = (int)32768;

To this:
int landSize = (int)32768 * 2;

By doing that you’ll notice that the center of the map has a valley through it.

29. valentin says:

ok, i will test that.

30. valentin says:

i tried it it works well.
It’s a valley yes.
It’s because when the size is 32768 you have one montain, and when it’s 32768*2 you have four. So between each montain it create a valley

31. Nic says:

Thanks for the feedback. So it is mirroring the terrain heightmap?

I guess what I’m trying to achieve is spreading out the vertices of the heightmap so that I get a map that is 4 times the size, without adding any more vertices. On another heightmap algorithm I simply multiplied the position of each vertex by 2, and that worked. How could I got about doing something like that with this terrain?

– Nic

32. valentin says:

Found the problem : this is my fault :
Look at the GetHeightMethod in QuadTree.cs :

///

/// Gets the height of the ground at the specified position. ///

public float GetHeight(float x, float y)
{
float x2 = Math.Abs((x / 128f) % 256);
float y2 = Math.Abs((y / 128f) % 256);

return _heightData[(int)x2, (int)y2];//
}

The 256 constant is the size of the heightmap texture. I use % 256 so to not get a Out of Bound exception. The 128 value if the number of node you can find in the original quadtree parameter creation.
But in your case, the QuadTree is two time bigger. So change 128 by 256 and it will be good.
I guess you will have to increment thresold with the Page up key and change the LoadHeightData in game.cs class. I multiply the height extracted from the texture par 32f, you will change value to 64f to get the same ratio.

33. Nic says:

That is great, thanks so much. I truly appreciate you posting the algorithms for this, it is hard to find decent terrain LOD examples in a working state.

34. valentin says:

i appreciate too to see that some people use my work ðŸ™‚

35. Nic says:

Hi again Valentin,

I’m getting close to getting this working for my needs, thanks for the help so far. I’ve run into the issue that the terrain seems to be rendering with the opposite winding order than the rest of the objects in my game. I can’t seem to find an easy way to swap the winding order of the rendering for the terrain. Any ideas?

36. Nic says:

Figures, spent two hours trying to figure it out, finally ask on here, figure it out 5 minutes later.

Thanks though :).

37. valentin says:

you’re welcome ðŸ™‚

send me a screen shot when you have time, so that i can see your work ðŸ™‚

38. Nic says:

I’ve got it up and running with a couple hiccups, but I haven’t had much time to work on it (about 1 hour in the last couple of weeks).

The one thing I’m still trying to work out is getting the distance between vertices figured out. I’ve got the heightmap scaled in such a way that the terrain is now taking up a 4096×4096 area like I wanted, however, the vertices are further apart than they should be. For example, with my previous terrain algorithm when I scaled a 1024×1024 heightmap to 4096×4096, that meant each vertex was 4 units apart (on the XZ plane). However, with your algorithm, the vertices are 16 units apart on the XZ plane when I scale the map by a factor of 4. I basically need the highest LOD to keep the vertices 4 units apart, but can’t seem to figure out how to do that correctly.

I have found that by removing the scaling from certain functions that I get vertices that are 4 units apart, however then I can only see 1/4th of the map, the other 3/4ths are completely missing.

Any ideas?

39. valentin says:

why do you need distance between vertices ?

40. Nic says:

Basically I need the same maximum density of vertices I had before. Using this new algorithm the maximum density is much less dense, which causes the highest detail of terrain to still look somewhat jagged.

41. Nic says:

Wanted to note that I’m making some significant headway in terms of performance for this algorithm. The biggest issue I was noticing is that every time an update occurs it hitches my framerate, so a smooth camera motion ends up hitching for a moment during each update. Through a lot of optimizations I’ve cut the update time in half. Most of the work was in the QuadNode.Initialize method. There is a lot of creating and copying of structs going on (mostly the vertex type, vector3s, and planes).

Anyway, the biggest thing remaining before I can publish the changes is to get the vertex density the same as I had it in my previous engine release. Once I can get that I think I’m good to go.

42. valentin says:

i am working on a new version based on GPU

43. Nic says:

Sounds exciting. Can’t wait to see it.

44. Carael says:

Hi. First of all thanks for this great article – I’m newbie in terrain generation and it helped me a lot. I have a question however – I’ve tried to load a 1024×1024 heighmap but the program still generates only the 256×256 piece of my heighmap. Do You have any fix for this?

45. valentin says:

There is a method that transform the picture into a heighfield array of values.
You have to change it. I guess this method is in the Game class.
If you don’t find it tell me i will check.

46. Carael says:

{
Color[] heightMapColors = new Color[heightMap.Width * heightMap.Height];
heightMap.GetData(heightMapColors);

tree.HeightData = new float[heightMap.Width, heightMap.Height];
for (int x = 0; x < heightMap.Width; x++) for (int y = 0; y < heightMap.Height; y++) tree.HeightData[x, y] = heightMapColors[x + y * heightMap.Width].R * 32f; } The HeightData property has the correct width and height (in my case 1024x1024 or 2048x2048) from the image but the program renders only 256x256. Here is a image how it looks like: http://img844.imageshack.us/img844/6656/terrain2048x2048.jpg

47. valentin says:

so maybe there is a constant that set in my code as a stupid guy
sorry, i will look at this tomorrow ok ?

48. Dark Blast says:

Looks good, but getting errors, im using XNA 4.0, could you update the project to 4.0 code, and republish it please

49. Steve says:

Hi Valentin,
Great work, but i was also wondering about how to reverse the winding order. if you could please point me in the right direction, i’d really appreciate it

Cheers

50. Wayne Causey says:

Thanks.

51. WixxedIves says:

@Dark Blast: Theres only about 10 errors you have to fix to get this to work in XNA 4.0. Valentin has already given you all the source.

52. Dustin Watson says:

Hey I am trying to do Multitexturing using the BilLOD Terrain but I cannot figure out how to set the Texture Weights.

53. pr400 says:

Hi Valentin!

Can You help with setting parameters for bigger height map i.e. 1024×1024? After many tries I can’t figure it out to work.

treeDepth ?
landSize ?
public float GetHeight(float x, float y) ?
{
float x2 = Math.Abs((x / 128f) % 256);
float y2 = Math.Abs((y / 128f) % 256);
return _heightData[(int)x2, (int)y2];
}

54. hello, is it possible to use quadtrees with reach graphics? Im limited to Reach because of my graphics card, so im not planning to use it on windows phone. I am very new to 3D concepts, and i want to create a game with fairly large terrain… please help.
Thanks,
XNANOOB (deathfalcon on the forum question)

55. Leaghte says:

Did someone figure out problem with rendering??? when i tried to load 2048×2048 heightmap, the program renders only 256×256 … but heightdata are correct ..

56. Is it possible to modify this algo such that the quads not in camera frustum dont take memory?

Is it possible to render only those quads which are visible in camera frustum using this algorithm?
Will it still be efficient and is there any easy way to do this in the project code you gave?

58. blackboard says:

Thanks for example.
And 512*512 or 1024*1024 … for full render, i change QuadTree.cs in GetHeight method.

a little late…
for 256*256 and biggers

public float GetHeight(float x, float y)
{
int a = _Width / 256;
int b = _Height / 256;
float x2 = Math.Abs((x / (128 / a)) % _Width);
float y2 = Math.Abs((y / (128 / b)) % _Height);

return _heightData[(int)x2, (int)y2];//
}

59. Mike says:

nice tutorial!
but how can i add more heightmaps if i want a bigger terrain? because 5kX5k wont work. someone has a good solution?