Tutorial 15: Terrain Bump Mapping
This tutorial will cover terrain bump mapping in DirectX 11 using HLSL and C++. You may want to review the bump mapping DirectX tutorial before proceeding with this terrain tutorial.
One of the ways to add a degree of realism to height map generated terrain is to use bump mapping. Bump mapping allows us to light the terrain per pixel instead of per triangle. We get the per pixel lighting information from normal maps. Each pixel in the normal map contains the information needed to calculate the unique lighting normal for that pixel. The tangent and binormal for each quad on the terrain also needs to be calculated to perform bump mapping calculations. For this tutorial we will use the following color detail map and normal map:
As bump mapping is very fine detail it generally can't be seen too far away from the camera. Also it would not be very efficient to apply the normal map to every single triangle in our terrain. So just like the detail mapping tutorial we will use the depth information that is passed into the pixel shader to determine if the normal map should be used or if just the regular lighting should be performed. To illustrate the concept I have created a screen shot where the close parts of the terrain that are bump mapped are tinted in blue. And anything beyond the distance that we don't want to apply normal maps to is tinted green.
Now when we add the color detail texture with the normal map for per pixel lighting we get very nice looking terrain at close ranges:
We will start the code section of the tutorial by examining the updated HLSL terrain shaders.
Terrain.vs
The terrain vertex shader is the same as the bump map vertex shader except that we also include depth information which is passed into the pixel shader.
//////////////////////////////////////////////////////////////////////////////// // Filename: terrain.vs //////////////////////////////////////////////////////////////////////////////// ///////////// // GLOBALS // ///////////// cbuffer MatrixBuffer { matrix worldMatrix; matrix viewMatrix; matrix projectionMatrix; }; ////////////// // TYPEDEFS // ////////////// struct VertexInputType { float4 position : POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; }; struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; float4 depthPosition : 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); // Store the position value in a second input value for depth value calculations. output.depthPosition = output.position; // Store the texture coordinates for the pixel shader. output.tex = input.tex; // 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; }
Terrain.ps
//////////////////////////////////////////////////////////////////////////////// // Filename: terrain.ps //////////////////////////////////////////////////////////////////////////////// ////////////// // TEXTURES // ////////////// Texture2D colorTexture : register(t0); Texture2D normalTexture : register(t1); ////////////// // SAMPLERS // ////////////// SamplerState SampleType; ////////////////////// // CONSTANT BUFFERS // ////////////////////// cbuffer LightBuffer { float4 diffuseColor; float3 lightDirection; float padding; }; ////////////// // TYPEDEFS // ////////////// struct PixelInputType { float4 position : SV_POSITION; float2 tex : TEXCOORD0; float3 normal : NORMAL; float3 tangent : TANGENT; float3 binormal : BINORMAL; float4 depthPosition : TEXCOORD1; }; //////////////////////////////////////////////////////////////////////////////// // Pixel Shader //////////////////////////////////////////////////////////////////////////////// float4 TerrainPixelShader(PixelInputType input) : SV_TARGET { float depthValue; float4 textureColor; float4 bumpMap; float3 bumpNormal; float3 lightDir; float lightIntensity; float4 color; // Get the depth value of the pixel by dividing the Z pixel depth by the homogeneous W coordinate. depthValue = input.depthPosition.z / input.depthPosition.w; // Sample the texture pixel at this location. textureColor = colorTexture.Sample(SampleType, input.tex);
Here is the part where we determine whether or not to use bump mapping. We use the depth of the pixel in the scene to determine whether to use the normal map texture as the normal or to just use the regular lighting normal.
if(depthValue < 0.9f) { // Sample the pixel in the bump map. bumpMap = normalTexture.Sample(SampleType, input.tex); // Expand the range of the normal value from (0, +1) to (-1, +1). bumpMap = (bumpMap * 2.0f) - 1.0f; // Calculate the normal from the data in the bump map. bumpNormal = input.normal + bumpMap.x * input.tangent + bumpMap.y * input.binormal; // Normalize the resulting bump normal. bumpNormal = normalize(bumpNormal); } else { bumpNormal = input.normal; } // Invert the light direction for calculations. lightDir = -lightDirection; // Calculate the amount of light on this pixel based on the bump map normal value. lightIntensity = saturate(dot(bumpNormal, lightDir)); // Determine the final diffuse color based on the diffuse color and the amount of light intensity. color = saturate(diffuseColor * lightIntensity); // Combine the final bump light color with the texture color. color = color * textureColor; return color; }
Terrainshaderclass.h
The TerrainShaderClass is just the BumpMapShaderClass renamed.
//////////////////////////////////////////////////////////////////////////////// // 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 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, D3DXVECTOR3, ID3D11ShaderResourceView*, ID3D11ShaderResourceView*); private: bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*); void ShutdownShader(); void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*); bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, D3DXVECTOR4, D3DXVECTOR3, 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; } bool TerrainShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 lightDirection, ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* normalMapTexture) { bool result; // Set the shader parameters that it will use for rendering. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, diffuseColor, lightDirection, colorTexture, normalMapTexture); 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[5]; 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; polygonLayout[3].SemanticName = "TANGENT"; polygonLayout[3].SemanticIndex = 0; polygonLayout[3].Format = DXGI_FORMAT_R32G32B32_FLOAT; polygonLayout[3].InputSlot = 0; polygonLayout[3].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[3].InputSlotClass = D3D11_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 = D3D11_APPEND_ALIGNED_ELEMENT; polygonLayout[4].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; polygonLayout[4].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. 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 pixel 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; } bool TerrainShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, D3DXVECTOR4 diffuseColor, D3DXVECTOR3 lightDirection, ID3D11ShaderResourceView* colorTexture, ID3D11ShaderResourceView* normalMapTexture) { 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->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 texture resources in the pixel shader. deviceContext->PSSetShaderResources(0, 1, &colorTexture); deviceContext->PSSetShaderResources(1, 1, &normalMapTexture); 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" //////////////////////////////////////////////////////////////////////////////// // Class name: TerrainClass //////////////////////////////////////////////////////////////////////////////// class TerrainClass { private: struct HeightMapType { float x, y, z; float nx, ny, nz; };
We have added and modified some of the structures to accommodate bump mapping.
struct VectorType { float x, y, z; }; struct ModelType { float x, y, z; float tu, tv; float nx, ny, nz; float tx, ty, tz; float bx, by, bz; }; struct TempVertexType { float x, y, z; float tu, tv; float nx, ny, nz; }; struct VertexType { D3DXVECTOR3 position; D3DXVECTOR2 texture; D3DXVECTOR3 normal; D3DXVECTOR3 tangent; D3DXVECTOR3 binormal; }; public: TerrainClass(); TerrainClass(const TerrainClass&); ~TerrainClass(); bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*); void Shutdown(); void Render(ID3D11DeviceContext*); int GetIndexCount(); ID3D11ShaderResourceView* GetColorTexture(); ID3D11ShaderResourceView* GetNormalMapTexture(); private: bool LoadHeightMap(char*); void ShutdownHeightMap(); void ReduceHeightMap(); bool CalculateNormals();
We have a new function to build a model structure from the terrain height data so that tangent and binormal calculations can be performed on the model data.
bool BuildTerrainModel(); void ReleaseTerrainModel();
These are the two new functions for calculating the tangent and binormal for each triangle in the terrain model.
void CalculateTerrainVectors(); void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&); bool InitializeBuffers(ID3D11Device*); void ShutdownBuffers(); void RenderBuffers(ID3D11DeviceContext*); bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*); void ReleaseTextures(); private: int m_terrainWidth, m_terrainHeight; HeightMapType* m_heightMap;
This is the new structure for holding the terrain model data.
ModelType* m_TerrainModel; int m_vertexCount, m_indexCount; ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
Here are the two textures for the color detail and normal map.
TextureClass *m_ColorTexture, *m_NormalMapTexture; }; #endif
Terrainclass.cpp
//////////////////////////////////////////////////////////////////////////////// // Filename: terrainclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "terrainclass.h" TerrainClass::TerrainClass() { m_heightMap = 0; m_TerrainModel = 0; m_vertexBuffer = 0; m_indexBuffer = 0; m_ColorTexture = 0; m_NormalMapTexture = 0; } TerrainClass::TerrainClass(const TerrainClass& other) { } TerrainClass::~TerrainClass() { } bool TerrainClass::Initialize(ID3D11Device* device, char* heightMapFilename, WCHAR* colorTextureFilename, WCHAR* normalMapFilename) { bool result;
The flow of the Initialize function has changed a bit. We load the height map as usual and then reduce its height. Also we still calculate shared normals. But after that we construct a model out of the terrain height map and normal data. This is done so calculating the tangent and binormal for each triangle in the terrain becomes easy. Once the model is built and we have calculated the bump map normals we then build the vertex and index buffer for rendering. And finally we load in the color detail texture and normal map texture.
// Load in the height map for the terrain. result = LoadHeightMap(heightMapFilename); if(!result) { return false; } // Reduce the height of the height map. ReduceHeightMap(); // 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 = BuildTerrainModel(); if(!result) { return false; } // Calculate the normal, tangent, and binormal vectors for the terrain model. CalculateTerrainVectors(); // Initialize the vertex and index buffer for the terrain. result = InitializeBuffers(device); if(!result) { return false; } // Load the textures. result = LoadTextures(device, colorTextureFilename, normalMapFilename); if(!result) { return false; } return true; } void TerrainClass::Shutdown() { // Release the textures. ReleaseTextures(); // Release the vertex and index buffer. ShutdownBuffers(); // Release the terrain model. ReleaseTerrainModel(); // 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; } ID3D11ShaderResourceView* TerrainClass::GetColorTexture() { return m_ColorTexture->GetTexture(); } ID3D11ShaderResourceView* TerrainClass::GetNormalMapTexture() { return m_NormalMapTexture->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_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::ShutdownHeightMap() { if(m_heightMap) { delete [] m_heightMap; m_heightMap = 0; } return; } void TerrainClass::ReduceHeightMap() { int i, j; for(j=0; j<m_terrainHeight; j++) { for(i=0; i<m_terrainWidth; i++) { m_heightMap[(m_terrainWidth * 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_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_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; }
The BuildTerrainModel function builds the terrain triangles the same way the InitializeBuffers function used to. But instead of creating a vertex and index buffer we load the terrain data into a ModelType structure array.
bool TerrainClass::BuildTerrainModel() { int i, j, index, index1, index2, index3, index4; // Set the number of vertices in the model. m_vertexCount = (m_terrainWidth - 1) * (m_terrainHeight - 1) * 6; // Create the terrain model array. m_TerrainModel = new ModelType[m_vertexCount]; if(!m_TerrainModel) { return false; } // 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_TerrainModel[index].x = m_heightMap[index3].x; m_TerrainModel[index].y = m_heightMap[index3].y; m_TerrainModel[index].z = m_heightMap[index3].z; m_TerrainModel[index].nx = m_heightMap[index3].nx; m_TerrainModel[index].ny = m_heightMap[index3].ny; m_TerrainModel[index].nz = m_heightMap[index3].nz; m_TerrainModel[index].tu = 0.0f; m_TerrainModel[index].tv = 0.0f; index++; // Upper right. m_TerrainModel[index].x = m_heightMap[index4].x; m_TerrainModel[index].y = m_heightMap[index4].y; m_TerrainModel[index].z = m_heightMap[index4].z; m_TerrainModel[index].nx = m_heightMap[index4].nx; m_TerrainModel[index].ny = m_heightMap[index4].ny; m_TerrainModel[index].nz = m_heightMap[index4].nz; m_TerrainModel[index].tu = 1.0f; m_TerrainModel[index].tv = 0.0f; index++; // Bottom left. m_TerrainModel[index].x = m_heightMap[index1].x; m_TerrainModel[index].y = m_heightMap[index1].y; m_TerrainModel[index].z = m_heightMap[index1].z; m_TerrainModel[index].nx = m_heightMap[index1].nx; m_TerrainModel[index].ny = m_heightMap[index1].ny; m_TerrainModel[index].nz = m_heightMap[index1].nz; m_TerrainModel[index].tu = 0.0f; m_TerrainModel[index].tv = 1.0f; index++; // Bottom left. m_TerrainModel[index].x = m_heightMap[index1].x; m_TerrainModel[index].y = m_heightMap[index1].y; m_TerrainModel[index].z = m_heightMap[index1].z; m_TerrainModel[index].nx = m_heightMap[index1].nx; m_TerrainModel[index].ny = m_heightMap[index1].ny; m_TerrainModel[index].nz = m_heightMap[index1].nz; m_TerrainModel[index].tu = 0.0f; m_TerrainModel[index].tv = 1.0f; index++; // Upper right. m_TerrainModel[index].x = m_heightMap[index4].x; m_TerrainModel[index].y = m_heightMap[index4].y; m_TerrainModel[index].z = m_heightMap[index4].z; m_TerrainModel[index].nx = m_heightMap[index4].nx; m_TerrainModel[index].ny = m_heightMap[index4].ny; m_TerrainModel[index].nz = m_heightMap[index4].nz; m_TerrainModel[index].tu = 1.0f; m_TerrainModel[index].tv = 0.0f; index++; // Bottom right. m_TerrainModel[index].x = m_heightMap[index2].x; m_TerrainModel[index].y = m_heightMap[index2].y; m_TerrainModel[index].z = m_heightMap[index2].z; m_TerrainModel[index].nx = m_heightMap[index2].nx; m_TerrainModel[index].ny = m_heightMap[index2].ny; m_TerrainModel[index].nz = m_heightMap[index2].nz; m_TerrainModel[index].tu = 1.0f; m_TerrainModel[index].tv = 1.0f; index++; } } return true; } void TerrainClass::ReleaseTerrainModel() { if(m_TerrainModel) { delete [] m_TerrainModel; m_TerrainModel = 0; } return; }
CalculateTerrainVectors is the function that goes through each triangle in the terrain model and calculates and stores the tangent and binormal vectors.
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_TerrainModel[index].x; vertex1.y = m_TerrainModel[index].y; vertex1.z = m_TerrainModel[index].z; vertex1.tu = m_TerrainModel[index].tu; vertex1.tv = m_TerrainModel[index].tv; vertex1.nx = m_TerrainModel[index].nx; vertex1.ny = m_TerrainModel[index].ny; vertex1.nz = m_TerrainModel[index].nz; index++; vertex2.x = m_TerrainModel[index].x; vertex2.y = m_TerrainModel[index].y; vertex2.z = m_TerrainModel[index].z; vertex2.tu = m_TerrainModel[index].tu; vertex2.tv = m_TerrainModel[index].tv; vertex2.nx = m_TerrainModel[index].nx; vertex2.ny = m_TerrainModel[index].ny; vertex2.nz = m_TerrainModel[index].nz; index++; vertex3.x = m_TerrainModel[index].x; vertex3.y = m_TerrainModel[index].y; vertex3.z = m_TerrainModel[index].z; vertex3.tu = m_TerrainModel[index].tu; vertex3.tv = m_TerrainModel[index].tv; vertex3.nx = m_TerrainModel[index].nx; vertex3.ny = m_TerrainModel[index].ny; vertex3.nz = m_TerrainModel[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_TerrainModel[index-1].tx = tangent.x; m_TerrainModel[index-1].ty = tangent.y; m_TerrainModel[index-1].tz = tangent.z; m_TerrainModel[index-1].bx = binormal.x; m_TerrainModel[index-1].by = binormal.y; m_TerrainModel[index-1].bz = binormal.z; m_TerrainModel[index-2].tx = tangent.x; m_TerrainModel[index-2].ty = tangent.y; m_TerrainModel[index-2].tz = tangent.z; m_TerrainModel[index-2].bx = binormal.x; m_TerrainModel[index-2].by = binormal.y; m_TerrainModel[index-2].bz = binormal.z; m_TerrainModel[index-3].tx = tangent.x; m_TerrainModel[index-3].ty = tangent.y; m_TerrainModel[index-3].tz = tangent.z; m_TerrainModel[index-3].bx = binormal.x; m_TerrainModel[index-3].by = binormal.y; m_TerrainModel[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; }
The InitializeBuffers function has been modified to build the vertex and index buffer from the terrain model instead of the height map data.
bool TerrainClass::InitializeBuffers(ID3D11Device* device) { VertexType* vertices; unsigned long* indices; int i; D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; D3D11_SUBRESOURCE_DATA vertexData, indexData; HRESULT result; // 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; } // Load the vertex and index array with data from the terrain model. for(i=0; i<m_vertexCount; i++) { vertices[i].position = D3DXVECTOR3(m_TerrainModel[i].x, m_TerrainModel[i].y, m_TerrainModel[i].z); vertices[i].texture = D3DXVECTOR2(m_TerrainModel[i].tu, m_TerrainModel[i].tv); vertices[i].normal = D3DXVECTOR3(m_TerrainModel[i].nx, m_TerrainModel[i].ny, m_TerrainModel[i].nz); vertices[i].tangent = D3DXVECTOR3(m_TerrainModel[i].tx, m_TerrainModel[i].ty, m_TerrainModel[i].tz); vertices[i].binormal = D3DXVECTOR3(m_TerrainModel[i].bx, m_TerrainModel[i].by, m_TerrainModel[i].bz); indices[i] = i; } // 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; }
LoadTextures now loads a color detail map texture and a normal map texture.
bool TerrainClass::LoadTextures(ID3D11Device* device, WCHAR* colorTexturefilename, WCHAR* normalMapFilename) { bool result; // Create the color texture object. m_ColorTexture = new TextureClass; if(!m_ColorTexture) { return false; } // Initialize the color texture object. result = m_ColorTexture->Initialize(device, colorTexturefilename); if(!result) { return false; } // Create the normal map texture object. m_NormalMapTexture = new TextureClass; if(!m_NormalMapTexture) { return false; } // Initialize the normal map texture object. result = m_NormalMapTexture->Initialize(device, normalMapFilename); if(!result) { return false; } return true; } void TerrainClass::ReleaseTextures() { // Release the normal map texture object. if(m_NormalMapTexture) { m_NormalMapTexture->Shutdown(); delete m_NormalMapTexture; m_NormalMapTexture = 0; } // Release the color texture object. if(m_ColorTexture) { m_ColorTexture->Shutdown(); delete m_ColorTexture; m_ColorTexture = 0; } return; }
Applicationclass.h
The ApplicationClass header has not been modified 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 = 1.0f; /////////////////////// // 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, -10.0f); m_Camera->RenderBaseViewMatrix(); m_Camera->GetBaseViewMatrix(baseViewMatrix); // Set the initial position of the camera. cameraX = 100.0f; cameraY = 2.0f; cameraZ = 5.0f; m_Camera->SetPosition(cameraX, cameraY, cameraZ); // Create the terrain object. m_Terrain = new TerrainClass; if(!m_Terrain) { return false; }
Initialization of the terrain now takes in the height map, color texture, and normal map.
// Initialize the terrain object. result = m_Terrain->Initialize(m_Direct3D->GetDevice(), "../Engine/data/heightmap01.bmp", L"../Engine/data/dirt.dds", L"../Engine/data/bump.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->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); m_Light->SetDirection(0.75f, -0.5f, 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, baseViewMatrix; 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, ortho, and base view matrices from the camera and Direct3D objects. m_Direct3D->GetWorldMatrix(worldMatrix); m_Camera->GetViewMatrix(viewMatrix); m_Direct3D->GetProjectionMatrix(projectionMatrix); m_Direct3D->GetOrthoMatrix(orthoMatrix); m_Camera->GetBaseViewMatrix(baseViewMatrix); // Render the terrain using the terrain shader. m_Terrain->Render(m_Direct3D->GetDeviceContext());
The Render function for the terrain shader now takes in the color texture and normal map inputs.
result = m_TerrainShader->Render(m_Direct3D->GetDeviceContext(), m_Terrain->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, m_Light->GetDiffuseColor(), m_Light->GetDirection(), m_Terrain->GetColorTexture(), m_Terrain->GetNormalMapTexture()); 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
The terrain is now rendered with bump mapping at close range to the camera and regular lighting for anything distant from the camera.
To Do Exercises
1. Compile and run the program. Use the arrow keys, A, Z, PgUp, and PgDn to navigate the terrain and look at the bump map effect. Press escape to quit.
2. Change the texture and normal map.
3. Change the pixel shader to output the color result without the color texture to see the bump map effect clearly on the terrain.
4. Modify the depth distance at which to stop bump mapping to something more desirable for your application needs.
Source Code
Source Code and Data Files: tersrc15.zip
Executable: terexe15.zip