Tutorial 14: Slope Based Texturing
This DirectX 11 terrain tutorial will cover one of the procedural methods for texturing terrain that uses slope as the determining factor. The code in this tutorial is based on the terrain texturing tutorial.
There are a number of applications that use procedural methods to texture terrain. This helps generate large amounts of terrain that look very realistic without the need for an artist. Terragen is one example of such an application that uses procedural texturing. If you are not familiar with Terragen I would definitely recommend taking a look at it. The procedural method that I am going to cover for this tutorial uses slope to determine how to texture any given pixel in the terrain. This is a very fast method that produces excellent results.
To determine slope for any pixel is simple if you already have the normal vector. You simply subtract one from the Y (height) value of the normal. This will give you a value between 0.0f (completely flat terrain) and 1.0f (terrain pointing straight up at 90 degrees). With the slope for each pixel you can then determine how to texture it. For example lets render some terrain and anything below 0.2f slope is green, anything between 0.2f and 0.7f is a combination of green to blue, and then anything above 0.7 is a combination of blue to red. This would give us the following slope image:
This visualization of slope makes it very clear with red being the extreme slopes, blue being a moderate slope, and green being mostly flat. Now lets take the exact same example further and use the following textures:
Now if we make anything below 0.2f slope the grass texture, and anything between 0.2f and 0.7f a combination of mossy and rock texture, and then anything above 0.7 the rock texture we would get the following appearance:
For another example we could use snow for anything below 0.2 slope and then everything above 0.2 rock and produce the following image with the same terrain:
As you can see slope works great for procedurally rendering textures on terrain. Also it is one of the better systems that correlates with how terrain looks in the real world. For example flat surfaces support growth and retain things such as snow. And high slope areas have less growth and expose more rock and earth.
We will start the code section by looking at the modified terrain shader:
Terrain.vs
The terrain vertex shader has not been modified for this tutorial.
//////////////////////////////////////////////////////////////////////////////// // Filename: terrain.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; //////////////////////////////////////////////////////////////////////////////// // 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); // Store the texture coordinates for the pixel shader. output.tex = input.tex; // Calculate the normal vector against the world matrix only. output.normal = mul(input.normal, (float3x3)worldMatrix); // Normalize the normal vector. output.normal = normalize(output.normal); return output; }
Terrain.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: terrain.ps //////////////////////////////////////////////////////////////////////////////// ////////////// // TEXTURES // //////////////
There are three textures we will be using for the three different degrees of slope that we want to handle.
Texture2D grassTexture : register(t0); Texture2D slopeTexture : register(t1); Texture2D rockTexture : register(t2); /////////////////// // SAMPLE STATES // /////////////////// SamplerState SampleType; ////////////////////// // CONSTANT BUFFERS // ////////////////////// cbuffer LightBuffer { float4 ambientColor; float4 diffuseColor; float3 lightDirection; float padding; }; ////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; }; //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TerrainPixelShader(PixelInputType input) : SV_TARGET { float4 grassColor; float4 slopeColor; float4 rockColor; float slope; float blendAmount; float4 textureColor; float3 lightDir; float lightIntensity; float4 color;
Start by sampling all three textures.
// Sample the grass color from the texture using the sampler at this texture coordinate location. grassColor = grassTexture.Sample(SampleType, input.tex); // Sample the slope color from the texture using the sampler at this texture coordinate location. slopeColor = slopeTexture.Sample(SampleType, input.tex); // Sample the rock color from the texture using the sampler at this texture coordinate location. rockColor = rockTexture.Sample(SampleType, input.tex);
Now determine the slope for this pixel, which is just one subtracted from the Y normal.
// Calculate the slope of this point. slope = 1.0f - input.normal.y;
Since we have the slope we can now use it in some if statements and determine which texture to use based on the slope of the pixel. To make things look smooth we do a linear interpolation between the textures so the transition between each one isn't a sharp line in the terrain.
// Determine which texture to use based on height. if(slope < 0.2) { blendAmount = slope / 0.2f; textureColor = lerp(grassColor, slopeColor, blendAmount); } if((slope < 0.7) && (slope >= 0.2f)) { blendAmount = (slope - 0.2f) * (1.0f / (0.7f - 0.2f)); textureColor = lerp(slopeColor, rockColor, blendAmount); } if(slope >= 0.7) { textureColor = rockColor; }
Now do the regular lighting and add the lighting value to the texture value to get the final output color.
// Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel. lightIntensity = saturate(dot(input.normal, lightDir)); // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color = diffuseColor * lightIntensity; // Saturate the final light color. color = saturate(color); // Multiply the texture color and the final light color to get the result. color = color * textureColor; return color; }
Terrainshaderclass.h
The header file has changed a bit to accommodate the extra textures that are set in the shader.
//////////////////////////////////////////////////////////////////////////////// // Filename: terrainshaderclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TERRAINSHADERCLASS_H_ #define _TERRAINSHADERCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <d3dx11async.h> #include <fstream> using namespace std; //////////////////////////////////////////////////////////////////////////////// // Class name: TerrainShaderClass //////////////////////////////////////////////////////////////////////////////// class TerrainShaderClass { private: struct MatrixBufferType { D3DXMATRIX world; D3DXMATRIX view; D3DXMATRIX projection; }; struct LightBufferType { D3DXVECTOR4 ambientColor; D3DXVECTOR4 diffuseColor; D3DXVECTOR3 lightDirection; float padding; }; public: TerrainShaderClass(); TerrainShaderClass(const TerrainShaderClass&); ~TerrainShaderClass(); bool Initialize(ID3D11Device*, HWND); void Shutdown(); bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXVECTOR4, D3DXVECTOR4, D3DXVECTOR3, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXVECTOR4, D3DXVECTOR4, D3DXVECTOR3, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*); void RenderShader(ID3D11DeviceContext*, int); private: ID3D11VertexShader* m_vertexShader; ID3D11PixelShader* m_pixelShader; ID3D11InputLayout* m_layout; ID3D11SamplerState* m_sampleState; ID3D11Buffer* m_matrixBuffer; ID3D11Buffer* m_lightBuffer; }; #endif
Terrainshaderclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: terrainshaderclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "terrainshaderclass.h" TerrainShaderClass::TerrainShaderClass() { m_vertexShader = 0; m_pixelShader = 0; m_layout = 0; m_sampleState = 0; m_matrixBuffer = 0; m_lightBuffer = 0; } TerrainShaderClass::TerrainShaderClass(const TerrainShaderClass& other) { } TerrainShaderClass::~TerrainShaderClass() { } bool TerrainShaderClass::Initialize(ID3D11Device* device, HWND hwnd) { bool result; // Initialize the vertex and pixel shaders. result = InitializeShader(device, hwnd, L"../Engine/terrain.vs", L"../Engine/terrain.ps"); if(!result) { return false; } return true; } void TerrainShaderClass::Shutdown() { // Shutdown the vertex and pixel shaders as well as the related objects. ShutdownShader(); return; }
The Render function now takes in the three textures that are used for rendering the terrain based on slope. They are then sent as input into the SetShaderParameters function.
bool TerrainShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 lightDirection, ID3D11ShaderResourceView* grassTexture, ID3D11ShaderResourceView* slopeTexture, ID3D11ShaderResourceView* rockTexture) { bool result; // Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, ambientColor, diffuseColor, lightDirection, grassTexture, slopeTexture, rockTexture); if(!result) { return false; } // Now render the prepared buffers with the shader. RenderShader(deviceContext, indexCount); return true; } bool TerrainShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename) { HRESULT result; ID3D10Blob* errorMessage; ID3D10Blob* vertexShaderBuffer; ID3D10Blob* pixelShaderBuffer; D3D11_INPUT_ELEMENT_DESC polygonLayout[3]; unsigned int numElements; D3D11_SAMPLER_DESC samplerDesc; D3D11_BUFFER_DESC matrixBufferDesc; D3D11_BUFFER_DESC lightBufferDesc; // Initialize the pointers this function will use to null. errorMessage = 0; vertexShaderBuffer = 0; pixelShaderBuffer = 0; // Compile the vertex shader code. result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TerrainVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &vertexShaderBuffer, &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, vsFilename); } // If there was nothing in the error message then it simply could not find the shader file itself. else { MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK); } return false; } // Compile the pixel shader code. result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TerrainPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, &pixelShaderBuffer, &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, psFilename); } // If there was nothing in the error message then it simply could not find the file itself. else { MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK); } return false; } // Create the vertex shader from the buffer. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader); if(FAILED(result)) { return false; } // Create the pixel shader from the buffer. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader); if(FAILED(result)) { return false; } // Create the vertex input layout description. 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 = D3D11_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 = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[1].InputSlotClass = D3D11_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 = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[2].InstanceDataStepRate = 0; // Get a count of the elements in the layout. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); // Create the vertex input layout. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout); if(FAILED(result)) { return false; } // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. vertexShaderBuffer->Release(); vertexShaderBuffer = 0; pixelShaderBuffer->Release(); pixelShaderBuffer = 0; // Create a texture sampler state description. samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; samplerDesc.MipLODBias = 0.0f; samplerDesc.MaxAnisotropy = 1; samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; samplerDesc.BorderColor[0] = 0; samplerDesc.BorderColor[1] = 0; samplerDesc.BorderColor[2] = 0; samplerDesc.BorderColor[3] = 0; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; // Create the texture sampler state. result = device->CreateSamplerState(&samplerDesc, &m_sampleState); if(FAILED(result)) { return false; } // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC; matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType); matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; matrixBufferDesc.MiscFlags = 0; matrixBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer); if(FAILED(result)) { return false; } // Setup the description of the light dynamic constant buffer that is in the pixel shader. // Note that ByteWidth always needs to be a multiple of 16 if using D3D11_BIND_CONSTANT_BUFFER or CreateBuffer will fail. lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC; lightBufferDesc.ByteWidth = sizeof(LightBufferType); lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; lightBufferDesc.MiscFlags = 0; lightBufferDesc.StructureByteStride = 0; // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer); if(FAILED(result)) { return false; } return true; } void TerrainShaderClass::ShutdownShader() { // Release the light constant buffer. if(m_lightBuffer) { m_lightBuffer->Release(); m_lightBuffer = 0; } // Release the matrix constant buffer. if(m_matrixBuffer) { m_matrixBuffer->Release(); m_matrixBuffer = 0; } // Release the sampler state. if(m_sampleState) { m_sampleState->Release(); m_sampleState = 0; } // Release the layout. if(m_layout) { m_layout->Release(); m_layout = 0; } // Release the pixel shader. if(m_pixelShader) { m_pixelShader->Release(); m_pixelShader = 0; } // Release the vertex shader. if(m_vertexShader) { m_vertexShader->Release(); m_vertexShader = 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; }
SetShaderParameters now takes in the three textures for rendering the terrain.
bool TerrainShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 lightDirection, ID3D11ShaderResourceView* grassTexture, ID3D11ShaderResourceView* slopeTexture, ID3D11ShaderResourceView* rockTexture) { HRESULT result; D3D11_MAPPED_SUBRESOURCE mappedResource; unsigned int bufferNumber; MatrixBufferType* dataPtr; LightBufferType* dataPtr2; // Transpose the matrices to prepare them for the shader. D3DXMatrixTranspose(&worldMatrix, &worldMatrix); D3DXMatrixTranspose(&viewMatrix, &viewMatrix); D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix); // Lock the constant buffer so it can be written to. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr = (MatrixBufferType*)mappedResource.pData; // Copy the matrices into the constant buffer. dataPtr->world = worldMatrix; dataPtr->view = viewMatrix; dataPtr->projection = projectionMatrix; // Unlock the constant buffer. deviceContext->Unmap(m_matrixBuffer, 0); // Set the position of the constant buffer in the vertex shader. bufferNumber = 0; // Now set the constant buffer in the vertex shader with the updated values. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer); // Lock the light constant buffer so it can be written to. result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); if(FAILED(result)) { return false; } // Get a pointer to the data in the constant buffer. dataPtr2 = (LightBufferType*)mappedResource.pData; // Copy the lighting variables into the constant buffer. dataPtr2->ambientColor = ambientColor; dataPtr2->diffuseColor = diffuseColor; dataPtr2->lightDirection = lightDirection; dataPtr2->padding = 0.0f; // Unlock the constant buffer. deviceContext->Unmap(m_lightBuffer, 0); // Set the position of the light constant buffer in the pixel shader. bufferNumber = 0; // Finally set the light constant buffer in the pixel shader with the updated values. deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);
Set the three textures in the pixel shader here.
// Set shader texture resources in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &grassTexture); deviceContext->PSSetShaderResources(1, 1, &slopeTexture); deviceContext->PSSetShaderResources(2, 1, &rockTexture); return true; } void TerrainShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount) { // Set the vertex input layout. deviceContext->IASetInputLayout(m_layout); // Set the vertex and pixel shaders that will be used to render this triangle. deviceContext->VSSetShader(m_vertexShader, NULL, 0); deviceContext->PSSetShader(m_pixelShader, NULL, 0); // Set the sampler state in the pixel shader. deviceContext->PSSetSamplers(0, 1, &m_sampleState); // Render the triangle. deviceContext->DrawIndexed(indexCount, 0, 0); return; }
Terrainclass.h
//////////////////////////////////////////////////////////////////////////////// // Filename: terrainclass.h //////////////////////////////////////////////////////////////////////////////// #ifndef _TERRAINCLASS_H_ #define _TERRAINCLASS_H_ ////////////// // INCLUDES // ////////////// #include <d3d11.h> #include <d3dx10math.h> #include <stdio.h> /////////////////////// // MY CLASS INCLUDES // /////////////////////// #include "textureclass.h" ///////////// // GLOBALS // ///////////// const int TEXTURE_REPEAT = 32; //////////////////////////////////////////////////////////////////////////////// // Class name: TerrainClass //////////////////////////////////////////////////////////////////////////////// class TerrainClass { private: struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; D3DXVECTOR3 normal; }; struct HeightMapType { float x, y, z; float tu, tv; float nx, ny, nz; }; struct VectorType { float x, y, z; }; public: TerrainClass(); TerrainClass(const TerrainClass&); ~TerrainClass(); bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount();
The TerrainClass now has functions for returning pointers to the three new texture resources that are used to render the terrain.
ID3D11ShaderResourceView* GetGrassTexture(); ID3D11ShaderResourceView* GetSlopeTexture(); ID3D11ShaderResourceView* GetRockTexture(); private: bool LoadHeightMap(char*); void NormalizeHeightMap(); bool CalculateNormals(); void ShutdownHeightMap(); void CalculateTextureCoordinates(); bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*, WCHAR*); void ReleaseTextures(); bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); private: int m_terrainWidth, m_terrainHeight; int m_vertexCount, m_indexCount; ID3D11Buffer *m_vertexBuffer, *m_indexBuffer; HeightMapType* m_heightMap;
We have three new TextureClass objects for the three new textures used for terrain rendering.
TextureClass *m_GrassTexture, *m_SlopeTexture, *m_RockTexture; }; #endif
Terrainclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: terrainclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "terrainclass.h" TerrainClass::TerrainClass() { m_vertexBuffer = 0; m_indexBuffer = 0; m_heightMap = 0;
The three new textures are set to null in the class constructor.
m_GrassTexture = 0; m_SlopeTexture = 0; m_RockTexture = 0; } TerrainClass::TerrainClass(const TerrainClass& other) { } TerrainClass::~TerrainClass() { } bool TerrainClass::Initialize(ID3D11Device* device, char* heightMapFilename, WCHAR* grassTextureFilename, WCHAR* slopeTextureFilename, WCHAR* rockTextureFilename) { bool result; // Load in the height map for the terrain. result = LoadHeightMap(heightMapFilename); if(!result) { return false; } // Normalize the height of the height map. NormalizeHeightMap(); // Calculate the normals for the terrain data. result = CalculateNormals(); if(!result) { return false; } // Calculate the texture coordinates. CalculateTextureCoordinates();
The file names of the three new textures are sent into the LoadTextures function.
// Load the textures. result = LoadTextures(device, grassTextureFilename, slopeTextureFilename, rockTextureFilename); if(!result) { return false; } // Initialize the vertex and index buffer that hold the geometry for the terrain. result = InitializeBuffers(device); if(!result) { return false; } return true; } void TerrainClass::Shutdown() { // Release the textures. ReleaseTextures(); // Release the vertex and index buffer. ShutdownBuffers(); // Release the height map data. ShutdownHeightMap(); return; } void TerrainClass::Render(ID3D11DeviceContext* deviceContext) { // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing. RenderBuffers(deviceContext); return; } int TerrainClass::GetIndexCount() { return m_indexCount; }
These are the three new functions that return pointers to the texture resources that are used in the pixel shader.
ID3D11ShaderResourceView* TerrainClass::GetGrassTexture() { return m_GrassTexture->GetTexture(); } ID3D11ShaderResourceView* TerrainClass::GetSlopeTexture() { return m_SlopeTexture->GetTexture(); } ID3D11ShaderResourceView* TerrainClass::GetRockTexture() { return m_RockTexture->GetTexture(); } 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_terrainHeight * 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::NormalizeHeightMap() { int i, j; for(j=0; j<m_terrainHeight; j++) { for(i=0; i<m_terrainWidth; i++) { m_heightMap[(m_terrainHeight * j) + i].y /= 15.0f; } } 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_terrainHeight) + i; index2 = (j * m_terrainHeight) + (i+1); index3 = ((j+1) * m_terrainHeight) + 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_terrainHeight-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_terrainHeight-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_terrainHeight-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_terrainHeight-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_terrainHeight-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_terrainHeight) + 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; } void TerrainClass::ShutdownHeightMap() { if(m_heightMap) { delete [] m_heightMap; m_heightMap = 0; } return; } void TerrainClass::CalculateTextureCoordinates() { int incrementCount, i, j, tuCount, tvCount; float incrementValue, tuCoordinate, tvCoordinate; // Calculate how much to increment the texture coordinates by. incrementValue = (float)TEXTURE_REPEAT / (float)m_terrainWidth; // Calculate how many times to repeat the texture. incrementCount = m_terrainWidth / TEXTURE_REPEAT; // Initialize the tu and tv coordinate values. tuCoordinate = 0.0f; tvCoordinate = 1.0f; // Initialize the tu and tv coordinate indexes. tuCount = 0; tvCount = 0; // Loop through the entire height map and calculate the tu and tv texture coordinates for each vertex. for(j=0; j<m_terrainHeight; j++) { for(i=0; i<m_terrainWidth; i++) { // Store the texture coordinate in the height map. m_heightMap[(m_terrainHeight * j) + i].tu = tuCoordinate; m_heightMap[(m_terrainHeight * j) + i].tv = tvCoordinate; // Increment the tu texture coordinate by the increment value and increment the index by one. tuCoordinate += incrementValue; tuCount++; // Check if at the far right end of the texture and if so then start at the beginning again. if(tuCount == incrementCount) { tuCoordinate = 0.0f; tuCount = 0; } } // Increment the tv texture coordinate by the increment value and increment the index by one. tvCoordinate -= incrementValue; tvCount++; // Check if at the top of the texture and if so then start at the bottom again. if(tvCount == incrementCount) { tvCoordinate = 1.0f; tvCount = 0; } } return; }
The three new textures are loaded here.
bool TerrainClass::LoadTextures(ID3D11Device* device, WCHAR* grassTextureFilename, WCHAR* slopeTextureFilename, WCHAR* rockTextureFilename) { bool result; // Create the grass texture object. m_GrassTexture = new TextureClass; if(!m_GrassTexture) { return false; } // Initialize the grass texture object. result = m_GrassTexture->Initialize(device, grassTextureFilename); if(!result) { return false; } // Create the slope texture object. m_SlopeTexture = new TextureClass; if(!m_SlopeTexture) { return false; } // Initialize the slope texture object. result = m_SlopeTexture->Initialize(device, slopeTextureFilename); if(!result) { return false; } // Create the rock texture object. m_RockTexture = new TextureClass; if(!m_RockTexture) { return false; } // Initialize the rock texture object. result = m_RockTexture->Initialize(device, rockTextureFilename); if(!result) { return false; } return true; }
The three new textures are released here in the ReleaseTextures function.
void TerrainClass::ReleaseTextures() { // Release the texture objects. if(m_GrassTexture) { m_GrassTexture->Shutdown(); delete m_GrassTexture; m_GrassTexture = 0; } if(m_SlopeTexture) { m_SlopeTexture->Shutdown(); delete m_SlopeTexture; m_SlopeTexture = 0; } if(m_RockTexture) { m_RockTexture->Shutdown(); delete m_RockTexture; m_RockTexture = 0; } return; } bool TerrainClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; int index, i, j; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; int index1, index2, index3, index4; float tu, tv; // Calculate the number of vertices in the terrain mesh. m_vertexCount = (m_terrainWidth - 1) * (m_terrainHeight - 1) * 6; // Set the index count to the same as the vertex count. m_indexCount = m_vertexCount; // Create the vertex array. vertices = new VertexType[m_vertexCount]; if(!vertices) { return false; } // Create the index array. indices = new unsigned long[m_indexCount]; if(!indices) { return false; } // Initialize the index to the vertex buffer. index = 0; // Load the vertex and index array with the terrain data. for(j=0; j<(m_terrainHeight-1); j++) { for(i=0; i<(m_terrainWidth-1); i++) { index1 = (m_terrainHeight * j) + i; // Bottom left. index2 = (m_terrainHeight * j) + (i+1); // Bottom right. index3 = (m_terrainHeight * (j+1)) + i; // Upper left. index4 = (m_terrainHeight * (j+1)) + (i+1); // Upper right. // Upper left. tv = m_heightMap[index3].tv; // Modify the texture coordinates to cover the top edge. if(tv == 1.0f) { tv = 0.0f; } vertices[index].position = D3DXVECTOR3(m_heightMap[index3].x, m_heightMap[index3].y, m_heightMap[index3].z); vertices[index].texture = D3DXVECTOR2(m_heightMap[index3].tu, tv); vertices[index].normal = D3DXVECTOR3(m_heightMap[index3].nx, m_heightMap[index3].ny, m_heightMap[index3].nz); indices[index] = index; index++; // Upper right. tu = m_heightMap[index4].tu; tv = m_heightMap[index4].tv; // Modify the texture coordinates to cover the top and right edge. if(tu == 0.0f) { tu = 1.0f; } if(tv == 1.0f) { tv = 0.0f; } vertices[index].position = D3DXVECTOR3(m_heightMap[index4].x, m_heightMap[index4].y, m_heightMap[index4].z); vertices[index].texture = D3DXVECTOR2(tu, tv); vertices[index].normal = D3DXVECTOR3(m_heightMap[index4].nx, m_heightMap[index4].ny, m_heightMap[index4].nz); indices[index] = index; index++; // Bottom left. vertices[index].position = D3DXVECTOR3(m_heightMap[index1].x, m_heightMap[index1].y, m_heightMap[index1].z); vertices[index].texture = D3DXVECTOR2(m_heightMap[index1].tu, m_heightMap[index1].tv); vertices[index].normal = D3DXVECTOR3(m_heightMap[index1].nx, m_heightMap[index1].ny, m_heightMap[index1].nz); indices[index] = index; index++; // Bottom left. vertices[index].position = D3DXVECTOR3(m_heightMap[index1].x, m_heightMap[index1].y, m_heightMap[index1].z); vertices[index].texture = D3DXVECTOR2(m_heightMap[index1].tu, m_heightMap[index1].tv); vertices[index].normal = D3DXVECTOR3(m_heightMap[index1].nx, m_heightMap[index1].ny, m_heightMap[index1].nz); indices[index] = index; index++; // Upper right. tu = m_heightMap[index4].tu; tv = m_heightMap[index4].tv; // Modify the texture coordinates to cover the top and right edge. if(tu == 0.0f) { tu = 1.0f; } if(tv == 1.0f) { tv = 0.0f; } vertices[index].position = D3DXVECTOR3(m_heightMap[index4].x, m_heightMap[index4].y, m_heightMap[index4].z); vertices[index].texture = D3DXVECTOR2(tu, tv); vertices[index].normal = D3DXVECTOR3(m_heightMap[index4].nx, m_heightMap[index4].ny, m_heightMap[index4].nz); indices[index] = index; index++; // Bottom right. tu = m_heightMap[index2].tu; // Modify the texture coordinates to cover the right edge. if(tu == 0.0f) { tu = 1.0f; } vertices[index].position = D3DXVECTOR3(m_heightMap[index2].x, m_heightMap[index2].y, m_heightMap[index2].z); vertices[index].texture = D3DXVECTOR2(tu, m_heightMap[index2].tv); vertices[index].normal = D3DXVECTOR3(m_heightMap[index2].nx, m_heightMap[index2].ny, m_heightMap[index2].nz); indices[index] = index; index++; } } // Set up the description of the static vertex buffer. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount; vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; vertexBufferDesc.CPUAccessFlags = 0; vertexBufferDesc.MiscFlags = 0; vertexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the vertex data. vertexData.pSysMem = vertices; vertexData.SysMemPitch = 0; vertexData.SysMemSlicePitch = 0; // Now create the vertex buffer. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer); if(FAILED(result)) { return false; } // Set up the description of the static index buffer. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount; indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; indexBufferDesc.CPUAccessFlags = 0; indexBufferDesc.MiscFlags = 0; indexBufferDesc.StructureByteStride = 0; // Give the subresource structure a pointer to the index data. indexData.pSysMem = indices; indexData.SysMemPitch = 0; indexData.SysMemSlicePitch = 0; // Create the index buffer. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer); if(FAILED(result)) { return false; } // Release the arrays now that the buffers have been created and loaded. delete [] vertices; vertices = 0; delete [] indices; indices = 0; return true; } void TerrainClass::ShutdownBuffers() { // 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(ID3D11DeviceContext* deviceContext) { 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. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset); // Set the index buffer to active in the input assembler so it can be rendered. deviceContext->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. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return; }
Applicationclass.h
The ApplicationClass header hasn't changed for this tutorial.
//////////////////////////////////////////////////////////////////////////////// // 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 "cameraclass.h" #include "terrainclass.h" #include "timerclass.h" #include "positionclass.h" #include "fpsclass.h" #include "cpuclass.h" #include "fontshaderclass.h" #include "textclass.h" #include "terrainshaderclass.h" #include "lightclass.h" //////////////////////////////////////////////////////////////////////////////// // Class name: ApplicationClass //////////////////////////////////////////////////////////////////////////////// class ApplicationClass { public: ApplicationClass(); ApplicationClass(const ApplicationClass&); ~ApplicationClass(); bool Initialize(HINSTANCE, HWND, int, int); void Shutdown(); bool Frame(); private: bool HandleInput(float); bool RenderGraphics(); private: InputClass* m_Input; D3DClass* m_Direct3D; CameraClass* m_Camera; TerrainClass* m_Terrain; TimerClass* m_Timer; PositionClass* m_Position; FpsClass* m_Fps; CpuClass* m_Cpu; FontShaderClass* m_FontShader; TextClass* m_Text; TerrainShaderClass* m_TerrainShader; LightClass* m_Light; }; #endif
Applicationclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: applicationclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "applicationclass.h" ApplicationClass::ApplicationClass() { m_Input = 0; m_Direct3D = 0; m_Camera = 0; m_Terrain = 0; m_Timer = 0; m_Position = 0; m_Fps = 0; m_Cpu = 0; m_FontShader = 0; m_Text = 0; m_TerrainShader = 0; m_Light = 0; } ApplicationClass::ApplicationClass(const ApplicationClass& other) { } ApplicationClass::~ApplicationClass() { } bool ApplicationClass::Initialize(HINSTANCE hinstance, HWND hwnd, int screenWidth, int screenHeight) { bool result; float cameraX, cameraY, cameraZ; D3DXMATRIX baseViewMatrix; char videoCard[128]; int videoMemory; // Create the input object. The input object will be used to handle reading the keyboard and mouse 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_Direct3D = new D3DClass; if(!m_Direct3D) { return false; } // Initialize the Direct3D object. result = m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize DirectX 11.", L"Error", MB_OK); return false; } // Create the camera object. m_Camera = new CameraClass; if(!m_Camera) { return false; } // Initialize a base view matrix with the camera for 2D user interface rendering. m_Camera->SetPosition(0.0f, 0.0f, -1.0f); m_Camera->Render(); m_Camera->GetViewMatrix(baseViewMatrix); // Set the initial position of the camera. cameraX = 150.0f; cameraY = 2.0f; cameraZ = 35.0f; m_Camera->SetPosition(cameraX, cameraY, cameraZ); // Create the terrain object. m_Terrain = new TerrainClass; if(!m_Terrain) { return false; }
The terrain object takes the names of the three new textures as input.
// Initialize the terrain object. result = m_Terrain->Initialize(m_Direct3D->GetDevice(), "../Engine/data/heightmap.bmp", L"../Engine/data/grass.dds", L"../Engine/data/slope.dds", L"../Engine/data/rock.dds"); if(!result) { MessageBox(hwnd, L"Could not initialize the terrain object.", 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; } // Create the position object. m_Position = new PositionClass; if(!m_Position) { return false; } // Set the initial position of the viewer to the same as the initial camera position. m_Position->SetPosition(cameraX, cameraY, cameraZ); // Create the fps object. m_Fps = new FpsClass; if(!m_Fps) { return false; } // Initialize the fps object. m_Fps->Initialize(); // Create the cpu object. m_Cpu = new CpuClass; if(!m_Cpu) { return false; } // Initialize the cpu object. m_Cpu->Initialize(); // Create the font shader object. m_FontShader = new FontShaderClass; if(!m_FontShader) { return false; } // Initialize the font shader object. result = m_FontShader->Initialize(m_Direct3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the font shader object.", L"Error", MB_OK); return false; } // Create the text object. m_Text = new TextClass; if(!m_Text) { return false; } // Initialize the text object. result = m_Text->Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), hwnd, screenWidth, screenHeight, baseViewMatrix); if(!result) { MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK); return false; } // Retrieve the video card information. m_Direct3D->GetVideoCardInfo(videoCard, videoMemory); // Set the video card information in the text object. result = m_Text->SetVideoCardInfo(videoCard, videoMemory, m_Direct3D->GetDeviceContext()); if(!result) { MessageBox(hwnd, L"Could not set video card info in the text 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_Direct3D->GetDevice(), hwnd); if(!result) { MessageBox(hwnd, L"Could not initialize the terrain shader object.", L"Error", MB_OK); return false; } // Create the light object. m_Light = new LightClass; if(!m_Light) { return false; } // Initialize the light object. m_Light->SetAmbientColor(0.05f, 0.05f, 0.05f, 1.0f); m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(-0.5f, -1.0f, 0.0f); return true; } void ApplicationClass::Shutdown() { // Release the light object. if(m_Light) { delete m_Light; m_Light = 0; } // Release the terrain shader object. if(m_TerrainShader) { m_TerrainShader->Shutdown(); delete m_TerrainShader; m_TerrainShader = 0; } // Release the text object. if(m_Text) { m_Text->Shutdown(); delete m_Text; m_Text = 0; } // Release the font shader object. if(m_FontShader) { m_FontShader->Shutdown(); delete m_FontShader; m_FontShader = 0; } // Release the cpu object. if(m_Cpu) { m_Cpu->Shutdown(); delete m_Cpu; m_Cpu = 0; } // Release the fps object. if(m_Fps) { delete m_Fps; m_Fps = 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 terrain object. if(m_Terrain) { m_Terrain->Shutdown(); delete m_Terrain; m_Terrain = 0; } // Release the camera object. if(m_Camera) { delete m_Camera; m_Camera = 0; } // Release the Direct3D object. if(m_Direct3D) { m_Direct3D->Shutdown(); delete m_Direct3D; m_Direct3D = 0; } // Release the input object. if(m_Input) { m_Input->Shutdown(); delete m_Input; m_Input = 0; } return; } bool ApplicationClass::Frame() { bool result; // Read the user input. 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 system stats. m_Timer->Frame(); m_Fps->Frame(); m_Cpu->Frame(); // Update the FPS value in the text object. result = m_Text->SetFps(m_Fps->GetFps(), m_Direct3D->GetDeviceContext()); if(!result) { return false; } // Update the CPU usage value in the text object. result = m_Text->SetCpu(m_Cpu->GetCpuPercentage(), m_Direct3D->GetDeviceContext()); if(!result) { return false; } // Do the frame input processing. result = HandleInput(m_Timer->GetTime()); if(!result) { return false; } // Render the graphics. result = RenderGraphics(); if(!result) { return false; } return result; } bool ApplicationClass::HandleInput(float frameTime) { bool keyDown, result; float posX, posY, posZ, rotX, rotY, rotZ; // Set the frame time for calculating the updated position. m_Position->SetFrameTime(frameTime); // 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); // Update the position values in the text object. result = m_Text->SetCameraPosition(posX, posY, posZ, m_Direct3D->GetDeviceContext()); if(!result) { return false; } // Update the rotation values in the text object. result = m_Text->SetCameraRotation(rotX, rotY, rotZ, m_Direct3D->GetDeviceContext()); if(!result) { return false; } return true; } bool ApplicationClass::RenderGraphics() { D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix; bool result; // Clear the scene. m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f); // Generate the view matrix based on the camera's position. m_Camera->Render(); // Get the world, view, projection, and ortho matrices from the camera and Direct3D objects. m_Direct3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_Direct3D->GetProjectionMatrix(projectionMatrix); m_Direct3D->GetOrthoMatrix(orthoMatrix); // Render the terrain buffers. m_Terrain->Render(m_Direct3D->GetDeviceContext());
When we render the terrain now we supply it the three new terrain textures that will be mapped procedurally according to the terrain slope.
// Render the terrain using the terrain shader. result = m_TerrainShader->Render(m_Direct3D->GetDeviceContext(), m_Terrain->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Light->GetAmbientColor(), m_Light->GetDiffuseColor(), m_Light->GetDirection(), m_Terrain->GetGrassTexture(), m_Terrain->GetSlopeTexture(), m_Terrain->GetRockTexture()); if(!result) { return false; } // Turn off the Z buffer to begin all 2D rendering. m_Direct3D->TurnZBufferOff(); // Turn on the alpha blending before rendering the text. m_Direct3D->TurnOnAlphaBlending(); // Render the text user interface elements. result = m_Text->Render(m_Direct3D->GetDeviceContext(), m_FontShader, worldMatrix, orthoMatrix); if(!result) { return false; } // Turn off alpha blending after rendering the text. m_Direct3D->TurnOffAlphaBlending(); // Turn the Z buffer back on now that all 2D rendering has completed. m_Direct3D->TurnZBufferOn(); // Present the rendered scene to the screen. m_Direct3D->EndScene(); return true; }
Summary
We now have terrain that can be automatically texture mapped per pixel based on the slope.
To Do Exercises
1. Compile and run the program. Move around the terrain to see the effect of textures mapped based on slope.
2. Change the textures to create a different looking terrain.
3. Modify the slope values in the pixel shader to see the effect. You may want to change the textures to be pure colors like red, green, blue.
4. Add a fourth range to the slope calculate in the pixel shader and add another texture.
Source Code
Source Code and Data Files: tersrc14.zip
Executable: terexe14.zip