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…

Main C++ File

#include <iostream>
#include <ctime>
#include <math.h>           // Needed for sin/cos/tan etc.
 
#include <GL/glew.h>		// Include glew, which calls GL.h for us
#include "glfw.h"		// Include OpenGL Framework library
#include <GL/glut.h>
 
#include <GLTools.h>	// OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <GLBatch.h>
#include <StopWatch.h>
 
// Include the DevIL libraties for image loading
#define ILUT_USE_OPENGL
#include <IL/il.h>
#include <IL/ilu.h>
#include <IL/ilut.h>
 
#include "../../GLTools/r3dTools.h"        // Include our custom tools to do things like load images
 
using namespace std;
 
GLint   windowWidth  = 800;     // Define our window width
GLint   windowHeight = 600;     // Define our window height
GLfloat fieldOfView  = 45.0f;   // Frustrum field of view
GLfloat zNear        = 1.0f;    // Frustrum near distance
GLfloat zFar         = 1000.0f; // Frustrum far distance
GLint   frameCount   = 0;       // Keep track of how many frames we've drawn
 
GLFrame             viewFrame;
GLFrustum           viewFrustum;
 
GLTriangleBatch     torusBatch;
GLTriangleBatch     torusBatch2;
 
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLGeometryTransform transformPipeline;
 
// Handles for our shader programs
GLuint	            r3dShaderProgram;
GLuint	            r3dTextureProgram;
 
// Diffuse shader uniform locations.
// Note: The type needs to be int not uint because if the location of the uniform variable cannot be found glGetUniformLocation returns -1!
GLint               locMVMatrix;
GLint               locMVPMatrix;
GLint               locNormalMatrix;
GLint               locLightPos;
GLint               locAmbientColour;
GLint               locDiffuseColour;
 
// Texture shader uniform locations.
GLint               locTexTextureMap;
GLint               locTexMVMatrix;
GLint               locTexMVPMatrix;
GLint               locTexNormalMatrix;
GLint               locTexLightPos;
GLint               locTexAmbientColour;
GLint               locTexDiffuseColour;
 
GLuint fbo;        // The frame buffer object
GLuint fboDepth;   // The depth buffer for the frame buffer object
GLuint fboTexture; // The handle for the texture we draw to in the framebuffer
 
GLenum windowBuff[] = { GL_BACK_LEFT };
GLenum fboBuff[] = { GL_COLOR_ATTACHMENT0 };
 
void GLFWCALL resizeWindow(GLsizei theNewWidth, GLsizei theNewHeight)
{
	// Set our globals so we can query the window size at any time
	windowWidth  = theNewWidth;
	windowHeight = theNewHeight;
 
	// Set the viewport to the full size of the new window
	glViewport(0, 0, windowWidth, windowHeight); // 0, 0 is the BOTTOM-LEFT!!
 
	// Create the projection matrix, and load it on to the projection matrix stack
	viewFrustum.SetPerspective(fieldOfView, float(windowWidth)/float(windowHeight), zNear, zFar);
 
	projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); // Get the projection matrix from our frustum (defined in the line above) and load it into our proper projection matrix stack
 
	// Set the transformation pipeline to use the two matrix stacks
	transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
 
	// ----- Resize the FBO and associated depth & colour buffers ------
 
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
 
	glBindRenderbuffer(GL_RENDERBUFFER, fboDepth);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, windowWidth, windowHeight);
 
	glBindTexture(GL_TEXTURE_2D, fboTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, windowWidth, windowHeight, 0, GL_RGBA, GL_FLOAT, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
	// Attach texture to first color attachment and the depth to the depth attachment
	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTexture, 0);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
 
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
 
	// ----- End of FBO resize section -----
 
	cout << "Resized window." << endl;
}
 
// Function to set some initial OpenGL state-machine properties
void initGL()
{
	//  ----- Initialise GLEW -----
 
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		cout << "GLEW initialisation error: " << glewGetErrorString(err) << endl;
		exit(-1);
	}
	cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << endl;
 
	// Set the window title
	glfwSetWindowTitle("Shaded FBO Textures | r3dux.org");
 
	// Lock to vsync
	glfwSwapInterval(1);
 
	// Initialise all DevIL functionality
	ilutRenderer(ILUT_OPENGL);
	ilInit();
	iluInit();
	ilutInit();
	ilutRenderer(ILUT_OPENGL);	// Tell DevIL that we're using OpenGL for our rendering
 
	// Background
	glClearColor(0.5f, 0.5f, 0.5f, 1.0f );
 
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
 
	viewFrame.MoveForward(4.0f);
 
	// Make the torus'
	gltMakeTorus(torusBatch, 0.80f, 0.25f, 52, 26);
	gltMakeTorus(torusBatch2, 0.5f, 0.2f, 52, 26);
 
	// Load shaders and bind attributes
	r3dShaderProgram = gltLoadShaderPairWithAttributes("r3dPointLightDiffuseShader.vp",  "r3dPointLightDiffuseShader.fp", 2, // We're passing in 2 attributes...
										GLT_ATTRIBUTE_VERTEX, "vVertex",
										GLT_ATTRIBUTE_NORMAL, "vNormal");
 
	locMVMatrix = glGetUniformLocation(r3dShaderProgram, "mvMatrix");
	checkValidUniformLocation(locMVMatrix, "mvMatrix");
 
	locMVPMatrix = glGetUniformLocation(r3dShaderProgram, "mvpMatrix");
	checkValidUniformLocation(locMVPMatrix, "mvpMatrix");
 
	locNormalMatrix = glGetUniformLocation(r3dShaderProgram, "normalMatrix");
	checkValidUniformLocation(locNormalMatrix, "normalMatrix");
 
	locLightPos = glGetUniformLocation(r3dShaderProgram, "vLightPosition");
	checkValidUniformLocation(locLightPos, "vLightPosition");
 
	locDiffuseColour = glGetUniformLocation(r3dShaderProgram, "diffuseColour");
	checkValidUniformLocation(locDiffuseColour, "diffuseColour");
 
 
	// Load shaders and bind attributes
	r3dTextureProgram = gltLoadShaderPairWithAttributes("r3dTexShader.vp",  "r3dTexShader.fp", 3,
										GLT_ATTRIBUTE_VERTEX,   "vVertex",
										GLT_ATTRIBUTE_NORMAL,   "vNormal",
										GLT_ATTRIBUTE_TEXTURE0, "vTexCoord0");
 
 
	locTexMVMatrix = glGetUniformLocation(r3dTextureProgram, "mvMatrix");
	checkValidUniformLocation(locTexMVMatrix, "mvMatrix");
 
	locTexMVPMatrix = glGetUniformLocation(r3dTextureProgram, "mvpMatrix");
	checkValidUniformLocation(locTexMVPMatrix, "mvpMatrix");
 
	locTexNormalMatrix = glGetUniformLocation(r3dTextureProgram, "normalMatrix");
	checkValidUniformLocation(locTexNormalMatrix, "normalMatrix");
 
	locTexLightPos = glGetUniformLocation(r3dTextureProgram, "vLightPosition");
	checkValidUniformLocation(locTexLightPos, "vLightPosition");
 
	locTexDiffuseColour = glGetUniformLocation(r3dTextureProgram, "diffuseColour");
	checkValidUniformLocation(locTexDiffuseColour, "diffuseColour");
 
	locTexTextureMap = glGetUniformLocation(r3dTextureProgram, "theTexture");
	checkValidUniformLocation(locTexTextureMap, "theTexture");
 
	// Create and bind an FBO
	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
 
	// Create depth renderbuffer
	glGenRenderbuffers(1, &fboDepth);
	glBindRenderbuffer(GL_RENDERBUFFER, fboDepth);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, windowWidth, windowHeight);
 
	// Create the texture
	glGenTextures(1, &fboTexture);
	glBindTexture(GL_TEXTURE_2D, fboTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, windowWidth, windowHeight, 0, GL_RGBA, GL_FLOAT, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
	// Attach texture to first color attachment and the depth to the depth attachment
	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTexture, 0);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
 
	GLenum fboStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);                                   // Check the status of our generated frame buffer
 
	if (fboStatus != GL_FRAMEBUFFER_COMPLETE)                                                      // If the frame buffer does not report back as complete...
	{
		std::cout << "Couldn't create frame buffer" << std::endl;                               // ...Output an error to the console
		exit(0);                                                                                // Exit the application
	}
 
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // Reset framebuffer binding
 
	checkGLError("InitGL-end");
}
 
// Function to draw our OpenGL scene
void drawScene()
{
	static CStopWatch rotTimer;
 
	// Set up to draw to our FBO texture...
 
	glActiveTexture(GL_TEXTURE0); // Try to do everything using the default texture unit for now (texture unit 0) - can spread things across texture units when it works!!!
 
	// Specify that we're going to draw to our framebuffer and not the screen
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
	glDrawBuffers(1, fboBuff);
 
	// Clear the window and the depth buffer
	glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	modelViewMatrix.PushMatrix(viewFrame);
 
		modelViewMatrix.LoadIdentity();
		modelViewMatrix.Translate(0.0f, 0.0f, -3.5f);
 
		modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 160.0f, 0.0f, 1.0f, 0.5f); // Rotate the torus being drawn for the texture at quite a fast rate
 
		GLfloat vEyeLightPos[]   = { -100.0f, 100.0f, 100.0f };
		GLfloat vDiffuseColour[] = { 1.0f, 1.0f, 0.0f, 1.0f }; // Texture torus is drawn in yellow
 
		glUseProgram(r3dShaderProgram); // Specify our shader program
 
		// Specify all the uniform variables for our shader program
		glUniform4fv(locDiffuseColour,      1, vDiffuseColour);
		glUniform3fv(locLightPos,           1, vEyeLightPos);
		glUniformMatrix4fv(locMVMatrix,     1, GL_FALSE, transformPipeline.GetModelViewMatrix());
		glUniformMatrix4fv(locMVPMatrix,    1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
		glUniformMatrix3fv(locNormalMatrix, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
 
		torusBatch.Draw();
 
	modelViewMatrix.PopMatrix();
 
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // Unbind our frambuffer for drawing
 
	// ----- End of draw yellow torus as texture to FBO section -----
 
	// Now set to draw to window
	glDrawBuffers(1, windowBuff);
 
	// Clear the window and the depth buffer
	glClearColor(0.4f, 0.2f, 0.2f, 1.0f );
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	// Draw full screen quad with blur shader and all blur textures
	projectionMatrix.PushMatrix();
 
		projectionMatrix.LoadIdentity();
 
		modelViewMatrix.PushMatrix();
 
			modelViewMatrix.LoadIdentity();
 
			modelViewMatrix.Translate(0.0f, 0.0f, -2.0f);
			modelViewMatrix.Rotate(rotTimer.GetElapsedSeconds() * 60.0f, 0.0f, 1.0f, 0.5f);
 
			// Create the projection matrix, and load it on to the projection matrix stack
			viewFrustum.SetPerspective(fieldOfView, float(windowWidth)/float(windowHeight), zNear, zFar);
 
			projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); // Get the projection matrix from our frustum (defined in the line above) and load it into our proper projection matrix stack
 
			// Set the transformation pipeline to use the two matrix stacks
			transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
 
			glBindTexture(GL_TEXTURE_2D, fboTexture); // Select the texture in our framebuffer
 
			// Set the blur program as the current one
			glUseProgram(r3dTextureProgram);
 
			GLfloat vTexEyeLightPos[]   = { 100.0f, 0.0f, 0.0f };
			GLfloat vTexDiffuseColour[] = { 0.0f, 0.5f, 1.0f, 1.0f };
 
			// Specify all the uniform variables for our shader program
			glUniform4fv(locTexDiffuseColour,      1, vTexDiffuseColour);
			glUniform3fv(locTexLightPos,           1, vTexEyeLightPos);
			glUniformMatrix4fv(locTexMVMatrix,     1, GL_FALSE, transformPipeline.GetModelViewMatrix());
			glUniformMatrix4fv(locTexMVPMatrix,    1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
			glUniformMatrix3fv(locTexNormalMatrix, 1, GL_FALSE, transformPipeline.GetNormalMatrix());
			glUniform1i(locTexTextureMap,          0); // The last digit here is the texture unit to use (as in the GL_TEXTURE0, GL_TEXTURE1 type texture unit)
 
			torusBatch2.Draw();
 
		modelViewMatrix.PopMatrix();
 
	projectionMatrix.PopMatrix();
 
    // ----- Stop Drawing Stuff! ------
 
    glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
 
} // End of drawScene function
 
void ShutdownRC(void)
{
	// Make sure default FBO is bound
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
 
	// Cleanup textures
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, 0);
 
	glDeleteTextures(1, &fboTexture);
 
	// Cleanup RBOs
	glDeleteRenderbuffers(1, &fboDepth);
 
	// Cleanup FBOs
	glDeleteFramebuffers(1, &fbo);
}
 
// Our main function
int main()
{
	srand((unsigned)time(NULL)); // Seed the random number generator
 
	// Define our buffer settings
	int redBits    = 8, greenBits = 8,    blueBits    = 8;
	int alphaBits  = 8, depthBits = 32,   stencilBits = 8;
 
	// Flag to keep our main loop running
	bool running = true;
 
	// Initialise glfw
	glfwInit();
 
	// Ask for 4x AntiAliasing (this doesn't mean we'll get it - it'll work only if the GLX_ARB_multisample extension is available)
	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
 
	// Create a window
	if(!glfwOpenWindow(windowWidth, windowHeight, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
	{
		cout << "Failed to open window!" << endl;
		glfwTerminate();
		return 0;
	}
 
	// Call our initGL function to set up our OpenGL options
	initGL();
 
	// Specify the callback function for when the window is resized
	glfwSetWindowSizeCallback(resizeWindow);
 
	while (running == true)
	{
		// Draw our scene
		drawScene();
 
		// Increase our frame counter
		frameCount++;
 
		checkGLError("Main loop");
 
		// Exit if ESC was pressed or the window was closed
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam( GLFW_OPENED);
	}
 
	ShutdownRC();
 
	glfwTerminate();
 
	return 0;
}

Point light diffuse vertex shader (first yellow torus)

#version 330
 
// Incoming per-vertex attribute values
in vec4 vVertex;
in vec3 vNormal;
 
// Uniform values shared across all vertexes (and read only!)
uniform vec4 diffuseColour;
uniform vec3 vLightPosition; 
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
 
// Outgoing colour value to fragment shader
smooth out vec4 vVaryingColour;
 
void main(void) 
{
    // Get vertex normal in eye coodinates
    vec3 vEyeNormal = normalMatrix * vNormal;
 
    // Get vertex position in eye coordinates
    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w; // Normalise vertex position
 
    // Get vector to light source
    vec3 vLightDir = normalize(vLightPosition - vPosition3);
 
    // Calculate the intensity of the light on the surface by calculating the dot product of the geometry's normal and the light direction
    float diffuseIntensity = max(0.0, dot(vEyeNormal, vLightDir));
 
    // Multiply diffuse intensity by (uniform) diffuse colour and force alpha to 1.0
    vVaryingColour.rgb = diffuseIntensity * diffuseColour.rgb;
 
    // Transform the geometry 
    gl_Position = mvpMatrix * vVertex;
}

Point light diffuse fragment shader (first yellow torus)

#version 330
 
// Input from our vertex shader
in vec4 vVaryingColour;
 
// Output fragments
out vec4 vFragColour;
 
void main(void)
{ 
   vFragColour = vVaryingColour;
}

Texture / Diffuse Shader vertex shader (final pass)

#version 330
 
// Uniform values shared across all vertexes (and read only!)
uniform vec4 diffuseColour;
uniform vec3 vLightPosition;
uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat3 normalMatrix;
 
in vec4 vVertex;
in vec3 vNormal;
in vec2 vTexCoord0;
 
// Outgoing colour value to fragment shader
smooth out vec2 vTex;
smooth out vec4 vVaryingColour;
 
void main(void) 
{
    // Get vertex normal in eye coodinates
    vec3 vEyeNormal = normalMatrix * vNormal;
 
    // Get vertex position in eye coordinates
    vec4 vPosition4 = mvMatrix * vVertex;
 
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w; // Normalise vertex position
 
    // Get vector to light source
    vec3 vLightDir = normalize(vLightPosition - vPosition3);
 
    // Calculate the intensity of the light on the surface by calculating the dot product of the geometry's normal and the light direction
    // Note: Doing this in the vertex shader is the lower quality but faster gouraud method.. throw this in the fragment shader for proper diffuse lighting
    float diffuseIntensity = max(0.0, dot(vEyeNormal, vLightDir));
 
    // ----- Outgoing to fragment shader -----
 
    // Multiply diffuse intensity by (uniform) diffuse colour and force alpha to 1.0
    vVaryingColour.rgb = diffuseIntensity * diffuseColour.rgb;
 
    // Pass through the texture
    vTex = vTexCoord0;
 
    // Transform the geometry 
    gl_Position = mvpMatrix * vVertex;
}

Texture / Diffuse Shader fragment shader (final pass)

#version 330
 
uniform sampler2D theTexture;
 
in vec2 vTex;
in vec4 vVaryingColour;
 
out vec4 vFragColour;
 
void main(void)
{
	vFragColour =  texture2D(theTexture, vTex); // Sample the texture...
	vFragColour += vVaryingColour;              // ...then add in the diffuse lighting.
}

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.