One of the issues with large data sets is that you eventually need to create a partitioning scheme to deal with and process the data efficiently.
When we start rendering several kilometers of highly detailed terrain we find ourselves in that situation.
Just one square kilometer is already over two million polygons, and ideally we would like to render way more than one kilometer.
The simplest way to partition terrain is just to subdivide it into evenly sized nodes.
Now node is a very generic term so we will instead call our partitioned unit a cell.
Picking the right amount of data to store in each cell is also key to good performance.
Generally you don't want cells to be too large as the purpose of partitioning is so that we can do math on a smaller subset of polygons.
You also don't want them too small that the search to find the right cell is also causing a performance hit.
The key is to make the cell size adjustable so that you can quickly change the size and see the change in performance.
This will help you quickly determine the correct cell size for your purposes.
In this tutorial we will be splitting our terrain up into 33x33 vertex cells.
The following screenshot shows just three of the terrain cells rendered with an orange bounding box around each:
Now if we render those same three cells in wireframe you can see that each cell has 32 quads inside of it. And those 32 quads are composed of a 33x33 vertex set:
And finally if we draw the entire terrain rendering it cell by cell with the orange bounding box we can see our fully partitioned terrain:
Note that the primary reason for splitting the terrain up into cells is so that we can achieve rendering efficiency by determining and only drawing what should be visible.
However this tutorial will just be focused around getting the initial cell partitioning scheme in place.
Terrainclass.h
We have made some major changes to the TerrainClass.
First thing is that we have included the header for the new TerrainCellClass.
We also have a new private array of terrain cells.
And we also have initialization and shutdown functions for the terrain cells.
And the largest change is that we no longer build the vertex and index buffer for the terrain in this class,
all of that functionality has been moved into the TerrainCellClass.
The TerrainClass still reads the height map and builds the terrain model,
but we now send the terrain model to the cells so that each cell can construct its own buffers for rendering the portion of terrain assigned to it.
////////////////////////////////////////////////////////////////////////////////
// Filename: terrainclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINCLASS_H_
#define _TERRAINCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <fstream>
#include <stdio.h>
using namespace std;
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "terraincellclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainClass
////////////////////////////////////////////////////////////////////////////////
class TerrainClass
{
private:
struct HeightMapType
{
float x, y, z;
float nx, ny, nz;
float r, g, b;
};
struct ModelType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
float tx, ty, tz;
float bx, by, bz;
float r, g, b;
};
struct VectorType
{
float x, y, z;
};
struct TempVertexType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
};
public:
TerrainClass();
TerrainClass(const TerrainClass&);
~TerrainClass();
bool Initialize(ID3D11Device*, char*);
void Shutdown();
We have a number of new functions used for rendering the cells and rendering the orange line bounding boxes for each cell.
bool RenderCell(ID3D11DeviceContext*, int);
void RenderCellLines(ID3D11DeviceContext*, int);
int GetCellIndexCount(int);
int GetCellLinesIndexCount(int);
int GetCellCount();
private:
bool LoadSetupFile(char*);
bool LoadRawHeightMap();
void ShutdownHeightMap();
void SetTerrainCoordinates();
bool CalculateNormals();
bool LoadColorMap();
bool BuildTerrainModel();
void ShutdownTerrainModel();
void CalculateTerrainVectors();
void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&);
These are the two new functions that will create and load the terrain cells, as well as releasing the cells once we are done using them.
bool LoadTerrainCells(ID3D11Device*);
void ShutdownTerrainCells();
private:
int m_terrainHeight, m_terrainWidth, m_vertexCount;
float m_heightScale;
char *m_terrainFilename, *m_colorMapFilename;
HeightMapType* m_heightMap;
ModelType* m_terrainModel;
This is the new terrain cell array and a count variable to keep track of how many cells are in the array.
TerrainCellClass* m_TerrainCells;
int m_cellCount;
};
#endif
Terrainclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: terrainclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "terrainclass.h"
Initialize the new terrain cell array pointer to null in the class constructor.
TerrainClass::TerrainClass()
{
m_terrainFilename = 0;
m_colorMapFilename = 0;
m_heightMap = 0;
m_terrainModel = 0;
m_TerrainCells = 0;
}
TerrainClass::TerrainClass(const TerrainClass& other)
{
}
TerrainClass::~TerrainClass()
{
}
bool TerrainClass::Initialize(ID3D11Device* device, char* setupFilename)
{
bool result;
// Get the terrain filename, dimensions, and so forth from the setup file.
result = LoadSetupFile(setupFilename);
if(!result)
{
return false;
}
// Initialize the terrain height map with the data from the raw file.
result = LoadRawHeightMap();
if(!result)
{
return false;
}
// Setup the X and Z coordinates for the height map as well as scale the terrain height by the height scale value.
SetTerrainCoordinates();
// Calculate the normals for the terrain data.
result = CalculateNormals();
if(!result)
{
return false;
}
// Load in the color map for the terrain.
result = LoadColorMap();
if(!result)
{
return false;
}
// Now build the 3D model of the terrain.
result = BuildTerrainModel();
if(!result)
{
return false;
}
// We can now release the height map since it is no longer needed in memory once the 3D terrain model has been built.
ShutdownHeightMap();
// Calculate the tangent and binormal for the terrain model.
CalculateTerrainVectors();
Instead of creating vertex and index buffers for the whole terrain we now instead load the terrain model into the terrain cell array.
This function will also create the array of terrain cells before loading them with the terrain model data.
// Create and load the cells with the terrain data.
result = LoadTerrainCells(device);
if(!result)
{
return false;
}
// Release the terrain model now that the terrain cells have been loaded.
ShutdownTerrainModel();
return true;
}
The Shutdown function now calls the new ShutdownTerrainCells function to release the terrain cell data when we are done using it.
void TerrainClass::Shutdown()
{
// Release the terrain cells.
ShutdownTerrainCells();
// Release the terrain model.
ShutdownTerrainModel();
// Release the height map.
ShutdownHeightMap();
return;
}
bool TerrainClass::LoadSetupFile(char* filename)
{
int stringLength;
ifstream fin;
char input;
// Initialize the strings that will hold the terrain file name and the color map file name.
stringLength = 256;
m_terrainFilename = new char[stringLength];
if(!m_terrainFilename)
{
return false;
}
m_colorMapFilename = new char[stringLength];
if(!m_colorMapFilename)
{
return false;
}
// Open the setup file. If it could not open the file then exit.
fin.open(filename);
if(fin.fail())
{
return false;
}
// Read up to the terrain file name.
fin.get(input);
while(input != ':')
{
fin.get(input);
}
// Read in the terrain file name.
fin >> m_terrainFilename;
// Read up to the value of terrain height.
fin.get(input);
while(input != ':')
{
fin.get(input);
}
// Read in the terrain height.
fin >> m_terrainHeight;
// Read up to the value of terrain width.
fin.get(input);
while (input != ':')
{
fin.get(input);
}
// Read in the terrain width.
fin >> m_terrainWidth;
// Read up to the value of terrain height scaling.
fin.get(input);
while (input != ':')
{
fin.get(input);
}
// Read in the terrain height scaling.
fin >> m_heightScale;
// Read up to the color map file name.
fin.get(input);
while(input != ':')
{
fin.get(input);
}
// Read in the color map file name.
fin >> m_colorMapFilename;
// Close the setup file.
fin.close();
return true;
}
bool TerrainClass::LoadRawHeightMap()
{
int error, i, j, index;
FILE* filePtr;
unsigned long long imageSize, count;
unsigned short* rawImage;
// Create the float array to hold the height map data.
m_heightMap = new HeightMapType[m_terrainWidth * m_terrainHeight];
if (!m_heightMap)
{
return false;
}
// Open the 16 bit raw height map file for reading in binary.
error = fopen_s(&filePtr, m_terrainFilename, "rb");
if (error != 0)
{
return false;
}
// Calculate the size of the raw image data.
imageSize = m_terrainHeight * m_terrainWidth;
// Allocate memory for the raw image data.
rawImage = new unsigned short[imageSize];
if(!rawImage)
{
return false;
}
// Read in the raw image data.
count = fread(rawImage, sizeof(unsigned short), imageSize, filePtr);
if(count != imageSize)
{
return false;
}
// Close the file.
error = fclose(filePtr);
if(error != 0)
{
return false;
}
// Copy the image data into the height map array.
for(j=0; j<m_terrainHeight; j++)
{
for(i=0; i<m_terrainWidth; i++)
{
index = (m_terrainWidth * j) + i;
// Store the height at this point in the height map array.
m_heightMap[index].y = (float)rawImage[index];
}
}
// Release the bitmap image data.
delete [] rawImage;
rawImage = 0;
// Release the terrain filename now that it has been read in.
delete [] m_terrainFilename;
m_terrainFilename = 0;
return true;
}
void TerrainClass::ShutdownHeightMap()
{
// Release the height map array.
if(m_heightMap)
{
delete [] m_heightMap;
m_heightMap = 0;
}
return;
}
void TerrainClass::SetTerrainCoordinates()
{
int i, j, index;
// Loop through all the elements in the height map array and adjust their coordinates correctly.
for(j=0; j<m_terrainHeight; j++)
{
for(i=0; i<m_terrainWidth; i++)
{
index = (m_terrainWidth * j) + i;
// Set the X and Z coordinates.
m_heightMap[index].x = (float)i;
m_heightMap[index].z = -(float)j;
// Move the terrain depth into the positive range. For example from (0, -256) to (256, 0).
m_heightMap[index].z += (float)(m_terrainHeight - 1);
// Scale the height.
m_heightMap[index].y /= m_heightScale;
}
}
return;
}
bool TerrainClass::CalculateNormals()
{
int i, j, index1, index2, index3, index;
float vertex1[3], vertex2[3], vertex3[3], vector1[3], vector2[3], sum[3], length;
VectorType* normals;
// Create a temporary array to hold the face 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+1) * m_terrainWidth) + i; // Bottom left vertex.
index2 = ((j+1) * m_terrainWidth) + (i+1); // Bottom right vertex.
index3 = (j * m_terrainWidth) + i; // Upper left vertex.
// 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]);
// Calculate the length.
length = (float)sqrt((normals[index].x * normals[index].x) + (normals[index].y * normals[index].y) +
(normals[index].z * normals[index].z));
// Normalize the final value for this face using the length.
normals[index].x = (normals[index].x / length);
normals[index].y = (normals[index].y / length);
normals[index].z = (normals[index].z / length);
}
}
// Now go through all the vertices and take a sum of the face normals that touch this 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;
// 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;
}
// Bottom right face.
if((i<(m_terrainWidth-1)) && ((j-1) >= 0))
{
index = ((j - 1) * (m_terrainWidth - 1)) + i;
sum[0] += normals[index].x;
sum[1] += normals[index].y;
sum[2] += normals[index].z;
}
// Upper left face.
if(((i-1) >= 0) && (j<(m_terrainHeight-1)))
{
index = (j * (m_terrainWidth-1)) + (i-1);
sum[0] += normals[index].x;
sum[1] += normals[index].y;
sum[2] += normals[index].z;
}
// Upper right face.
if((i < (m_terrainWidth-1)) && (j < (m_terrainHeight-1)))
{
index = (j * (m_terrainWidth-1)) + i;
sum[0] += normals[index].x;
sum[1] += normals[index].y;
sum[2] += normals[index].z;
}
// Calculate the length of this normal.
length = (float)sqrt((sum[0] * sum[0]) + (sum[1] * sum[1]) + (sum[2] * sum[2]));
// Get an index to the vertex location in the height map array.
index = (j * m_terrainWidth) + i;
// Normalize the final shared normal for this vertex and store it in the height map array.
m_heightMap[index].nx = (sum[0] / length);
m_heightMap[index].ny = (sum[1] / length);
m_heightMap[index].nz = (sum[2] / length);
}
}
// Release the temporary normals.
delete [] normals;
normals = 0;
return true;
}
bool TerrainClass::LoadColorMap()
{
int error, imageSize, i, j, k, index;
FILE* filePtr;
unsigned long long count;
BITMAPFILEHEADER bitmapFileHeader;
BITMAPINFOHEADER bitmapInfoHeader;
unsigned char* bitmapImage;
// Open the color map file in binary.
error = fopen_s(&filePtr, m_colorMapFilename, "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;
}
// Make sure the color map dimensions are the same as the terrain dimensions for easy 1 to 1 mapping.
if((bitmapInfoHeader.biWidth != m_terrainWidth) || (bitmapInfoHeader.biHeight != m_terrainHeight))
{
return false;
}
// Calculate the size of the bitmap image data.
// Since this is non-divide by 2 dimensions (eg. 257x257) need to add extra byte to each line.
imageSize = m_terrainHeight * ((m_terrainWidth * 3) + 1);
// 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;
}
// Initialize the position in the image data buffer.
k=0;
// Read the image data into the color map portion of the height map structure.
for(j=0; j<m_terrainHeight; j++)
{
for(i=0; i<m_terrainWidth; i++)
{
// Bitmaps are upside down so load bottom to top into the array.
index = (m_terrainWidth * (m_terrainHeight - 1 - j)) + i;
m_heightMap[index].b = (float)bitmapImage[k] / 255.0f;
m_heightMap[index].g = (float)bitmapImage[k + 1] / 255.0f;
m_heightMap[index].r = (float)bitmapImage[k + 2] / 255.0f;
k += 3;
}
// Compensate for extra byte at end of each line in non-divide by 2 bitmaps (eg. 257x257).
k++;
}
// Release the bitmap image data.
delete [] bitmapImage;
bitmapImage = 0;
// Release the color map filename now that is has been read in.
delete [] m_colorMapFilename;
m_colorMapFilename = 0;
return true;
}
bool TerrainClass::BuildTerrainModel()
{
int i, j, index, index1, index2, index3, index4;
// Calculate the number of vertices in the 3D terrain model.
m_vertexCount = (m_terrainHeight - 1) * (m_terrainWidth - 1) * 6;
// Create the 3D terrain model array.
m_terrainModel = new ModelType[m_vertexCount];
if(!m_terrainModel)
{
return false;
}
// Initialize the index into the height map array.
index = 0;
// Load the 3D terrain model with the height map terrain data.
// We will be creating 2 triangles for each of the four points in a quad.
for(j=0; j<(m_terrainHeight-1); j++)
{
for(i=0; i<(m_terrainWidth-1); i++)
{
// Get the indexes to the four points of the quad.
index1 = (m_terrainWidth * j) + i; // Upper left.
index2 = (m_terrainWidth * j) + (i+1); // Upper right.
index3 = (m_terrainWidth * (j+1)) + i; // Bottom left.
index4 = (m_terrainWidth * (j+1)) + (i+1); // Bottom right.
// Now create two triangles for that quad.
// Triangle 1 - Upper 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].tu = 0.0f;
m_terrainModel[index].tv = 0.0f;
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].r = m_heightMap[index1].r;
m_terrainModel[index].g = m_heightMap[index1].g;
m_terrainModel[index].b = m_heightMap[index1].b;
index++;
// Triangle 1 - Upper 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].tu = 1.0f;
m_terrainModel[index].tv = 0.0f;
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].r = m_heightMap[index2].r;
m_terrainModel[index].g = m_heightMap[index2].g;
m_terrainModel[index].b = m_heightMap[index2].b;
index++;
// Triangle 1 - Bottom 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].tu = 0.0f;
m_terrainModel[index].tv = 1.0f;
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].r = m_heightMap[index3].r;
m_terrainModel[index].g = m_heightMap[index3].g;
m_terrainModel[index].b = m_heightMap[index3].b;
index++;
// Triangle 2 - Bottom 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].tu = 0.0f;
m_terrainModel[index].tv = 1.0f;
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].r = m_heightMap[index3].r;
m_terrainModel[index].g = m_heightMap[index3].g;
m_terrainModel[index].b = m_heightMap[index3].b;
index++;
// Triangle 2 - Upper 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].tu = 1.0f;
m_terrainModel[index].tv = 0.0f;
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].r = m_heightMap[index2].r;
m_terrainModel[index].g = m_heightMap[index2].g;
m_terrainModel[index].b = m_heightMap[index2].b;
index++;
// Triangle 2 - Bottom 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].tu = 1.0f;
m_terrainModel[index].tv = 1.0f;
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].r = m_heightMap[index4].r;
m_terrainModel[index].g = m_heightMap[index4].g;
m_terrainModel[index].b = m_heightMap[index4].b;
index++;
}
}
return true;
}
void TerrainClass::ShutdownTerrainModel()
{
// Release the terrain model data.
if(m_terrainModel)
{
delete [] m_terrainModel;
m_terrainModel = 0;
}
return;
}
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 the tangent.
length = (float)sqrt((tangent.x * tangent.x) + (tangent.y * tangent.y) + (tangent.z * tangent.z));
// Normalize the tangent and then store it.
tangent.x = tangent.x / length;
tangent.y = tangent.y / length;
tangent.z = tangent.z / length;
// Calculate the length of the binormal.
length = (float)sqrt((binormal.x * binormal.x) + (binormal.y * binormal.y) + (binormal.z * binormal.z));
// Normalize the binormal and then store it.
binormal.x = binormal.x / length;
binormal.y = binormal.y / length;
binormal.z = binormal.z / length;
return;
}
LoadTerrainCells is the function where we create the array of terrain cell objects.
We specify the size of the cell (33x33 for this tutorial) and then create the array.
Once the array is created we loop through it and initialize each cell with its part of the terrain model.
We send a pointer to the terrain model and indices (i and j) for the current position of the cell so that it knows where to read in the data from the terrain model to build the current cell.
bool TerrainClass::LoadTerrainCells(ID3D11Device* device)
{
int cellHeight, cellWidth, cellRowCount, i, j, index;
bool result;
// Set the height and width of each terrain cell to a fixed 33x33 vertex array.
cellHeight = 33;
cellWidth = 33;
// Calculate the number of cells needed to store the terrain data.
cellRowCount = (m_terrainWidth-1) / (cellWidth-1);
m_cellCount = cellRowCount * cellRowCount;
// Create the terrain cell array.
m_TerrainCells = new TerrainCellClass[m_cellCount];
if(!m_TerrainCells)
{
return false;
}
// Loop through and initialize all the terrain cells.
for(j=0; j<cellRowCount; j++)
{
for(i=0; i<cellRowCount; i++)
{
index = (cellRowCount * j) + i;
result = m_TerrainCells[index].Initialize(device, m_terrainModel, i, j, cellHeight, cellWidth, m_terrainWidth);
if(!result)
{
return false;
}
}
}
return true;
}
The ShutdownTerrainCells is used to shutdown each of the cells and then release the array of terrain cell objects.
void TerrainClass::ShutdownTerrainCells()
{
int i;
// Release the terrain cell array.
if(m_TerrainCells)
{
for(i=0; i<m_cellCount; i++)
{
m_TerrainCells[i].Shutdown();
}
delete [] m_TerrainCells;
m_TerrainCells = 0;
}
return;
}
The following functions are used to render the individual terrain cells as well as the orange bounding boxes around each cell.
Each of the render functions takes as input the cell ID so it knows which cell to render or which cell to get the index count from.
In ZoneClass we will now need a loop to render all of the terrain cells and terrain cell bounding boxes.
bool TerrainClass::RenderCell(ID3D11DeviceContext* deviceContext, int cellId)
{
m_TerrainCells[cellId].Render(deviceContext);
return true;
}
void TerrainClass::RenderCellLines(ID3D11DeviceContext* deviceContext, int cellId)
{
m_TerrainCells[cellId].RenderLineBuffers(deviceContext);
return;
}
int TerrainClass::GetCellIndexCount(int cellId)
{
return m_TerrainCells[cellId].GetIndexCount();
}
int TerrainClass::GetCellLinesIndexCount(int cellId)
{
return m_TerrainCells[cellId].GetLineBuffersIndexCount();
}
int TerrainClass::GetCellCount()
{
return m_cellCount;
}
Terraincellclass.h
TerrainCellClass is a new class that encapsulates the functionality of rendering and other calculations for individual terrain cells.
Each terrain cell is created from a subset of the terrain model and represents a unique 33x33 vertex section of that terrain.
The ModelType structure in this class definition must be the same as the one in the TerrainClass definition.
We also have additional buffers and structures to build the orange line list bounding box around this cell for debugging purposes.
Pretty much everything related to rendering was just moved out of TerrainClass and placed in this new class.
////////////////////////////////////////////////////////////////////////////////
// Filename: terraincellclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINCELLCLASS_H_
#define _TERRAINCELLCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <directxmath.h>
using namespace DirectX;
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainCellClass
////////////////////////////////////////////////////////////////////////////////
class TerrainCellClass
{
private:
struct ModelType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
float tx, ty, tz;
float bx, by, bz;
float r, g, b;
};
struct VertexType
{
XMFLOAT3 position;
XMFLOAT2 texture;
XMFLOAT3 normal;
XMFLOAT3 tangent;
XMFLOAT3 binormal;
XMFLOAT3 color;
};
struct VectorType
{
float x, y, z;
};
struct ColorVertexType
{
XMFLOAT3 position;
XMFLOAT4 color;
};
public:
TerrainCellClass();
TerrainCellClass(const TerrainCellClass&);
~TerrainCellClass();
bool Initialize(ID3D11Device*, void*, int, int, int, int, int);
void Shutdown();
void Render(ID3D11DeviceContext*);
void RenderLineBuffers(ID3D11DeviceContext*);
int GetVertexCount();
int GetIndexCount();
int GetLineBuffersIndexCount();
void GetCellDimensions(float&, float&, float&, float&, float&, float&);
private:
bool InitializeBuffers(ID3D11Device*, int, int, int, int, int, ModelType*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);
void CalculateCellDimensions();
bool BuildLineBuffers(ID3D11Device*);
void ShutdownLineBuffers();
public:
VectorType* m_vertexList;
private:
int m_vertexCount, m_indexCount, m_lineIndexCount;
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer, *m_lineVertexBuffer, *m_lineIndexBuffer;;
float m_maxWidth, m_maxHeight, m_maxDepth, m_minWidth, m_minHeight, m_minDepth;
float m_positionX, m_positionY, m_positionZ;
};
#endif
Terraincellclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: terraincellclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "terraincellclass.h"
Initialize all of the member pointers to null in the class constructor.
TerrainCellClass::TerrainCellClass()
{
m_vertexList = 0;
m_vertexBuffer = 0;
m_indexBuffer = 0;
m_lineVertexBuffer = 0;
m_lineIndexBuffer = 0;
}
TerrainCellClass::TerrainCellClass(const TerrainCellClass& other)
{
}
TerrainCellClass::~TerrainCellClass()
{
}
The Initialize function creates the buffers for the terrain cell, calculates the dimensions of this terrain cell, and also builds the bounding box line buffers.
bool TerrainCellClass::Initialize(ID3D11Device* device, void* terrainModelPtr, int nodeIndexX, int nodeIndexY,
int cellHeight, int cellWidth, int terrainWidth)
{
ModelType* terrainModel;
bool result;
// Coerce the pointer to the terrain model into the model type.
terrainModel = (ModelType*)terrainModelPtr;
// Load the rendering buffers with the terrain data for this cell index.
result = InitializeBuffers(device, nodeIndexX, nodeIndexY, cellHeight, cellWidth, terrainWidth, terrainModel);
if(!result)
{
return false;
}
// Release the pointer to the terrain model now that we no longer need it.
terrainModel = 0;
// Calculuate the dimensions of this cell.
CalculateCellDimensions();
// Build the debug line buffers to produce the bounding box around this cell.
result = BuildLineBuffers(device);
if(!result)
{
return false;
}
return true;
}
Shutdown will release the buffers used to render the cell terrain data and the bounding box lines.
void TerrainCellClass::Shutdown()
{
// Release the line rendering buffers.
ShutdownLineBuffers();
// Release the cell rendering buffers.
ShutdownBuffers();
return;
}
The Render function will put the vertex and index buffer for this terrain cell on the GPU for rendering.
void TerrainCellClass::Render(ID3D11DeviceContext* deviceContext)
{
// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
return;
}
GetVertexCount and GetIndexCount return the number of vertices and indices in this terrain cell.
int TerrainCellClass::GetVertexCount()
{
return m_vertexCount;
}
int TerrainCellClass::GetIndexCount()
{
return m_indexCount;
}
InitializeBuffers creates the buffers used for rendering the terrain cell.
It also creates a vertex list that is used for other calculations such as determining the size of this cell.
The terrain model that was built in the TerrainClass is passed into this function,
and then an index into the terrain model is created based on the physical location of this cell using nodeIndexX and nodeIndexY.
bool TerrainCellClass::InitializeBuffers(ID3D11Device* device, int nodeIndexX, int nodeIndexY, int cellHeight, int cellWidth,
int terrainWidth, ModelType* terrainModel)
{
VertexType* vertices;
unsigned long* indices;
int i, j, modelIndex, index;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
// Calculate the number of vertices in this terrain cell.
m_vertexCount = (cellHeight - 1) * (cellWidth - 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;
}
// Setup the indexes into the terrain model data and the local vertex/index array.
modelIndex = ((nodeIndexX * (cellWidth - 1)) + (nodeIndexY * (cellHeight - 1) * (terrainWidth - 1))) * 6;
index = 0;
// Load the vertex array and index array with data.
for(j=0; j<(cellHeight - 1); j++)
{
for(i=0; i<((cellWidth - 1) * 6); i++)
{
vertices[index].position = XMFLOAT3(terrainModel[modelIndex].x, terrainModel[modelIndex].y, terrainModel[modelIndex].z);
vertices[index].texture = XMFLOAT2(terrainModel[modelIndex].tu, terrainModel[modelIndex].tv);
vertices[index].normal = XMFLOAT3(terrainModel[modelIndex].nx, terrainModel[modelIndex].ny, terrainModel[modelIndex].nz);
vertices[index].tangent = XMFLOAT3(terrainModel[modelIndex].tx, terrainModel[modelIndex].ty, terrainModel[modelIndex].tz);
vertices[index].binormal = XMFLOAT3(terrainModel[modelIndex].bx, terrainModel[modelIndex].by, terrainModel[modelIndex].bz);
vertices[index].color = XMFLOAT3(terrainModel[modelIndex].r, terrainModel[modelIndex].g, terrainModel[modelIndex].b);
indices[index] = index;
modelIndex++;
index++;
}
modelIndex += (terrainWidth * 6) - (cellWidth * 6);
}
// 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;
}
// Create a public vertex array that will be used for accessing vertex information about this cell.
m_vertexList = new VectorType[m_vertexCount];
if(!m_vertexList)
{
return false;
}
// Keep a local copy of the vertex position data for this cell.
for(i=0; i<m_vertexCount; i++)
{
m_vertexList[i].x = vertices[i].position.x;
m_vertexList[i].y = vertices[i].position.y;
m_vertexList[i].z = vertices[i].position.z;
}
// Release the arrays now that the buffers have been created and loaded.
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return true;
}
The ShutdownBuffers releases the vertex list and the two buffers used for rendering the terrain cell.
void TerrainCellClass::ShutdownBuffers()
{
// Release the public vertex list.
if(m_vertexList)
{
delete [] m_vertexList;
m_vertexList = 0;
}
// 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;
}
RenderBuffers puts the terrain cell vertex and index buffer on the GPU for rendering.
void TerrainCellClass::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;
}
The new CalculateCellDimensions is used to determine the size of this cell.
It uses the vertex list we created in InitializeBuffers to do so.
void TerrainCellClass::CalculateCellDimensions()
{
int i;
float width, height, depth;
// Initialize the dimensions of the node.
m_maxWidth = -1000000.0f;
m_maxHeight = -1000000.0f;
m_maxDepth = -1000000.0f;
m_minWidth = 1000000.0f;
m_minHeight = 1000000.0f;
m_minDepth = 1000000.0f;
for(i=0; i<m_vertexCount; i++)
{
width = m_vertexList[i].x;
height = m_vertexList[i].y;
depth = m_vertexList[i].z;
// Check if the width exceeds the minimum or maximum.
if(width > m_maxWidth)
{
m_maxWidth = width;
}
if(width < m_minWidth)
{
m_minWidth = width;
}
// Check if the height exceeds the minimum or maximum.
if(height > m_maxHeight)
{
m_maxHeight = height;
}
if(height < m_minHeight)
{
m_minHeight = height;
}
// Check if the depth exceeds the minimum or maximum.
if(depth > m_maxDepth)
{
m_maxDepth = depth;
}
if(depth < m_minDepth)
{
m_minDepth = depth;
}
}
// Calculate the center position of this cell.
m_positionX = (m_maxWidth - m_minWidth) + m_minWidth;
m_positionY = (m_maxHeight - m_minHeight) + m_minHeight;
m_positionZ = (m_maxDepth - m_minDepth) + m_minDepth;
return;
}
BuildLineBuffers creates the bounding box that surrounds the terrain cell.
It is made up of a series of lines creating a box around the exact dimensions of the terrain cell.
This is used for debugging purposes mostly.
bool TerrainCellClass::BuildLineBuffers(ID3D11Device* device)
{
ColorVertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
XMFLOAT4 lineColor;
int index, vertexCount, indexCount;
// Set the color of the lines to orange.
lineColor = XMFLOAT4(1.0f, 0.5f, 0.0f, 1.0f);
// Set the number of vertices in the vertex array.
vertexCount = 24;
// Set the number of indices in the index array.
indexCount = vertexCount;
// Create the vertex array.
vertices = new ColorVertexType[vertexCount];
if(!vertices)
{
return false;
}
// Create the index array.
indices = new unsigned long[indexCount];
if(!indices)
{
return false;
}
// Set up the description of the vertex buffer.
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(ColorVertexType) * 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;
// Set up the description of the index buffer.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * 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;
// Load the vertex and index array with data.
index = 0;
// 8 Horizontal lines.
vertices[index].position = XMFLOAT3(m_minWidth, m_minHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_minHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_minHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_minHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_minHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_minHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_minHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_minHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_maxHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_maxHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_maxHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_maxHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_maxHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_maxHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_maxHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_maxHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
// 4 Verticle lines.
vertices[index].position = XMFLOAT3(m_maxWidth, m_maxHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_minHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_maxHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_minHeight, m_maxDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_maxHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_maxWidth, m_minHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_maxHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
index++;
vertices[index].position = XMFLOAT3(m_minWidth, m_minHeight, m_minDepth);
vertices[index].color = lineColor;
indices[index] = index;
// Create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_lineVertexBuffer);
if(FAILED(result))
{
return false;
}
// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_lineIndexBuffer);
if(FAILED(result))
{
return false;
}
// Store the index count for rendering.
m_lineIndexCount = indexCount;
// Release the arrays now that the vertex and index buffers have been created and loaded.
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return true;
}
The ShutdownLineBuffers function releases the vertex and index buffer that were used to render the bounding box around the terrain cell.
void TerrainCellClass::ShutdownLineBuffers()
{
// Release the index buffer.
if(m_lineIndexBuffer)
{
m_lineIndexBuffer->Release();
m_lineIndexBuffer = 0;
}
// Release the vertex buffer.
if(m_lineVertexBuffer)
{
m_lineVertexBuffer->Release();
m_lineVertexBuffer = 0;
}
return;
}
RenderLineBuffers puts the vertex and index buffer for the bounding box on the GPU for rendering.
It is rendered as a line list.
void TerrainCellClass::RenderLineBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;
// Set vertex buffer stride and offset.
stride = sizeof(ColorVertexType);
offset = 0;
// Set the vertex buffer to active in the input assembler so it can be rendered.
deviceContext->IASetVertexBuffers(0, 1, &m_lineVertexBuffer, &stride, &offset);
// Set the index buffer to active in the input assembler so it can be rendered.
deviceContext->IASetIndexBuffer(m_lineIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
// Set the type of primitive that should be rendered from this vertex buffer, in this case lines.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
return;
}
GetLineBuffersIndexCount returns the number of indices in the terrain cell bounding box buffer.
int TerrainCellClass::GetLineBuffersIndexCount()
{
return m_lineIndexCount;
}
The GetCellDimensions returns the physical sizing of the terrain cell.
void TerrainCellClass::GetCellDimensions(float& maxWidth, float& maxHeight, float& maxDepth,
float& minWidth, float& minHeight, float& minDepth)
{
maxWidth = m_maxWidth;
maxHeight = m_maxHeight;
maxDepth = m_maxDepth;
minWidth = m_minWidth;
minHeight = m_minHeight;
minDepth = m_minDepth;
return;
}
Zoneclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: zoneclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _ZONECLASS_H_
#define _ZONECLASS_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "inputclass.h"
#include "shadermanagerclass.h"
#include "texturemanagerclass.h"
#include "timerclass.h"
#include "userinterfaceclass.h"
#include "cameraclass.h"
#include "lightclass.h"
#include "positionclass.h"
#include "skydomeclass.h"
#include "terrainclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: ZoneClass
////////////////////////////////////////////////////////////////////////////////
class ZoneClass
{
public:
ZoneClass();
ZoneClass(const ZoneClass&);
~ZoneClass();
bool Initialize(D3DClass*, HWND, int, int, float);
void Shutdown();
bool Frame(D3DClass*, InputClass*, ShaderManagerClass*, TextureManagerClass*, float, int);
private:
void HandleMovementInput(InputClass*, float);
bool Render(D3DClass*, ShaderManagerClass*, TextureManagerClass*);
private:
UserInterfaceClass* m_UserInterface;
CameraClass* m_Camera;
LightClass* m_Light;
PositionClass* m_Position;
SkyDomeClass* m_SkyDome;
TerrainClass* m_Terrain;
The m_cellLines is a new boolean variable indicating whether the bounding boxes around the terrain cells should be drawn or not.
bool m_displayUI, m_wireFrame, m_cellLines;
};
#endif
Zoneclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: zoneclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "zoneclass.h"
ZoneClass::ZoneClass()
{
m_UserInterface = 0;
m_Camera = 0;
m_Light = 0;
m_Position = 0;
m_SkyDome = 0;
m_Terrain = 0;
}
ZoneClass::ZoneClass(const ZoneClass& other)
{
}
ZoneClass::~ZoneClass()
{
}
bool ZoneClass::Initialize(D3DClass* Direct3D, HWND hwnd, int screenWidth, int screenHeight, float screenDepth)
{
bool result;
// Create the user interface object.
m_UserInterface = new UserInterfaceClass;
if(!m_UserInterface)
{
return false;
}
// Initialize the user interface object.
result = m_UserInterface->Initialize(Direct3D, screenHeight, screenWidth);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the user interface object.", L"Error", MB_OK);
return false;
}
// Create the camera object.
m_Camera = new CameraClass;
if(!m_Camera)
{
return false;
}
// Set the initial position of the camera and build the matrices needed for rendering.
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
m_Camera->Render();
m_Camera->RenderBaseViewMatrix();
// 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.5f, -1.0f, -0.5f);
// Create the position object.
m_Position = new PositionClass;
if(!m_Position)
{
return false;
}
// Set the initial position and rotation.
m_Position->SetPosition(512.0f, 30.0f, -10.0f);
m_Position->SetRotation(0.0f, 0.0f, 0.0f);
// Create the sky dome object.
m_SkyDome = new SkyDomeClass;
if(!m_SkyDome)
{
return false;
}
// Initialize the sky dome object.
result = m_SkyDome->Initialize(Direct3D->GetDevice());
if(!result)
{
MessageBox(hwnd, L"Could not initialize the sky dome object.", L"Error", MB_OK);
return false;
}
// Create the terrain object.
m_Terrain = new TerrainClass;
if(!m_Terrain)
{
return false;
}
// Initialize the terrain object.
result = m_Terrain->Initialize(Direct3D->GetDevice(), "../Engine/data/setup.txt");
if(!result)
{
MessageBox(hwnd, L"Could not initialize the terrain object.", L"Error", MB_OK);
return false;
}
// Set the UI to display by default.
m_displayUI = true;
// Set wire frame rendering initially to disabled.
m_wireFrame = false;
Turn on the rendering of the bounding box around each terrain cell.
// Set the rendering of cell lines initially to enabled.
m_cellLines = true;
return true;
}
void ZoneClass::Shutdown()
{
// Release the terrain object.
if(m_Terrain)
{
m_Terrain->Shutdown();
delete m_Terrain;
m_Terrain = 0;
}
// Release the sky dome object.
if(m_SkyDome)
{
m_SkyDome->Shutdown();
delete m_SkyDome;
m_SkyDome = 0;
}
// Release the position object.
if(m_Position)
{
delete m_Position;
m_Position = 0;
}
// Release the light object.
if(m_Light)
{
delete m_Light;
m_Light = 0;
}
// Release the camera object.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
// Release the user interface object.
if(m_UserInterface)
{
m_UserInterface->Shutdown();
delete m_UserInterface;
m_UserInterface = 0;
}
return;
}
bool ZoneClass::Frame(D3DClass* Direct3D, InputClass* Input, ShaderManagerClass* ShaderManager, TextureManagerClass* TextureManager,
float frameTime, int fps)
{
bool result;
float posX, posY, posZ, rotX, rotY, rotZ;
// Do the frame input processing.
HandleMovementInput(Input, frameTime);
// Get the view point position/rotation.
m_Position->GetPosition(posX, posY, posZ);
m_Position->GetRotation(rotX, rotY, rotZ);
// Do the frame processing for the user interface.
result = m_UserInterface->Frame(Direct3D->GetDeviceContext(), fps, posX, posY, posZ, rotX, rotY, rotZ);
if(!result)
{
return false;
}
// Render the graphics.
result = Render(Direct3D, ShaderManager, TextureManager);
if(!result)
{
return false;
}
return true;
}
void ZoneClass::HandleMovementInput(InputClass* Input, float frameTime)
{
bool keyDown;
float posX, posY, posZ, rotX, rotY, rotZ;
// Set the frame time for calculating the updated position.
m_Position->SetFrameTime(frameTime);
// Handle the input.
keyDown = Input->IsLeftPressed();
m_Position->TurnLeft(keyDown);
keyDown = Input->IsRightPressed();
m_Position->TurnRight(keyDown);
keyDown = Input->IsUpPressed();
m_Position->MoveForward(keyDown);
keyDown = Input->IsDownPressed();
m_Position->MoveBackward(keyDown);
keyDown = Input->IsAPressed();
m_Position->MoveUpward(keyDown);
keyDown = Input->IsZPressed();
m_Position->MoveDownward(keyDown);
keyDown = Input->IsPgUpPressed();
m_Position->LookUpward(keyDown);
keyDown = 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);
// Determine if the user interface should be displayed or not.
if(Input->IsF1Toggled())
{
m_displayUI = !m_displayUI;
}
// Determine if the terrain should be rendered in wireframe or not.
if(Input->IsF2Toggled())
{
m_wireFrame = !m_wireFrame;
}
Use F3 to toggle the rendering of the terrain cell bounding box.
// Determine if we should render the lines around each terrain cell.
if(Input->IsF3Toggled())
{
m_cellLines = !m_cellLines;
}
return;
}
bool ZoneClass::Render(D3DClass* Direct3D, ShaderManagerClass* ShaderManager, TextureManagerClass* TextureManager)
{
XMMATRIX worldMatrix, viewMatrix, projectionMatrix, baseViewMatrix, orthoMatrix;
bool result;
XMFLOAT3 cameraPosition;
int i;
// Generate the view matrix based on the camera's position.
m_Camera->Render();
// Get the world, view, and projection matrices from the camera and d3d objects.
Direct3D->GetWorldMatrix(worldMatrix);
m_Camera->GetViewMatrix(viewMatrix);
Direct3D->GetProjectionMatrix(projectionMatrix);
m_Camera->GetBaseViewMatrix(baseViewMatrix);
Direct3D->GetOrthoMatrix(orthoMatrix);
// Get the position of the camera.
cameraPosition = m_Camera->GetPosition();
// Clear the buffers to begin the scene.
Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
// Turn off back face culling and turn off the Z buffer.
Direct3D->TurnOffCulling();
Direct3D->TurnZBufferOff();
// Translate the sky dome to be centered around the camera position.
worldMatrix = XMMatrixTranslation(cameraPosition.x, cameraPosition.y, cameraPosition.z);
// Render the sky dome using the sky dome shader.
m_SkyDome->Render(Direct3D->GetDeviceContext());
result = ShaderManager->RenderSkyDomeShader(Direct3D->GetDeviceContext(), m_SkyDome->GetIndexCount(), worldMatrix, viewMatrix,
projectionMatrix, m_SkyDome->GetApexColor(), m_SkyDome->GetCenterColor());
if(!result)
{
return false;
}
// Reset the world matrix.
Direct3D->GetWorldMatrix(worldMatrix);
// Turn the Z buffer back and back face culling on.
Direct3D->TurnZBufferOn();
Direct3D->TurnOnCulling();
// Turn on wire frame rendering of the terrain if needed.
if(m_wireFrame)
{
Direct3D->EnableWireframe();
}
We have modified how we render the terrain.
Since the terrain is composed of individual cells we now need to loop through all the cells that we want to render.
In the case of this tutorial we want to render all of them.
As well we render the cell lines using the color shader if we have the cell lines rendering toggled on.
// Render the terrain cells (and cell lines if needed).
for(i=0; i<m_Terrain->GetCellCount(); i++)
{
// Put the terrain cell buffers on the pipeline.
result = m_Terrain->RenderCell(Direct3D->GetDeviceContext(), i);
if(!result)
{
return false;
}
// Render the cell buffers using the terrain shader.
result = ShaderManager->RenderTerrainShader(Direct3D->GetDeviceContext(), m_Terrain->GetCellIndexCount(i), worldMatrix, viewMatrix,
projectionMatrix, TextureManager->GetTexture(0), TextureManager->GetTexture(1),
m_Light->GetDirection(), m_Light->GetDiffuseColor());
if(!result)
{
return false;
}
// If needed then render the bounding box around this terrain cell using the color shader.
if(m_cellLines)
{
m_Terrain->RenderCellLines(Direct3D->GetDeviceContext(), i);
ShaderManager->RenderColorShader(Direct3D->GetDeviceContext(), m_Terrain->GetCellLinesIndexCount(i), worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
}
}
// Turn off wire frame rendering of the terrain if it was on.
if(m_wireFrame)
{
Direct3D->DisableWireframe();
}
// Render the user interface.
if(m_displayUI)
{
result = m_UserInterface->Render(Direct3D, ShaderManager, worldMatrix, baseViewMatrix, orthoMatrix);
if(!result)
{
return false;
}
}
// Present the rendered scene to the screen.
Direct3D->EndScene();
return true;
}
Summary
We have now successfully partitioned our terrain so that we can move onto more efficient rendering in future tutorials.
To Do Exercises
1. Recompile the code in 64 bit mode and run the program. Toggle F3 to turn on and off the bounding boxes around each cell.
2. Render just a couple individual cells instead of the entire terrain.
3. Toggle on wireframe so you can see the exact number of quads in each terrain cell.
Source Code
Source Code and Data Files: dx11ter09_src.zip
Executable: dx11ter09_exe.zip