Tutorial 17: Terrain Texture Layers

This DirectX 10 terrain tutorial will cover the implementation of terrain texture layers. The code in this tutorial builds on the previous terrain tutorials.

The pixel shader is an incredibly powerful tool that allows us to have complete control over how our terrain can look on a per-pixel basis. With this fine grained pixel control we can employ similar methods such as what Photoshop uses; layers and masks. Layers and masks will allow us to paint multiple texture layers over top of each other. This is just like how an artist would paint a terrain, layer by layer applying different colored paint each time.

To illustrate this concept we will first draw a base layer of dirt for the terrain resulting in the following image:

Next we will paint a second layer over top of the first one with a different dirt texture that is placed randomly using noise:

After that we can manually paint a third layer using a sand texture to create a sandy path over the terrain:

And finally we can manually paint another fourth layer using a brick texture to create a brick road on top of the sandy path:

We can continue adding layers until we have achieved the exact look we want for our terrain. All of these layers are painted on top of each other using what are called texture masks. Texture masks are usually created manually by an artist or they can be generated by using noise algorithms and other rendering techniques. For example the first texture mask used to place the second layer of dirt on top of the base layer looks like the following:

The white to black values represent the intensity or percentage amount that the new texture pixel should be added on top of the previous pixel.

The second texture mask that was used to paint the sandy path was the following:

And finally the third texture mask that was used to paint the brick road on top of the sandy path was the following:

For the purposes of efficiency we packed them all into a single texture. The first layer went to the red channel, the second layer to the green channel, and the third layer to the blue channel. It creates the following image when viewed all together:

An overhead view of the resulting terrain looks like the following:

When we sample the texture in the pixel shader it is simple to pull out just the red, green, blue, or alpha channel and then use that as the per-pixel texture mask for a specific layer of terrain. This is the same idea as the alpha blending tutorial. You can pack four texture masks into a single RGBA texture (or even more if you are using a texture atlas). For every four layers of terrain you need to send in one RGBA combined texture mask into the pixel shader. So if we had 12 terrain layers we would need three RGBA textures to send in all the required texture masks to the pixel shader.

In the above image you will also note that the terrain is smaller than the other tutorials. To be exact it is using just a 32x32 height map. The reason for this is that the entire terrain now needs to be split up into 32x32 sections. Each 32x32 section will have its own texture mask. The resolution of the texture mask used for this tutorial is 256x256. This size covers the 32x32 well enough without using too much texture memory. Going smaller sacrifices a lot of detail however.

Splitting the terrain into these sections and assigning each section its own texture mask also incorporates well into the quad tree structure. Just the nodes will now be a fixed size (such as 32x32) that correlates to the texture mask. Note also that the texture mask textures will require a second set of texture coordinates since we are sampling for the 32x32 section, not per quad.

On a side note you can use this method to create a color map for this section of the terrain. Then for level of detail optimizations you can switch to rendering just the color map for the 32x32 section if this section becomes distant enough from the camera.

To start the code section of the tutorial we will look at the modified terrain HLSL shader.


Terrain.fx

////////////////////////////////////////////////////////////////////////////////
// Filename: terrain.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
float3 lightDirection;

We have four color textures that will be used to render the terrain.

Texture2D colorTexture1;
Texture2D colorTexture2;
Texture2D colorTexture3;
Texture2D colorTexture4;

We have a single alpha texture which contains the 3 texture layer masks in the red, green, and blue channel.

Texture2D alphaTexture1;

We use two normal maps to improve the look of the terrain textures by applying bump map calculations.

Texture2D normalMap1;
Texture2D normalMap2;


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Clamp;
    AddressV = Clamp;
};


//////////////
// TYPEDEFS //
//////////////

The two input structures now have an additional texture coordinate member for the set of texture coordinates that are used to sample the texture layer masks out of the alpha texture.

struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
    float3 binormal : BINORMAL;
    float2 tex2 : TEXCOORD1;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
    float3 binormal : BINORMAL;
    float2 tex2 : TEXCOORD1;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType TerrainVertexShader(VertexInputType input)
{
    PixelInputType output;
    

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

Send in both sets of texture coordinates to the pixel shader.

    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    output.tex2 = input.tex2;
   
    // Calculate the normal vector against the world matrix only and then normalize the final value.
    output.normal = mul(input.normal, (float3x3)worldMatrix);
    output.normal = normalize(output.normal);

    // Calculate the tangent vector against the world matrix only and then normalize the final value.
    output.tangent = mul(input.tangent, (float3x3)worldMatrix);
    output.tangent = normalize(output.tangent);

    // Calculate the binormal vector against the world matrix only and then normalize the final value.
    output.binormal = mul(input.binormal, (float3x3)worldMatrix);
    output.binormal = normalize(output.binormal);

    return output;
}


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 TerrainPixelShader(PixelInputType input) : SV_Target
{
    float3 lightDir;
    float4 bumpMap;
    float3 bumpNormal;
    float lightIntensity1;
    float lightIntensity2;
    float4 textureColor1;
    float4 textureColor2;
    float4 textureColor3;
    float4 textureColor4;
    float4 alphaMap1;
    float4 baseColor;
    float4 color;

Start by calculating the two bump maps. We use the first bump map for the dirt and sand textures since they are fairly similar and can share a single normal map. We use the second bump map for the stone path since it is fairly distinct looking and requires its own unique normal map.

    // Invert the light direction for calculations.
    lightDir = -lightDirection;

    // Calculate the first bump map using the first normal map.
    bumpMap = normalMap1.Sample(SampleType, input.tex);
    bumpMap = (bumpMap * 2.0f) - 1.0f;
    bumpNormal = input.normal + bumpMap.x * input.tangent + bumpMap.y * input.binormal;
    bumpNormal = normalize(bumpNormal);
    lightIntensity1 = saturate(dot(bumpNormal, lightDir));

    // Calculate the second bump map using the second normal map.
    bumpMap = normalMap2.Sample(SampleType, input.tex);
    bumpMap = (bumpMap * 2.0f) - 1.0f;
    bumpNormal = input.normal + bumpMap.x * input.tangent + bumpMap.y * input.binormal;
    bumpNormal = normalize(bumpNormal);
    lightIntensity2 = saturate(dot(bumpNormal, lightDir));

Now sample the four textures that will be used for rendering the terrain.

    // Sample the color textures.
    textureColor1 = colorTexture1.Sample(SampleType, input.tex);
    textureColor2 = colorTexture2.Sample(SampleType, input.tex);
    textureColor3 = colorTexture3.Sample(SampleType, input.tex);
    textureColor4 = colorTexture4.Sample(SampleType, input.tex);

Combine the four textures with their bump maps.

    // Add the bump maps to their respective textures.
    textureColor1 = saturate(lightIntensity1 * textureColor1);
    textureColor2 = saturate(lightIntensity1 * textureColor2);
    textureColor3 = saturate(lightIntensity1 * textureColor3);
    textureColor4 = saturate(lightIntensity2 * textureColor4);

Now sample the alpha texture which contains the three texture mask layers in the red, green, and blue channel. Note that we use the second set of texture coordinates as it maps over the entire 32x32 section and not just the individual quads.

    // Sample the alpha map using second set of texture coordinates.
    alphaMap1 = alphaTexture1.Sample(SampleType, input.tex2);

Paint the first layer of terrain. In this case it is the dark brown dirt texture. This texture must cover 100% of the terrain since it is the base layer and we don't want any black holes in our terrain. It also saves us from using up a texture mask layer.

    // Set the base color to the first color texture.
    baseColor = textureColor1;

Paint the second layer of the terrain on top of the base layer using the second light brown dirt texture and the texture layer mask from the red channel in the alpha map. We use the lerp function to do a linear interpolation of the two pixels which gives us the adding (painting on top of) result we are looking for.

    // Add the second layer using the red channel of the alpha map.
    color = lerp(baseColor, textureColor2, alphaMap1.r);

Now add the third layer on top of the previous result. We use the sand texture and the texture layer mask from the green channel of the alpha map.

    // Add the third layer using the green channel of the alpha map.
    color = lerp(color, textureColor3, alphaMap1.g);

Finally add the fourth layer on top of the previous result using the stone texture and the texture layer mask from the blue channel of the alpha map.

    // Add the fourth layer using the blue channel of the alpha map.
    color = lerp(color, textureColor4, alphaMap1.b);
	
    return color;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 TerrainTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, TerrainVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, TerrainPixelShader()));
        SetGeometryShader(NULL);
    }
}

Terrainshaderclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: terrainshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINSHADERCLASS_H_
#define _TERRAINSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d10.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainShaderClass
////////////////////////////////////////////////////////////////////////////////
class TerrainShaderClass
{
public:
	TerrainShaderClass();
	TerrainShaderClass(const TerrainShaderClass&);
	~TerrainShaderClass();

	bool Initialize(ID3D10Device*, HWND);
	void Shutdown();
	void Render(ID3D10Device*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXVECTOR3, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*,
		    ID3D10ShaderResourceView*, ID3D10ShaderResourceView*);

private:
	bool InitializeShader(ID3D10Device*, HWND, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

	void SetShaderParameters(D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXVECTOR3, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*, ID3D10ShaderResourceView*,
				 ID3D10ShaderResourceView*, ID3D10ShaderResourceView*);
	void RenderShader(ID3D10Device*, int);

private:
	ID3D10Effect* m_effect;
	ID3D10EffectTechnique* m_technique;
	ID3D10InputLayout* m_layout;

	ID3D10EffectMatrixVariable* m_worldMatrixPtr;
	ID3D10EffectMatrixVariable* m_viewMatrixPtr;
	ID3D10EffectMatrixVariable* m_projectionMatrixPtr;

	ID3D10EffectVectorVariable* m_lightDirectionPtr;

We have added pointers for the four color textures, the alpha map texture which contains the texture layer masks, and the two normal maps.

	ID3D10EffectShaderResourceVariable* m_colorTexture1Ptr;
	ID3D10EffectShaderResourceVariable* m_colorTexture2Ptr;
	ID3D10EffectShaderResourceVariable* m_colorTexture3Ptr;
	ID3D10EffectShaderResourceVariable* m_colorTexture4Ptr;
	ID3D10EffectShaderResourceVariable* m_alphaTexture1Ptr;
	ID3D10EffectShaderResourceVariable* m_normalTexture1Ptr;
	ID3D10EffectShaderResourceVariable* m_normalTexture2Ptr;
};

#endif

Terrainshaderclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: terrainshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "terrainshaderclass.h"


TerrainShaderClass::TerrainShaderClass()
{
	m_effect = 0;
	m_technique = 0;
	m_layout = 0;

	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

	m_lightDirectionPtr = 0;
	m_colorTexture1Ptr = 0;
	m_colorTexture2Ptr = 0;
	m_colorTexture3Ptr = 0;
	m_colorTexture4Ptr = 0;
	m_alphaTexture1Ptr = 0;
	m_normalTexture1Ptr = 0;
	m_normalTexture2Ptr = 0;
}


TerrainShaderClass::TerrainShaderClass(const TerrainShaderClass& other)
{
}


TerrainShaderClass::~TerrainShaderClass()
{
}


bool TerrainShaderClass::Initialize(ID3D10Device* device, HWND hwnd)
{
	bool result;


	// Initialize the shader that will be used to render the terrain.
	result = InitializeShader(device, hwnd, L"../Engine/terrain.fx");
	if(!result)
	{
		return false;
	}

	return true;
}


void TerrainShaderClass::Shutdown()
{
	// Shutdown the shader effect.
	ShutdownShader();

	return;
}


void TerrainShaderClass::Render(ID3D10Device* device, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, D3DXVECTOR3 lightDirection, ID3D10ShaderResourceView* colorTexture1, 
				ID3D10ShaderResourceView* colorTexture2, ID3D10ShaderResourceView* colorTexture3, ID3D10ShaderResourceView* colorTexture4, ID3D10ShaderResourceView* alphaTexture1,
				ID3D10ShaderResourceView* normalMap1, ID3D10ShaderResourceView* normalMap2)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, lightDirection, colorTexture1, colorTexture2, colorTexture3, colorTexture4, alphaTexture1, normalMap1, normalMap2);

	// Now render the prepared buffers with the shader.
	RenderShader(device, indexCount);

	return;
}


bool TerrainShaderClass::InitializeShader(ID3D10Device* device, HWND hwnd, WCHAR* filename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	D3D10_INPUT_ELEMENT_DESC polygonLayout[6];
	unsigned int numElements;
	D3D10_PASS_DESC passDesc;


	// Initialize the error message.
	errorMessage = 0;

	// Load the shader in from the file.
	result = D3DX10CreateEffectFromFile(filename, NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, device, NULL, NULL, &m_effect, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, filename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, filename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Get a pointer to the technique inside the shader.
	m_technique = m_effect->GetTechniqueByName("TerrainTechnique");
	if(!m_technique)
	{
		return false;
	}

	// Now setup the layout of the data that goes into the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	polygonLayout[2].SemanticName = "NORMAL";
	polygonLayout[2].SemanticIndex = 0;
	polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[2].InputSlot = 0;
	polygonLayout[2].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[2].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[2].InstanceDataStepRate = 0;

	polygonLayout[3].SemanticName = "TANGENT";
	polygonLayout[3].SemanticIndex = 0;
	polygonLayout[3].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[3].InputSlot = 0;
	polygonLayout[3].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[3].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[3].InstanceDataStepRate = 0;

	polygonLayout[4].SemanticName = "BINORMAL";
	polygonLayout[4].SemanticIndex = 0;
	polygonLayout[4].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[4].InputSlot = 0;
	polygonLayout[4].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[4].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[4].InstanceDataStepRate = 0;

We have added a six member to the polygon layout for the second set of texture coordinates that are used for sampling the texture layer masks from the alpha texture. Remember to also set the SemanticIndex to one since this is the second TEXCOORD semantic in this layout.

	polygonLayout[5].SemanticName = "TEXCOORD";
	polygonLayout[5].SemanticIndex = 1;
	polygonLayout[5].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[5].InputSlot = 0;
	polygonLayout[5].AlignedByteOffset = D3D10_APPEND_ALIGNED_ELEMENT;
	polygonLayout[5].InputSlotClass = D3D10_INPUT_PER_VERTEX_DATA;
	polygonLayout[5].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Get the description of the first pass described in the shader technique.
	m_technique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &m_layout);
	if(FAILED(result))
	{
		MessageBox(hwnd, L"Could not create input layout.", L"Error", MB_OK);
		return false;
	}

	// Get pointers to the three matrices inside the shader so we can update them from this class.
	m_worldMatrixPtr = m_effect->GetVariableByName("worldMatrix")->AsMatrix();
	m_viewMatrixPtr = m_effect->GetVariableByName("viewMatrix")->AsMatrix();
	m_projectionMatrixPtr = m_effect->GetVariableByName("projectionMatrix")->AsMatrix();

	// Get pointers to the global resources inside the shader.
	m_lightDirectionPtr = m_effect->GetVariableByName("lightDirection")->AsVector();
	m_colorTexture1Ptr = m_effect->GetVariableByName("colorTexture1")->AsShaderResource();
	m_colorTexture2Ptr = m_effect->GetVariableByName("colorTexture2")->AsShaderResource();
	m_colorTexture3Ptr = m_effect->GetVariableByName("colorTexture3")->AsShaderResource();
	m_colorTexture4Ptr = m_effect->GetVariableByName("colorTexture4")->AsShaderResource();
	m_alphaTexture1Ptr = m_effect->GetVariableByName("alphaTexture1")->AsShaderResource();
	m_normalTexture1Ptr = m_effect->GetVariableByName("normalMap1")->AsShaderResource();
	m_normalTexture2Ptr = m_effect->GetVariableByName("normalMap2")->AsShaderResource();
	
	return true;
}


void TerrainShaderClass::ShutdownShader()
{
	// Release the pointers to the global resources in the shader file.
	m_lightDirectionPtr = 0;
	m_colorTexture1Ptr = 0;
	m_colorTexture2Ptr = 0;
	m_colorTexture3Ptr = 0;
	m_colorTexture4Ptr = 0;
	m_alphaTexture1Ptr = 0;
	m_normalTexture1Ptr = 0;
	m_normalTexture2Ptr = 0;

	// Release the pointers to the matrices inside the shader.
	m_worldMatrixPtr = 0;
	m_viewMatrixPtr = 0;
	m_projectionMatrixPtr = 0;

	// Release the pointer to the shader layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pointer to the shader technique.
	m_technique = 0;

	// Release the pointer to the shader.
	if(m_effect)
	{
		m_effect->Release();
		m_effect = 0;
	}

	return;
}


void TerrainShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}


void TerrainShaderClass::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, D3DXVECTOR3 lightDirection, ID3D10ShaderResourceView* colorTexture1, ID3D10ShaderResourceView* colorTexture2,
					     ID3D10ShaderResourceView* colorTexture3, ID3D10ShaderResourceView* colorTexture4, ID3D10ShaderResourceView* alphaTexture1, ID3D10ShaderResourceView* normalMap1,
					     ID3D10ShaderResourceView* normalMap2)
{
	// Set the world matrix variable inside the shader.
	m_worldMatrixPtr->SetMatrix((float*)&worldMatrix);

	// Set the view matrix variable inside the shader.
	m_viewMatrixPtr->SetMatrix((float*)&viewMatrix);

	// Set the projection matrix variable inside the shader.
	m_projectionMatrixPtr->SetMatrix((float*)&projectionMatrix);

	// Set the global values inside the shader.
	m_lightDirectionPtr->SetFloatVector((float*)&lightDirection);

	// Bind the textures.
	m_colorTexture1Ptr->SetResource(colorTexture1);
	m_colorTexture2Ptr->SetResource(colorTexture2);
	m_colorTexture3Ptr->SetResource(colorTexture3);
	m_colorTexture4Ptr->SetResource(colorTexture4);
	m_alphaTexture1Ptr->SetResource(alphaTexture1);
	m_normalTexture1Ptr->SetResource(normalMap1);
	m_normalTexture2Ptr->SetResource(normalMap2);

	return;
}


void TerrainShaderClass::RenderShader(ID3D10Device* device, int indexCount)
{
	D3D10_TECHNIQUE_DESC techniqueDesc;
	unsigned int i;
	

	// Set the input layout.
	device->IASetInputLayout(m_layout);

	// Get the description structure of the technique from inside the shader so it can be used for rendering.
	m_technique->GetDesc(&techniqueDesc);

	// Go through each pass in the technique (should be just one currently) and render the triangles.
	for(i=0; i<techniqueDesc.Passes; ++i)
	{
		m_technique->GetPassByIndex(i)->Apply(0);
		device->DrawIndexed(indexCount, 0, 0);
	}

	return;
}

Terrainclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: terrainclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINCLASS_H_
#define _TERRAINCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d10.h>
#include <d3dx10math.h>
#include <stdio.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainClass
////////////////////////////////////////////////////////////////////////////////
class TerrainClass
{
private:
	struct HeightMapType 
	{ 
		float x, y, z;
		float nx, ny, nz;
	};

	struct VectorType 
	{ 
		float x, y, z;
	};

We have added a second set of texture coordinates that are used for sampling the texture layer masks from the alpha texture.

	struct ModelType 
	{ 
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
		float tx, ty, tz;
		float bx, by, bz;
		float tu2, tv2;
	};

	struct TempVertexType
	{
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
	};

The second set of texture coordinates is also added to the VertexType.

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
		D3DXVECTOR3 normal;
		D3DXVECTOR3 tangent;
		D3DXVECTOR3 binormal;
		D3DXVECTOR2 texture2;
	};
	
public:
	TerrainClass();
	TerrainClass(const TerrainClass&);
	~TerrainClass();

	bool Initialize(ID3D10Device*, char*, float);
	void Shutdown();
	void Render(ID3D10Device*);

	int GetIndexCount();

private:
	bool LoadHeightMap(char*);
	void ReduceHeightMap(float);
	bool CalculateNormals();
	bool BuildModel();
	void CalculateTerrainVectors();
	void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&);
	bool InitializeBuffers(ID3D10Device*);

	void ReleaseHeightMap();
	void ReleaseModel();
	void ReleaseBuffers();

	void RenderBuffers(ID3D10Device*);

private:
	int m_terrainWidth, m_terrainHeight, m_vertexCount, m_indexCount;
	HeightMapType* m_heightMap;
	ModelType* m_model;
	ID3D10Buffer *m_vertexBuffer, *m_indexBuffer;
};

#endif

Terrainclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: terrainclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "terrainclass.h"


TerrainClass::TerrainClass()
{
	m_heightMap = 0;
	m_model = 0;
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
}


TerrainClass::TerrainClass(const TerrainClass& other)
{
}


TerrainClass::~TerrainClass()
{
}


bool TerrainClass::Initialize(ID3D10Device* device, char* heightMapFilename, float maximumHeight)
{
	bool result;


	// Load in the height map for the terrain.
	result = LoadHeightMap(heightMapFilename);
	if(!result)
	{
		return false;
	}

	// Reduce the height of the height map.
	ReduceHeightMap(maximumHeight);

	// Calculate the normals for the terrain data.
	result = CalculateNormals();
	if(!result)
	{
		return false;
	}

	// Construct a 3D model from the height map and normal data.
	result = BuildModel();
	if(!result)
	{
		return false;
	}

	// Calculate the tangent and binormal.
	CalculateTerrainVectors();

	// Initialize the vertex and index buffer that hold the geometry for the terrain.
	result = InitializeBuffers(device);
	if(!result)
	{
		return false;
	}

	// Release the height map and the model since the data is now loaded into the vertex and index buffers.
	ReleaseHeightMap();
	ReleaseModel();

	return true;
}


void TerrainClass::Shutdown()
{
	// Release the buffers.
	ReleaseBuffers();

	// Release the model.
	ReleaseModel();

	// Release the height map.
	ReleaseHeightMap();

	return;
}


void TerrainClass::Render(ID3D10Device* device)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(device);

	return;
}


int TerrainClass::GetIndexCount()
{
	return m_indexCount;
}


bool TerrainClass::LoadHeightMap(char* filename)
{
	FILE* filePtr;
	int error;
	unsigned int count;
	BITMAPFILEHEADER bitmapFileHeader;
	BITMAPINFOHEADER bitmapInfoHeader;
	int imageSize, i, j, k, index;
	unsigned char* bitmapImage;
	unsigned char height;


	// Open the height map file in binary.
	error = fopen_s(&filePtr, filename, "rb");
	if(error != 0)
	{
		return false;
	}

	// Read in the file header.
	count = fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
	if(count != 1)
	{
		return false;
	}

	// Read in the bitmap info header.
	count = fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
	if(count != 1)
	{
		return false;
	}

	// Save the dimensions of the terrain.
	m_terrainWidth = bitmapInfoHeader.biWidth;
	m_terrainHeight = bitmapInfoHeader.biHeight;

	// Calculate the size of the bitmap image data.
	imageSize = m_terrainWidth * m_terrainHeight * 3;

	// Allocate memory for the bitmap image data.
	bitmapImage = new unsigned char[imageSize];
	if(!bitmapImage)
	{
		return false;
	}

	// Move to the beginning of the bitmap data.
	fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);

	// Read in the bitmap image data.
	count = fread(bitmapImage, 1, imageSize, filePtr);
	if(count != imageSize)
	{
		return false;
	}

	// Close the file.
	error = fclose(filePtr);
	if(error != 0)
	{
		return false;
	}

	// Create the structure to hold the height map data.
	m_heightMap = new HeightMapType[m_terrainWidth * m_terrainHeight];
	if(!m_heightMap)
	{
		return false;
	}

	// Initialize the position in the image data buffer.
	k=0;

	// Read the image data into the height map.
	for(j=0; j<m_terrainHeight; j++)
	{
		for(i=0; i<m_terrainWidth; i++)
		{
			height = bitmapImage[k];
			
			index = (m_terrainWidth * j) + i;

			m_heightMap[index].x = (float)i;
			m_heightMap[index].y = (float)height;
			m_heightMap[index].z = (float)j;

			k+=3;
		}
	}

	// Release the bitmap image data.
	delete [] bitmapImage;
	bitmapImage = 0;

	return true;
}


void TerrainClass::ReduceHeightMap(float value)
{
	int i, j;


	for(j=0; j<m_terrainHeight; j++)
	{
		for(i=0; i<m_terrainWidth; i++)
		{
			m_heightMap[(m_terrainWidth * j) + i].y /= value;
		}
	}

	return;
}


bool TerrainClass::CalculateNormals()
{
	int i, j, index1, index2, index3, index, count;
	float vertex1[3], vertex2[3], vertex3[3], vector1[3], vector2[3], sum[3], length;
	VectorType* normals;


	// Create a temporary array to hold the un-normalized normal vectors.
	normals = new VectorType[(m_terrainHeight-1) * (m_terrainWidth-1)];
	if(!normals)
	{
		return false;
	}

	// Go through all the faces in the mesh and calculate their normals.
	for(j=0; j<(m_terrainHeight-1); j++)
	{
		for(i=0; i<(m_terrainWidth-1); i++)
		{
			index1 = (j * m_terrainWidth) + i;
			index2 = (j * m_terrainWidth) + (i+1);
			index3 = ((j+1) * m_terrainWidth) + i;

			// Get three vertices from the face.
			vertex1[0] = m_heightMap[index1].x;
			vertex1[1] = m_heightMap[index1].y;
			vertex1[2] = m_heightMap[index1].z;
		
			vertex2[0] = m_heightMap[index2].x;
			vertex2[1] = m_heightMap[index2].y;
			vertex2[2] = m_heightMap[index2].z;
		
			vertex3[0] = m_heightMap[index3].x;
			vertex3[1] = m_heightMap[index3].y;
			vertex3[2] = m_heightMap[index3].z;

			// Calculate the two vectors for this face.
			vector1[0] = vertex1[0] - vertex3[0];
			vector1[1] = vertex1[1] - vertex3[1];
			vector1[2] = vertex1[2] - vertex3[2];
			vector2[0] = vertex3[0] - vertex2[0];
			vector2[1] = vertex3[1] - vertex2[1];
			vector2[2] = vertex3[2] - vertex2[2];

			index = (j * (m_terrainWidth-1)) + i;

			// Calculate the cross product of those two vectors to get the un-normalized value for this face normal.
			normals[index].x = (vector1[1] * vector2[2]) - (vector1[2] * vector2[1]);
			normals[index].y = (vector1[2] * vector2[0]) - (vector1[0] * vector2[2]);
			normals[index].z = (vector1[0] * vector2[1]) - (vector1[1] * vector2[0]);
		}
	}

	// Now go through all the vertices and take an average of each face normal that the vertex touches to get the averaged normal for that vertex.
	for(j=0; j<m_terrainHeight; j++)
	{
		for(i=0; i<m_terrainWidth; i++)
		{
			// Initialize the sum.
			sum[0] = 0.0f;
			sum[1] = 0.0f;
			sum[2] = 0.0f;

			// Initialize the count.
			count = 0;

			// Bottom left face.
			if(((i-1) >= 0) && ((j-1) >= 0))
			{
				index = ((j-1) * (m_terrainWidth-1)) + (i-1);

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}

			// Bottom right face.
			if((i < (m_terrainWidth-1)) && ((j-1) >= 0))
			{
				index = ((j-1) * (m_terrainWidth-1)) + i;

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}

			// Upper left face.
			if(((i-1) >= 0) && (j < (m_terrainHeight-1)))
			{
				index = (j * (m_terrainWidth-1)) + (i-1);

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}

			// Upper right face.
			if((i < (m_terrainWidth-1)) && (j < (m_terrainHeight-1)))
			{
				index = (j * (m_terrainWidth-1)) + i;

				sum[0] += normals[index].x;
				sum[1] += normals[index].y;
				sum[2] += normals[index].z;
				count++;
			}
			
			// Take the average of the faces touching this vertex.
			sum[0] = (sum[0] / (float)count);
			sum[1] = (sum[1] / (float)count);
			sum[2] = (sum[2] / (float)count);

			// Calculate the length of this normal.
			length = sqrt((sum[0] * sum[0]) + (sum[1] * sum[1]) + (sum[2] * sum[2]));
			
			// Get an index to the vertex location in the height map array.
			index = (j * m_terrainWidth) + i;

			// Normalize the final shared normal for this vertex and store it in the height map array.
			m_heightMap[index].nx = (sum[0] / length);
			m_heightMap[index].ny = (sum[1] / length);
			m_heightMap[index].nz = (sum[2] / length);
		}
	}

	// Release the temporary normals.
	delete [] normals;
	normals = 0;

	return true;
}

In the BuildModel function we now create a second set of texture coordinates for sampling the texture layer masks since they cover the entire 32x32 section and not just the individual quads. So in the loop that builds the model we need to divide the number of quads (31 quads since it is made of 32 height points) by one and use that as an increment value as we move along the width and height of the terrain section.

bool TerrainClass::BuildModel()
{
	int i, j, index, index1, index2, index3, index4;
	float incrementSize, tu2Left, tu2Right, tv2Top, tv2Bottom;


	// Set the number of vertices in the model.
	m_vertexCount = (m_terrainWidth - 1) * (m_terrainHeight - 1) * 6;

	// Create the terrain model array.
	m_model = new ModelType[m_vertexCount];
	if(!m_model)
	{
		return false;
	}

	// Setup the increment size for the second set of textures (alpha map).
	incrementSize = 1.0f / 31.0f;

	// Initialize the texture increments.
	tu2Left = 0.0f;
	tu2Right = incrementSize;
	tv2Bottom = 1.0f;
	tv2Top = 1.0f - incrementSize;
	
	// Load the terrain model with the height map terrain data.
	index = 0;

	for(j=0; j<(m_terrainHeight-1); j++)
	{
		for(i=0; i<(m_terrainWidth-1); i++)
		{
			index1 = (m_terrainWidth * j) + i;          // Bottom left.
			index2 = (m_terrainWidth * j) + (i+1);      // Bottom right.
			index3 = (m_terrainWidth * (j+1)) + i;      // Upper left.
			index4 = (m_terrainWidth * (j+1)) + (i+1);  // Upper right.

			// Upper left.
			m_model[index].x  = m_heightMap[index3].x;
			m_model[index].y  = m_heightMap[index3].y;
			m_model[index].z  = m_heightMap[index3].z;
			m_model[index].nx = m_heightMap[index3].nx;
			m_model[index].ny = m_heightMap[index3].ny;
			m_model[index].nz = m_heightMap[index3].nz;
			m_model[index].tu = 0.0f;
			m_model[index].tv = 0.0f;
			m_model[index].tu2 = tu2Left;
			m_model[index].tv2 = tv2Top;
			index++;

			// Upper right.
			m_model[index].x  = m_heightMap[index4].x;
			m_model[index].y  = m_heightMap[index4].y;
			m_model[index].z  = m_heightMap[index4].z;
			m_model[index].nx = m_heightMap[index4].nx;
			m_model[index].ny = m_heightMap[index4].ny;
			m_model[index].nz = m_heightMap[index4].nz;
			m_model[index].tu = 1.0f;
			m_model[index].tv = 0.0f;
			m_model[index].tu2 = tu2Right;
			m_model[index].tv2 = tv2Top;
			index++;

			// Bottom left.
			m_model[index].x  = m_heightMap[index1].x;
			m_model[index].y  = m_heightMap[index1].y;
			m_model[index].z  = m_heightMap[index1].z;
			m_model[index].nx = m_heightMap[index1].nx;
			m_model[index].ny = m_heightMap[index1].ny;
			m_model[index].nz = m_heightMap[index1].nz;
			m_model[index].tu = 0.0f;
			m_model[index].tv = 1.0f;
			m_model[index].tu2 = tu2Left;
			m_model[index].tv2 = tv2Bottom;
			index++;

			// Bottom left.
			m_model[index].x  = m_heightMap[index1].x;
			m_model[index].y  = m_heightMap[index1].y;
			m_model[index].z  = m_heightMap[index1].z;
			m_model[index].nx = m_heightMap[index1].nx;
			m_model[index].ny = m_heightMap[index1].ny;
			m_model[index].nz = m_heightMap[index1].nz;
			m_model[index].tu = 0.0f;
			m_model[index].tv = 1.0f;
			m_model[index].tu2 = tu2Left;
			m_model[index].tv2 = tv2Bottom;
			index++;

			// Upper right.
			m_model[index].x  = m_heightMap[index4].x;
			m_model[index].y  = m_heightMap[index4].y;
			m_model[index].z  = m_heightMap[index4].z;
			m_model[index].nx = m_heightMap[index4].nx;
			m_model[index].ny = m_heightMap[index4].ny;
			m_model[index].nz = m_heightMap[index4].nz;
			m_model[index].tu = 1.0f;
			m_model[index].tv = 0.0f;
			m_model[index].tu2 = tu2Right;
			m_model[index].tv2 = tv2Top;
			index++;

			// Bottom right.
			m_model[index].x  = m_heightMap[index2].x;
			m_model[index].y  = m_heightMap[index2].y;
			m_model[index].z  = m_heightMap[index2].z;
			m_model[index].nx = m_heightMap[index2].nx;
			m_model[index].ny = m_heightMap[index2].ny;
			m_model[index].nz = m_heightMap[index2].nz;
			m_model[index].tu = 1.0f;
			m_model[index].tv = 1.0f;
			m_model[index].tu2 = tu2Right;
			m_model[index].tv2 = tv2Bottom;
			index++;

			// Increment the tu texture coords for the alpha map.
			tu2Left += incrementSize;
			tu2Right += incrementSize;
		}

		// Reset the tu texture coordinate increments for the alpha map.
		tu2Left = 0.0f;
		tu2Right = incrementSize;

		// Increment the tv texture coords for the alpha map.
		tv2Top -= incrementSize;
		tv2Bottom -= incrementSize;
	}

	return true;
}


void TerrainClass::CalculateTerrainVectors()
{
	int faceCount, i, index;
	TempVertexType vertex1, vertex2, vertex3;
	VectorType tangent, binormal;


	// Calculate the number of faces in the terrain model.
	faceCount = m_vertexCount / 3;

	// Initialize the index to the model data.
	index = 0;

	// Go through all the faces and calculate the the tangent, binormal, and normal vectors.
	for(i=0; i<faceCount; i++)
	{
		// Get the three vertices for this face from the terrain model.
		vertex1.x  = m_model[index].x;
		vertex1.y  = m_model[index].y;
		vertex1.z  = m_model[index].z;
		vertex1.tu = m_model[index].tu;
		vertex1.tv = m_model[index].tv;
		vertex1.nx = m_model[index].nx;
		vertex1.ny = m_model[index].ny;
		vertex1.nz = m_model[index].nz;
		index++;

		vertex2.x  = m_model[index].x;
		vertex2.y  = m_model[index].y;
		vertex2.z  = m_model[index].z;
		vertex2.tu = m_model[index].tu;
		vertex2.tv = m_model[index].tv;
		vertex2.nx = m_model[index].nx;
		vertex2.ny = m_model[index].ny;
		vertex2.nz = m_model[index].nz;
		index++;

		vertex3.x  = m_model[index].x;
		vertex3.y  = m_model[index].y;
		vertex3.z  = m_model[index].z;
		vertex3.tu = m_model[index].tu;
		vertex3.tv = m_model[index].tv;
		vertex3.nx = m_model[index].nx;
		vertex3.ny = m_model[index].ny;
		vertex3.nz = m_model[index].nz;
		index++;

		// Calculate the tangent and binormal of that face.
		CalculateTangentBinormal(vertex1, vertex2, vertex3, tangent, binormal);

		// Store the tangent and binormal for this face back in the model structure.
		m_model[index-1].tx = tangent.x;
		m_model[index-1].ty = tangent.y;
		m_model[index-1].tz = tangent.z;
		m_model[index-1].bx = binormal.x;
		m_model[index-1].by = binormal.y;
		m_model[index-1].bz = binormal.z;

		m_model[index-2].tx = tangent.x;
		m_model[index-2].ty = tangent.y;
		m_model[index-2].tz = tangent.z;
		m_model[index-2].bx = binormal.x;
		m_model[index-2].by = binormal.y;
		m_model[index-2].bz = binormal.z;

		m_model[index-3].tx = tangent.x;
		m_model[index-3].ty = tangent.y;
		m_model[index-3].tz = tangent.z;
		m_model[index-3].bx = binormal.x;
		m_model[index-3].by = binormal.y;
		m_model[index-3].bz = binormal.z;
	}

	return;
}


void TerrainClass::CalculateTangentBinormal(TempVertexType vertex1, TempVertexType vertex2, TempVertexType vertex3, VectorType& tangent, VectorType& binormal)
{
	float vector1[3], vector2[3];
	float tuVector[2], tvVector[2];
	float den;
	float length;


	// Calculate the two vectors for this face.
	vector1[0] = vertex2.x - vertex1.x;
	vector1[1] = vertex2.y - vertex1.y;
	vector1[2] = vertex2.z - vertex1.z;

	vector2[0] = vertex3.x - vertex1.x;
	vector2[1] = vertex3.y - vertex1.y;
	vector2[2] = vertex3.z - vertex1.z;

	// Calculate the tu and tv texture space vectors.
	tuVector[0] = vertex2.tu - vertex1.tu;
	tvVector[0] = vertex2.tv - vertex1.tv;

	tuVector[1] = vertex3.tu - vertex1.tu;
	tvVector[1] = vertex3.tv - vertex1.tv;

	// Calculate the denominator of the tangent/binormal equation.
	den = 1.0f / (tuVector[0] * tvVector[1] - tuVector[1] * tvVector[0]);

	// Calculate the cross products and multiply by the coefficient to get the tangent and binormal.
	tangent.x = (tvVector[1] * vector1[0] - tvVector[0] * vector2[0]) * den;
	tangent.y = (tvVector[1] * vector1[1] - tvVector[0] * vector2[1]) * den;
	tangent.z = (tvVector[1] * vector1[2] - tvVector[0] * vector2[2]) * den;

	binormal.x = (tuVector[0] * vector2[0] - tuVector[1] * vector1[0]) * den;
	binormal.y = (tuVector[0] * vector2[1] - tuVector[1] * vector1[1]) * den;
	binormal.z = (tuVector[0] * vector2[2] - tuVector[1] * vector1[2]) * den;

	// Calculate the length of this normal.
	length = sqrt((tangent.x * tangent.x) + (tangent.y * tangent.y) + (tangent.z * tangent.z));
			
	// Normalize the normal and then store it
	tangent.x = tangent.x / length;
	tangent.y = tangent.y / length;
	tangent.z = tangent.z / length;

	// Calculate the length of this normal.
	length = sqrt((binormal.x * binormal.x) + (binormal.y * binormal.y) + (binormal.z * binormal.z));
			
	// Normalize the normal and then store it
	binormal.x = binormal.x / length;
	binormal.y = binormal.y / length;
	binormal.z = binormal.z / length;

	return;
}


bool TerrainClass::InitializeBuffers(ID3D10Device* device)
{
	VertexType* vertices;
	unsigned long* indices;
	D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
	D3D10_SUBRESOURCE_DATA vertexData, indexData;
	HRESULT result;
	int i;


	// Create the vertex array.
	vertices = new VertexType[m_vertexCount];
	if(!vertices)
	{
		return false;
	}

	// Set the index count to the same as the vertex count.
	m_indexCount = m_vertexCount;

	// Create the index array.
	indices = new unsigned long[m_indexCount];
	if(!indices)
	{
		return false;
	}

The second set of texture coordinates has been added to the creation of the vertex array.

	// Load the vertex array and index array with data.
	for(i=0; i<m_vertexCount; i++)
	{
		vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z);
		vertices[i].texture  = D3DXVECTOR2(m_model[i].tu, m_model[i].tv);
		vertices[i].normal   = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz);
		vertices[i].tangent  = D3DXVECTOR3(m_model[i].tx, m_model[i].ty, m_model[i].tz);
		vertices[i].binormal = D3DXVECTOR3(m_model[i].bx, m_model[i].by, m_model[i].bz);
		vertices[i].texture2 = D3DXVECTOR2(m_model[i].tu2, m_model[i].tv2);

		indices[i] = i;
	}

	// Set up the description of the vertex buffer.
	vertexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
	vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = 0;
	vertexBufferDesc.MiscFlags = 0;

	// Give the subresource structure a pointer to the vertex data.
	vertexData.pSysMem = vertices;

	// Now finally create the vertex buffer.
	result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Set up the description of the index buffer.
	indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
	indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;

	// Give the subresource structure a pointer to the index data.
	indexData.pSysMem = indices;

	// Create the index buffer.
	result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Release the arrays now that the vertex and index buffers have been created and loaded.
	delete [] vertices;
	vertices = 0;

	delete [] indices;
	indices = 0;

	return true;
}


void TerrainClass::ReleaseHeightMap()
{
	if(m_heightMap)
	{
		delete [] m_heightMap;
		m_heightMap = 0;
	}

	return;
}


void TerrainClass::ReleaseModel()
{
	if(m_model)
	{
		delete [] m_model;
		m_model = 0;
	}

	return;
}


void TerrainClass::ReleaseBuffers()
{
	// Release the index buffer.
	if(m_indexBuffer)
	{
		m_indexBuffer->Release();
		m_indexBuffer = 0;
	}

	// Release the vertex buffer.
	if(m_vertexBuffer)
	{
		m_vertexBuffer->Release();
		m_vertexBuffer = 0;
	}

	return;
}


void TerrainClass::RenderBuffers(ID3D10Device* device)
{
	unsigned int stride;
	unsigned int offset;


	// Set vertex buffer stride and offset.
	stride = sizeof(VertexType); 
	offset = 0;
    
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	device->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	device->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
	device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	return;
}

Applicationclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: applicationclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _APPLICATIONCLASS_H_
#define _APPLICATIONCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "d3dclass.h"
#include "timerclass.h"
#include "positionclass.h"
#include "cameraclass.h"
#include "lightclass.h"
#include "terrainclass.h"
#include "terrainshaderclass.h"
#include "textureclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ApplicationClass
////////////////////////////////////////////////////////////////////////////////
class ApplicationClass
{
public:
	ApplicationClass();
	ApplicationClass(const ApplicationClass&);
	~ApplicationClass();

	bool Initialize(HINSTANCE, HWND, int, int);
	void Shutdown();
	bool Frame();

private:
	void HandleMovementInput();
	void Render();

private:
	InputClass* m_Input;
	D3DClass* m_D3D;
	TimerClass* m_Timer;
	PositionClass* m_Position;
	CameraClass* m_Camera;
	LightClass* m_Light;
	TerrainClass* m_Terrain;
	TerrainShaderClass* m_TerrainShader;

We have a number of new textures for the terrain rendering. Ideally these should instead be inside a texture manager object and then referenced by index. But for the purposes of keeping the tutorial simple I have just placed them all here.

	TextureClass *m_ColorTexture1;
	TextureClass *m_ColorTexture2;
	TextureClass *m_ColorTexture3;
	TextureClass *m_ColorTexture4;
	TextureClass *m_AlphaTexture1;
	TextureClass *m_NormalTexture1;
	TextureClass *m_NormalTexture2;
};

#endif

Applicationclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: applicationclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "applicationclass.h"


ApplicationClass::ApplicationClass()
{
	m_Input = 0;
	m_D3D = 0;
	m_Timer = 0;
	m_Position = 0;
	m_Camera = 0;
	m_Light = 0;
	m_Terrain = 0;
	m_TerrainShader = 0;
	m_ColorTexture1 = 0;
	m_ColorTexture2 = 0;
	m_ColorTexture3 = 0;
	m_ColorTexture4 = 0;
	m_AlphaTexture1 = 0;
	m_NormalTexture1 = 0;
	m_NormalTexture2 = 0;
}


ApplicationClass::ApplicationClass(const ApplicationClass& other)
{
}


ApplicationClass::~ApplicationClass()
{
}


bool ApplicationClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight)
{
	bool result;

	
	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(hinstance, hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
		return false;
	}

	// Create the timer object.
	m_Timer = new TimerClass;
	if(!m_Timer)
	{
		return false;
	}

	// Initialize the timer object.
	result = m_Timer->Initialize();
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the timer object.", L"Error", MB_OK);
		return false;
	}

	// Initialize the position object.
	m_Position = new PositionClass;
	if(!m_Position)
	{
		return false;
	}

	// Set the initial position.
	m_Position->SetPosition(14.0f, 13.0f, 10.0f);
	m_Position->SetRotation(25.0f, 0.0f, 0.0f);

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Create the light object.
	m_Light = new LightClass;
	if(!m_Light)
	{
		return false;
	}

	// Initialize the light object.
	m_Light->SetDirection(0.5f, -0.75f, 0.0f);

	// Create the terrain object.
	m_Terrain = new TerrainClass;
	if(!m_Terrain)
	{
		return false;
	}

	// Initialize the terrain object.
	result = m_Terrain->Initialize(m_D3D->GetDevice(), "../Engine/data/hm.bmp", 10.0f);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the terrain object.", L"Error", MB_OK);
		return false;
	}

	// Create the terrain shader object.
	m_TerrainShader = new TerrainShaderClass;
	if(!m_TerrainShader)
	{
		return false;
	}

	// Initialize the terrain shader object.
	result = m_TerrainShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the terrain shader object.", L"Error", MB_OK);
		return false;
	}

Load the four color textures.

	// Create the first color texture object.
	m_ColorTexture1 = new TextureClass;
	if(!m_ColorTexture1)
	{
		return false;
	}

	// Load the first color texture object.
	result = m_ColorTexture1->Initialize(m_D3D->GetDevice(), L"../Engine/data/dirt001.dds");
	if(!result)
	{
		return false;
	}

	// Create the second color texture object.
	m_ColorTexture2 = new TextureClass;
	if(!m_ColorTexture2)
	{
		return false;
	}

	// Load the second color texture object.
	result = m_ColorTexture2->Initialize(m_D3D->GetDevice(), L"../Engine/data/dirt004.dds");
	if(!result)
	{
		return false;
	}

	// Create the third color texture object.
	m_ColorTexture3 = new TextureClass;
	if(!m_ColorTexture3)
	{
		return false;
	}

	// Load the third color texture object.
	result = m_ColorTexture3->Initialize(m_D3D->GetDevice(), L"../Engine/data/dirt002.dds");
	if(!result)
	{
		return false;
	}

	// Create the fourth color texture object.
	m_ColorTexture4 = new TextureClass;
	if(!m_ColorTexture4)
	{
		return false;
	}

	// Load the fourth color texture object.
	result = m_ColorTexture4->Initialize(m_D3D->GetDevice(), L"../Engine/data/stone001.dds");
	if(!result)
	{
		return false;
	}

Load the texture layer masks that are inside the alpha001.dds texture.

	// Create the first alpha texture object.
	m_AlphaTexture1 = new TextureClass;
	if(!m_AlphaTexture1)
	{
		return false;
	}

	// Load the first alpha texture object.
	result = m_AlphaTexture1->Initialize(m_D3D->GetDevice(), L"../Engine/data/alpha001.dds");
	if(!result)
	{
		return false;
	}

Load the two bump maps.

	// Create the first normal texture object.
	m_NormalTexture1 = new TextureClass;
	if(!m_NormalTexture1)
	{
		return false;
	}

	// Load the first alpha texture object.
	result = m_NormalTexture1->Initialize(m_D3D->GetDevice(), L"../Engine/data/normal001.dds");
	if(!result)
	{
		return false;
	}

	// Create the second normal texture object.
	m_NormalTexture2 = new TextureClass;
	if(!m_NormalTexture2)
	{
		return false;
	}

	// Load the second alpha texture object.
	result = m_NormalTexture2->Initialize(m_D3D->GetDevice(), L"../Engine/data/normal002.dds");
	if(!result)
	{
		return false;
	}

	return true;
}


void ApplicationClass::Shutdown()
{
	// Release the texture objects.
	if(m_ColorTexture1)
	{
		m_ColorTexture1->Shutdown();
		delete m_ColorTexture1;
		m_ColorTexture1 = 0;
	}

	if(m_ColorTexture2)
	{
		m_ColorTexture2->Shutdown();
		delete m_ColorTexture2;
		m_ColorTexture2 = 0;
	}

	if(m_ColorTexture3)
	{
		m_ColorTexture3->Shutdown();
		delete m_ColorTexture3;
		m_ColorTexture3 = 0;
	}

	if(m_ColorTexture4)
	{
		m_ColorTexture4->Shutdown();
		delete m_ColorTexture4;
		m_ColorTexture4 = 0;
	}

	if(m_AlphaTexture1)
	{
		m_AlphaTexture1->Shutdown();
		delete m_AlphaTexture1;
		m_AlphaTexture1 = 0;
	}

	if(m_NormalTexture1)
	{
		m_NormalTexture1->Shutdown();
		delete m_NormalTexture1;
		m_NormalTexture1 = 0;
	}

	if(m_NormalTexture2)
	{
		m_NormalTexture2->Shutdown();
		delete m_NormalTexture2;
		m_NormalTexture2 = 0;
	}

	// Release the terrain shader object.
	if(m_TerrainShader)
	{
		m_TerrainShader->Shutdown();
		delete m_TerrainShader;
		m_TerrainShader = 0;
	}

	// Release the terrain object.
	if(m_Terrain)
	{
		m_Terrain->Shutdown();
		delete m_Terrain;
		m_Terrain = 0;
	}

	// Release the light object.
	if(m_Light)
	{
		delete m_Light;
		m_Light = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the position object.
	if(m_Position)
	{
		delete m_Position;
		m_Position = 0;
	}

	// Release the timer object.
	if(m_Timer)
	{
		delete m_Timer;
		m_Timer = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	return;
}


bool ApplicationClass::Frame()
{
	bool result;


	// Update the system stats.
	m_Timer->Frame();

	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}
	
	// Check if the user pressed escape and wants to exit the application.
	if(m_Input->IsEscapePressed() == true)
	{
		return false;
	}

	// Update the movement of the camera location based on the user input.
	HandleMovementInput();

	// Render the graphics.
	Render();

	return true;
}


void ApplicationClass::HandleMovementInput()
{
	bool keyDown;
	float posX, posY, posZ, rotX, rotY, rotZ;


	// Set the frame time for calculating the updated position.
	m_Position->SetFrameTime(m_Timer->GetTime());

	// Handle the input.
	keyDown = m_Input->IsLeftPressed();
	m_Position->TurnLeft(keyDown);

	keyDown = m_Input->IsRightPressed();
	m_Position->TurnRight(keyDown);

	keyDown = m_Input->IsUpPressed();
	m_Position->MoveForward(keyDown);

	keyDown = m_Input->IsDownPressed();
	m_Position->MoveBackward(keyDown);

	keyDown = m_Input->IsAPressed();
	m_Position->MoveUpward(keyDown);

	keyDown = m_Input->IsZPressed();
	m_Position->MoveDownward(keyDown);

	keyDown = m_Input->IsPgUpPressed();
	m_Position->LookUpward(keyDown);

	keyDown = m_Input->IsPgDownPressed();
	m_Position->LookDownward(keyDown);

	// Get the view point position/rotation.
	m_Position->GetPosition(posX, posY, posZ);
	m_Position->GetRotation(rotX, rotY, rotZ);

	// Set the position of the camera.
	m_Camera->SetPosition(posX, posY, posZ);
	m_Camera->SetRotation(rotX, rotY, rotZ);

	return;
}


void ApplicationClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;


	// Clear the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the matrices from the camera and d3d objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);

	// Render the terrain using the terrain shader.
	m_Terrain->Render(m_D3D->GetDevice());

Render the terrain using the four color textures, the texture layer masks, and the normal maps.

	m_TerrainShader->Render(m_D3D->GetDevice(), m_Terrain->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Light->GetDirection(), m_ColorTexture1->GetTexture(), m_ColorTexture2->GetTexture(), 
				m_ColorTexture3->GetTexture(), m_ColorTexture4->GetTexture(), m_AlphaTexture1->GetTexture(), m_NormalTexture1->GetTexture(), m_NormalTexture2->GetTexture());

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return;
}

Summary

We now have a multi-layered terrain in which the layer rendering is controlled on a per-pixel basis.


To Do Exercises

1. Compile and run the program. Move around using the arrow keys, A, Z, and PgUp and PgDn. Press escape to quit.

2. Modify the red, green, and blue channel in the alpha001.dds texture to see the effect on the terrain.

3. Add a fifth layer to the terrain. Place the new texture mask layer in the alpha channel of the alpha001.dds texture.

4. Create a different terrain scene by creating your own texture layer mask and terrain textures.

5. Try different resolutions for the texture layer mask to see the difference it produces when rendering the multiple layers together.

6. Modify the terrain section to be a terrain node. And then load four terrain nodes (can be the same textures and mask in each one for now) and render them based on visibility (see the quad tree tutorial for a general idea about how this should work, but note it will need to be modified for this to work correctly).

7. Use this technique to create a color map for each section, and then create a simple level of detail system which renders the sections the camera in not currently on with the color map instead.


Source Code

Source Code and Data Files: terdx10src17.zip

Executable: terdx10exe17.zip

Back to Tutorial Index