One of the drawbacks of our current terrain shader is that we are using a high frequency effect for the terrain normal mapping.
We are using a 512x512 normal map for each quad on the terrain, and at a distance those details can't entirely be seen and instead contribute to a repeating pattern.
For example if we render our terrain with just normal map lighting we currently get the following:

A simple way to correct this issue is to use a distance normal map that is mapped over a larger number of quads.
So when our shader determines a pixel is far enough away from the camera it switches to using the distance normal map.
The distance normal map provides the low frequency details we require.
Now when we render the same scene with only normal map lighting we get much better results with distant terrain:

The key to being able to correctly map a distance normal map is to use the depth of the pixel (which is easily calculated in the shader), and to provide a second set of uv coordinates that map over a larger area.
Currently the texture UV coordinates we have been providing are just a one to one mapping per quad.
For example if we render the test.tga with the current texture coordinate mapping we get the following:

But now we will generate a second set of UV coordinates that will map one to one for each terrain cell instead (so one texture per 64 quads basically).
Then the distance normal map can be sampled using the second set of uv coordinates and give a wider area to map the distant normal map to.
So now when we render the same scene with the test.tga file using the second set of coordinates we get the following mapping:

With these two sets of texture coordinates we can now quickly switch between which one to use based on the depth of the pixel that we are currently rendering.
Now for this tutorial I used just a single generic distance normal map that works well for mountainous rock. However most modern terrain generation programs such as World Machine can do tiled builds and output normal maps for each section of your terrain. Using multiple customized normal maps will allow you to have highly detailed and appropriate distance lighting which can help achieve greater realism in your rendered terrain.
Also note that distance normal maps need to be subtle.
If they are too detailed, or if they cause the lighting to vary too much, then it gives the type of an effect that you would expect from a displacement map.
And when you move closer and switch to the regular high frequency normal map it creates a very abrupt transition.
So that always needs to be watched for.
And even with a good distance normal map, there will still be a transition line where the pixel shader transitions between the two normal maps.
However, the distance of the transition line is usually close enough to the camera that we are still usually covering it up with trees and foliage.
And so it becomes hard to notice.
And finally, you can extend this technique past using just normal maps.
You can use different materials that cover more space, or render lower detail textures to gain rendering performance increases.
There is a lot you can do with the depth, and most of them fall into the "level of detail" category.
But for this tutorial I will just cover the basic distance normal map technique, and leave the rest of it for you to build upon.
For the code section will we only look at the code changes that differ from the last tutorial.
We will start with the TerrainClass code:
Terrainclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: terrainclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINCLASS_H_
#define _TERRAINCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <fstream>
using namespace std;
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "shadermanagerclass.h"
#include "textureclass.h"
#include "lightclass.h"
#include "terrainnodeclass.h"
#include "quadtreeclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainClass
////////////////////////////////////////////////////////////////////////////////
class TerrainClass
{
private:
struct VertexType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
float tx, ty, tz;
float bx, by, bz;
float r, g, b;
};
struct HeightMapType
{
float x, y, z;
float nx, ny, nz;
float r, g, b;
};
The terrain model type structure now includes a second set of uv texture coordinates for distance normal mapping.
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;
float tu2, tv2;
};
struct VectorType
{
float x, y, z;
};
struct TargaHeader
{
unsigned char data1[12];
unsigned short width;
unsigned short height;
unsigned char bpp;
unsigned char data2;
};
struct TempVertexType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
};
public:
TerrainClass();
TerrainClass(const TerrainClass&);
~TerrainClass();
bool Initialize(OpenGLClass*, char*);
void Shutdown(OpenGLClass*);
bool Render(OpenGLClass*, ShaderManagerClass*, LightClass*, FrustumClass*, float*, float*, float*);
void GetNodesDrawn(int&, int&);
bool GetHeightAtPosition(float, float, float&);
private:
bool LoadSetupFile(char*, char*, float&, char*, char*, char*);
bool LoadRawHeightMap(char*);
void SetTerrainCoordinates(float);
void CalculateNormals();
bool LoadColorMap(char*);
void BuildTerrainModel();
void ReleaseHeightMap();
void ReleaseTerrainModel();
void CalculateTerrainVectors();
void CalculateTangentBinormal(TempVertexType, TempVertexType, TempVertexType, VectorType&, VectorType&);
bool LoadTerrainNodes(OpenGLClass*);
void ReleaseTerrainNodes(OpenGLClass*);
void CalculateMeshDimensions();
private:
int m_vertexCount;
int m_terrainHeight, m_terrainWidth;
HeightMapType* m_heightMap;
ModelType* m_terrainModel;
TerrainNodeClass* m_Nodes;
int m_nodeCount;
We have added a third normal map which will be used for distance normal mapping.
TextureClass *m_Texture, *m_NormalMap, *m_NormalMap2, *m_NormalMap3;
QuadTreeClass* m_QuadTree;
int* m_renderList;
float m_minHeight, m_maxHeight;
};
#endif
Terrainclass.cpp
///////////////////////////////////////////////////////////////////////////////
// Filename: terrainclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "terrainclass.h"
TerrainClass::TerrainClass()
{
m_heightMap = 0;
m_terrainModel = 0;
m_Nodes = 0;
m_Texture = 0;
m_NormalMap = 0;
m_NormalMap2 = 0;
m_NormalMap3 = 0;
m_QuadTree = 0;
m_renderList = 0;
}
TerrainClass::TerrainClass(const TerrainClass& other)
{
}
TerrainClass::~TerrainClass()
{
}
bool TerrainClass::Initialize(OpenGLClass* OpenGL, char* setupFilename)
{
char terrainFilename[256], textureFilename[256], colorMapFilename[256], normalFilename[256], normalFilename2[256], normalFilename3[256];
float heightScale;
bool result;
// Get the terrain filename, dimensions, and so forth from the setup file.
result = LoadSetupFile(setupFilename, terrainFilename, heightScale, textureFilename, colorMapFilename, normalFilename);
if(!result)
{
return false;
}
// Initialize the terrain height map with the data from the raw file.
result = LoadRawHeightMap(terrainFilename);
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(heightScale);
// Calculate the normals for the terrain data.
CalculateNormals();
// Load in the color map for the terrain.
result = LoadColorMap(colorMapFilename);
if(!result)
{
return false;
}
// Now build the 3D model of the terrain.
BuildTerrainModel();
// We can now release the height map since it is no longer needed in memory once the 3D terrain model has been built.
ReleaseHeightMap();
// Calculate the tangent and binormal for the terrain model.
CalculateTerrainVectors();
// Calculate the dimensions of the mesh for the quad tree's usage.
CalculateMeshDimensions();
// Copy the terrain model data into the individual terrain nodes.
result = LoadTerrainNodes(OpenGL);
if(!result)
{
return false;
}
// Release the terrain model now that the nodes have been loaded.
ReleaseTerrainModel();
// Override the setup.txt textures with rock and snow textures.
strcpy(textureFilename, "../Engine/data/textures/rock01d.tga");
strcpy(normalFilename, "../Engine/data/textures/rock01n.tga");
strcpy(normalFilename2, "../Engine/data/textures/snow01n.tga");
Set the file name of the distance normal map texture.
strcpy(normalFilename3, "../Engine/data/textures/distance01n.tga");
// Create and initialize the diffuse texture object.
m_Texture = new TextureClass;
result = m_Texture->Initialize(OpenGL, textureFilename, false);
if(!result)
{
return false;
}
// Create and initialize the normal map texture object.
m_NormalMap = new TextureClass;
result = m_NormalMap->Initialize(OpenGL, normalFilename, false);
if(!result)
{
return false;
}
// Create and initialize the second normal map texture object.
m_NormalMap2 = new TextureClass;
result = m_NormalMap2->Initialize(OpenGL, normalFilename2, false);
if(!result)
{
return false;
}
Load the distance normal map texture.
// Create and initialize the third normal map texture object.
m_NormalMap3 = new TextureClass;
result = m_NormalMap3->Initialize(OpenGL, normalFilename3, false);
if(!result)
{
return false;
}
return true;
}
void TerrainClass::Shutdown(OpenGLClass* OpenGL)
{
Release the distance normal map texture.
// Release the third normal map texture object.
if(m_NormalMap3)
{
m_NormalMap3->Shutdown();
delete m_NormalMap3;
m_NormalMap3 = 0;
}
// Release the second normal map texture object.
if(m_NormalMap2)
{
m_NormalMap2->Shutdown();
delete m_NormalMap2;
m_NormalMap2 = 0;
}
// Release the normal map texture object.
if(m_NormalMap)
{
m_NormalMap->Shutdown();
delete m_NormalMap;
m_NormalMap = 0;
}
// Release the diffuse texture object.
if(m_Texture)
{
m_Texture->Shutdown();
delete m_Texture;
m_Texture = 0;
}
// Release the terrain nodes.
ReleaseTerrainNodes(OpenGL);
return;
}
bool TerrainClass::Render(OpenGLClass* OpenGL, ShaderManagerClass* ShaderManager, LightClass* Light, FrustumClass* Frustum, float* worldMatrix, float* viewMatrix, float* projectionMatrix)
{
float diffuseLightColor[4], lightDirection[3];
int i, j, renderCount;
bool result, wireFrame, renderNodeLines;
// Get the light properties.
Light->GetDirection(lightDirection);
Light->GetDiffuseColor(diffuseLightColor);
// Set wireframe mode off.
wireFrame = false;
// Set node bounding box line rendering on.
renderNodeLines = false;
// Enable wireframe mode to see triangles composing the terrain clearly.
if(wireFrame)
{
OpenGL->EnableWireframe();
}
// Get the list of nodes to render.
m_QuadTree->Render(Frustum, m_minHeight, m_maxHeight);
m_QuadTree->GetRenderList(m_renderList, renderCount);
// Set the terrain shader as the current shader program and set the matrices that it will use for rendering.
result = ShaderManager->RenderTerrainShader(worldMatrix, viewMatrix, projectionMatrix, lightDirection, diffuseLightColor);
if(!result)
{
return false;
}
// Set the diffuse texture for the terrain in the pixel shader texture unit 0.
m_Texture->SetTexture(OpenGL, 0);
// Set the normal map texture for the terrain in the pixel shader texture unit 1.
m_NormalMap->SetTexture(OpenGL, 1);
// Set the second normal map texture for the terrain in the pixel shader texture unit 2.
m_NormalMap2->SetTexture(OpenGL, 2);
When we render we will need to set the new distance normal map in the pixel shader.
// Set the distance normal map texture for the terrain in the pixel shader texture unit 3.
m_NormalMap3->SetTexture(OpenGL, 3);
// Put the node vertex and index buffers on the graphics pipeline to prepare them for drawing.
for(i=0; i<renderCount; i++)
{
// Only render the list of nodes that the quad tree determined are visible.
j = m_renderList[i];
m_Nodes[j].Render(OpenGL);
}
// Disable wireframe mode after rendering terrain.
if(wireFrame)
{
OpenGL->DisableWireframe();
}
// Render the node bounding box lines.
if(renderNodeLines)
{
result = ShaderManager->RenderColorShader(worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
for(i=0; i<renderCount; i++)
{
// Only render the node bounding boxes that the quad tree determined are visible.
j = m_renderList[i];
m_Nodes[j].RenderLines(OpenGL);
}
}
return true;
}
bool TerrainClass::LoadSetupFile(char* filename, char* terrainFilename, float& heightScale, char* textureFilename, char* colorMapFilename, char* normalFilename)
{
ifstream fin;
char input;
// 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 >> terrainFilename;
// Read up to the color map file name.
fin.get(input);
while(input != ':')
{
fin.get(input);
}
// Read in the color map file name.
fin >> colorMapFilename;
// 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 >> heightScale;
// Read up to the texture file name.
fin.get(input);
while(input != ':')
{
fin.get(input);
}
// Read in the texture file name.
fin >> textureFilename;
// Read up to the normal map texture file name.
fin.get(input);
while(input != ':')
{
fin.get(input);
}
// Read in the normal map file name.
fin >> normalFilename;
// Close the setup file.
fin.close();
return true;
}
bool TerrainClass::LoadRawHeightMap(char* terrainFilename)
{
FILE* filePtr;
unsigned short* rawImage;
unsigned int imageSize, count;
int error, i, j, index;
// Create the float array to hold the height map data.
m_heightMap = new HeightMapType[m_terrainWidth * m_terrainHeight];
// Open the 16 bit raw height map file for reading in binary.
filePtr = fopen(terrainFilename, "rb");
if(filePtr == NULL)
{
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];
// 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;
return true;
}
void TerrainClass::SetTerrainCoordinates(float heightScale)
{
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 /= heightScale;
}
}
return;
}
void 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)];
// 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;
}
bool TerrainClass::LoadColorMap(char* colorMapFilename)
{
FILE* filePtr;
TargaHeader targaFileHeader;
unsigned char* targaImage;
unsigned long count, imageSize;
int height, width, bpp, error, i, j, k, index;
// Open the color map file in binary.
filePtr = fopen(colorMapFilename, "rb");
if(filePtr == NULL)
{
return false;
}
// Read in the file header.
count = fread(&targaFileHeader, sizeof(TargaHeader), 1, filePtr);
if(count != 1)
{
return false;
}
// Get the important information from the header.
width = (int)targaFileHeader.width;
height = (int)targaFileHeader.height;
bpp = (int)targaFileHeader.bpp;
// Make sure the color map dimensions are the same as the terrain dimensions for easy 1 to 1 mapping.
if((height != m_terrainHeight) || (width != m_terrainWidth))
{
return false;
}
// Make sure we are dealing with 24 bit color maps.
if(bpp != 24)
{
return false;
}
// Calculate the size of the 24 bit targa image data.
imageSize = width * height * 3;
// Allocate memory for the targa image data.
targaImage = new unsigned char[imageSize];
// Read in the targa image data.
count = fread(targaImage, 1, imageSize, filePtr);
if(count != imageSize)
{
return false;
}
// Close the file.
error = fclose(filePtr);
if(error != 0)
{
return false;
}
// Initialize the position in the targa 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++)
{
// Targa are saved upside down by default in most image editors, so load bottom to top into the array.
index = (m_terrainWidth * (m_terrainHeight - 1 - j)) + i;
m_heightMap[index].r = (float)targaImage[k+2] / 255.0f;
m_heightMap[index].g = (float)targaImage[k+1] / 255.0f;
m_heightMap[index].b = (float)targaImage[k+0] / 255.0f;
k+=3;
}
}
// Release the targa image data.
delete [] targaImage;
targaImage = 0;
return true;
}
When we build the terrain model we need to incorporate the generation of a second set of UV texture coordinates for the distance normal mapping.
Since each cell is 65x65 vertices we will generate a second set of UV coordinates that map to those dimensions.
This way each cell can have its own distance normal map.
This also makes it easy to implement and debug.
void TerrainClass::BuildTerrainModel()
{
float quadsCovered, incrementSize, tu2Left, tu2Right, tv2Bottom, tv2Top;
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];
// Setup the increment size for the second set of textures.
// This is a 65x65 vertex array per cell so there will be 64 rows of quads in a cell.
quadsCovered = 64.0f;
incrementSize = 1.0f / quadsCovered;
// Initialize the texture increments.
tu2Left = 0.0f;
tu2Right = incrementSize;
tv2Top = 1.0f;
tv2Bottom = 1.0f - incrementSize;
// 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 = 1.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;
m_terrainModel[index].tu2 = tu2Left;
m_terrainModel[index].tv2 = tv2Top;
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 = 1.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;
m_terrainModel[index].tu2 = tu2Right;
m_terrainModel[index].tv2 = tv2Top;
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 = 0.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;
m_terrainModel[index].tu2 = tu2Left;
m_terrainModel[index].tv2 = tv2Bottom;
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 = 0.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;
m_terrainModel[index].tu2 = tu2Left;
m_terrainModel[index].tv2 = tv2Bottom;
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 = 1.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;
m_terrainModel[index].tu2 = tu2Right;
m_terrainModel[index].tv2 = tv2Top;
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 = 0.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;
m_terrainModel[index].tu2 = tu2Right;
m_terrainModel[index].tv2 = tv2Bottom;
index++;
// Increment the second tu texture coords.
tu2Left += incrementSize;
tu2Right += incrementSize;
// Reset the second tu texture coordinate increments.
if(tu2Right > 1.0f)
{
tu2Left = 0.0f;
tu2Right = incrementSize;
}
}
// Increment the second tv texture coords.
tv2Top -= incrementSize;
tv2Bottom -= incrementSize;
// Reset the second tu texture coordinate increments.
if(tv2Bottom < 0.0f)
{
tv2Top = 1.0f;
tv2Bottom = 1.0f - incrementSize;
}
}
return;
}
void TerrainClass::ReleaseHeightMap()
{
// Release the height map array.
if(m_heightMap)
{
delete [] m_heightMap;
m_heightMap = 0;
}
return;
}
void TerrainClass::ReleaseTerrainModel()
{
// Release the terrain model data.
if(m_terrainModel)
{
delete [] m_terrainModel;
m_terrainModel = 0;
}
return;
}
void TerrainClass::CalculateTerrainVectors()
{
int vertexCount, faceCount, i, index;
TempVertexType vertex1, vertex2, vertex3;
VectorType tangent, binormal;
// Calculate the number of vertices in the terrain.
vertexCount = (m_terrainHeight - 1) * (m_terrainWidth - 1) * 6;
// Calculate the number of faces in the terrain model.
faceCount = 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;
}
bool TerrainClass::LoadTerrainNodes(OpenGLClass* OpenGL)
{
int nodeWidth, arrayHeight, arrayWidth, i, j, index;
// Set the width of the node. 64 +1 (0-64) since dealing with 1025 sized terrain and need even division.
nodeWidth = 65;
// Set size of the node array that we are breaking the terrain up into. Must alight with the terrain size and the node width.
arrayHeight = 16;
arrayWidth = 16;
// Set the number of nodes in the 2D array.
m_nodeCount = arrayHeight * arrayWidth;
// Create the terrain node array.
m_Nodes = new TerrainNodeClass[m_nodeCount];
// Loop through and initialize all the terrain nodes.
for(j=0; j<arrayHeight; j++)
{
for(i=0; i<arrayWidth; i++)
{
index = (arrayWidth * j) + i;
m_Nodes[index].Initialize(OpenGL, m_terrainModel, m_terrainWidth, nodeWidth, nodeWidth, i, j);
}
}
// Create and initialize the quad tree object.
m_QuadTree = new QuadTreeClass;
m_QuadTree->Initialize((m_terrainWidth-1), (nodeWidth - 1), 512.0f, 512.0f);
// Create the render list for the quad tree.
m_renderList = new int[m_nodeCount];
return true;
}
void TerrainClass::ReleaseTerrainNodes(OpenGLClass* OpenGL)
{
int i;
// Release the render list.
if(m_renderList)
{
delete [] m_renderList;
m_renderList = 0;
}
// Release the quad tree object.
if(m_QuadTree)
{
m_QuadTree->Shutdown();
delete m_QuadTree;
m_QuadTree = 0;
}
// Release the terrain node array.
if(m_Nodes)
{
for(i=0; i<m_nodeCount; i++)
{
m_Nodes[i].Shutdown(OpenGL);
}
delete [] m_Nodes;
m_Nodes = 0;
}
return;
}
void TerrainClass::CalculateMeshDimensions()
{
int i;
// Determine the minimum and maximum height of the terrain for the quad tree object.
m_minHeight = m_terrainModel[0].y;
m_maxHeight = m_terrainModel[0].y;
for(i=1; i<m_vertexCount; i++)
{
if(m_terrainModel[i].y < m_minHeight)
{
m_minHeight = m_terrainModel[i].y;
}
if(m_terrainModel[i].y > m_maxHeight)
{
m_maxHeight = m_terrainModel[i].y;
}
}
return;
}
void TerrainClass::GetNodesDrawn(int& nodesDrawn, int& nodesCulled)
{
m_QuadTree->GetNodesDrawn(nodesDrawn, nodesCulled);
return;
}
bool TerrainClass::GetHeightAtPosition(float positionX, float positionZ, float& height)
{
int nodeID;
bool result;
result = m_QuadTree->GetHeightAtPosition(positionX, positionZ, nodeID);
if(!result)
{
return false;
}
m_Nodes[nodeID].GetHeightAtPosition(positionX, positionZ, height);
return true;
}
Terrainnodeclass.h
////////////////////////////////////////////////////////////////////////////////
// Filename: terrainnodeclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINNODECLASS_H_
#define _TERRAINNODECLASS_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "openglclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainNodeClass
////////////////////////////////////////////////////////////////////////////////
class TerrainNodeClass
{
private:
The model type structure in TerrainCellClass must mimic the model type structure in TerrainClass.
struct VertexType
{
float x, y, z;
float tu, tv;
float nx, ny, nz;
float tx, ty, tz;
float bx, by, bz;
float r, g, b;
float tu2, tv2;
};
struct LineVertexType
{
float x, y, z;
float r, g, b;
};
struct VectorType
{
float x, y, z;
};
public:
TerrainNodeClass();
TerrainNodeClass(const TerrainNodeClass&);
~TerrainNodeClass();
void Initialize(OpenGLClass*, void*, int, int, int, int, int);
void Shutdown(OpenGLClass*);
void Render(OpenGLClass*);
void RenderLines(OpenGLClass*);
void GetHeightAtPosition(float, float, float&);
private:
void InitializeBuffers(OpenGLClass*, VertexType*, int, int, int, int, int);
void ShutdownBuffers(OpenGLClass*);
void RenderBuffers(OpenGLClass*);
void BuildLines(OpenGLClass*);
void ReleaseLines(OpenGLClass*);
bool CheckHeightOfTriangle(float, float, float&, float[3], float[3], float[3]);
private:
int m_vertexCount, m_indexCount;
unsigned int m_vertexArrayId, m_vertexBufferId, m_indexBufferId;
int m_indexCount2;
unsigned int m_vertexArrayId2, m_vertexBufferId2, m_indexBufferId2;
float m_maxX, m_maxY, m_maxZ, m_minX, m_minY, m_minZ;
VectorType* m_vertexArray;
};
#endif
Terrainnodeclass.cpp
///////////////////////////////////////////////////////////////////////////////
// Filename: terrainnodeclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "terrainnodeclass.h"
TerrainNodeClass::TerrainNodeClass()
{
m_vertexArray = 0;
}
TerrainNodeClass::TerrainNodeClass(const TerrainNodeClass& other)
{
}
TerrainNodeClass::~TerrainNodeClass()
{
}
void TerrainNodeClass::Initialize(OpenGLClass* OpenGL, void* terrainModelPtr, int terrainWidth, int nodeWidth, int nodeHeight, int nodeIndexX, int nodeIndexY)
{
VertexType* terrainModel;
// Coerce the pointer to the terrain model data into the vertex type of the model.
terrainModel = (VertexType*)terrainModelPtr;
// Copy just the node subset from the terrain model data.
InitializeBuffers(OpenGL, terrainModel, terrainWidth, nodeWidth, nodeHeight, nodeIndexX, nodeIndexY);
// Release the pointer to the terrain model.
terrainModel = 0;
// Build the debug lines for the node's bounding box.
BuildLines(OpenGL);
return;
}
void TerrainNodeClass::Shutdown(OpenGLClass* OpenGL)
{
// Release the vertex array.
if(m_vertexArray)
{
delete [] m_vertexArray;
m_vertexArray = 0;
}
// Release the debug bounding box lines rendering buffers.
ReleaseLines(OpenGL);
// Release the buffers used to render the terrain node.
ShutdownBuffers(OpenGL);
return;
}
void TerrainNodeClass::Render(OpenGLClass* OpenGL)
{
// Render the terrain node's buffers.
RenderBuffers(OpenGL);
return;
}
void TerrainNodeClass::InitializeBuffers(OpenGLClass* OpenGL, VertexType* terrainModel, int terrainWidth, int nodeHeight, int nodeWidth, int nodeIndexX, int nodeIndexY)
{
VertexType* vertices;
unsigned int* indices;
int modelIndex, index, i, j;
// Calculate the number of vertices in this terrain node.
m_vertexCount = (nodeHeight - 1) * (nodeWidth - 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];
// Create the index array.
indices = new unsigned int[m_indexCount];
// Create the position only vertex array used for height based movement.
m_vertexArray = new VectorType[m_vertexCount];
// Setup the indexes into the terrain model data we are using to build this smaller node subset.
modelIndex = ((nodeIndexX * (nodeWidth - 1)) + (nodeIndexY * (nodeHeight - 1) * (terrainWidth - 1))) * 6;
When we build the vertex buffer for this terrain cell we now incorporate the second set of texture UV coordinates for distance normal mapping from the terrain model.
// Load the vertex array and index array with data.
index = 0;
for(j=0; j<(nodeHeight - 1); j++)
{
for(i=0; i<((nodeWidth - 1) * 6); i++)
{
vertices[index].x = terrainModel[modelIndex].x;
vertices[index].y = terrainModel[modelIndex].y;
vertices[index].z = terrainModel[modelIndex].z;
vertices[index].tu = terrainModel[modelIndex].tu;
vertices[index].tv = terrainModel[modelIndex].tv;
vertices[index].nx = terrainModel[modelIndex].nx;
vertices[index].ny = terrainModel[modelIndex].ny;
vertices[index].nz = terrainModel[modelIndex].nz;
vertices[index].r = terrainModel[modelIndex].r;
vertices[index].g = terrainModel[modelIndex].g;
vertices[index].b = terrainModel[modelIndex].b;
vertices[index].tx = terrainModel[modelIndex].tx;
vertices[index].ty = terrainModel[modelIndex].ty;
vertices[index].tz = terrainModel[modelIndex].tz;
vertices[index].bx = terrainModel[modelIndex].bx;
vertices[index].by = terrainModel[modelIndex].by;
vertices[index].bz = terrainModel[modelIndex].bz;
vertices[index].tu2 = terrainModel[modelIndex].tu2;
vertices[index].tv2 = terrainModel[modelIndex].tv2;
// Store a copy for the triangle height lookup function.
m_vertexArray[index].x = terrainModel[modelIndex].x;
m_vertexArray[index].y = terrainModel[modelIndex].y;
m_vertexArray[index].z = terrainModel[modelIndex].z;
indices[index] = index;
modelIndex++;
index++;
}
modelIndex += (terrainWidth * 6) - (nodeWidth * 6);
}
// Allocate an OpenGL vertex array object.
OpenGL->glGenVertexArrays(1, &m_vertexArrayId);
// Bind the vertex array object to store all the buffers and vertex attributes we create here.
OpenGL->glBindVertexArray(m_vertexArrayId);
// Generate an ID for the vertex buffer.
OpenGL->glGenBuffers(1, &m_vertexBufferId);
// Bind the vertex buffer and load the vertex data into the vertex buffer.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId);
OpenGL->glBufferData(GL_ARRAY_BUFFER, m_vertexCount * sizeof(VertexType), vertices, GL_STATIC_DRAW);
Enable the second texture coordinates in the vertex array attributes.
// Enable the vertex array attributes.
OpenGL->glEnableVertexAttribArray(0); // Vertex position.
OpenGL->glEnableVertexAttribArray(1); // Texture coordinates.
OpenGL->glEnableVertexAttribArray(2); // Normals.
OpenGL->glEnableVertexAttribArray(3); // Tangent
OpenGL->glEnableVertexAttribArray(4); // Binormal
OpenGL->glEnableVertexAttribArray(5); // Color.
OpenGL->glEnableVertexAttribArray(6); // Texture coordinates two.
// Specify the location and format of the position portion of the vertex buffer.
OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(VertexType), 0);
// Specify the location and format of the texture portion of the vertex buffer.
OpenGL->glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (3 * sizeof(float)));
// Specify the location and format of the normal vector portion of the vertex buffer.
OpenGL->glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (5 * sizeof(float)));
// Specify the location and format of the tangent vector portion of the vertex buffer.
OpenGL->glVertexAttribPointer(3, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (8 * sizeof(float)));
// Specify the location and format of the binormal vector portion of the vertex buffer.
OpenGL->glVertexAttribPointer(4, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (11 * sizeof(float)));
// Specify the location and format of the color vector portion of the vertex buffer.
OpenGL->glVertexAttribPointer(5, 3, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (14 * sizeof(float)));
The vertex layout now includes the second set of texture coordinates.
// Specify the location and format of the second texture portion of the vertex buffer.
OpenGL->glVertexAttribPointer(6, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (17 * sizeof(float)));
// Generate an ID for the index buffer.
OpenGL->glGenBuffers(1, &m_indexBufferId);
// Bind the index buffer and load the index data into it.
OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId);
OpenGL->glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount* sizeof(unsigned int), indices, GL_STATIC_DRAW);
// Calculate the node dimensions so we can build bounding box lines.
m_maxX = vertices[0].x;
m_minX = vertices[0].x;
m_maxY = vertices[0].y;
m_minY = vertices[0].y;
m_maxZ = vertices[0].z;
m_minZ = vertices[0].z;
for(i=1; i<m_vertexCount; i++)
{
if(vertices[i].x > m_maxX) { m_maxX = vertices[i].x; }
if(vertices[i].x < m_minX) { m_minX = vertices[i].x; }
if(vertices[i].y > m_maxY) { m_maxY = vertices[i].y; }
if(vertices[i].y < m_minY) { m_minY = vertices[i].y; }
if(vertices[i].z > m_maxZ) { m_maxZ = vertices[i].z; }
if(vertices[i].z < m_minZ) { m_minZ = vertices[i].z; }
}
// Now that the buffers have been loaded we can release the array data.
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return;
}
void TerrainNodeClass::ShutdownBuffers(OpenGLClass* OpenGL)
{
// Release the vertex array object.
OpenGL->glBindVertexArray(0);
OpenGL->glDeleteVertexArrays(1, &m_vertexArrayId);
// Release the vertex buffer.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, 0);
OpenGL->glDeleteBuffers(1, &m_vertexBufferId);
// Release the index buffer.
OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
OpenGL->glDeleteBuffers(1, &m_indexBufferId);
return;
}
void TerrainNodeClass::RenderBuffers(OpenGLClass* OpenGL)
{
// Bind the vertex array object that stored all the information about the vertex and index buffers.
OpenGL->glBindVertexArray(m_vertexArrayId);
// Render the vertex buffer as triangles using the index buffer.
glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);
return;
}
void TerrainNodeClass::BuildLines(OpenGLClass* OpenGL)
{
LineVertexType* vertices;
unsigned int* indices;
float red, green, blue;
int vertexCount2, index;
// Set the color of the bound box lines to yellow.
red = 1.0f;
green = 1.0f;
blue = 0.0f;
// Set the number of vertices for the 12 lines in the box.
vertexCount2 = 24;
// Set the index count to the same as the vertex count.
m_indexCount2 = vertexCount2;
// Create the vertex array.
vertices = new LineVertexType[vertexCount2];
// Create the index array.
indices = new unsigned int[m_indexCount2];
// Initialize the index into the vertices.
index = 0;
// Base 4 lines.
vertices[index].x = m_minX;
vertices[index].y = m_minY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_minY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_minY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_minY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_minY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_minY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_minY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_minY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
// Top 4 lines.
vertices[index].x = m_minX;
vertices[index].y = m_maxY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_maxY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_maxY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_maxY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_maxY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_maxY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_maxY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_maxY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
// Vertical 4 lines.
vertices[index].x = m_minX;
vertices[index].y = m_minY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_maxY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_minY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_maxY;
vertices[index].z = m_minZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_minY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_minX;
vertices[index].y = m_maxY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_minY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
vertices[index].x = m_maxX;
vertices[index].y = m_maxY;
vertices[index].z = m_maxZ;
vertices[index].r = red;
vertices[index].g = green;
vertices[index].b = blue;
indices[index] = index;
index++;
// Allocate an OpenGL vertex array object.
OpenGL->glGenVertexArrays(1, &m_vertexArrayId2);
// Bind the vertex array object to store all the buffers and vertex attributes we create here.
OpenGL->glBindVertexArray(m_vertexArrayId2);
// Generate an ID for the vertex buffer.
OpenGL->glGenBuffers(1, &m_vertexBufferId2);
// Bind the vertex buffer and load the vertex data into the vertex buffer.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId2);
OpenGL->glBufferData(GL_ARRAY_BUFFER, vertexCount2 * sizeof(LineVertexType), vertices, GL_STATIC_DRAW);
// Enable the two vertex array attributes.
OpenGL->glEnableVertexAttribArray(0); // Vertex position.
OpenGL->glEnableVertexAttribArray(1); // Vertex color.
// Specify the location and format of the position portion of the vertex buffer.
OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(LineVertexType), 0);
// Specify the location and format of the color portion of the vertex buffer.
OpenGL->glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof(LineVertexType), (unsigned char*)NULL + (3 * sizeof(float)));
// Generate an ID for the index buffer.
OpenGL->glGenBuffers(1, &m_indexBufferId2);
// Bind the index buffer and load the index data into it.
OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId2);
OpenGL->glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount2 * sizeof(unsigned int), indices, GL_STATIC_DRAW);
// Now that the buffers have been loaded we can release the array data.
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return;
}
void TerrainNodeClass::ReleaseLines(OpenGLClass* OpenGL)
{
// Release the vertex array object.
OpenGL->glBindVertexArray(0);
OpenGL->glDeleteVertexArrays(1, &m_vertexArrayId2);
// Release the vertex buffer.
OpenGL->glBindBuffer(GL_ARRAY_BUFFER, 0);
OpenGL->glDeleteBuffers(1, &m_vertexBufferId2);
// Release the index buffer.
OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
OpenGL->glDeleteBuffers(1, &m_indexBufferId2);
return;
}
void TerrainNodeClass::RenderLines(OpenGLClass* OpenGL)
{
// Bind the vertex array object that stored all the information about the vertex and index buffers.
OpenGL->glBindVertexArray(m_vertexArrayId2);
// Render the vertex buffer as lines using the index buffer.
glDrawElements(GL_LINES, m_indexCount2, GL_UNSIGNED_INT, 0);
return;
}
void TerrainNodeClass::GetHeightAtPosition(float positionX, float positionZ, float& height)
{
float vertex1[3], vertex2[3], vertex3[3];
int i, index;
bool foundHeight;
// Check all the polygons in this node to find the height of which one the polygon we are looking for.
for(i=0; i<(m_vertexCount/3); i++)
{
index = i * 3;
vertex1[0] = m_vertexArray[index].x;
vertex1[1] = m_vertexArray[index].y;
vertex1[2] = m_vertexArray[index].z;
index++;
vertex2[0] = m_vertexArray[index].x;
vertex2[1] = m_vertexArray[index].y;
vertex2[2] = m_vertexArray[index].z;
index++;
vertex3[0] = m_vertexArray[index].x;
vertex3[1] = m_vertexArray[index].y;
vertex3[2] = m_vertexArray[index].z;
// Check to see if this is the polygon we are looking for.
foundHeight = CheckHeightOfTriangle(positionX, positionZ, height, vertex1, vertex2, vertex3);
// If this was the triangle then quit the function and the height will be returned to the calling function.
if(foundHeight)
{
return;
}
}
return;
}
bool TerrainNodeClass::CheckHeightOfTriangle(float x, float z, float& height, float v0[3], float v1[3], float v2[3])
{
float startVector[3], directionVector[3], edge1[3], edge2[3], normal[3];
float Q[3], e1[3], e2[3], e3[3], edgeNormal[3], temp[3];
float magnitude, D, denominator, numerator, t, determinant;
// Starting position of the ray that is being cast.
startVector[0] = x;
startVector[1] = 0.0f;
startVector[2] = z;
// The direction the ray is being cast.
directionVector[0] = 0.0f;
directionVector[1] = -1.0f;
directionVector[2] = 0.0f;
// Calculate the two edges from the three points given.
edge1[0] = v1[0] - v0[0];
edge1[1] = v1[1] - v0[1];
edge1[2] = v1[2] - v0[2];
edge2[0] = v2[0] - v0[0];
edge2[1] = v2[1] - v0[1];
edge2[2] = v2[2] - v0[2];
// Calculate the normal of the triangle from the two edges.
normal[0] = (edge1[1] * edge2[2]) - (edge1[2] * edge2[1]);
normal[1] = (edge1[2] * edge2[0]) - (edge1[0] * edge2[2]);
normal[2] = (edge1[0] * edge2[1]) - (edge1[1] * edge2[0]);
magnitude = (float)sqrt((normal[0] * normal[0]) + (normal[1] * normal[1]) + (normal[2] * normal[2]));
normal[0] = normal[0] / magnitude;
normal[1] = normal[1] / magnitude;
normal[2] = normal[2] / magnitude;
// Find the distance from the origin to the plane.
D = ((-normal[0] * v0[0]) + (-normal[1] * v0[1]) + (-normal[2] * v0[2]));
// Get the denominator of the equation.
denominator = ((normal[0] * directionVector[0]) + (normal[1] * directionVector[1]) + (normal[2] * directionVector[2]));
// Make sure the result doesn't get too close to zero to prevent divide by zero.
if(fabs(denominator) < 0.0001f)
{
return false;
}
// Get the numerator of the equation.
numerator = -1.0f * (((normal[0] * startVector[0]) + (normal[1] * startVector[1]) + (normal[2] * startVector[2])) + D);
// Calculate where we intersect the triangle.
t = numerator / denominator;
// Find the intersection vector.
Q[0] = startVector[0] + (directionVector[0] * t);
Q[1] = startVector[1] + (directionVector[1] * t);
Q[2] = startVector[2] + (directionVector[2] * t);
// Find the three edges of the triangle.
e1[0] = v1[0] - v0[0];
e1[1] = v1[1] - v0[1];
e1[2] = v1[2] - v0[2];
e2[0] = v2[0] - v1[0];
e2[1] = v2[1] - v1[1];
e2[2] = v2[2] - v1[2];
e3[0] = v0[0] - v2[0];
e3[1] = v0[1] - v2[1];
e3[2] = v0[2] - v2[2];
// Calculate the normal for the first edge.
edgeNormal[0] = (e1[1] * normal[2]) - (e1[2] * normal[1]);
edgeNormal[1] = (e1[2] * normal[0]) - (e1[0] * normal[2]);
edgeNormal[2] = (e1[0] * normal[1]) - (e1[1] * normal[0]);
// Calculate the determinant to see if it is on the inside, outside, or directly on the edge.
temp[0] = Q[0] - v0[0];
temp[1] = Q[1] - v0[1];
temp[2] = Q[2] - v0[2];
determinant = ((edgeNormal[0] * temp[0]) + (edgeNormal[1] * temp[1]) + (edgeNormal[2] * temp[2]));
// Check if it is outside.
if(determinant > 0.001f)
{
return false;
}
// Calculate the normal for the second edge.
edgeNormal[0] = (e2[1] * normal[2]) - (e2[2] * normal[1]);
edgeNormal[1] = (e2[2] * normal[0]) - (e2[0] * normal[2]);
edgeNormal[2] = (e2[0] * normal[1]) - (e2[1] * normal[0]);
// Calculate the determinant to see if it is on the inside, outside, or directly on the edge.
temp[0] = Q[0] - v1[0];
temp[1] = Q[1] - v1[1];
temp[2] = Q[2] - v1[2];
determinant = ((edgeNormal[0] * temp[0]) + (edgeNormal[1] * temp[1]) + (edgeNormal[2] * temp[2]));
// Check if it is outside.
if(determinant > 0.001f)
{
return false;
}
// Calculate the normal for the third edge.
edgeNormal[0] = (e3[1] * normal[2]) - (e3[2] * normal[1]);
edgeNormal[1] = (e3[2] * normal[0]) - (e3[0] * normal[2]);
edgeNormal[2] = (e3[0] * normal[1]) - (e3[1] * normal[0]);
// Calculate the determinant to see if it is on the inside, outside, or directly on the edge.
temp[0] = Q[0] - v2[0];
temp[1] = Q[1] - v2[1];
temp[2] = Q[2] - v2[2];
determinant = ((edgeNormal[0] * temp[0]) + (edgeNormal[1] * temp[1]) + (edgeNormal[2] * temp[2]));
// Check if it is outside.
if(determinant > 0.001f)
{
return false;
}
// Now we have our height.
height = Q[1];
return true;
}
Terrain.vs
The vertex shader has been modified to add both the new texture coordinates as well as a depth position that is sent to the pixel shader.
////////////////////////////////////////////////////////////////////////////////
// Filename: terrain.vs
////////////////////////////////////////////////////////////////////////////////
#version 400
/////////////////////
// INPUT VARIABLES //
/////////////////////
in vec3 inputPosition;
in vec2 inputTexCoord;
in vec3 inputNormal;
in vec3 inputTangent;
in vec3 inputBinormal;
in vec3 inputColor;
in vec2 inputTexCoord2;
//////////////////////
// OUTPUT VARIABLES //
//////////////////////
out vec2 texCoord;
out vec3 normal;
out vec3 tangent;
out vec3 binormal;
out vec4 color;
out vec2 texCoord2;
out vec4 depthPosition;
///////////////////////
// UNIFORM VARIABLES //
///////////////////////
uniform mat4 worldMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
void main(void)
{
// Calculate the position of the vertex against the world, view, and projection matrices.
gl_Position = vec4(inputPosition, 1.0f) * worldMatrix;
gl_Position = gl_Position * viewMatrix;
gl_Position = gl_Position * projectionMatrix;
// Store the texture coordinates for the pixel shader.
texCoord = inputTexCoord;
texCoord2 = inputTexCoord2;
// Calculate the normal vector against the world matrix only.
normal = inputNormal * mat3(worldMatrix);
// Normalize the normal vector.
normal = normalize(normal);
// Calculate the tangent vector against the world matrix only and then normalize the final value.
tangent = inputTangent * mat3(worldMatrix);
tangent = normalize(tangent);
// Calculate the binormal vector against the world matrix only and then normalize the final value.
binormal = inputBinormal * mat3(worldMatrix);
binormal = normalize(binormal);
// Store the input color for the pixel shader to use.
color = vec4(inputColor, 1.0f);
// Store the position value in a second input value for depth value calculations.
depthPosition = gl_Position;
}
Terrain.ps
In the modified terrain pixel shader we add a new normal map texture for the distance normal mapping.
The pixel input structure now has the second set of texture coordinates as well as the depth.
At the start of the shader we calculate the depth of this pixel using the input depth.
Then with that depth we sample either the regular normal map, or if it is further away we now sample the distance normal map and use the second set of uv texture coordinates to do so.
Note we only do this with the rock material as the snow should not have a distance normal map since it should appear flat.
////////////////////////////////////////////////////////////////////////////////
// Filename: terrain.ps
////////////////////////////////////////////////////////////////////////////////
#version 400
/////////////////////
// INPUT VARIABLES //
/////////////////////
in vec2 texCoord;
in vec3 normal;
in vec3 tangent;
in vec3 binormal;
in vec4 color;
in vec2 texCoord2;
in vec4 depthPosition;
//////////////////////
// OUTPUT VARIABLES //
//////////////////////
out vec4 outputColor;
///////////////////////
// UNIFORM VARIABLES //
///////////////////////
uniform sampler2D shaderTexture;
uniform sampler2D normalTexture;
uniform sampler2D normalTexture2;
uniform sampler2D normalTexture3;
uniform vec3 lightDirection;
uniform vec4 diffuseLightColor;
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
void main(void)
{
vec4 textureColor;
vec4 bumpMap;
vec3 bumpNormal;
vec3 lightDir;
float lightIntensity;
float slope;
vec4 lightColor;
vec4 material1;
vec4 material2;
vec4 finalColor;
float blendAmount;
float depthValue;
vec4 normalMapRock;
// Get the depth value of the pixel by dividing the Z pixel depth by the homogeneous W coordinate.
depthValue = depthPosition.z / depthPosition.w;
// Select the normal map for the first material based on the distance.
if(depthValue > 0.998f)
{
normalMapRock = texture(normalTexture3, texCoord2);
}
else
{
normalMapRock = texture(normalTexture, texCoord);
}
// Invert the light direction for calculations.
lightDir = -lightDirection;
// Setup the first material.
textureColor = texture(shaderTexture, texCoord);
bumpMap = (normalMapRock * 2.0f) - 1.0f; // Will now be near or distance LOD texture.
bumpNormal = (bumpMap.x * tangent) + (bumpMap.y * binormal) + (bumpMap.z * normal);
bumpNormal = normalize(bumpNormal);
lightIntensity = clamp(dot(bumpNormal, lightDir), 0.0f, 1.0f);
lightColor = clamp((diffuseLightColor * lightIntensity), 0.0f, 1.0f);
material1 = textureColor * lightColor;
// Setup the second material.
textureColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); // Snow color.
bumpMap = texture(normalTexture2, texCoord);
bumpMap = (bumpMap * 2.0f) - 1.0f;
bumpNormal = (bumpMap.x * tangent) + (bumpMap.y * binormal) + (bumpMap.z * normal);
bumpNormal = normalize(bumpNormal);
lightIntensity = clamp(dot(bumpNormal, lightDir), 0.0f, 1.0f);
lightColor = clamp((diffuseLightColor * lightIntensity), 0.0f, 1.0f);
material2 = textureColor * lightColor;
// Calculate the slope of this point.
slope = 1.0f - normal.y;
// Determine which material to use based on slope.
if(slope < 0.2)
{
blendAmount = slope / 0.2f;
finalColor = mix(material2, material1, blendAmount);
}
if(slope >= 0.2)
{
finalColor = material1;
}
outputColor = finalColor;
}
Terrainshaderclass.h
The TerrainShaderClass will now set an additional texture for the distance normal map.
It will also need to have it's vertex shader inputs updated.
////////////////////////////////////////////////////////////////////////////////
// Filename: terrainshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TERRAINSHADERCLASS_H_
#define _TERRAINSHADERCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <iostream>
using namespace std;
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "openglclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: TerrainShaderClass
////////////////////////////////////////////////////////////////////////////////
class TerrainShaderClass
{
public:
TerrainShaderClass();
TerrainShaderClass(const TerrainShaderClass&);
~TerrainShaderClass();
bool Initialize(OpenGLClass*);
void Shutdown();
bool SetShaderParameters(float*, float*, float*, float*, float*);
private:
bool InitializeShader(char*, char*);
void ShutdownShader();
char* LoadShaderSourceFile(char*);
void OutputShaderErrorMessage(unsigned int, char*);
void OutputLinkerErrorMessage(unsigned int);
private:
OpenGLClass* m_OpenGLPtr;
unsigned int m_vertexShader;
unsigned int m_fragmentShader;
unsigned int m_shaderProgram;
};
#endif
Terrainshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: terrainshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "terrainshaderclass.h"
TerrainShaderClass::TerrainShaderClass()
{
m_OpenGLPtr = 0;
}
TerrainShaderClass::TerrainShaderClass(const TerrainShaderClass& other)
{
}
TerrainShaderClass::~TerrainShaderClass()
{
}
bool TerrainShaderClass::Initialize(OpenGLClass* OpenGL)
{
char vsFilename[128];
char psFilename[128];
bool result;
// Store the pointer to the OpenGL object.
m_OpenGLPtr = OpenGL;
// Set the location and names of the shader files.
strcpy(vsFilename, "../Engine/terrain.vs");
strcpy(psFilename, "../Engine/terrain.ps");
// Initialize the vertex and pixel shaders.
result = InitializeShader(vsFilename, psFilename);
if(!result)
{
return false;
}
return true;
}
void TerrainShaderClass::Shutdown()
{
// Shutdown the shader.
ShutdownShader();
// Release the pointer to the OpenGL object.
m_OpenGLPtr = 0;
return;
}
bool TerrainShaderClass::InitializeShader(char* vsFilename, char* fsFilename)
{
const char* vertexShaderBuffer;
const char* fragmentShaderBuffer;
int status;
// Load the vertex shader source file into a text buffer.
vertexShaderBuffer = LoadShaderSourceFile(vsFilename);
if(!vertexShaderBuffer)
{
return false;
}
// Load the fragment shader source file into a text buffer.
fragmentShaderBuffer = LoadShaderSourceFile(fsFilename);
if(!fragmentShaderBuffer)
{
return false;
}
// Create a vertex and fragment shader object.
m_vertexShader = m_OpenGLPtr->glCreateShader(GL_VERTEX_SHADER);
m_fragmentShader = m_OpenGLPtr->glCreateShader(GL_FRAGMENT_SHADER);
// Copy the shader source code strings into the vertex and fragment shader objects.
m_OpenGLPtr->glShaderSource(m_vertexShader, 1, &vertexShaderBuffer, NULL);
m_OpenGLPtr->glShaderSource(m_fragmentShader, 1, &fragmentShaderBuffer, NULL);
// Release the vertex and fragment shader buffers.
delete [] vertexShaderBuffer;
vertexShaderBuffer = 0;
delete [] fragmentShaderBuffer;
fragmentShaderBuffer = 0;
// Compile the shaders.
m_OpenGLPtr->glCompileShader(m_vertexShader);
m_OpenGLPtr->glCompileShader(m_fragmentShader);
// Check to see if the vertex shader compiled successfully.
m_OpenGLPtr->glGetShaderiv(m_vertexShader, GL_COMPILE_STATUS, &status);
if(status != 1)
{
// If it did not compile then write the syntax error message out to a text file for review.
OutputShaderErrorMessage(m_vertexShader, vsFilename);
return false;
}
// Check to see if the fragment shader compiled successfully.
m_OpenGLPtr->glGetShaderiv(m_fragmentShader, GL_COMPILE_STATUS, &status);
if(status != 1)
{
// If it did not compile then write the syntax error message out to a text file for review.
OutputShaderErrorMessage(m_fragmentShader, fsFilename);
return false;
}
// Create a shader program object.
m_shaderProgram = m_OpenGLPtr->glCreateProgram();
// Attach the vertex and fragment shader to the program object.
m_OpenGLPtr->glAttachShader(m_shaderProgram, m_vertexShader);
m_OpenGLPtr->glAttachShader(m_shaderProgram, m_fragmentShader);
The vertex layout now includes the second set of texture coordinates.
// Bind the shader input variables.
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 0, "inputPosition");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 1, "inputTexCoord");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 2, "inputNormal");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 3, "inputTangent");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 4, "inputBinormal");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 5, "inputColor");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 6, "inputTexCoord2");
// Link the shader program.
m_OpenGLPtr->glLinkProgram(m_shaderProgram);
// Check the status of the link.
m_OpenGLPtr->glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, &status);
if(status != 1)
{
// If it did not link then write the syntax error message out to a text file for review.
OutputLinkerErrorMessage(m_shaderProgram);
return false;
}
return true;
}
void TerrainShaderClass::ShutdownShader()
{
// Detach the vertex and fragment shaders from the program.
m_OpenGLPtr->glDetachShader(m_shaderProgram, m_vertexShader);
m_OpenGLPtr->glDetachShader(m_shaderProgram, m_fragmentShader);
// Delete the vertex and fragment shaders.
m_OpenGLPtr->glDeleteShader(m_vertexShader);
m_OpenGLPtr->glDeleteShader(m_fragmentShader);
// Delete the shader program.
m_OpenGLPtr->glDeleteProgram(m_shaderProgram);
return;
}
char* TerrainShaderClass::LoadShaderSourceFile(char* filename)
{
FILE* filePtr;
char* buffer;
long fileSize, count;
int error;
// Open the shader file for reading in text modee.
filePtr = fopen(filename, "r");
if(filePtr == NULL)
{
return 0;
}
// Go to the end of the file and get the size of the file.
fseek(filePtr, 0, SEEK_END);
fileSize = ftell(filePtr);
// Initialize the buffer to read the shader source file into, adding 1 for an extra null terminator.
buffer = new char[fileSize + 1];
// Return the file pointer back to the beginning of the file.
fseek(filePtr, 0, SEEK_SET);
// Read the shader text file into the buffer.
count = fread(buffer, 1, fileSize, filePtr);
if(count != fileSize)
{
return 0;
}
// Close the file.
error = fclose(filePtr);
if(error != 0)
{
return 0;
}
// Null terminate the buffer.
buffer[fileSize] = '\0';
return buffer;
}
void TerrainShaderClass::OutputShaderErrorMessage(unsigned int shaderId, char* shaderFilename)
{
long count;
int logSize, error;
char* infoLog;
FILE* filePtr;
// Get the size of the string containing the information log for the failed shader compilation message.
m_OpenGLPtr->glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logSize);
// Increment the size by one to handle also the null terminator.
logSize++;
// Create a char buffer to hold the info log.
infoLog = new char[logSize];
// Now retrieve the info log.
m_OpenGLPtr->glGetShaderInfoLog(shaderId, logSize, NULL, infoLog);
// Open a text file to write the error message to.
filePtr = fopen("shader-error.txt", "w");
if(filePtr == NULL)
{
cout << "Error opening shader error message output file." << endl;
return;
}
// Write out the error message.
count = fwrite(infoLog, sizeof(char), logSize, filePtr);
if(count != logSize)
{
cout << "Error writing shader error message output file." << endl;
return;
}
// Close the file.
error = fclose(filePtr);
if(error != 0)
{
cout << "Error closing shader error message output file." << endl;
return;
}
// Notify the user to check the text file for compile errors.
cout << "Error compiling shader. Check shader-error.txt for error message. Shader filename: " << shaderFilename << endl;
return;
}
void TerrainShaderClass::OutputLinkerErrorMessage(unsigned int programId)
{
long count;
FILE* filePtr;
int logSize, error;
char* infoLog;
// Get the size of the string containing the information log for the failed shader compilation message.
m_OpenGLPtr->glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logSize);
// Increment the size by one to handle also the null terminator.
logSize++;
// Create a char buffer to hold the info log.
infoLog = new char[logSize];
// Now retrieve the info log.
m_OpenGLPtr->glGetProgramInfoLog(programId, logSize, NULL, infoLog);
// Open a file to write the error message to.
filePtr = fopen("linker-error.txt", "w");
if(filePtr == NULL)
{
cout << "Error opening linker error message output file." << endl;
return;
}
// Write out the error message.
count = fwrite(infoLog, sizeof(char), logSize, filePtr);
if(count != logSize)
{
cout << "Error writing linker error message output file." << endl;
return;
}
// Close the file.
error = fclose(filePtr);
if(error != 0)
{
cout << "Error closing linker error message output file." << endl;
return;
}
// Pop a message up on the screen to notify the user to check the text file for linker errors.
cout << "Error linking shader program. Check linker-error.txt for message." << endl;
return;
}
bool TerrainShaderClass::SetShaderParameters(float* worldMatrix, float* viewMatrix, float* projectionMatrix, float* lightDirection, float* diffuseLightColor)
{
float tpWorldMatrix[16], tpViewMatrix[16], tpProjectionMatrix[16];
int location;
// Transpose the matrices to prepare them for the shader.
m_OpenGLPtr->MatrixTranspose(tpWorldMatrix, worldMatrix);
m_OpenGLPtr->MatrixTranspose(tpViewMatrix, viewMatrix);
m_OpenGLPtr->MatrixTranspose(tpProjectionMatrix, projectionMatrix);
// Install the shader program as part of the current rendering state.
m_OpenGLPtr->glUseProgram(m_shaderProgram);
// Set the world matrix in the vertex shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "worldMatrix");
if(location == -1)
{
return false;
}
m_OpenGLPtr ->glUniformMatrix4fv(location, 1, false, tpWorldMatrix);
// Set the view matrix in the vertex shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "viewMatrix");
if(location == -1)
{
return false;
}
m_OpenGLPtr->glUniformMatrix4fv(location, 1, false, tpViewMatrix);
// Set the projection matrix in the vertex shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "projectionMatrix");
if(location == -1)
{
return false;
}
m_OpenGLPtr->glUniformMatrix4fv(location, 1, false, tpProjectionMatrix);
// Set the texture in the pixel shader to use the data from the first texture unit.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "shaderTexture");
if(location == -1)
{
cout << "Shader texture not set." << endl;
}
m_OpenGLPtr->glUniform1i(location, 0);
// Set the normal map texture in the pixel shader to use the data from the second texture unit.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "normalTexture");
if(location == -1)
{
cout << "Normal texture not set." << endl;
}
m_OpenGLPtr->glUniform1i(location, 1);
// Set the normal map texture in the pixel shader to use the data from the second texture unit.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "normalTexture2");
if(location == -1)
{
cout << "Normal texture two not set." << endl;
}
m_OpenGLPtr->glUniform1i(location, 2);
The distance normal map is set in the pixel shader here.
// Set the normal map texture in the pixel shader to use the data from the third texture unit.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "normalTexture3");
if(location == -1)
{
cout << "Normal texture three not set." << endl;
}
m_OpenGLPtr->glUniform1i(location, 3);
// Set the light direction in the pixel shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "lightDirection");
if(location == -1)
{
cout << "Light direction not set." << endl;
}
m_OpenGLPtr->glUniform3fv(location, 1, lightDirection);
// Set the diffuse light color in the pixel shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "diffuseLightColor");
if(location == -1)
{
cout << "Diffuse light color not set." << endl;
}
m_OpenGLPtr->glUniform4fv(location, 1, diffuseLightColor);
return true;
}
Summary
The appearance of our distant terrain has been improved by the use of a distance normal map.

To Do Exercises
1. Recompile the code and run the program. Move around watching the effect your distance has on the appearance of the new distance normal map.
2. Create your own distance normal map.
3. Play around with the depth value to modify when the distance normal map or the high quality normal map is used.
4. Try a different mapping other than one normal map per cell.
Source Code
Source Code and Data Files: gl4terlinux16.tar.gz