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.

A Simple C++ OpenGL Shader Loader

Update: There’s a re-worked and improved version of this shader loading code here: https://r3dux.org/2015/01/a-simple-c-opengl-shader-loader-improved/ – you should probably use that instead of this.


I’ve been doing a bunch of OpenGL programming recently and wanted to create my own shader classes to make setting up shaders as easy as possible – so I did ;-) To create vertex and fragment shaders and tie them into a shader program you can just import the Shader.hpp and ShaderProgram.hpp classes and use code like the following:

// Set up vertex shader
Shader vertexShader(GL_VERTEX_SHADER);
vertexShader.loadFromFile("MyVertexShader.vert");
vertexShader.compile();
 
// Set up fragment shader
Shader fragmentShader(GL_FRAGMENT_SHADER);
fragmentShader.loadFromFile("MyFragmentShader.frag");
fragmentShader.compile();
 
// Set up shader program
shaderProgram = new ShaderProgram();
shaderProgram->attachShader(vertexShader);
shaderProgram->attachShader(fragmentShader);
shaderProgram->linkProgram();

There’s also a loadFromString(some-string-containing-GLSL-source-code) method, if that’s your preference.

The ShaderProgram class uses a string/int map as a key/value pair, so to add attributes or uniforms you just specify their name and they’ll have a location assigned to them:

// Add the shader attributes
shaderProgram->addAttribute("vVertex");
// ...
 
// Add the shader uniforms
shaderProgram->addUniform("pMatrix");
// ...

The ShaderProgram class then uses two methods called attribute and uniform to return the bound locations (you could argue that I should have called these methods getAttribute and getUniform – but I felt that just attribute and uniform were cleaner in use. Feel free to mod if you feel strongly about it). When binding vertex attribute pointers you can use code like this:

// Set up a vertex buffer object to hold the vertex position data
GLuint vertexBufferId;
glGenBuffers(1, &vertexBufferId);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
glBufferData(GL_ARRAY_BUFFER, model.getVertexDataSizeBytes(), model.getVertexData(), GL_STATIC_DRAW);
 
// Set up the vertex attribute pointer for the vVertex attribute
glVertexAttribPointer(
    shaderProgram->attribute("vVertex"),  // Attribute location
    VERTEX_COMPONENTS,                    // Number of elements per vertex, here (x,y,z), so 3
    GL_FLOAT,                             // Data type of each element
    GL_FALSE,                             // Normalised?
    0,                                    // Stride
    0                                     // Offset
);

Finally, when drawing your geometry you can get just enable the shader program, provide the location and data for bound uniforms, and then disable it like this (I’m using the GL Mathematics library for matrices – you can use anything you fancy):

shaderProgram->use();
 
    // Provide uniform data
    glUniformMatrix4fv( shaderProgram->uniform("mMatrix"), 1, GL_FALSE, glm::value_ptr(mMatrix) );
    // ...
 
    // Draw stuff
 
shaderProgram->disable();

That’s pretty much it – nice and simple. I haven’t done anything with geometry shaders yet so I’ve no idea if there’s anything else you’ll need, but if so it likely won’t be too tricky a job to implement it yourself. Anyways, you can look at the source code for the classes themselves below, and I’ll put the two classes in a zip file here: ShaderHelperClasses.zip.

As a final note, you can’t create anything shader-y without having a valid OpenGL rendering context (i.e. a window to draw stuff to) or the code will segfault – that’s just how it works. The easiest way around this if you want to keep a global ShaderProgram object around is to create it as a pointer (i.e. ShaderProgram *shaderProgram;) and then initialise it later on when you’ve got the window open with shaderProgram = new ShaderProgram(); like I’ve done above.

Cheers! =D

Source code after the jump…

OpenGL – A Modified Phong-Blinn Light Model For Shadowed Areas

I was looking through the book Graphics Programming Methods (2003) the other day when I came across a paper titled “A Modified Phong-Blinn Light Model for Shadowed Areas” and thought it was pretty good, so I implemented it. It’s a really short paper, but what’s it’s basically saying is that by not allowing ambient light to be modified by geometry normals, any characteristics of the geometry are lost because we calculate the diffuse intensity as the dot product of the light location and the surface normal and limit the result between 0 and 1 (i.e. we only take diffuse lighting into consideration for surfaces facing our light source).

What the paper proposes instead is that we allow the dot product to run as -1.0 to +1.0, and when the value is within the range 0.0 to 1.0 (i.e. facing the light) we shade as usual, but when it’s -1.0 to 0.0 (facing away from the light) we calculate the lighting as the ambient light plus the negative diffuse intensity multiplied by the ambient light multiplied by q, where q is a value between 0.0 and 1.0. When q is 0.0, it’s like we’re using a traditional Phong-Blinn model, when it’s 1.0 we’re using the fully modified version, and any value in between is a sliding-scale combination of them.

To put that another way, if our surface is facing away from the light source, we use: Lighting = Ambient + (Diffuse * Ambient * q)

In the following images, the light source is roughly in line with the geometry on the Y and Z axis’, and is a way over on the positive X axis (i.e. to the right). Check out the difference…

Standard lighting with q = 0.0
Standard lighting with q = 0.0 - notice that the inside right area of the torus is getting no diffuse lighting, and all detail is lost to the ambient lighting.
Improved lighting with q = 0.5
Improved lighting with q = 0.5 - there's now some detail in the shadowed area on the inside-right of the torus
Improved lighting with q = 1.0
Improved lighting with q = 1.0 - there's now significantly more detail in the shadowed area on the inside-right of the torus

To create this effect, I used the following fragment shader:

Modified Phong-Blinn Light Model Fragment Shader

#version 330
 
// Uniforms
uniform vec4 ambientColour;
uniform vec4 diffuseColour;
uniform vec4 specularColour;
uniform sampler2D colourMap;
uniform sampler2D normalMap;
uniform float qUniform; // A value to manipulate our lighting by when surface is facing away from light source
 
// Input from our vertex shader
smooth in vec3 vVaryingNormal;
smooth in vec3 vVaryingLightDir;
smooth in vec2 vTexCoords;
 
// Output fragments
out vec4 vFragColour;
 
void main(void)
{ 
	const float maxVariance = 2.0; // Mess around with this value to increase/decrease Normal pertubation
	const float minVariance = maxVariance / 2.0;
 
	// Create a normal which is our standard normal + the normal map perturbation (which is going to be either positive or negative)
	vec3 normalAdjusted = vVaryingNormal + normalize(texture2D(normalMap, vTexCoords.st).rgb * maxVariance - minVariance);
 
	// Get our diffuse intensity as a dot product which is NOT limited 0..1
	float diffuseIntensity = dot(normalize(normalAdjusted), normalize(vVaryingLightDir)); 
 
	vec3 colour = vec3(0.0);
 
	// If the surface is facing the light then shade as normal
	if (diffuseIntensity > 0.0)
	{
		colour = (diffuseIntensity * diffuseColour.rgb) * texture2D(colourMap, vTexCoords.st).rgb + ambientColour.rgb;
	}
	else // Otherwise use the modified light model
	{
		colour = (diffuseIntensity * diffuseColour.rgb * ambientColour.rgb * qUniform) + ambientColour.rgb;
	}
 
	// Set the almost final output color as a vec4 - only specular to go
	vFragColour = vec4(colour, 1.0);
 
	// Specular light
	vec3 vReflection        = normalize(reflect(-normalize(normalAdjusted), normalize(vVaryingLightDir)));
	float specularIntensity = max(0.0, dot(normalize(normalAdjusted), vReflection));
 
	// Add in specular component
	if (diffuseIntensity > 0.0)
	{
		float fSpec = pow(specularIntensity, 64.0);
		vFragColour.rgb += vec3(fSpec * specularColour.rgb);
	}
}

Credits for this method go to Anders Hast, Tony Barrera, and Ewer Bengtsson – good work, fellas! =D

Simple OpenGL FBO Textures

I was playing around with FBOs and rendering to textures the other week and came up with this. A spinning yellow torus is rendered to a texture, then a second spinning torus is textured using the texture we just created of the first spinning torus… Yeah, I need to stop using donuts as my test objects.

Full source code & shaders after the jump…

Continue reading Simple OpenGL FBO Textures

Anaglyphic 3D in GLSL

I’ve been playing around with getting some red/cyan stereoscopic 3D working of late, and it’s all turned out rather well – take a look… (Red/Blue or Red/Cyan glasses are required for the effect to work):

Anaglyphic 3D in GLSL
Click for bigger version.

If you’ve got suitable glasses you should definitely see a 3D effect, although I don’t think me rescaling the image has done it any favours – so click the image to see the full sized version for the full effect.

The trick to this has been to render the scene twice to two separate FBO textures, then sample from the left and right textures to draw a fullscreen quad with a combined version as follows:

// Fragment shader to perform Analyphic 3D conversion of two textures from the left and right eyes
#version 330
 
uniform sampler2D leftEyeTexture;
uniform sampler2D rightEyeTexture;
 
in vec2 vTexCoord;
 
out vec4 vFragColour;
 
void main(void)
{
	vec4 leftFrag = texture(leftEyeTexture, vTexCoord);
	leftFrag = vec4(1.0, leftFrag.g, leftFrag.b, 1.0); // Left eye is full red and actual green and blue
 
	vec4 rightFrag = texture(rightEyeTexture, vTexCoord);
	rightFrag = vec4(rightFrag.r, 1.0, 1.0, 1.0); // Right eye is full green and blue and actual red
 
	// Multiply left and right components for final ourput colour
	vFragColour = vec4(leftFrag.rgb * rightFrag.rgb, 1.0); 
}

In the code itself, the torus’ spin around on the spot and look pretty good, although there’s no anti-aliasing as yet as I need to create some multisample buffers instead of straight/normal buffers for the FBO, but it’s not decided to play ball just yet – not to worry though, the hard part’s done and I’m sure multisampling will be sorted in a day or so. After that, I might give ColorCode3D (TM)(R)(C)(Blah) a go, as it seems to give a better colour representation whilst still allowing the same amount of depth as traditional anaglyphic techniques. Also, I’ve got to start using asymmetric frustums for the projection to minimise the likelihood of eye-strain, but I don’t see that as being too much of a problem.

Good times! =D