This OpenGL 4.0 terrain tutorial will cover how to implement clouds.
The particular implementation will be bitmap clouds which is one of the simpler methods to use.
The code in this tutorial builds off the previous sky dome tutorial as the sky dome is required for coloring the clouds.
To implement clouds we are going to first need some geometry to render the cloud texture onto.
The ideal geometry to use is a plane that is slightly curved.
You can create one in a modeling program but since they are very easy to build we will implement the geometry in the code so that we can modify the curvature as needed.
The plane model will be encapsulated in a class called SkyPlaneClass.
And just like the sky dome in the previous tutorial, we will always center the sky plane at the camera's location and place it just slightly above us.
This way as the camera moves in the world the sky plane moves with it.
For rendering the sky plane we will turn off the depth buffer so that it overwrites and combines with everything behind it giving the illusion that the geometry is much larger than what it actually is.
This was the same approach used with the sky dome.
And since we are generating the plane in the code we won't need to turn off back face culling as we will create the plane facing downwards.

Note that the edges of the sky plane will be visible so we will need to adjust for this in the shader.
The approach we will use is to apply an alpha texture that creates a fade effect at the edges.
This way it looks like the clouds blend into the horizon.
The clouds that we will be using are going to be a single bitmap texture that we will render onto the sky plane:

To color the clouds we simply turn on alpha blending and make sure that the sky dome is rendered first.
This way the clouds will blend with the sky dome's color:

Framework
The frame work has been updated to include the new SkyPlaneClass and SkyPlaneShaderClass:

We will begin the code section by examining the SkyPlaneClass first.
Skyplaneclass.h
The SkyPlaneClass encapsulates everything related to the plane used for rendering the clouds.
It holds the geometry for the sky plane, the bitmap texture for the clouds, and all the variables for the shader that relate to how to draw the sky plane.
////////////////////////////////////////////////////////////////////////////////
// Filename: skyplaneclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SKYPLANECLASS_H_
#define _SKYPLANECLASS_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "textureclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: SkyPlaneClass
////////////////////////////////////////////////////////////////////////////////
class SkyPlaneClass
{
private:
The SkyPlaneType structure is used for storing the sky plane geometry.
We generate position and texture coordinates for the plane and then store them in an array of SkyPlaneType.
There are no normals since the clouds use the sky dome for color and lighting appearance.
struct VertexType
{
float x, y, z;
float tu, tv;
};
public:
SkyPlaneClass();
SkyPlaneClass(const SkyPlaneClass&);
~SkyPlaneClass();
bool Initialize(OpenGLClass*);
void Shutdown(OpenGLClass*);
void Render(OpenGLClass*);
void Frame(float);
void SetCloudTexture(OpenGLClass*, unsigned int);
void SetFadeTexture(OpenGLClass*, unsigned int);
float GetTranslation(int);
private:
bool InitializeSkyPlane(int, float, float, float, int);
void ShutdownSkyPlane();
bool InitializeBuffers(OpenGLClass*, int);
void ShutdownBuffers(OpenGLClass*);
void RenderBuffers(OpenGLClass*);
bool LoadTextures(OpenGLClass*, char*, char*);
void ReleaseTextures();
private:
The m_skyPlane array is used to hold the plane geometry.
VertexType* m_skyPlane;
The sky plane will use two textures that are rendered to it.
TextureClass *m_CloudTexture, *m_FadeTexture;
int m_vertexCount, m_indexCount;
unsigned int m_vertexArrayId, m_vertexBufferId, m_indexBufferId;
The cloud location and speed are stored in these two arrays.
float m_translationSpeed[2];
float m_textureTranslation[2];
};
#endif
Skyplaneclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: skyplaneclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "skyplaneclass.h"
We set the class private member pointers to null in the class constructor.
SkyPlaneClass::SkyPlaneClass()
{
m_skyPlane = 0;
m_CloudTexture = 0;
m_FadeTexture = 0;
}
SkyPlaneClass::SkyPlaneClass(const SkyPlaneClass& other)
{
}
SkyPlaneClass::~SkyPlaneClass()
{
}
The Initialize function is where we do all the setup for the sky plane.
bool SkyPlaneClass::Initialize(OpenGLClass* OpenGL)
{
char filename1[256], filename2[256];
float skyPlaneWidth, skyPlaneTop, skyPlaneBottom;
int skyPlaneResolution, textureRepeat;
bool result;
// Set the names of the textures.
strcpy(filename1, "../Engine/data/textures/cloud001.tga");
strcpy(filename2, "../Engine/data/textures/fade002.tga");
Here is where we set the sky plane parameters that are used for generating the plane geometry.
The skyPlaneResolution is used for specifying how many quads that sky plane should be composed of in the X and Z direction, increasing this value makes it higher poly and smoother.
The skyPlaneWidth is the length of the plane.
The skyPlaneTop and skyPlaneBottom represent the height and base of the curved plane.
The bottom four corners of the plane will be at skyPlaneBottom and the center of the plane will be at skyPlaneTop.
All other points are interpolated between those two values.
The textureRepeat value determines how many times to repeat the texture over the sky plane.
This is used to generate the UV coordinates.
Note that a single 256x256 texture mapped just once over the sky plane will look fairly pixelated, therefore you either want to increase the texture size or map it multiple times to reduce how pixelated it looks.
Also note that the higher the resolution this program runs at the higher resolution the texture will need to be to look non-pixelated (likewise how many times it needs to be repeated).
The fade texture should not use the same UV coordinates if you are going to texture repeat several times.
// Set the sky plane parameters.
skyPlaneResolution = 10;
skyPlaneWidth = 10.0f;
skyPlaneTop = 0.5f;
skyPlaneBottom = 0.0f;
textureRepeat = 1;
The translation speed is how fast we translate the cloud texture over the sky plane.
The cloud can be translated on both the X and Z axis.
// Setup the cloud translation speed increments.
m_translationSpeed[0] = 0.00003f; // Texture X translation speed.
m_translationSpeed[1] = 0.0f; // Texture Z translation speed.
We also store the current translation for the texture and provide it to the pixel shader during rendering.
// Initialize the texture translation values.
m_textureTranslation[0] = 0.0f;
m_textureTranslation[1] = 0.0f;
Once all our values are set we then create the sky plane, load it into a vertex and index buffer, and then load the textures.
Note that I would generally make all the sky plane parameters as input to the Initialize function but it is easier for the tutorial explanation and for modifying them at first by having them inside the function for now.
// Create the sky plane.
result = InitializeSkyPlane(skyPlaneResolution, skyPlaneWidth, skyPlaneTop, skyPlaneBottom, textureRepeat);
if(!result)
{
return false;
}
// Create the vertex and index buffer for the sky plane.
result = InitializeBuffers(OpenGL, skyPlaneResolution);
if(!result)
{
return false;
}
// Release the sky plane array now that the buffers have been loaded.
ShutdownSkyPlane();
// Load the sky plane textures.
result = LoadTextures(OpenGL, filename1, filename2);
if(!result)
{
return false;
}
return true;
}
The Shutdown function is where we release the sky plane, the buffers, and the textures.
void SkyPlaneClass::Shutdown(OpenGLClass* OpenGL)
{
// Release the sky plane textures.
ReleaseTextures();
// Release the vertex and index buffer that were used for rendering the sky plane.
ShutdownBuffers(OpenGL);
// Release the sky plane array.
ShutdownSkyPlane();
return;
}
The Render function calls RenderBuffers to put the sky plane geometry on the graphics pipeline for drawing.
void SkyPlaneClass::Render(OpenGLClass* OpenGL)
{
// Render the sky plane.
RenderBuffers(OpenGL);
return;
}
The frame processing that we do for the sky plane is the cloud texture translation which simulates movement of the clouds across the sky.
The coordinates are translated according to the speed given for that direction.
Index 0 and 1 is for the X and Z on the clouds.
We also truncate the values so they never go over 1.0f.
The frame time is used to maintain the same speed cloud translation regardless of what speed the graphics are rendering at.
void SkyPlaneClass::Frame(float frameTime)
{
// Increment the translation values to simulate the moving clouds.
m_textureTranslation[0] += m_translationSpeed[0] * frameTime * 100.0f;
m_textureTranslation[1] += m_translationSpeed[1] * frameTime * 100.0f;
// Keep the values in the zero to one range.
if(m_textureTranslation[0] > 1.0f) { m_textureTranslation[0] -= 1.0f; }
if(m_textureTranslation[1] > 1.0f) { m_textureTranslation[1] -= 1.0f; }
return;
}
The SetCloudTexture function sets the cloud texture resource in the pixel shader.
void SkyPlaneClass::SetCloudTexture(OpenGLClass* OpenGL, unsigned int textureUnit)
{
m_CloudTexture->SetTexture(OpenGL, textureUnit);
return;
}
The SetFadeTexture function sets the fade texture resource in the pixel shader.
void SkyPlaneClass::SetFadeTexture(OpenGLClass* OpenGL, unsigned int textureUnit)
{
m_FadeTexture->SetTexture(OpenGL, textureUnit);
return;
}
The GetTranslation function returns the texture translation value for the given index.
float SkyPlaneClass::GetTranslation(int index)
{
return m_textureTranslation[index];
}
InitializeSkyPlane is where we build the geometry for the sky plane.
We first create an array to hold the geometry and then we setup the increment values needed to build the sky plane in the for loop.
Then we run the for loop and create the position and texture coordinates for each vertex based on the increment values.
This process builds the curved plane that we will use to render the clouds onto.
bool SkyPlaneClass::InitializeSkyPlane(int skyPlaneResolution, float skyPlaneWidth, float skyPlaneTop, float skyPlaneBottom, int textureRepeat)
{
float quadSize, radius, constant, textureDelta;
int i, j, index;
float positionX, positionY, positionZ, tu, tv;
// Create the array to hold the sky plane coordinates.
m_skyPlane = new VertexType[(skyPlaneResolution + 1) * (skyPlaneResolution + 1)];
// Determine the size of each quad on the sky plane.
quadSize = skyPlaneWidth / (float)skyPlaneResolution;
// Calculate the radius of the sky plane based on the width.
radius = skyPlaneWidth / 2.0f;
// Calculate the height constant to increment by.
constant = (skyPlaneTop - skyPlaneBottom) / (radius * radius);
// Calculate the texture coordinate increment value.
textureDelta = (float)textureRepeat / (float)skyPlaneResolution;
// Loop through the sky plane and build the coordinates based on the increment values given.
for(j=0; j<=skyPlaneResolution; j++)
{
for(i=0; i<=skyPlaneResolution; i++)
{
// Calculate the vertex coordinates.
positionX = (-0.5f * skyPlaneWidth) + ((float)i * quadSize);
positionZ = (-0.5f * skyPlaneWidth) + ((float)j * quadSize);
positionY = skyPlaneTop - (constant * ((positionX * positionX) + (positionZ * positionZ)));
// Calculate the texture coordinates.
tu = (float)i * textureDelta;
tv = (float)j * textureDelta;
// Calculate the index into the sky plane array to add this coordinate.
index = j * (skyPlaneResolution + 1) + i;
// Add the coordinates to the sky plane array.
m_skyPlane[index].x = positionX;
m_skyPlane[index].y = positionY;
m_skyPlane[index].z = positionZ;
m_skyPlane[index].tu = tu;
m_skyPlane[index].tv = tv;
}
}
return true;
}
The ShutdownSkyPlane function releases the array that was holding the sky plane geometry.
void SkyPlaneClass::ShutdownSkyPlane()
{
// Release the sky plane array.
if(m_skyPlane)
{
delete [] m_skyPlane;
m_skyPlane = 0;
}
return;
}
InitializeBuffers loads the array that was holding the sky plane geometry into a vertex and index buffer so that it can be rendered.
bool SkyPlaneClass::InitializeBuffers(OpenGLClass* OpenGL, int skyPlaneResolution)
{
VertexType* vertices;
unsigned int* indices;
int i, j, index, index1, index2, index3, index4;
int width, height;
// Set the dimensions.
width = skyPlaneResolution + 1;
height = skyPlaneResolution + 1;
// Calculate the number of vertices in the sky plane mesh.
m_vertexCount = (width-1) * (height-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];
// Initialize the index into the vertex array.
index = 0;
// Load the vertex and index array with the sky plane array data.
for(j=0; j<(height-1); j++)
{
for(i=0; i<(width-1); i++)
{
index1 = (width * j) + i;
index2 = (width * j) + (i+1);
index3 = (width * (j+1)) + i;
index4 = (width * (j+1)) + (i+1);
// Triangle 1 - Upper Left
vertices[index].x = m_skyPlane[index1].x;
vertices[index].y = m_skyPlane[index1].y;
vertices[index].z = m_skyPlane[index1].z;
vertices[index].tu = m_skyPlane[index1].tu;
vertices[index].tv = m_skyPlane[index1].tv;
indices[index] = index;
index++;
// Triangle 1 - Upper Right
vertices[index].x = m_skyPlane[index2].x;
vertices[index].y = m_skyPlane[index2].y;
vertices[index].z = m_skyPlane[index2].z;
vertices[index].tu = m_skyPlane[index2].tu;
vertices[index].tv = m_skyPlane[index2].tv;
indices[index] = index;
index++;
// Triangle 1 - Bottom Left
vertices[index].x = m_skyPlane[index3].x;
vertices[index].y = m_skyPlane[index3].y;
vertices[index].z = m_skyPlane[index3].z;
vertices[index].tu = m_skyPlane[index3].tu;
vertices[index].tv = m_skyPlane[index3].tv;
indices[index] = index;
index++;
// Triangle 2 - Bottom Left
vertices[index].x = m_skyPlane[index3].x;
vertices[index].y = m_skyPlane[index3].y;
vertices[index].z = m_skyPlane[index3].z;
vertices[index].tu = m_skyPlane[index3].tu;
vertices[index].tv = m_skyPlane[index3].tv;
indices[index] = index;
index++;
// Triangle 2 - Upper Right
vertices[index].x = m_skyPlane[index2].x;
vertices[index].y = m_skyPlane[index2].y;
vertices[index].z = m_skyPlane[index2].z;
vertices[index].tu = m_skyPlane[index2].tu;
vertices[index].tv = m_skyPlane[index2].tv;
indices[index] = index;
index++;
// Triangle 2 - Bottom Right
vertices[index].x = m_skyPlane[index4].x;
vertices[index].y = m_skyPlane[index4].y;
vertices[index].z = m_skyPlane[index4].z;
vertices[index].tu = m_skyPlane[index4].tu;
vertices[index].tv = m_skyPlane[index4].tv;
indices[index] = index;
index++;
}
}
// 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 vertex array attribute.
OpenGL->glEnableVertexAttribArray(0); // Vertex position.
OpenGL->glEnableVertexAttribArray(1); // Texture coordinates.
// 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 coordinate portion of the vertex buffer.
OpenGL->glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(VertexType), (unsigned char*)NULL + (3 * 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);
// 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 ShutdownBuffers function releases the buffers that were used to render the sky plane.
void SkyPlaneClass::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;
}
RenderBuffers puts the sky plane geometry on the graphics pipeline for rendering by the shader.
void SkyPlaneClass::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 using the index buffer.
glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);
return;
}
The LoadTextures function loads the two textures that will be used for rendering with.
bool SkyPlaneClass::LoadTextures(OpenGLClass* OpenGL, char* filename1, char* filename2)
{
bool result;
// Create and initialize the cloud texture object.
m_CloudTexture = new TextureClass;
result = m_CloudTexture->Initialize(OpenGL, filename1, true);
if(!result)
{
return false;
}
// Create and initialize the fade texture object.
m_FadeTexture = new TextureClass;
result = m_FadeTexture->Initialize(OpenGL, filename2, true);
if(!result)
{
return false;
}
return true;
}
ReleaseTextures releases the two textures that were used for rendering.
void SkyPlaneClass::ReleaseTextures()
{
// Release the fade texture.
if(m_FadeTexture)
{
m_FadeTexture->Shutdown();
delete m_FadeTexture;
m_FadeTexture = 0;
}
// Release the cloud texture.
if(m_CloudTexture)
{
m_CloudTexture->Shutdown();
delete m_CloudTexture;
m_CloudTexture = 0;
}
return;
}
Skyplane.vs
The sky plane vertex shader is fairly generic and just sends through the transformed position and texture coordinates to the pixel shader.
////////////////////////////////////////////////////////////////////////////////
// Filename: skyplane.vs
////////////////////////////////////////////////////////////////////////////////
#version 400
/////////////////////
// INPUT VARIABLES //
/////////////////////
in vec3 inputPosition;
in vec2 inputTexCoord;
//////////////////////
// OUTPUT VARIABLES //
//////////////////////
out vec2 texCoord;
///////////////////////
// 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;
}
Skyplane.ps
////////////////////////////////////////////////////////////////////////////////
// Filename: skyplane.ps
////////////////////////////////////////////////////////////////////////////////
#version 400
/////////////////////
// INPUT VARIABLES //
/////////////////////
in vec2 texCoord;
//////////////////////
// OUTPUT VARIABLES //
//////////////////////
out vec4 outputColor;
///////////////////////
// UNIFORM VARIABLES //
///////////////////////
The sky plane shader uses two textures. One for the cloud and one for the fade values.
The sky plane shader also takes the two translation values for the clouds. Note we will only sample the clouds and not the fade texture using the translation values.
uniform sampler2D cloudTexture;
uniform sampler2D fadeTexture;
uniform float translationX;
uniform float translationZ;
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
void main(void)
{
vec2 sampleLocation;
vec4 textureColor;
vec4 fadeValue;
The pixel shader is fairly simple. We sample the clouds at the texture translation coordinates which creates the movement simulation.
// Translate the position where we sample the pixel from using the first texture translation values.
sampleLocation.x = texCoord.x + translationX;
sampleLocation.y = texCoord.y + translationZ;
// Sample the pixel color from the first cloud texture using the sampler at this texture coordinate location.
textureColor = texture(cloudTexture, sampleLocation);
// Sample the fade texture at the regular texture coordinates.
fadeValue = texture(fadeTexture, texCoord);
// Combine the cloud with the fade value.
outputColor = textureColor * fadeValue;
}
Skyplaneshaderclass.h
The SkyPlaneShaderClass is the shader used for rendering the clouds on the sky plane.
It is based on the TextureShaderClass, and just has some simple modifications for rendering the sky plane.
////////////////////////////////////////////////////////////////////////////////
// Filename: skyplaneshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SKYPLANESHADERCLASS_H_
#define _SKYPLANESHADERCLASS_H_
//////////////
// INCLUDES //
//////////////
#include <iostream>
using namespace std;
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "openglclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: SkyPlaneShaderClass
////////////////////////////////////////////////////////////////////////////////
class SkyPlaneShaderClass
{
public:
SkyPlaneShaderClass();
SkyPlaneShaderClass(const SkyPlaneShaderClass&);
~SkyPlaneShaderClass();
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
Skyplaneshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: skyplaneshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "skyplaneshaderclass.h"
SkyPlaneShaderClass::SkyPlaneShaderClass()
{
m_OpenGLPtr = 0;
}
SkyPlaneShaderClass::SkyPlaneShaderClass(const SkyPlaneShaderClass& other)
{
}
SkyPlaneShaderClass::~SkyPlaneShaderClass()
{
}
bool SkyPlaneShaderClass::Initialize(OpenGLClass* OpenGL)
{
char vsFilename[128];
char psFilename[128];
bool result;
// Store the pointer to the OpenGL object.
m_OpenGLPtr = OpenGL;
Load the sky plane shaders.
// Set the location and names of the shader files.
strcpy(vsFilename, "../Engine/skyplane.vs");
strcpy(psFilename, "../Engine/skyplane.ps");
// Initialize the vertex and pixel shaders.
result = InitializeShader(vsFilename, psFilename);
if(!result)
{
return false;
}
return true;
}
void SkyPlaneShaderClass::Shutdown()
{
// Shutdown the shader.
ShutdownShader();
// Release the pointer to the OpenGL object.
m_OpenGLPtr = 0;
return;
}
bool SkyPlaneShaderClass::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 layout will only require position and texture coordinates.
// Bind the shader input variables.
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 0, "inputPosition");
m_OpenGLPtr->glBindAttribLocation(m_shaderProgram, 1, "inputTexCoord");
// 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 SkyPlaneShaderClass::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* SkyPlaneShaderClass::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 SkyPlaneShaderClass::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 SkyPlaneShaderClass::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 SkyPlaneShaderClass::SetShaderParameters(float* worldMatrix, float* viewMatrix, float* projectionMatrix, float translationX, float translationZ)
{
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)
{
cout << "World matrix not set." << endl;
}
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)
{
cout << "View matrix not set." << endl;
}
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)
{
cout << "Projection matrix not set." << endl;
}
m_OpenGLPtr->glUniformMatrix4fv(location, 1, false, tpProjectionMatrix);
Set the two textures in the pixel shader.
// Set the texture in the pixel shader to use the data from the first texture unit.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "cloudTexture");
if(location == -1)
{
cout << "Cloud texture not set." << endl;
}
m_OpenGLPtr->glUniform1i(location, 0);
// Set the texture in the pixel shader to use the data from the second texture unit.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "fadeTexture");
if(location == -1)
{
cout << "Fade texture not set." << endl;
}
m_OpenGLPtr->glUniform1i(location, 1);
Set our updated translation value in the pixel shader for sampling the cloud texture to simulate movement.
// Set the translation X in the pixel shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "translationX");
if(location == -1)
{
cout << "Translation X not set." << endl;
}
m_OpenGLPtr->glUniform1f(location, translationX);
// Set the translation Z in the pixel shader.
location = m_OpenGLPtr->glGetUniformLocation(m_shaderProgram, "translationZ");
if(location == -1)
{
cout << "Translation Z not set." << endl;
}
m_OpenGLPtr->glUniform1f(location, translationZ);
return true;
}
Shadermanagerclass.h
We have added the SkyPlaneShaderClass to the ShaderManagerClass.
////////////////////////////////////////////////////////////////////////////////
// Filename: shadermanagerclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SHADERMANAGERCLASS_H_
#define _SHADERMANAGERCLASS_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "colorshaderclass.h"
#include "fontshaderclass.h"
#include "terrainshaderclass.h"
#include "skydomeshaderclass.h"
#include "skyplaneshaderclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: ShaderManagerClass
////////////////////////////////////////////////////////////////////////////////
class ShaderManagerClass
{
public:
ShaderManagerClass();
ShaderManagerClass(const ShaderManagerClass&);
~ShaderManagerClass();
bool Initialize(OpenGLClass*);
void Shutdown();
bool RenderColorShader(float*, float*, float*);
bool RenderFontShader(float*, float*, float*, float*);
bool RenderTerrainShader(float*, float*, float*, float*, float*);
bool RenderSkyDomeShader(float*, float*, float*, float*, float*);
bool RenderSkyPlaneShader(float*, float*, float*, float, float);
private:
ColorShaderClass* m_ColorShader;
FontShaderClass* m_FontShader;
TerrainShaderClass* m_TerrainShader;
SkyDomeShaderClass* m_SkyDomeShader;
SkyPlaneShaderClass* m_SkyPlaneShader;
};
#endif
Shadermanagerclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: shadermanagerclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "shadermanagerclass.h"
ShaderManagerClass::ShaderManagerClass()
{
m_ColorShader = 0;
m_FontShader = 0;
m_TerrainShader = 0;
m_SkyDomeShader = 0;
m_SkyPlaneShader = 0;
}
ShaderManagerClass::ShaderManagerClass(const ShaderManagerClass& other)
{
}
ShaderManagerClass::~ShaderManagerClass()
{
}
bool ShaderManagerClass::Initialize(OpenGLClass* OpenGL)
{
bool result;
// Create and initialize the color shader object.
m_ColorShader = new ColorShaderClass;
result = m_ColorShader->Initialize(OpenGL);
if(!result)
{
return false;
}
// Create and initialize the font shader object.
m_FontShader = new FontShaderClass;
result = m_FontShader->Initialize(OpenGL);
if(!result)
{
return false;
}
// Create and initialize the terrain shader object.
m_TerrainShader = new TerrainShaderClass;
result = m_TerrainShader->Initialize(OpenGL);
if(!result)
{
return false;
}
// Create and initialize the sky dome shader object.
m_SkyDomeShader = new SkyDomeShaderClass;
result = m_SkyDomeShader->Initialize(OpenGL);
if(!result)
{
return false;
}
// Create and initialize the sky plane shader object.
m_SkyPlaneShader = new SkyPlaneShaderClass;
result = m_SkyPlaneShader->Initialize(OpenGL);
if(!result)
{
return false;
}
return true;
}
void ShaderManagerClass::Shutdown()
{
// Release the sky plane shader object.
if(m_SkyPlaneShader)
{
m_SkyPlaneShader->Shutdown();
delete m_SkyPlaneShader;
m_SkyPlaneShader = 0;
}
// Release the sky dome shader object.
if(m_SkyDomeShader)
{
m_SkyDomeShader->Shutdown();
delete m_SkyDomeShader;
m_SkyDomeShader = 0;
}
// Release the terrain shader object.
if(m_TerrainShader)
{
m_TerrainShader->Shutdown();
delete m_TerrainShader;
m_TerrainShader = 0;
}
// Release the font shader object.
if(m_FontShader)
{
m_FontShader->Shutdown();
delete m_FontShader;
m_FontShader = 0;
}
// Release the color shader object.
if(m_ColorShader)
{
m_ColorShader->Shutdown();
delete m_ColorShader;
m_ColorShader = 0;
}
return;
}
bool ShaderManagerClass::RenderColorShader(float* worldMatrix, float* viewMatrix, float* projectionMatrix)
{
return m_ColorShader->SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix);
}
bool ShaderManagerClass::RenderFontShader(float* worldMatrix, float* viewMatrix, float* projectionMatrix, float* pixelColor)
{
return m_FontShader->SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, pixelColor);
}
bool ShaderManagerClass::RenderTerrainShader(float* worldMatrix, float* viewMatrix, float* projectionMatrix, float* lightDirection, float* diffuseLightColor)
{
return m_TerrainShader->SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, lightDirection, diffuseLightColor);
}
bool ShaderManagerClass::RenderSkyDomeShader(float* worldMatrix, float* viewMatrix, float* projectionMatrix, float* apexColor, float* centerColor)
{
return m_SkyDomeShader->SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, apexColor, centerColor);
}
bool ShaderManagerClass::RenderSkyPlaneShader(float* worldMatrix, float* viewMatrix, float* projectionMatrix, float translationX, float translationZ)
{
return m_SkyPlaneShader->SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, translationX, translationZ);
}
Zoneclass.h
The SkyPlaneClass has been added to the ZoneClass.
///////////////////////////////////////////////////////////////////////////////
// Filename: zoneclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _ZONECLASS_H_
#define _ZONECLASS_H_
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "openglclass.h"
#include "inputclass.h"
#include "userinterfaceclass.h"
#include "cameraclass.h"
#include "positionclass.h"
#include "terrainclass.h"
#include "skydomeclass.h"
#include "skyplaneclass.h"
////////////////////////////////////////////////////////////////////////////////
// Class name: ZoneClass
////////////////////////////////////////////////////////////////////////////////
class ZoneClass
{
public:
ZoneClass();
ZoneClass(const ZoneClass&);
~ZoneClass();
bool Initialize(OpenGLClass*);
void Shutdown(OpenGLClass*);
bool Frame(OpenGLClass*, ShaderManagerClass*, FontClass*, UserInterfaceClass*, InputClass*, float);
private:
bool Render(OpenGLClass*, ShaderManagerClass*, FontClass*, UserInterfaceClass*);
void HandleMovementInput(InputClass*, float);
bool RenderSkyDome(OpenGLClass*, ShaderManagerClass*, float*, float*);
bool RenderSkyPlane(OpenGLClass*, ShaderManagerClass*, float*, float*);
private:
CameraClass* m_Camera;
PositionClass* m_Position;
TerrainClass* m_Terrain;
LightClass* m_Light;
FrustumClass* m_Frustum;
SkyDomeClass* m_SkyDome;
SkyPlaneClass* m_SkyPlane;
};
#endif
Zoneclass.cpp
////////////////////////////////////////////////////////////////////////////////
// Filename: zoneclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "zoneclass.h"
ZoneClass::ZoneClass()
{
m_Camera = 0;
m_Position = 0;
m_Terrain = 0;
m_Light = 0;
m_Frustum = 0;
m_SkyDome = 0;
m_SkyPlane = 0;
}
ZoneClass::ZoneClass(const ZoneClass& other)
{
}
ZoneClass::~ZoneClass()
{
}
bool ZoneClass::Initialize(OpenGLClass* OpenGL)
{
char configFilename[256];
bool result;
// Create and initialize the camera object.
m_Camera = new CameraClass;
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
m_Camera->Render();
m_Camera->RenderBaseViewMatrix();
// Create and initialize the position object.
m_Position = new PositionClass;
m_Position->SetPosition(500.0f, 50.0f, -10.0f);
m_Position->SetRotation(0.0f, 0.0f, 0.0f);
// Create and initialize the terrain object.
m_Terrain = new TerrainClass;
strcpy(configFilename, "../Engine/data/setup.txt");
result = m_Terrain->Initialize(OpenGL, configFilename);
if(!result)
{
cout << "Error: Could not initialize the terrain object." << endl;
return false;
}
// Create and initialize the light object.
m_Light = new LightClass;
m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
m_Light->SetDirection(-0.5f, -1.0f, -0.5f);
// Create the frustum object.
m_Frustum = new FrustumClass;
// Create and initialize the sky dome object.
m_SkyDome = new SkyDomeClass;
result = m_SkyDome->Initialize(OpenGL);
if(!result)
{
cout << "Error: Could not initialize the sky dome object." << endl;
return false;
}
The sky plane is created here.
// Create and initialize the sky plane object.
m_SkyPlane = new SkyPlaneClass;
result = m_SkyPlane->Initialize(OpenGL);
if(!result)
{
cout << "Error: Could not initialize the sky plane object." << endl;
return false;
}
return true;
}
void ZoneClass::Shutdown(OpenGLClass* OpenGL)
{
The sky plane is released here.
// Release the sky plane object.
if(m_SkyPlane)
{
m_SkyPlane->Shutdown(OpenGL);
delete m_SkyPlane;
m_SkyPlane = 0;
}
// Release the sky dome object.
if(m_SkyDome)
{
m_SkyDome->Shutdown(OpenGL);
delete m_SkyDome;
m_SkyDome = 0;
}
// Release the frustum object.
if(m_Frustum)
{
delete m_Frustum;
m_Frustum = 0;
}
// Release the light object.
if(m_Light)
{
delete m_Light;
m_Light = 0;
}
// Release the terrain object.
if(m_Terrain)
{
m_Terrain->Shutdown(OpenGL);
delete m_Terrain;
m_Terrain = 0;
}
// Release the position object.
if(m_Position)
{
delete m_Position;
m_Position = 0;
}
// Release the camera object.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
return;
}
bool ZoneClass::Frame(OpenGLClass* OpenGL, ShaderManagerClass* ShaderManager, FontClass* Font, UserInterfaceClass* UserInterface, InputClass* Input, float frameTime)
{
float posX, posY, posZ, rotX, rotY, rotZ, height;
bool result, foundHeight, heightLocked;
// Set whether we lock to the terrain height or not.
heightLocked = false;
The sky plane needs to update the texture translation each frame.
// Do the sky plane frame processing.
m_SkyPlane->Frame(frameTime);
// Do the frame input processing for the movement.
HandleMovementInput(Input, frameTime);
// Get the view point position/rotation.
m_Position->GetPosition(posX, posY, posZ);
m_Position->GetRotation(rotX, rotY, rotZ);
// Get the height of the triangle that is directly underneath the given camera position.
foundHeight = m_Terrain->GetHeightAtPosition(posX, posZ, height);
if(foundHeight)
{
// If there was a triangle under the camera then position the camera just above it by two units.
if(heightLocked)
{
posY = height + 2.0f;
}
}
// Set the position of the camera and update the camera view matrix for rendering.
m_Camera->SetPosition(posX, posY, posZ);
m_Camera->SetRotation(rotX, rotY, rotZ);
m_Camera->Render();
// Update the UI with the position.
UserInterface->UpdatePositonStrings(Font, posX, posY, posZ, rotX, rotY, rotZ);
// Render the zone.
result = Render(OpenGL, ShaderManager, Font, UserInterface);
if(!result)
{
return false;
}
return true;
}
bool ZoneClass::Render(OpenGLClass* OpenGL, ShaderManagerClass* ShaderManager, FontClass* Font, UserInterfaceClass* UserInterface)
{
float worldMatrix[16], viewMatrix[16], projectionMatrix[16], baseViewMatrix[16], orthoMatrix[16];
int nodesDrawn, nodesCulled;
bool result;
// Get the world, view, and ortho matrices from the opengl and camera objects.
OpenGL->GetWorldMatrix(worldMatrix);
m_Camera->GetViewMatrix(viewMatrix);
OpenGL->GetProjectionMatrix(projectionMatrix);
m_Camera->GetBaseViewMatrix(baseViewMatrix);
OpenGL->GetOrthoMatrix(orthoMatrix);
// Construct the frustum.
m_Frustum->ConstructFrustum(OpenGL, viewMatrix, projectionMatrix);
// Clear the scene.
OpenGL->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
// Render the sky dome.
result = RenderSkyDome(OpenGL, ShaderManager, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
The sky plane rendering function is called after the sky dome has been rendered, as it requires the colors for blending from the sky dome.
// Render the sky plane.
result = RenderSkyPlane(OpenGL, ShaderManager, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
// Render the terrain using the terrain shader.
result = m_Terrain->Render(OpenGL, ShaderManager, m_Light, m_Frustum, worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
// Update with nodes drawn/culled.
m_Terrain->GetNodesDrawn(nodesDrawn, nodesCulled);
result = UserInterface->UpdateNodeStrings(Font, nodesDrawn, nodesCulled);
if(!result)
{
return false;
}
// Render the user interface.
result = UserInterface->Render(OpenGL, ShaderManager, Font, worldMatrix, baseViewMatrix, orthoMatrix);
if(!result)
{
return false;
}
// Present the rendered scene to the screen.
OpenGL->EndScene();
return true;
}
void ZoneClass::HandleMovementInput(InputClass* Input, float frameTime)
{
bool keyDown;
// Set the frame time for calculating the updated position.
m_Position->SetFrameTime(frameTime);
// Check if the input keys have been pressed. If so, then update the position accordingly.
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);
keyDown = Input->IsQPressed();
m_Position->StrafeLeft(keyDown);
keyDown = Input->IsEPressed();
m_Position->StrafeRight(keyDown);
return;
}
bool ZoneClass::RenderSkyDome(OpenGLClass* OpenGL, ShaderManagerClass* ShaderManager, float* viewMatrix, float* projectionMatrix)
{
float translateMatrix[16];
float apexColor[4], centerColor[4];
float cameraPosition[3];
bool result;
// Get the sky dome colors.
m_SkyDome->GetApexColor(apexColor);
m_SkyDome->GetCenterColor(centerColor);
// Get the position of the camera.
m_Camera->GetPosition(cameraPosition);
// Translate the sky dome to be centered around the camera position.
OpenGL->MatrixTranslation(translateMatrix, cameraPosition[0], cameraPosition[1], cameraPosition[2]);
// Turn off back face culling and turn off the Z buffer.
OpenGL->TurnOffCulling();
OpenGL->TurnZBufferOff();
// Render the sky dome using the sky dome shader.
result = ShaderManager->RenderSkyDomeShader(translateMatrix, viewMatrix, projectionMatrix, apexColor, centerColor);
if(!result)
{
return false;
}
m_SkyDome->Render(OpenGL);
// Turn back face culling back on and turn the Z buffer back on.
OpenGL->TurnOnCulling();
OpenGL->TurnZBufferOn();
return true;
}
The RenderSkyPlane function works similar to the RenderSkyDome function.
You first get the camera position and translate the sky plane to be under the camera at all times.
After that the sky plane shader is used to draw the sky plane with the cloud and fade texture, as well as the cloud translation coordinates.
We disable the Z buffer and enable alpha blending so we can blend with the already rendered sky dome colors.
bool ZoneClass::RenderSkyPlane(OpenGLClass* OpenGL, ShaderManagerClass* ShaderManager, float* viewMatrix, float* projectionMatrix)
{
float translateMatrix[16];
float cameraPosition[3];
float translationX, translationZ;
bool result;
// Get the position of the camera.
m_Camera->GetPosition(cameraPosition);
// Translate the sky dome to be centered around the camera position.
OpenGL->MatrixTranslation(translateMatrix, cameraPosition[0], cameraPosition[1], cameraPosition[2]);
translationX = m_SkyPlane->GetTranslation(0);
translationZ = m_SkyPlane->GetTranslation(1);
// Turn Z buffer off and enable additive blending so the clouds blend with the sky dome color.
OpenGL->TurnZBufferOff();
OpenGL->EnableUIAlphaBlending();
// Render the sky plane using the sky plane shader.
result = ShaderManager->RenderSkyPlaneShader(translateMatrix, viewMatrix, projectionMatrix, translationX, translationZ);
if(!result)
{
return false;
}
m_SkyPlane->SetCloudTexture(OpenGL, 0);
m_SkyPlane->SetFadeTexture(OpenGL, 1);
m_SkyPlane->Render(OpenGL);
// Turn Z buffer back on and disable blending.
OpenGL->TurnZBufferOn();
OpenGL->DisableAlphaBlending();
return true;
}
Summary
We now have a sky plane with clouds rendered on it that blend with the color dome creating a fairly realistic moving cloud effect.

To Do Exercises
1. Recompile the code and run the program. You should see a layer of clouds moving. Use PgUp to look up at the sky.
2. Play around with the parameters SkyPlaneClass::Initialize to see the effects they have on the sky plane.
3. Use some different cloud textures to generate you own desired sky appearance.
4. Add a second layer of clouds with a second texture. Move the second layer at different speeds to give more of a depth illusion to your clouds.
5. Setup varying wind conditions over time to modify the direction and speed of your clouds as time proceeds.
6. Try different combination techniques in the shader. For example a subtraction will have the effect of shrinking the clouds which can add more variation to the cloud scene. You may need a modified alpha blending function also.
Source Code
Source Code and Data Files: gl4terlinux12.tar.gz