Detailing a Terrain With Multitexturing

  CU_Multitexture.zip (904.1 KiB, 5,060 hits)

If you looked at the terrain from previous tutorials, you probably noticed that when you get close to the terrain, it becomes very blurry and pixelated. Even with a 1024×1024 or even a 2048×2048 terrain texture, if you view a section of the terrain up close, it will appear very blocky. Another way to texture a terrain is to blend the terrain texture with another detail texture. This is a straightforward way of achieving the standard of graphic effects which you might normally find on popular gaming sites like PartyPoker, simply through using texture blending techniques. If we repeat the detail texture over smaller patches of the terrain, the terrain will have the detail of the detail texture and the color characteristics of the base texture.

Base texture
Base Texture
Detail texture
Detail Texture
Without Detail
Base Texture Alone
With Detail
Base Texture Blended With Detail Texture

To use texture blending, we have to make use of texture stages. Texture stages are used to perform separate blending operations to a pixel. Think of each stage as a blending station. We perform a blending operation in one stage and then pass the result on to the next stage or, if there are no more stages, to the rasterizer. When multiple stages are used, the blending flow between stages is often called the texture blending cascade because the blended color cascades through all the stages as shown below.

Texture stages

If you recall from my texturing tutorial, when we render textures, we have to assign a texture to a texture stage by calling IDirect3DDevice9::SetTexture. When more than one stage is used, we usually perform a blending operation on the texture in one stage and then pass the result on to the next stage so it can be blended with the texture in that stage.

I created both the base and detail textures with Terragen. You have to make sure the detail texture is tileable or else you’ll be able to see the borders of the detail texture when it repeats. A little run through Photoshop helps make the texture tileable.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Position and 2 textures
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
typedef struct Position2Textured
{
public:
    Position2Textured() : X(0), Y(0), Z(0), Tu1(0), Tv1(0), Tu2(0), Tv2(0) {}
    Position2Textured( float x, float y, float z, float tu1, float tv1, float tu2, float tv2 ) 
        : X(x), Y(y), Z(z), Tu1(tu1), Tv1(tv1), Tu2(tu2), Tv2(tv2) {}
    float X, Y, Z;
    float Tu1, Tv1;
    float Tu2, Tv2;
} Position2Textured;

To detail the terrain, we’ll use a vertex that has two sets of texture coordinates: one for the base terrain texture and another for the detail texture.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Generates vertices with a position, normal, and two texture coordinates to create an indexed 
triangle strip plane.
Parameters: 
[in/out] ppVertices - Pointer to an array to be filled up.
[in] verticesAlongWidth - Number of vertices along the width
[in] verticesAlongLength - Number of vertices along the length
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CTriangleStripPlane::GeneratePosition2TexturedWithHeight( cuCustomVertex::Position2Textured** ppVertices,
        int verticesAlongWidth, int verticesAlongLength, UCHAR* pHeight )
{
    SAFE_DELETE_ARRAY( *ppVertices );
    *ppVertices = new cuCustomVertex::Position2Textured[verticesAlongLength * verticesAlongWidth];
    for ( int z = 0; z < verticesAlongLength; z++ )
    {
        for ( int x = 0; x < verticesAlongWidth; x++ )
        {
            float halfWidth = ((float)verticesAlongWidth - 1.0f) / 2.0f;
            float halfLength = ((float)verticesAlongLength - 1.0f) / 2.0f;
            (*ppVertices)[z * verticesAlongLength + x] = cuCustomVertex::Position2Textured(
                (float)x - halfWidth, 
                (float)pHeight[z * verticesAlongLength + x], 
                (float)z - halfLength,
                (float)x / (verticesAlongWidth - 1), (float)z / (verticesAlongLength - 1),
                (float)x / 10.0f, (float)z / 10.0f
            );
        }
    }
}

When we generate the terrain vertices, we specify two sets of texture coordinates. The first set of texture coordinates span the entire terrain. This is the base texture. The second set of texture coordinates are the coordinates for the detail texture. The detail texture will span a square subset of vertices and be repeated over the terrain. What happens when the coordinates are out of the [0.0, 1.0] range? We can control what DirectX does with texture coordinates outside of the 0.0 to 1.0 range by specifying a texture addressing mode. By default, DirectX uses the wrap texture address mode. In this mode, the texture is simply repeated for every integer interval. This is what we’ll use for the detail terrain texture so it is repeated over the surface of the terrain.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Renders the terrain.
Parameters:
[in] pDevice - D3D Device
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CTerrain::Render( LPDIRECT3DDEVICE9 pDevice )
{
    pDevice->SetTransform( D3DTS_WORLD, GetTransform() );
    pDevice->SetTexture( 0, m_pTextureBase );
    if ( m_pTextureDetail )
    {
        pDevice->SetTexture( 1, m_pTextureDetail );
        pDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
        pDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
        pDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_CURRENT );
        pDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_TEXTURE );
        pDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_ADDSIGNED );
    }
    m_vb.Render( pDevice, m_numIndices - 2, D3DPT_TRIANGLESTRIP );
}

To use texture blending, we first need to specify how the textures will be blended. We use IDirect3dDevice9::SetTextureStageState to specify which values to blend and how to blend them. In the above example, we first assign the texel color (D3DTA_TEXTURE) to one of the two available arguments for the blending equation (D3DTSS_COLORARG1). Then, we send this color value straight to stage 1 (D3DTSS_COLOROP, D3DTOP_SELECTARG1). In stage 1, we grab the color value that we passed from stage 0 (D3DTSS_COLORARG1, D3DTA_CURRENT). We then assign the texel color from stage 1 to the other argument of this stage (D3DTSS_COLORARG2, D3DTA_TEXTURE). Finally, we tell the sampler what to do with the two arguments. In this case, we add the two color values together and subtract 0.5 from the value to reduce the brightness (D3DTSS_COLOROP, D3DTOP_ADDSIGNED). There are many other texture operations you can use to blend the textures so make sure you read up about them in MSDN.

You’ll usually want to query the hardware for texture blending capabilities as older hardware may not support as many texture stages as more modern hardware. As you can see, texture blending improves the visual quality of the terrain by quite a bit.

Take note that the technique in this tutorial is the fixed-function multitexturing process and is somewhat old school. Nowadays, people are more likely to use pixel shaders to perform multitexturing.

3 thoughts on “Detailing a Terrain With Multitexturing”

monte April 14, 2011 at 10:33 am

Thank you so much for the example….If your willing, I have a few questions for you.

Jessica August 9, 2013 at 1:10 pm

Hello, I was wondering where you got the texture that you are using for your terragen created height map. I have created my own height map, but I am unsure on how to create a texture in the way that you have that would work well with the hills and such that I have.

Thanks

Chad August 9, 2013 at 10:06 pm

It’s been a while since I did it but I think it also came out of Terragen. That was about 10 years ago so I haven’t played with Terragen since.

Comments are closed.

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.