Terrain Blending tutorial – Saberpeak

This document is part one of a series of tutorials I’m writing on the techniques used in Saberpeak on terrain and water making both in terms of 3d design and shader/texure blending. It is aimed at all levels from beginner to advanced although some basic knowledge of what shaders are etc. is required. Throughout the document there are many references made to the shader manual, which can be found here. Also I recommend you download the source files and the .pk3 for Saberpeak where you will find many applications of the techniques described here.

NOTE: The techniques described here require q3map2 2.5.14 or later. Get the latest version here.

 Contents:

  • Terrain Blending
    Part 1 – Eliminating terrain texture stretching and blending textures projected
      on different axis.
  • Terrain Making
       – General notes on terrain making
  • Additional Notes:
       –  Alpha blending and alphaGen notes
       –  tcGen commands

 

 

 

TERRAIN BLENDING:

Part 1.

This first part will cover how to eliminate texture stretching on very steep terrain and how to make a smooth transition between two textures projected on two different axis. This technique was used in Saberpeak to make the trasitions from sand:sp8 or dirt to the rocky cliffsp10

 

 

The Problem:
If you’ve ever used easygen or some other terrain making program to make your terrain you’ve undoubtedly noticed that if you make very steep and high cliffs the textures on them will stretch a lot and loose most of their detail .

stretch
The reason this happens is because terrain textures are projected on the z-axis to prevent ugly seams on your terrain. This is done automatically by the compiler for all terrain entities and it is necessary especially if your terrain has lots of “bumps” etc.
To understand why the stretching happens imagine your texture being like a flat rubber sheet hovering on top of your terrain and then lower it to cover your whole terrain. The texture will stretch to cover the very steep portions. The key word here is projected. To put it another way, the texture can never tile in the projected axis direction (z in the terrain case) although it can tile as many times as you wish in the other two axis.

 

The bad solution:  
One easy solution to the problem is simply to retexture the tall cliffs manually in radiant manualRad so that their texture is not projected in the z-axis anymore manual. This however creates a very ugly seam seam where the cliff meets the rest of the z-axis projected terrain. Sometimes this might not be a problem, other times though (and especially if you’re using a different texture for the cliffs) it might be very visible. You could try to remove the seam by attempting to fix the mapping manually in radiant, but this is extremely hard since the flat portion has the terrain texture in radiant, plus if you want to add some “bumps” to your cliffs so they don’t look like a wall, you have to use x or y-axis projection for the cliff shader through q3map_tcgen ivector  which would mean you have virtually no control over the mapping in radiant. Fortunately though thanks to ‘The Amazing ydnar (I believe that’s his official title now), there are better ways of doing it.

 

The good solution:  
The problem with the previous solution was that wherever two textures projected in different axis meet they would cause a visible seam. Now we have to have the flat portion of the terrain projected on the z-axis. We can’t change that. We also have to have the tall portions of the terrain projected on a different axis (let’s say x). We can’t change that either. What we need is a merging area in between, where the transition from one axis to the other can happen smoothly. To that end you need to understand texture blending a bit and especially the shader command alphaGen. If you know and are comfortable with these keep reading, otherwise read the notes on alphaGen and alphaMod blending before you continue(bottom of page).
The solution to our problem is very simple, and pretty obvious if you know a few very useful commands. These commands in question are q3map_tcGen ivector(and its sister q3map_tcGen vector) and tcGen vector. I would recommend checking the manual for their full description or you can check my small explanation.(bottom of page)

To overcome the problem we will write 3 shaders. One for the z-axis projection of the flat portions, one for the x-axis projection of the cliffs and one for the merging area in between the two.

For the flat portions most likely you will have a program such as easygen write the shaders for you. In that case you don’t really have to touch them. But for reference here’s a very simple shader that projects the rock texture on the z-axis:

textures/tutorial/rockZ
{
qer_editorimage textures/snow_sd/bigrock_rounded_faint.tga
q3map_tcGen ivector ( 512 0 0 ) ( 0 512 0 )
//map all onto z
{
map textures/snow_sd/bigrock_rounded_faint.tga
}
{
map $lightmap
blendFunc GL_DST_COLOR GL_ZERO
rgbGen identity
}
}

Here’s also a shader for the vertical portions of the cliffs that projects the textures on the x-axis:

textures/tutorial/rockX
{
qer_editorimage textures/snow_sd/bigrock_rounded_faint.tga
q3map_tcGen ivector ( 0 512 0 ) ( 0 0 512 )
//map all onto x
{
map textures/snow_sd/bigrock_rounded_faint.tga
}
{
map $lightmap
blendFunc GL_DST_COLOR GL_ZERO
rgbGen identity
}
}

And here’s finally the shader that blends a texture projected on the z-axis onto a texture projected on the x-axis:

textures/tutorial/rockZtoX
{
q3map_tcGen ivector ( 512 0 0 ) ( 0 512 0 )
//map all onto z
{
map textures/snow_sd/bigrock_rounded_faint.tga
tcGen vector ( 0 0.00195 0 ) ( 0 0 0.00195 )
//map onto x
}
{
map textures/snow_sd/bigrock_rounded_faint.tga
blendFunc blend
alphaGen vertex
}
{
map $lightmap
blendFunc GL_DST_COLOR GL_ZERO
rgbGen identity
}
}

This shader is applied on a “merge” brush finalRad or finalRad2 between the vertical brushes and the horizontal ones. Since the shader contains textures projected both in the z and x axis, it’s best that the “merge” brushes are at an angle around 45 degrees to the horizontal so there’s no stretching in any direction. It’s important to note that the parameters of all tcGen commands in the rockZtoX shader mach the corresponding parameters of the z projected shaders and the x projected shaders. Otherwise we will have a seam where this shader meets the rest of them. Lets now examine how this shader works in detail:

First the q3map_tcGen ivector ( 512 0 0 ) ( 0 512 0 ) command tells the compiler that all textures in the shader are to be projected on the z-axis every 512 units. This matches the projection on the rockZ shader so there will be no seam where the two shaders meet.

Next in the first stage the rock texture is mapped and then the command tcGen vector ( 0 0.00195 0 ) ( 0 0 0.00195 )overwrites the coordinates of the first q3map_tcGen ivector command and projects the texture in the x-axis every 512 units. This matches the x-axis projection of the rockX shader.

In the second stage the rock texture is mapped again but this time the coordinates are left to the ones specified by the first q3map_tcGen ivector command which says project in the z-axis every 512 units. This stage however is blended with the previous one using the vertices alpha value to determine its transparency. The alpha value of the vertices of the “merge” brush is modified using alphaMod brushes to be 1 for the bottom portion where the merge brush meets the z-axis projected terrain and 0 in the top portion where it meets the x-axis projected cliff. This means that the texture on the z-axis projected stage(second stage) will be fully opaque on the bottom portion where it meets the flat terrain and completely transparent in the top portion where it meets the cliff thus allowing the x-axis projected texture of the first stage to come through. The result is a completely seamless transition from the horizontal terrain to the vertical cliff without any stretching final Pretty nice…

In this example I used the same texture to demonstrate the shaders but this same technique can be used with different textures the dirt to rock transition:sp10  and also with different axis (for example x to y). You can now also add bumps to your cliffs(i.e make them come in and out) because they’re completely independent of the rest of the terrain. You can look at the terrain and terrain shaders of Saberpeak for more examples of this kind of blending and some more complicated shaders.

 

 

 

Terrain making

This part covers the techniques used in saberpeak to make the terrain in terms of brushwork. Nothing really special here, mostly just useful “tips and tricks”.

The terrain in saberpeak was made primarily with easygen(although it was modified later in radiant). Some portions of it were made in 3dmax since it allows for much more flexibility in terms of vertex tweaking. There was absolutely no “technical” reason for the choices made in terms of which regions were made with 3dMax and which ones with easygen(pretty much just what program I felt like working with that day). That’s why it’s important to stress that the whole terrain can be made in easygen(with some tweaking latter on in radiant), although you might find it easier to work in max(or some other modeling program) for regions requiring a lot of vertex tweaking since the radiant vertex tweaking capabilities at this point in time(release 1.4.0) are less than perfect.

A “rough” version of the terrain was first made in easygen(see the easygen file included in the source files for the map). Then in radiant the terrain was edited by removing all the unnecessary parts, such as the hills etc. since they were made separately. As a side note when deleting portions of easygen generated terrain be careful not to delete the brushes at the corners of the terrain. If you do the alphamap will not be aligned with terrain anymore and you will loose your texture indexing information. Also if you ever accidentally delete a brush that you didn’t mean to delete and have no way of undoing, you can just redraw the brush in radiant and then add it to the terrain entity(select the new brush, select one of the terrain brushes, right click and select move into entity).

 

Making the “bumpy” clifs.

Some of side cliffs were also made in easygen as prefabs. The basic idea is to make a relatively bumpy terrain in easygen and then in radiant “flip” it on one side (rotate 90 degrees with respect to the x or y axis). This is the way all the “non-model” hills were made, including the inside of the submarine hangar. The easygen prefabs need to be tweaked a little bit in radiant to make them fit the existing terrain, but this is still a lot less work than doing the whole thing from scratch in radiant. It’s important to notice that the texturing info from easygen (shaders alphamap etc) are completely discarded because terrain entities can only have z-axis projected textures, which is not what we want in this case. Instead the cliffs were re-textured in radiant.

 

Fitting terrain models with existing terrain.

Other cliffs were made using 3dMax and they were imported in the game as misc_models. ( these are mountainTest.ase, SP_beachMountain.ase, and SP_hangarMountain.ase ).

Although not strictly necessary most of the terrain models in saberpeak are fit perfectly with the existing terrain(for example the hangar cliff). By perfect fit I mean they don’t intersect anywhere with the existing terrain; they touch but they don’t intersect. The biggest benefit of perfectly fitting the models with the terrain is that, if properly textured, you will have no seams of any kind at the points where the model terrain touches the rest of the brush work terrain. To make a perfect fit is very easy. Here’s how I did it:

First compile your existing brush work terrain.(or just the portion your model terrain will touch.)
Then decompile the map with the –ase flag so the map becomes one big ase model.
Now import the ase terrain in max(or whatever 3d program you use). I used Deep Exploration for all model file format conversions.
Now make your new model terrain making sure that it fits in with the rest of the existing terrain perfectly. It’s very easy to do this in program like 3dmax. All you have to do is jot down the coordinates of the vertexes from the existing brush work ase terrain that your model terrain will touch, and then move the appropriate vertexes of the model terrain at the same coordinates. (if that makes any sense).
Actually it’s much easier to just make everything part of the same mesh, then turn on snap to vertex and just move the vertexes around to their appropriate locations. In the end you can just delete the original brushwork terrain and leave just the model terrain.

Done. You can now just export your model terrain and here comes the best part. When placing it in your map all you have to do to put it in the right position is put its origin at 0 0 0. This can be done very easily by just opening the entity window, selecting the origin key and giving it the value 0 0 0.

 

Water

In terms of brushwork the water is very simple so there isn’t much to discuss. Most of the work is done by the water shaders which perform the blending and pretty much everything else water related (movement etc). The blending from one water color to the next is done using alphaMod brushes, while the general water transparency is controlled through the alpha channel of the water texture. It’s worth noticing that both the general water transparency and the blending from one color to the next can be easily controlled using alpha mod brushes only, which eliminates the need for alpha channels on the water textures completely. This can save you some space because textures with alpha channels can be quite large. See this sample map for an example of this.

The water merges with the horizon by blending the water color with the horizon RGB value. This is also done using alphamod brushes and it is a lot easier when using textures without alpha channels.( one more reason to use them). Saberpeak uses textures with alpha channels which actually made the blending needlessly overcomplicated.

The water brushes also rise up as they go out to open sea to give the impression that they extend further than they actually do.

 

Conclusion

In conclusion I think the most important thing to keep in mind when designing terrain is to use all the tools at your disposal. Good results take time and require a lot of work. Personally I found the terrain in saberpeak much more challenging that the general structural brushwork of the resort area etc. I spend a lot of time and I tried a lot of things which is why saberpeak contains an example of pretty much every terrain making and blending technique available. In the end here are my recommendations:

  • 1) Easygen is a great tool. Although it is now possible to make the terrain entirely in radiant using alphamod brushes, don’t get stuck spending hours and hours making something that you could easily make in easygen in 10 minutes. Unless you need to use some type of special blending(dot product etc.), it’s ok to use easygen for most of your terrain.
  • 2) Alpha mod brushes are the greatest thing added to q3map2. Use them mainly for terrain going in the vertical direction where easygen generated shaders and z-axis projection will not do. Don’t forget about dot product blending which can come in very handy when doing this kind of terrain.
  • 3) For brushwork involving a lot of vertex tweaking(I’m not just talking about terrain here) use a modeling program such as 3d max. It will save you a lot of time and a lot of radiant crashes.
  • 4) Mix and match everything to get done as quickly and as efficiently as possible. In the end the most precious resource you have is time.

 

Alpha blending and alphaGen notes:

The alpha value for a texture determines the transparency of the texture. There are many sources the alpha value can be obtained from. The simplest is the texture’s own alpha channel. Another very useful source are the vertices of the surface the texture is applied on. If left alone all vertices have a default alpha value of 1, which is basically fully opaque. However you can alter this value and make it anything you want which can add transparency to your texture (without the texture itself having an alpha channel). All surfaces are composed of triangles and thus have 3 vertices. A texture that is using the vertex alpha value for blending will gradually change its transparency over the surface based on the alpha value of the vertices. For example on a surface that has 2 vertices with an alpha value of 1.0 and one vertex with an alpha value of 0 the texture will be fully opaque at the 2 vertices with the 1.0 value and it will gradually turn transparent until it’s completely invisible at the vertex with the value 0.
To use the vertex alpha for blending you have to use the shader command alphaGen in the mapping stage. The shader command alphaGen tells the engine to generate an alpha value for the current mapping stage from the source passed as a parameter to the command. The source passed as parameter can be any of the ones described in the shader manual which obviously include Vertex and also oneMinusVertex which is very useful.
VERY IMPORTANT
: To be able to use the vertex value to add transparency to the texture you need a blending command that uses the alpha value (i.e. blendFunc blend) in the stage of the texture you want to be affected by the vertex alpha. It should be pretty obvious why (to have transparency basically means to blend some of the background in with the current texture) and you can check the blendFunc command on the manual for more info.

How to assign an alpha value to a vertex:
There are a few ways to assign an alpha value to a vertex.
q3map_alphaMod
is the basic command that is used to modify the vertex alpha. It can take one of various parameters, which determine the type of modification performed. I will not cover all of them but the most important ones.

dotproduct2 ( X Y Z ) is one very useful parameter and it determines the vertex alpha value by performing a dotproduct calculation between the (X Y Z) values of the parameter and the normals of the vertices. It can be very useful when you want a random distribution of a certain texture over an area (i.e. terrain) because the alpha value for the vertex is depended on the slope of the various surfaces. This also means that the area where you are applying the dotproduct2 blending needs to have some “bumpiness” otherwise all the vertices on it will all have the same alpha value. The grass on the cliffs of Saberpeak uses dotproduct2 blending for it’s apparent random distribution. Check the manual for more info on dotproduct2.

volume,         set N.N and scale N.N are 3 other parameters and can be explained better in the context of alphaMod brushes. An alphaMod brush is a just a regular brush used to enclose any vertices whose alpha value we want to modify. The shader applied to this brush must have the command q3map_alphaMod volume in it, which is saying that this brush is to be used solely to modify the alpha values of the vertices it encloses and is not part of the map geometry. The shader then must also have one of the commands q3map_alphaMod set N.N or q3map_alphaMod scale N.N which effectively set or scale the alpha value of the enclosed vertices to their N.N parameter (for example q3map_alphaMod set 0.5 would set the alpha value of the enclosed vertices to half transparent.) You don’t however need to write these shaders yourself as they are part of the common shaders that come with q3map2 2.5.14 or later. The common shaders include 4 shaders which scale the alpha values by 0, 0.25 0.5 and 0.75. These should be more than enough for 95% of your work but if you need finer distribution you can always write your own. Don’t forget to check the manual for more info.

It’s also worth mentioning that the vertex alphas can also be modified by using terrain entities and alphamaps, but this can only be used for portions that have their textures projected on the z-axis and the vertex alphas can only be set to 1 or 0.

Example: Using the alphaMod brushes and the alphaGen command.
In this small example we will write a shader and use an alphaMod brush to make a smooth transition from rock sd_rock to grass.sd_grass

In the example the alpha values for the lower vertices were set to 0 by using an alphaMod
brush (editor)alpha1 (game)alpha2) (r_showtris 1)alpha3.  The upper value was left untouched, which means it’s 1.         Lets examine the shader that performs the blending:

textures/tutorial/rockToGrass
{
{
map textures/egypt_floor_sd/sandygrass_b.tga
}
{
map textures/temperate_sd/rock_grayvar.tga
blendFunc blend
alphaGen vertex
}
{
map $lightmap
blendFunc GL_DST_COLOR GL_ZERO
rgbGen identity
}
}

First the map textures/egypt_floor_sd/sandygrass_b.tga command maps the grass texture all over the surface. Then in the second stage the rock texture is mapped all over the surface too, but this texture is blended with the previous texture using blendFunc blend. The command alphaGen vertex tells the engine to use the vertex alphas to decide the transparency settings for the blending. The upper vertices have an alpha value of 1 which means that the rock texture is fully opaque here and completely covers the grass texture underneath. However the bottom vertices have a value of 0 which means the rock texture is completely transparent here and the grass texture mapped in the previous stage is fully visible. The in-between areas are gradually blended and give a very nice transition from rock to grass. The last stage is the standard lightmapp stage and it’s not even visible in the pictures since they were taken on a –meta compile. This is a very simple shader and contains the minimum commands required to make the blend.

 

 

TcGen commands:

q3map_tcGen ivector projects all the textures in the shader on a specific axis and tiles them based on the parameters passed. The parameters are passed as two groups of ( X Y Z ) numbers where each group decides the tiling distance for one axis only. The axis that has a zero value in both groups is the projection axis. For example q3map_tcGen ivector ( 512 0 0 ) ( 0 0 512) will project the textures on the y-axis and tile them every 512 units in the x and z directions while the command q3map_tcGen ivector ( 0 155 0 ) ( 0 0 315) projects the textures on the x-axis and tiles them every 155 units on the y direction and every 315 units in the z direction. The q3map_tcGen ivector command is a compiler directive and it applies to all the textures on the shader (except for the lightmap). It obviously has no runtime cost but any changes made to it will require a recompile to be visible. The sister command q3map_tcGen vector behaves exactly the same as q3map_tcGen ivector except that the parameter values passed to it must be the inverses (1/ivector value) of the ones for q3map_tcGen ivector. For the examples above they would be q3map_tcGen vector ( 0.00195 0 0 ) ( 0 0 0.00195 ) and q3map_tcGen vector ( 0 0.0065 0 ) ( 0 0 0.0032 ) because 1/512 = 0.00195, 1/155 = 0.0065 and 1/315 = 0.0032.

The tcGen vector is the same as q3map_tcGen vector except that it’s a runtime command and it’s stage specific. It will overwrite any previous coordinates for the stage it’s on and replace them with its own parameters. Because it’s a runtime command it has a very small overhead so use the q3map_tcGen commands whenever you can.