A Simple C++ OpenGL Shader Loader – Improved

I wrote one back in 2013, but I’ve learnt some things since then, so I’ve re-written it all with some changes. You can still load/compile shaders from strings or files, and then link/validate/use the resulting shader programs, only this time its all a little bit cleaner and more robust.

The main deficiencies of the previous incarnation were:

  • The Shader class wasn’t required for what I was using this stuff for, so I stripped it out – now you just create a ShaderProgram directly,
  • Error logs weren’t shown for any shader or shader program errors,
  • The shader program didn’t get validated,
  • Abuse of exit(-1) rather than throwing unchecked runtime_errors,
  • Inclusion of using clauses in headers, and
  • Possible wonky static declaration and initialisation of const_iterator error in uniform location getter meant that it might have only “got” the uniform location correcty on first run, when the const_iterator was declared – but in practice I haven’t seen any issues, perhaps because I spotted and fixed it but forgot to update article though…

Anyway, below is an improved version that fixes all of the above – here’s how you use it…

Usage

// Start by declaring a pointer to a ShaderProgram. A ShaderProgram can only be instantiated when there
// is a valid OpenGL context (i.e. window) - so you may like to declare the ShaderProgram globally, then
// instantiate it later on when you have an OpenGL context.
ShaderProgram *shaderProgram;
 
// ...later on when we have a OpenGL context...
shaderProgram = new ShaderProgram();
 
// To provide the source code for the vertex and fragment shaders you can either initialise from strings or from files:
shaderProgram->initFromStrings(vertexShaderString, fragmentShaderString);
shaderProgram->initFromFiles(vertexShaderFilename, fragmentShaderFilename);
 
// Add attributes to suit
shaderProgram->addAttribute("vertexLocation");
shaderProgram->addAttribute("vertexColour");
 
// Add uniforms to suit
shaderProgram->addUniform("mvpMatrix");
 
// Enable attributes
// 1.) Create and bind to a vertex array object (VAO)...
// 2.) Create and bind to a vertex buffer object (VBO) and specify your vertex attrib pointers
// 3.) Provide geometry data then unbind from the VBO
// --- Now you can enable vertex attributes with, for example:
             glEnableVertexAttribArray( shaderProgram->attribute("vertexLocation") );
             glEnableVertexAttribArray( shaderProgram->attribute("vertexColour") );
// 5.) Unbind from VAO
 
// Finally, to draw the geometry using the shader program you can use code similar to this:
// Note: This code assumes you're using GLM for your matrices, modify as appropriate if not. Also
// note that to use glm::value_ptr you must "#include <glm/gtc/type_ptr.hpp>"
shaderProgram->use();
             glUniformMatrix4fv(shaderProgram->uniform("mvpMatrix"), 1, GL_FALSE, glm::value_ptr(mvpMatrix) );
             <YOUR-DRAW-CALL-HERE> i.e. glDrawArrays(GL_TRIANGLES, 0, 100) etc.
shaderProgram->disable();

ShaderProgram Class Source Code

Here’s the code for the class itself:

/***
author     : r3dux
version    : 0.3 - 15/01/2014
description: Gets GLSL source code either provided as strings or can load from filenames,
             compiles the shaders, creates a shader program which the shaders are linked
             to, then the program is validated and is ready for use via myProgram.use(),
             <draw-stuff-here> then calling myProgram.disable();
 
             Attributes and uniforms are stored in <string, int> maps and can be added
             via calls to addAttribute(<name-of-attribute>) and then the attribute
             index can be obtained via myProgram.attribute(<name-of-attribute>) - Uniforms
             work in the exact same way.
***/
 
#ifndef SHADER_PROGRAM_HPP
#define SHADER_PROGRAM_HPP
 
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
 
class ShaderProgram
{
private:
	// static DEBUG flag - if set to false then, errors aside, we'll run completely silent
	static const bool DEBUG = true;
 
	// We'll use an enum to differentiate between shaders and shader programs when querying the info log
	enum class ObjectType
	{
		SHADER, PROGRAM
	};
 
	// Shader program and individual shader Ids
	GLuint programId;
	GLuint vertexShaderId;
	GLuint fragmentShaderId;
 
	// How many shaders are attached to the shader program
	GLuint shaderCount;
 
	// Map of attributes and their binding locations
	std::map<std::string, int> attributeMap;
 
	// Map of uniforms and their binding locations
	std::map<std::string, int> uniformMap;
 
	// Has this shader program been initialised?
	bool initialised;
 
	// ---------- PRIVATE METHODS ----------
 
	// Private method to compile a shader of a given type
	GLuint compileShader(std::string shaderSource, GLenum shaderType)
	{
		std::string shaderTypeString;
		switch (shaderType)
		{
			case GL_VERTEX_SHADER:
				shaderTypeString = "GL_VERTEX_SHADER";
				break;
			case GL_FRAGMENT_SHADER:
				shaderTypeString = "GL_FRAGMENT_SHADER";
				break;
			case GL_GEOMETRY_SHADER:
				throw std::runtime_error("Geometry shaders are unsupported at this time.");
				break;
			default:
				throw std::runtime_error("Bad shader type enum in compileShader.");
				break;
		}
 
		// Generate a shader id
		// Note: Shader id will be non-zero if successfully created.
		GLuint shaderId = glCreateShader(shaderType);
		if (shaderId == 0)
		{
			// Display the shader log via a runtime_error
			throw std::runtime_error("Could not create shader of type " + shaderTypeString + ": " + getInfoLog(ObjectType::SHADER, shaderId) );
		}
 
		// Get the source string as a pointer to an array of characters
		const char *shaderSourceChars = shaderSource.c_str();
 
		// Attach the GLSL source code to the shader
		// Params: GLuint shader, GLsizei count, const GLchar **string, const GLint *length
		// Note: The pointer to an array of source chars will be null terminated, so we don't need to specify the length and can instead use NULL.
		glShaderSource(shaderId, 1, &shaderSourceChars, NULL);
 
		// Compile the shader
		glCompileShader(shaderId);
 
		// Check the compilation status and throw a runtime_error if shader compilation failed
		GLint shaderStatus;
		glGetShaderiv(shaderId, GL_COMPILE_STATUS, &shaderStatus);
		if (shaderStatus == GL_FALSE)
		{
			throw std::runtime_error(shaderTypeString + " compilation failed: " + getInfoLog(ObjectType::SHADER, shaderId) );
		}
		else
		{
			if (DEBUG)
			{
				std::cout << shaderTypeString << " shader compilation successful." << std::endl;
			}
		}
 
		// If everything went well, return the shader id
		return shaderId;
	}
 
	// Private method to compile/attach/link/verify the shaders.
	// Note: Rather than returning a boolean as a success/fail status we'll just consider
	// a failure here to be an unrecoverable error and throw a runtime_error.
	void initialise(std::string vertexShaderSource, std::string fragmentShaderSource)
	{
		// Compile the shaders and return their id values
		vertexShaderId   = compileShader(vertexShaderSource,   GL_VERTEX_SHADER);
		fragmentShaderId = compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
 
		// Attach the compiled shaders to the shader program
		glAttachShader(programId, vertexShaderId);
		glAttachShader(programId, fragmentShaderId);
 
		// Link the shader program - details are placed in the program info log
		glLinkProgram(programId);
 
		// Once the shader program has the shaders attached and linked, the shaders are no longer required.
		// If the linking failed, then we're going to abort anyway so we still detach the shaders.
		glDetachShader(programId, vertexShaderId);
		glDetachShader(programId, fragmentShaderId);
 
		// Check the program link status and throw a runtime_error if program linkage failed.
		GLint programLinkSuccess = GL_FALSE;
		glGetProgramiv(programId, GL_LINK_STATUS, &programLinkSuccess);
		if (programLinkSuccess == GL_TRUE)
		{
			if (DEBUG)
			{
				std::cout << "Shader program link successful." << std::endl;
			}
		}
		else
		{
			throw std::runtime_error("Shader program link failed: " + getInfoLog(ObjectType::PROGRAM, programId) );
		}
 
		// Validate the shader program
		glValidateProgram(programId);
 
		// Check the validation status and throw a runtime_error if program validation failed
		GLint programValidatationStatus;
		glGetProgramiv(programId, GL_VALIDATE_STATUS, &programValidatationStatus);
		if (programValidatationStatus == GL_TRUE)
		{
			if (DEBUG)
			{
				std::cout << "Shader program validation successful." << std::endl;
			}
		}
		else
		{
			throw std::runtime_error("Shader program validation failed: " + getInfoLog(ObjectType::PROGRAM, programId) );
		}
 
		// Finally, the shader program is initialised
		initialised = true;
	}
 
	// Private method to load the shader source code from a file
	std::string loadShaderFromFile(const std::string filename)
	{
		// Create an input filestream and attempt to open the specified file
		std::ifstream file( filename.c_str() );
 
		// If we couldn't open the file we'll bail out
		if ( !file.good() )
		{
			throw std::runtime_error("Failed to open file: " + filename);
		}
 
		// Otherwise, create a string stream...
		std::stringstream stream;
 
		// ...and dump the contents of the file into it.
		stream << file.rdbuf();
 
		// Now that we've read the file we can close it
		file.close();
 
		// Finally, convert the stringstream into a string and return it
		return stream.str();
	}
 
	// Private method to return the current shader program info log as a string
	std::string getInfoLog(ObjectType type, int id)
	{
		GLint infoLogLength;
		if (type == ObjectType::SHADER)
		{
			glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
		}
		else // type must be ObjectType::PROGRAM
		{
			glGetProgramiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
		}
 
		GLchar *infoLog = new GLchar[infoLogLength + 1];
		if (type == ObjectType::SHADER)
		{
			glGetShaderInfoLog(id, infoLogLength, NULL, infoLog);
		}
		else // type must be ObjectType::PROGRAM
		{
			glGetProgramInfoLog(id, infoLogLength, NULL, infoLog);
		}
 
		// Convert the info log to a string
		std::string infoLogString(infoLog);
 
		// Delete the char array version of the log
		delete[] infoLog;
 
		// Finally, return the string version of the info log
		return infoLogString;
	}
 
public:
	// Constructor
	ShaderProgram()
	{
		// We start in a non-initialised state - calling initFromFiles() or initFromStrings() will
		// initialise us.
		initialised = false;
 
		// Generate a unique Id / handle for the shader program
		// Note: We MUST have a valid rendering context before generating the programId or we'll segfault!
		programId = glCreateProgram();
		glUseProgram(programId);
 
		// Initially, we have zero shaders attached to the program
		shaderCount = 0;
	}
 
	// Destructor
	~ShaderProgram()
	{
		// Delete the shader program from the graphics card memory to
		// free all the resources it's been using
		glDeleteProgram(programId);
	}
 
	// Method to initialise a shader program from shaders provided as files
	void initFromFiles(std::string vertexShaderFilename, std::string fragmentShaderFilename)
	{
		// Get the shader file contents as strings
		std::string vertexShaderSource   = loadShaderFromFile(vertexShaderFilename);
		std::string fragmentShaderSource = loadShaderFromFile(fragmentShaderFilename);
 
		initialise(vertexShaderSource, fragmentShaderSource);
	}
 
	// Method to initialise a shader program from shaders provided as strings
	void initFromStrings(std::string vertexShaderSource, std::string fragmentShaderSource)
	{
		initialise(vertexShaderSource, fragmentShaderSource);
	}
 
	// Method to enable the shader program - we'll suggest this for inlining
	inline void use()
	{
		// Santity check that we're initialised and ready to go...
		if (initialised)
		{
			glUseProgram(programId);
		}
		else
		{
			std::string msg = "Shader program " + programId;
			msg += " not initialised - aborting.";
			throw std::runtime_error(msg);
		}
	}
 
	// Method to disable the shader - we'll also suggest this for inlining
	inline void disable()
	{
		glUseProgram(0);
	}
 
	// Method to return the bound location of a named attribute, or -1 if the attribute was not found
	GLuint attribute(const std::string attributeName)
	{
		// You could do this method with the single line:
		//
		//		return attributeMap[attribute];
		//
		// BUT, if you did, and you asked it for a named attribute which didn't exist
		// like: attributeMap["FakeAttrib"] then the method would return an invalid
		// value which will likely cause the program to segfault. So we're making sure
		// the attribute asked for exists, and if it doesn't then we alert the user & bail.
 
		// Create an iterator to look through our attribute map (only create iterator on first run -
		// reuse it for all further calls).
		static std::map<std::string, int>::const_iterator attributeIter;
 
		// Try to find the named attribute
		attributeIter = attributeMap.find(attributeName);
 
		// Not found? Bail.
		if ( attributeIter == attributeMap.end() )
		{
			throw std::runtime_error("Could not find attribute in shader program: " + attributeName);
		}
 
		// Otherwise return the attribute location from the attribute map
		return attributeMap[attributeName];
	}
 
	// Method to returns the bound location of a named uniform
	GLuint uniform(const std::string uniformName)
	{
		// Note: You could do this method with the single line:
		//
		// 		return uniformLocList[uniform];
		//
		// But we're not doing that. Explanation in the attribute() method above.
 
		// Create an iterator to look through our uniform map (only create iterator on first run -
		// reuse it for all further calls).
		static std::map<std::string, int>::const_iterator uniformIter;
 
		// Try to find the named uniform
		uniformIter = uniformMap.find(uniformName);
 
		// Found it? Great - pass it back! Didn't find it? Alert user and halt.
		if ( uniformIter == uniformMap.end() )
		{
			throw std::runtime_error("Could not find uniform in shader program: " + uniformName);
		}
 
		// Otherwise return the attribute location from the uniform map
		return uniformMap[uniformName];
	}
 
	// Method to add an attribute to the shader and return the bound location
	int addAttribute(const std::string attributeName)
	{
		// Add the attribute location value for the attributeName key
		attributeMap[attributeName] = glGetAttribLocation( programId, attributeName.c_str() );
 
		// Check to ensure that the shader contains an attribute with this name
		if (attributeMap[attributeName] == -1)
		{
			throw std::runtime_error("Could not add attribute: " + attributeName + " - location returned -1.");
		}
		else // Valid attribute location? Inform user if we're in debug mode.
		{
			if (DEBUG)
			{
				std::cout << "Attribute " << attributeName << " bound to location: " << attributeMap[attributeName] << std::endl;
			}
		}
 
		// Return the attribute location
		return attributeMap[attributeName];
	}
 
	// Method to add a uniform to the shader and return the bound location
	int addUniform(const std::string uniformName)
	{
		// Add the uniform location value for the uniformName key
		uniformMap[uniformName] = glGetUniformLocation( programId, uniformName.c_str() );
 
		// Check to ensure that the shader contains a uniform with this name
		if (uniformMap[uniformName] == -1)
		{
			throw std::runtime_error("Could not add uniform: " + uniformName + " - location returned -1.");
		}
		else // Valid uniform location? Inform user if we're in debug mode.
		{
			if (DEBUG)
			{
				std::cout << "Uniform " << uniformName << " bound to location: " << uniformMap[uniformName] << std::endl;
			}
		}
 
		// Return the uniform location
		return uniformMap[uniformName];
	}
 
}; // End of class
 
#endif // SHADER_PROGRAM_HPP

Note: I’m not convinced that the compiler will even consider inlining the use() and disable() methods when the program is built as a hpp – I think I’d have to break it into .h and .cpp files for that… other than that I’m thinking the above code is pretty clean, robust and usable.

How-To: Create A Simple OpenGL 2D Particle Fountain in C++

I was recently asked about some particle systems I’d put together, so in response, this isn’t really a “look what I’ve coded” post – instead, it’s more of a “this is an easy way to set up a particle system framework” post.

To demonstrate the setup of a particle system using OpenGL, I’ve put together some simple starter code which displays a 2D particle fountain. I was going to just dump this code as a zipped project into a reply, but then (as my wife pointed out) it would be pretty much buried in the comments, so if other people starting OpenGL coding wanted to learn the basics of particle systems my code wouldn’t be as visible. As such, I’ve made this into a separate post – and all our example code does is this:

It’s a dirt-simple effect, but what it does isn’t so important right now – it’s deliberately simple. How it does what it does is what we’ll talk about.

Looking at particle systems from a high level – you generally have to choose between two different approaches to the system as a whole:

  1. You have a fixed number of particles (i.e. you have a fixed size array of however many particles) and you reset each particle to “recycle” it, or
  2. You have a dynamic number of particles (i.e. you have a dynamically resizable array) and you destroy each particle and create a new one as required.

I’ve gone with the latter approach for this code using a vector of particles (nothing to do with Vec2/Vec3 math – just the name of resizable array kinda construct). Admittedly, there’s more overhead in the creation and destruction of particles, but on the upside you don’t get that initial rush of particles you get when using fixed size arrays and as soon as you start the program BLAM! All your particles going at once.

To demonstrate what I mean, in the video below I’ve modified the code to instantiate ALL particles up to the particle limit at once, and then as soon as a particle goes off the bottom of the screen it gets destroyed and a new particle is created. The effect of which is that you get a single big burst of particles, and then as they all get destroyed and recreated at different times they turn into a smooth flow within a couple of seconds:

If you’re using a fixed size particle array, you’ll need to implement some mechanism to delay the instantiation or “launch” of the particles – for example, you might give each particle a random framesUntilLaunch value and decrease it by 1 each frame until it gets to 0 and you can let the particle do its thing. I wrote such a delay system for some fireworks code I did a while back if you’d like to see a concrete example.

Anyways, back at this code, our main is using three main classes to encapsulate data and provide methods to manipulate it:

  • Colour4f.hpp – A class to store a colour as red/green/blue/alpha floating point values and manipulate ’em (including interpolation of colours),
  • Vec2.hpp – A templatised class to store two values as a vector (Not a resizable array this time! An actually “euclidian vector” – i.e. two values which represent a direction and magnitude). It also includes lots of overloaded operators so you can add two vectors together (“up” + “right” gives you “up-right” etc.), multiply a vector by a scalar (i.e. moving “up-right” multiplied by 10 means you’re now moving up-right ten times as fast). By templatised, I mean that you can create a Vec2 of ints, or floats, or doubles, or shorts, or whatever numeric type makes sense for your application – take a look at the source below if you want examples, and finally
  • Particle2D.hpp – A particle class which uses the above Colour4f and Vec2 classes to provide powerful movement and colour modification options in a small & easy to use package.

You can download the complete source code as a Code::Blocks project here, if you’d like: PointSprite_Particle_Fountain_2D.zip.

Note: As my OS of choice is GNU/Linux (LMDE, to be specific), the source files provided will have Linux line-endings – so you’ll need to open them with a good text editor like Notepad++ if you’re in in Windows, otherwise each file will look like a single massive block of noise!

Or, if you’d prefer to just browse the source code so you can copy and paste sections, you’ll find it all laid out below.

I love particle systems – you can do some visually stunning things with really simple code, or you can do amazing things if you take it further. Either way, I hope you have a lot of fun with them (there’s lots more particle stuff on this site if you’re looking for inspiration – try Actionscript tag for a start!) – and if you make anything cool or pretty using and you think I’ve helped – please do show me or let me know – I’ve love to see or hear about what you’ve done! =D

Also – if you make this – show me how, okay?

Cheers!

Continue reading How-To: Create A Simple OpenGL 2D Particle Fountain in C++

Vec3: A Simple Vector Class in C++

This is really the second post of three updating an earlier post on how to write some simple FPS-type controls for OpenGL – last post we looked at a FpsManager class so we could get frame timings to implement framerate independent movement. This time, we’re looking at a simple Vec3 class which stores 3D coordinates and helps to perform some operations on them. This is going to form part of the Camera class in the final part, but I thought I’d put it here on its own so it can be re-used.

There’s probably about ten million different vector classes out there, but I really wanted to write my own so I understood exactly how it worked, and I’ve commented it pretty heavily in the process – so hopefully if you’re looking for a vector class you might be able to look at this one, see how it works and what it does, and take and modify it for your own work.

Before skipping to the code, let’s just take a quick look at what it does – it packages up 3 values called x, y and z, and allows us to easily manipulate them. Quite what you store in these values is up to you, it could be a vertex position, or a direction vector, or a RGB colour – anything that has three values, really.

If we were dealing with two vertexes, we can do stuff like this:

    // Define two initial vectors
    Vec3<double> vector1(1, 2, 3); // A coordinate +1 on the X axis (horiz), +2 on the Y axis (vert), and +3 on the Z axis (depth)
    Vec3<double> vector2(4, 5, 6); // A coordinate +4 on the X axis (horiz), +5 on the Y axis (vert), and +6 on the Z axis (depth)
 
    // Create a new vector which is the sum of these two vectors added together
    Vec3<double>result = vector1 + vector2;
    result.display();           // (5, 7, 9)
 
    // Add the first vector to our result vector
    result += vector1;
    result.display();           // 6, 9, 12
 
    // Subtract the second vector from our result vector
    result -= vector2;
    result.display();           // 2, 4, 6
 
    // Divide our result vector by the scalar value 2
    result /= 2;
    result.display();           // 1, 2, 3
 
    // Multiply both vectors together and assign to our result vector (this is a dot-product
    // operation, but we have specific dot product functions we can use, too)
    result = vector1 * vector2;
    result.display();           // 4, 10, 18
 
    // Multiply our result vector by 2
    result *= 2;
    result.display();           // 8, 20, 36
 
    // Normalise our result vector so that all values fall within the range -1 to +1
    result.normalise();
    result.display();           // 0.190693, 0.476731, 0.858116
 
     // Calculate the distance between two points in 3D space
    double distance = Vec3<double>::getDistance(vector1, vector2);
    cout << "Distance between points: " << distance << endl; // 5.19615
 
    // Dot products only work on normalised values (i.e. each x/y/z value in the vector must be in the range -1 to +1)
    // So remember to normalise your vectors before computing the dot product!
    vector1.normalise();
    vector2.normalise();
    double dotProduct = Vec3<double>::dotProduct(vector1, vector2);
    cout << "Dot product: " << dotProduct << endl;           // 0.974632
 
    // Define some vectors pointing up, down, left, and right
    Vec3<double> up(   0,  1, 0);
    Vec3<double> down( 0, -1, 0);
    Vec3<double> left(-1,  0, 0);
    Vec3<double> right(1,  0, 0);
 
    // ------------ Dot Product Tests ------------
 
    // The dot product of two vectors pointing the same direction is 1
    dotProduct = Vec3<double>::dotProduct(up, up);
    cout << "Dot product of up and up: " << dotProduct << endl;
 
    // The dot product of two vectors which are perpendicular to each other is is 0
    dotProduct = Vec3<double>::dotProduct(up, right);
    cout << "Dot product of up and right: " << dotProduct << endl;
 
    // The dot product of two vectors pointing in opposite directions is -1
    dotProduct = Vec3<double>::dotProduct(up, down);
    cout << "Dot product of up and down: " << dotProduct << endl;
 
    // ------------ Cross Product Tests ------------
 
    // The cross product of a vector is the vector which is perpendicular to the plane made
    // by the two vectors specified. Whether it points "up" or "down" depends on the
    // handedness of the coordinate system and/or the order of vectors provided.
 
    // Test 1
    Vec3<double> crossProduct = Vec3<double>::crossProduct(up, right);
 
    // x = 0, y = 0, z = -1 (i.e. the vector perpendicular to up and right points INTO the screen)
    crossProduct.display(); 
 
    // Test 2
    crossProduct = Vec3<double>::crossProduct(right, up);
 
    // x = 0, y = 0, z = 1 (i.e. the vector perpendicular to right and up points OUT from screen)
    crossProduct.display();

Seems pretty easy to work with to me…

Here’s the code for the Vec3 class itself as a templatised, header-only C++ .hpp file – just include the Vec3.hpp class and you’re good to go:

#ifndef VEC3_HPP
#define VEC3_HPP
 
#include <iostream>
 
template <class T> class Vec3
{
    private:
        // A Vec3 simply has three properties called x, y and z
        T x, y, z;
 
    public:
        // ------------ Constructors ------------
 
        // Default constructor
        Vec3() { x = y = z = 0; };
 
        // Three parameter constructor
        Vec3(T xValue, T yValue, T zValue)
        {
            x = xValue;
            y = yValue;
            z = zValue;
        }
 
        // ------------ Getters and setters ------------
 
        void set(const T &xValue, const T &yValue, const T &zValue)
        {
            x = xValue;
            y = yValue;
            z = zValue;
        }
 
        T getX() const { return x; }
        T getY() const { return y; }
        T getZ() const { return z; }
 
        void setX(const T &xValue) { x = xValue; }
        void setY(const T &yValue) { y = yValue; }
        void setZ(const T &zValue) { z = zValue; }
 
        // ------------ Helper methods ------------
 
        // Method to reset a vector to zero
        void zero()
        {
            x = y = z = 0;
        }
 
        // Method to normalise a vector
        void normalise()
        {
            // Calculate the magnitude of our vector
            T magnitude = sqrt((x * x) + (y * y) + (z * z));
 
            // As long as the magnitude isn't zero, divide each element by the magnitude
            // to get the normalised value between -1 and +1
            if (magnitude != 0)
            {
                x /= magnitude;
                y /= magnitude;
                z /= magnitude;
            }
        }
 
        // Static method to calculate and return the scalar dot product of two vectors
        //
        // Note: The dot product of two vectors tell us things about the angle between
        // the vectors. That is, it tells us if they are pointing in the same direction
        // (i.e. are they parallel? If so, the dot product will be 1), or if they're
        // perpendicular (i.e. at 90 degrees to each other) the dot product will be 0,
        // or if they're pointing in opposite directions then the dot product will be -1.
        //
        // Usage example: double foo = Vec3<double>::dotProduct(vectorA, vectorB);
        static T dotProduct(const Vec3 &vec1, const Vec3 &vec2)
        {
            return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
        }
 
        // Non-static method to calculate and return the scalar dot product of this vector and another vector
        //
        // Usage example: double foo = vectorA.dotProduct(vectorB);
        T dotProduct(const Vec3 &vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z;
        }
 
        // Static method to calculate and return a vector which is the cross product of two vectors
        //
        // Note: The cross product is simply a vector which is perpendicular to the plane formed by
        // the first two vectors. Think of a desk like the one your laptop or keyboard is sitting on.
        // If you put one pencil pointing directly away from you, and then another pencil pointing to the
        // right so they form a "L" shape, the vector perpendicular to the plane made by these two pencils
        // points directly upwards.
        //
        // Whether the vector is perpendicularly pointing "up" or "down" depends on the "handedness" of the
        // coordinate system that you're using.
        //
        // Further reading: http://en.wikipedia.org/wiki/Cross_product
        //
        // Usage example: Vec3<double> crossVect = Vec3<double>::crossProduct(vectorA, vectorB);
        static Vec3 crossProduct(const Vec3 &vec1, const Vec3 &vec2)
        {
            return Vec3(vec1.y * vec2.z - vec1.z * vec2.y, vec1.z * vec2.x - vec1.x * vec2.z, vec1.x * vec2.y - vec1.y * vec2.x);
        }
 
        // Easy adders
        void addX(T value) { x += value; }
        void addY(T value) { y += value; }
        void addZ(T value) { z += value; }
 
        // Method to return the distance between two vectors in 3D space
        //
        // Note: This is accurate, but not especially fast - depending on your needs you might
        // like to use the Manhattan Distance instead: http://en.wikipedia.org/wiki/Taxicab_geometry
        // There's a good discussion of it here: http://stackoverflow.com/questions/3693514/very-fast-3d-distance-check
        // The gist is, to find if we're within a given distance between two vectors you can use:
        //
        // bool within3DManhattanDistance(Vec3 c1, Vec3 c2, float distance)
        // {
        //      float dx = abs(c2.x - c1.x);
        //      if (dx > distance) return false; // too far in x direction
        //
        //      float dy = abs(c2.y - c1.y);
        //      if (dy > distance) return false; // too far in y direction
        //
        //      float dz = abs(c2.z - c1.z);
        //      if (dz > distance) return false; // too far in z direction
        //
        //      return true; // we're within the cube
        // }
        //
        // Or to just calculate the straight Manhattan distance you could use:
        //
        // float getManhattanDistance(Vec3 c1, Vec3 c2)
        // {
        //      float dx = abs(c2.x - c1.x);
        //      float dy = abs(c2.y - c1.y);
        //      float dz = abs(c2.z - c1.z);
        //      return dx+dy+dz;
        // }
        //
        static T getDistance(const Vec3 &v1, const Vec3 &v2)
        {
            T dx = v2.x - v1.x;
            T dy = v2.y - v1.y;
            T dz = v2.z - v1.z;
 
            return sqrt(dx * dx + dy * dy + dz * dz);
        }
 
        // Method to display the vector so you can easily check the values
        void display()
        {
            std::cout << "X: " << x << "\t Y: " << y << "\t Z: " << z << std::endl;
        }
 
        // ------------ Overloaded operators ------------
 
        // Overloaded addition operator to add Vec3s together
        Vec3 operator+(const Vec3 &vector) const
        {
            return Vec3<T>(x + vector.x, y + vector.y, z + vector.z);
        }
 
        // Overloaded add and asssign operator to add Vec3s together
        void operator+=(const Vec3 &vector)
        {
            x += vector.x;
            y += vector.y;
            z += vector.z;
        }
 
        // Overloaded subtraction operator to subtract a Vec3 from another Vec3
        Vec3 operator-(const Vec3 &vector) const
        {
            return Vec3<T>(x - vector.x, y - vector.y, z - vector.z);
        }
 
        // Overloaded subtract and asssign operator to subtract a Vec3 from another Vec3
        void operator-=(const Vec3 &vector)
        {
            x -= vector.x;
            y -= vector.y;
            z -= vector.z;
        }
 
        // Overloaded multiplication operator to multiply two Vec3s together
        Vec3 operator*(const Vec3 &vector) const
        {
            return Vec3<T>(x * vector.x, y * vector.y, z * vector.z);
        }
 
        // Overloaded multiply operator to multiply a vector by a scalar
        Vec3 operator*(const T &value) const
        {
            return Vec3<T>(x * value, y * value, z * value);
        }
 
        // Overloaded multiply and assign operator to multiply a vector by a scalar
        void operator*=(const T &value)
        {
            x *= value;
            y *= value;
            z *= value;
        }
 
        // Overloaded multiply operator to multiply a vector by a scalar
        Vec3 operator/(const T &value) const
        {
            return Vec3<T>(x / value, y / value, z / value);
        }
 
        // Overloaded multiply and assign operator to multiply a vector by a scalar
        void operator/=(const T &value)
        {
            x /= value;
            y /= value;
            z /= value;
        }
};
 
#endif

Next post, we’re going to be implementing a FPS-style camera class using this Vec3 class to move around a 3D scene – and because of all our hard work in getting this vector class together, it’s going to make moving the camera around a doddle =D

Cheers!

How To: Perform manual frustum culling

The Problem

Be Careful Machine SignWhen you use OpenGL or DirectX or whatever to draw geometry, the API itself does its own vertex culling (i.e. it doesn’t spend time drawing things which will not be visible on the screen – it opts to not draw them), but it has to do this in the only way it can – by testing each vertex against the projected window-coordinates and making a decision from that. This, as you might not be surprised to learn, is not the most efficient way to consider geometry.

Let’s take an example:

If you had a small sphere visible 5 units directly in front of you, and then you move 10 units forward, the sphere’s obviously now 5 units behind you – but your graphics API won’t (and can’t) care – it’ll just test each vertex of the sphere to see if it’s within your viewing frustum. If the sphere has a couple of hundred vertexes, you’re doing a couple of hundred checks, and making the decision not to draw anything a couple of hundred times. What about if you had ten thousand small spheres 5 units in front of you, and then you move 10 units forward? That’s a lot of per-vertex checking, and it’ll put a significant dent in your frame rate, to draw absolutely nothing at all!

Surely there’s a better way?! You betcha =D

The Solution in Theory

We, or you, know a lot more about the nature of your geometry than your graphics API – which knows nothing at all. It can’t make assumptions, it doesn’t have common sense – we have to provide it. So let’s!

frustrum

Any object that you draw can be enclosed in a sphere, where the size of the sphere will vary depending on the size of the longest axis of the object. Drawing a tree? You can enclose the tree in an imaginary sphere which is the height of the tree and centred on the tree. Drawing a person? You can enclose the person in an imaginary sphere of diameter the person’s height and centred on the person. Once we can check whether this bounding-sphere is within the viewing frustum, we can make a conscious choice as to whether we should even attempt to draw the geometry or not. So if the sphere bounding my tree, or person, or whatever doesn’t at least intersect all 6 planes (top/bottom, left/right, near/far) of my viewing frustum – then it’s not on the screen, and I can choose not to draw it in the first place – thus skipping all that per-vertex checking by applying a little bit of common sense. Sound good?

Closer to Practice

To project a vertex position in world coordinates, say, (-10, 15, 3) [i.e. -10 on the X-Axis, 15 on the Y-Axis, 3 on the Z-Axis], into screen/window coordinates, we can multiply the ModelViewProjection matrix by the vertex position (Careful: The MVP matrix multiplied by the vertex is NOT the same as the vertex multiplied by the MVP matrix! They are not commutative!).

If we then add a radius around that vertex (which is the centre of our object, and something you have to provide – i.e. you need to pick a centre point), we can check if the bounding sphere around that central point intersects any of the six planes of our viewing frustum, and if it doesn’t, we simply don’t even try to draw the object, and gain however-many per-vertex checks for our effort. If you’re checking a single 8 vertex cube you’re not going to gain all much, but if you’re checking multiple 5,000 vertex objects, then you’ll save the GPU that many per-vertex checks per frame! :)

The Solution in Practice

Now that you get the concept, here’s comes the code. This is written in C++, but really, it’s just some math about whether a sphere intercepts or is within the boundary of six planes which comprise our viewing frustum. You can adapt it to anything, and I hope you do :)

The function takes the following parameters:

  1. An array of 16 floating point values which specify the ModelViewProjection Matrix
  2. The x position of the central point
  3. The y position of the central point
  4. The z position of the central point
  5. The radius of the bounding sphere

The function returns a simple boolean of true or false to indicate whether the bounding sphere falls even partially within the viewing frustum.

bool objectVisible(const GLfloat &MVPMatrix[16], const GLfloat &xPos, const GLfloat &yPos, const GLfloat &zPos, const GLfloat &radius)
{
	// The equation for a plane is: Ax + By + Cz + D = 0, where A, B and C define the plane's normal vector, D is the distance from the origin to the plane,
	// and x, y and z are any points on the plane.. You can plug any point into the equation and if the result is 0 then the point lies on the plane. If the
	// result is greater than 0 then the point is in front of the plane, and if it's negative the point is behind the plane.
	enum term { A = 0, B, C, D };
 
	GLfloat leftPlane[4];
	leftPlane[A] = MVPMatrix[3]  + MVPMatrix[0];
	leftPlane[B] = MVPMatrix[7]  + MVPMatrix[4];
	leftPlane[C] = MVPMatrix[11] + MVPMatrix[8];
	leftPlane[D] = MVPMatrix[15] + MVPMatrix[12];
 
	// Normalise the plane
	GLfloat length = sqrtf(leftPlane[A] * leftPlane[A] + leftPlane[B] * leftPlane[B] + leftPlane[C] * leftPlane[C]);
	leftPlane[A] /= length;
	leftPlane[B] /= length;
	leftPlane[C] /= length;
	leftPlane[D] /= length;
 
	// Check the point's location with respect to the left plane of our viewing frustrum
	GLfloat distance = leftPlane[A] * xPos + leftPlane[B] * yPos + leftPlane[C] * zPos + leftPlane[D];
	if (distance <= -radius)
	{
		return false; // Bounding sphere is completely outside the left plane
	}
 
	// Check the point's location with respect to the right plane of our viewing frustum
	GLfloat rightPlane[4];
	rightPlane[A] = MVPMatrix[3]  - MVPMatrix[0];
	rightPlane[B] = MVPMatrix[7]  - MVPMatrix[4];
	rightPlane[C] = MVPMatrix[11] - MVPMatrix[8];
	rightPlane[D] = MVPMatrix[15] - MVPMatrix[12];
 
	// Normalise the plane
	length = sqrtf(rightPlane[A] * rightPlane[A] + rightPlane[B] * rightPlane[B] + rightPlane[C] * rightPlane[C]);
	rightPlane[A] /= length;
	rightPlane[B] /= length;
	rightPlane[C] /= length;
	rightPlane[D] /= length;
 
	distance = rightPlane[A] * xPos + rightPlane[B] * yPos + rightPlane[C] * zPos + rightPlane[D];
	if (distance <= -radius)
	{
		return false; // Bounding sphere is completely outside the right plane
	}
 
	// Check the point's location with respect to the bottom plane of our viewing frustum
	GLfloat bottomPlane[4];
	bottomPlane[A] = MVPMatrix[3]  + MVPMatrix[1];
	bottomPlane[B] = MVPMatrix[7]  + MVPMatrix[5];
	bottomPlane[C] = MVPMatrix[11] + MVPMatrix[9];
	bottomPlane[D] = MVPMatrix[15] + MVPMatrix[13];
 
	// Normalise the plane
	length = sqrtf(bottomPlane[A] * bottomPlane[A] + bottomPlane[B] * bottomPlane[B] + bottomPlane[C] * bottomPlane[C]);
	bottomPlane[A] /= length;
	bottomPlane[B] /= length;
	bottomPlane[C] /= length;
	bottomPlane[D] /= length;
 
	distance = bottomPlane[A] * xPos + bottomPlane[B] * yPos + bottomPlane[C] * zPos + bottomPlane[D];
	if (distance <= -radius)
	{
		return false; // Bounding sphere is completely outside the bottom plane
	}
 
	// Check the point's location with respect to the top plane of our viewing frustrum
	GLfloat topPlane[4];
	topPlane[A] = MVPMatrix[3]  - MVPMatrix[1];
	topPlane[B] = MVPMatrix[7]  - MVPMatrix[5];
	topPlane[C] = MVPMatrix[11] - MVPMatrix[9];
	topPlane[D] = MVPMatrix[15] - MVPMatrix[13];
 
	// Normalise the plane
	length = sqrtf(topPlane[A] * topPlane[A] + topPlane[B] * topPlane[B] + topPlane[C] * topPlane[C]);
	topPlane[A] /= length;
	topPlane[B] /= length;
	topPlane[C] /= length;
	topPlane[D] /= length;
 
	distance = topPlane[A] * xPos + topPlane[B] * yPos + topPlane[C] * zPos + topPlane[D];
	if (distance <= -radius)
	{
		return false; // Bounding sphere is completely outside the top plane
	}
 
	// Check the point's location with respect to the near plane of our viewing frustum
	GLfloat nearPlane[4];
	nearPlane[A] = MVPMatrix[3]  + MVPMatrix[2];
	nearPlane[B] = MVPMatrix[7]  + MVPMatrix[6];
	nearPlane[C] = MVPMatrix[11] + MVPMatrix[10];
	nearPlane[D] = MVPMatrix[15] + MVPMatrix[14];
 
	// Normalise the plane
	length = sqrtf(nearPlane[A] * nearPlane[A] + nearPlane[B] * nearPlane[B] + nearPlane[C] * nearPlane[C]);
	nearPlane[A] /= length;
	nearPlane[B] /= length;
	nearPlane[C] /= length;
	nearPlane[D] /= length;
 
	distance = nearPlane[A] * xPos + nearPlane[B] * yPos + nearPlane[C] * zPos + nearPlane[D];
	if (distance <= -radius)
	{
		return false; // Bounding sphere is completely outside the near plane
	}
 
	// Check the point's location with respect to the far plane of our viewing frustum
	GLfloat farPlane[4];
	farPlane[A] = MVPMatrix[3]  - MVPMatrix[2];
	farPlane[B] = MVPMatrix[7]  - MVPMatrix[6];
	farPlane[C] = MVPMatrix[11] - MVPMatrix[10];
	farPlane[D] = MVPMatrix[15] - MVPMatrix[14];
 
	// Normalise the plane
	length = sqrtf(farPlane[A] * farPlane[A] + farPlane[B] * farPlane[B] + farPlane[C] * farPlane[C]);
	farPlane[A] /= length;
	farPlane[B] /= length;
	farPlane[C] /= length;
	farPlane[D] /= length;
 
	distance = farPlane[A] * xPos + farPlane[B] * yPos + farPlane[C] * zPos + farPlane[D];
	if (distance <= -radius)
	{
		return false; // Bounding sphere is completely outside the far plane
	}
 
	// If we got here, then the bounding sphere is within at least all six sides of the view frustum, so it's visible and we should draw it!
	return true;
}

If you’re not sure how to get the ModelViewProjection matrix, you can calculate it by mulitplying the Projection matrix by the ModelView matrix (in that specific order!).

So in OpenGL fixed-pipeline code, you could get it into a 16 element array like this:

// Get the Projection matrix
GLfloat projectionMatrix[16];
glGetFloatv(GL_PROJECTION_MATRIX, projectionMatrix);
 
// Get the ModelView matrix
GLfloat modelViewMatrix[16];
glGetFloatv(GL_MODELVIEW_MATRIX, MVMatrix);
 
// To calculate the ModelViewProjection matrix...
glMatrixMode(GL_PROJECTION);                          // Set our mode to the Projection matrix
glPushMatrix();                                       // Save a copy
	glMultMatrixf(modelViewMatrix);               // Multiply the current matrix (i.e. the Projection matrix) by our copy of the ModelView matrix and store the result in the Projection Matrix
	GLfloat modelViewProjectionMatrix[16];        // Create an array to hold this ModelViewProjection matrix
	glGetFloatv(GL_PROJECTION_MATRIX, MVPMatrix); // Grab our ModelViewProjection matrix (which was stored in the Projection matrix)
glPopMatrix();                                        // Restore our original Projection matrix by discarding the top level we used to calculate the MVP matrix

While if you were working from the OpenGL SuperBible 5th Edition, you could just throw it into the function like this:

bool vis = objectVisible(transformPipeline.GetModelViewProjectionMatrix(), some-x-vertex, some-y-vertex, some-z-vertex, some-bounding-radius);

Cheers!