Level of detail and other considerations
It isn't too uncommon now a day if not downright required for terrains to be rendered using levels of detail. If using a level of detail algorithm that takes the ruggedness of the terrain into consideration giving more detail where needed, such as the ROAM algorithm, it shouldn't have as much of an impact on the river. If however you are using a simpler method such as geomipmapping, I have found you need to restrict the detail levels of the patches containing portions of the river. Often enough if you don't the terrain will be over simplified in a current patch completely eliminating any obvious sign of the river. This may or may not be a problem depending on your camera angle and how fast you move throughout the world.
Creating the water
Creating the water can be quite a tricky task, depending on whether you want it to flow or move up and down, adapt to the LOD or merely remain static. The actual simulation of flowing water is beyond the scope of this article, however, I will cover a very simple way to simulate a static triangle strip of water for this newly formed river. In actual implementations you might want to break it into different triangle strips per terrain patch and adapt it with the current LOD. The first thing we must do is recognize that although it is a seemingly random river there is still a systematic method to its madness. If we always start at one end of the river and traverse through the list of nodes we can visualize each needed vertex in a triangle strip alternating from our right and our left as we traverse the nodes, the real question here is how we figure out their positions.
Let's first lay out some assumptions, we will be traveling from the high end of the river to the low end and we will be creating our triangle strip in a right, left, right, left, … clockwise fashion. In order to calculate the orientation of the particular vertices we are going to use a little bit of linear algebra, however we will be able to keep it all in 2D space making it much easier. If we imagine a line passing through our current position from where we were to where we are going we will have an approximation of our current orientation (which way the water is flowing). While standing in the middle of the river facing along the direction of orientation the width of the river will be to our left and right. In other words the width of the river will be perpendicular to our orientation. So to find the position of the vertices for our current position we will move out half the rivers width in both directions perpendicular to our orientation. Because we are simplifying the entire process to 2D we can easily represent the perpendicular line as one with the negative inverse slope of our current line. Following is some sample code to hopefully explain the process a little bit better.
// Calculate the perpendicular vector v2Norm.x = -(m_vp3River[i+1].z - m_vp3River[i-1].z); v2Norm.y = (m_vp3River[i+1].x - m_vp3River[i-1].x); v2Norm *= 1.15f*0.5f; // Project our right point our along the normal v3Pos1.x = m_vp3River[i].x + v2Norm.x*(Width/2.0f); v3Pos1.z = m_vp3River[i].z + v2Norm.y*(Width/2.0f); v3Pos1.y = m_vp3River[i].y + Depth*0.4f; // project our left point out along the negative normal v3Pos2.x = m_vp3River[i].x - v2Norm.x*(Width/2.0f); v3Pos2.z = m_vp3River[i].z - v2Norm.y*(Width/2.0f); v3Pos2.y = m_vp3River[i].y + Depth*0.4f;
What we see above is v2Norm is defining our perpendicular vector by using the negative/inverse slope of the orientation vector. In this case I negated the delta z of the orientation vector, which one you negate is relative to your winding and which way you are traversing through the river. You may notice that I multiply the normal by 1.15 to slightly extend the projection for the water to slightly stick into the side of the riverbed preventing gaps. I also multiply it by 0.5f which is to accommodate for the fact I am not normalizing the vector. Normalizing the vector would be a more accurate representation but I found an unnecessary slow down in most instances. You can also see that I setup two vertices with this data. This merely shows how we take into consideration our current position and the river width vector. Also I have not yet mentioned how I am initializing the height of the vertices, in this case I stuck with a very simple static height compared to the bottom of the river. Since we used a depth variable twice once to carve the river shore and again for the bed I chose to multiply the depth by 0.4 as it is slightly less than half the depth and therefore shouldn't be hovering above the riverbed. Traversing through the entire river this should form a decent static water surface following your newly carved riverbed. Obviously this method does not take into consideration of LOD nor animated water. You would also want to take special precaution of the edges of the rivers as orientation can't be calculated with both the vertices in front and behind you.
Carving random rivers into a random terrain is not an easy task, what I have presented here is only one of many ways we can accomplish this task. Rivers can add a wonderful change in scenery in an otherwise regular terrain, often adding character and depth. Through breaking down the idea into smaller parts and analyzing what our goals are we can easily move towards accomplishing what we are looking for. This article does not describe the fastest method, the best method, or even the most realistic method for creating rivers but instead presents us with a good base to establish the style we are looking for.
This article was the result of a months worth of research while at Full Sail for my final project Liege and this was the resulting process I used, however there were many more looks and styles one can use by changing different parts of the process. I would encourage everyone to try different path finding algorithms like Dijkstra which can result in pooling, similar to that of small lakes. For a glimpse at how I went about creating this process you can go here, it also includes some information regarding how I later placed trees on the terrain. If anyone has any questions or suggestions about the article feel free to email me or post in the forums. I will be sure to take the time to read them all, hopefully I will get around to doing a couple more articles benefiting from your comments when I find another topic.