• 10
• 10
• 12
• 12
• 14
• entries
438
1183
• views
767251

# Terrain blending

1352 views

As per request by jollyjeffers (see journal entry immediately preceding this one), I decided to write up a little bit about how I am implementing the new terrain blending scheme in Golem3D.

To start with, I build a large vertex buffer to hold the terrain vertex grid. Each vertex has a xyz, normal, diffuse color (currently, I use a static pre-generated lightmap for terrain shading), and UV coordinate component. Pretty standard stuff of course.

During the initialization phase, I iterate through the map and assign UV coordinates such that a given terrain texture tiles repeatedly across the map. The number of vertices a texture covers is configurable; currently, I tile a single 256x256 texture across 12 'tiles'. A 'tile' in this map is a patch of 5x5 vertices which is drawn by using 4 indexed triangle strips. A separate map of tiles is created to hold these indexed triangle strips. By setting the vertex buffer as the stream source, then calling a tile's Draw() function, I can draw a single tile once with a given texture. To setup the UVs, I simply calculate how much the U and V values should increment as I iterate the map, increment the values as I go, then enable texture repeating when I render.

Now, the map can currently have only 8 different terrain layers, so I simply store the tiling textures in an 8 element array. The map is drawn in 8 passes, working from the bottom layer upward. In order to perform the blending, I needed a way to specify a certain alpha value at each vertex for each given terrain type. To solve the problem, I made use of a custom shader and 8 separate float arrays to hold the blend components. These blend arrays are the same size as the grid buffer, and are placed in vertex buffer objects of their own when the map terrain is set and finalized. The vertex program (in it's initial, simple version without complicated lighting and stuff) looks somewhat like this: (Written by hand, because I still am not too familiar with more complicated shader programming paradigms; the program is written for the ARB_vertex_program extension to OpenGL.)

"!!ARBvp1.0   ATTRIB pos = vertex.position;   ATTRIB alpha = vertex.attrib[1];    <--- Our blend array is set as vertex attribute 1   ATTRIB tex = vertex.texcoord;   ATTRIB incol = vertex.color;   PARAM mvp[4] = { state.matrix.mvp };   TEMP outcolor;   TEMP transform;   OUTPUT out = result.position;   OUTPUT col = result.color;   OUTPUT tc = result.texcoord[0];   MOV tc, tex;   MOV col, incol;   MOV col.w, alpha.x;       <--- The magic line   DP4 out.x, pos, mvp[0];   DP4 out.y, pos, mvp[1];   DP4 out.z, pos, mvp[2];   DP4 out.w, pos, mvp[3];   END";

The shader really doesn't do very much at all. It simply does a straight across transformation of the position, and a straight across copy of the diffuse color (specified from the color buffer) and UV coords. The only 'magic', if you will, is where I copy vertex attribute 1 into the alpha component of the color. Remember, I have 8 blend buffers that I can specify for this vertex attribute, one for each layer. So if I specify a blend value of 1 for a given vertex, then that vertex is drawn with an alpha of 1. If I specify 0, then it is of course drawn at alpha 0, revealing the lower layers. Typically, I set all blend values for the base terrain layer to 1, to prevent successive blended layers from inadvertently blending with the black background. This looks bad, and causes an ugly banding effect at terrain transitions.

The actual rendering is performed using a custom technique I developed specifically for this type of top-down, pseudo-isometric viewpoint. That is, it does not work well for 'general' 3D camera viewpoints. It relies on the fundamental tile-based nature of the map. I calculate the intersection of the view frustum with the tiles in the map, and construct a list of all tiles that intersect the frustum. Then I simply iterate through the layers, set the texture to current and set the alpha buffer to vertex attribute 1, then draw the tiles in the list 8 times, once for each texture and blend buffer. Currently, I'm drawing every tile in the list all 8 times, but an optimization I will implement in the next few hours will be to mark tiles that have 0 alpha for all of the vertices the tile touches, and skip rendering of these tiles since doing so has no effect. Even without this check, I still get pretty good frame rate--about 250FPS on average with my GF6800.

I can manipulate the various alpha blending weights as well, to achieve sharp or gradual terrain transitions as needed. In this screenshot, I blend grass onto dirt using the sharpest possible transition, setting all vertices covered by grass to a weight of 1, and all vertices not covered by grass to 0 for that layer. All verts for the dirt are set to 1.

This is the blend alpha map I used for the grass for this shot. White areas are grass.

As you can see, this creates a rather sharp transition from one to the next. In this next shot, I applied a couple passes of Gaussian blur (with a 7x7 kernel) to the noise buffer used to determine the grass blend factors, in effect blurring the transition out across more vertices to smooth it out some.

And the blurred blend map I used:

By careful manipulation of the vertex blend values, all sorts of terrain blends can be achieved. I gotta say, I vastly prefer this method to the old way I was doing things. Can't believe I put off the switch for so long. [grin] If anyone has any questions, feel free to drop me a reply or PM or mail or whathaveyou.

Very Nice!! [smile] Thanks for the good explanation!

Cheers for the explanation - was an interesting read [smile]

Jack