RenderTexture class

Framebuffer objects offer a fast and easy way to render into a texture. In this short tutorial, I'll be presenting a simple RenderTexture class. With such a class, rendering into a shadowmap is as easy as:

RenderTexture myRenderTexture(512, 512, GL_DEPTH_COMPONENT);
myRenderTexture.startRender();
//  draw from light position
myRenderTexture.finishRender();
myRenderTexture.bind();

If you're on Windows or Linux, you'll have to use GLEW to access the framebuffer object extension. If you're using the latest version of Mac OS X, you shouldn't have to change anything.

Allocating and using a framebuffer object is a lot like allocating and using a texture. It all comes down to these three functions:

void glGenFramebuffersEXT (GLsizei n, GLuint *ids);
void glBindFramebufferEXT(GLenum target, GLuint framebuffer);
void glFramebufferTexture2DEXT (GLenum target, GLenum attachment,
                                 GLenum textarget, GLuint texture,
                                 GLint level);
glGenFramebuffers and glBindFramebuffer work exactly like their texture counterparts. To attach a framebuffer to a texture, you call glFramebufferTexture2D. Sounds simple, right? It is. Without further ado, here is a basic RenderTexture class:
class RenderTexture {
	private:
		//  we don't want to allow copies to be made
		RenderTexture&  operator = (const RenderTexture& other) {}
		RenderTexture(const RenderTexture& other) {}

	protected:
		GLuint texID;
		GLuint fbo;
		GLuint width;
		GLuint height;

	public:
		RenderTexture(GLuint p_width, GLuint p_height, GLuint format);
		~RenderTexture();
		void startRender();
		void finishRender();
		void bind(GLuint unit=0);
};

RenderTexture::RenderTexture(GLuint p_width, GLuint p_height, GLuint format) : width(p_width), height(p_height) {
	//  allocate the texture that we will render into
	glGenTextures( 1, &texID );
	glBindTexture(GL_TEXTURE_2D, texID);
	if(format==GL_DEPTH_COMPONENT)
		glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
	else
		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);

	//  allocate a framebuffer object
	glGenFramebuffersEXT(1, &fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

	//  attach the framebuffer to our texture, which may be a depth texture
	if(format==GL_DEPTH_COMPONENT) {
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texID, 0);
		//  disable drawing to any buffers, we only want the depth
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);
	} else
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texID, 0);

	//  you can check to see if the framebuffer is 'complete' with no errors
	if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)!=GL_FRAMEBUFFER_COMPLETE_EXT)
		//  error!  might want to handle this somehow
		;

	//  unbind our framebuffer, return to default state
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

RenderTexture::~RenderTexture() {
	//  free our texture
	glDeleteTextures( 1, &texID );
	//  free our framebuffer
	glDeleteFramebuffersEXT( 1, &fbo );
}

void RenderTexture::startRender() {
	//  bind the framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
	//  set the viewport to our texture dimensions
	glViewport(0,0,width,height);
}

void RenderTexture::finishRender() {
	//  unbind our framebuffer, return to default state
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	//  remember to restore the viewport when you are ready to render to the screen!
}

void RenderTexture::bind(GLuint unit) {
	glActiveTextureARB(GL_TEXTURE0_ARB + unit);
	glBindTexture( GL_TEXTURE_2D, texID );
}
One problem you'll notice if you try to use the z-buffer while rendering to the texture is that there is no depth buffer! The texture, of course, doesn't come with one. We need to allocate a render buffer to act as our depth buffer and attach it to the frame buffer. Let's give the user of the class an option of whether to have a depth buffer or not in the constructor's parameters. We only need to change the constructor and destructor to attach the depth buffer. Changes are highlighted:
class RenderTexture {
	private:
		//  we don't want to allow copies to be made
		RenderTexture&  operator = (const RenderTexture& other) {}
		RenderTexture(const RenderTexture& other) {}
	protected:
		GLuint texID;
		GLuint fbo;
		GLuint width;
		GLuint height;
		bool hasDepthBuffer;
		GLuint depthBuffer;

	public:
		RenderTexture(GLuint p_width, GLuint p_height, GLuint format, bool p_hasDepthBuffer=false);
		~RenderTexture();
		void startRender();
		void finishRender();
		void bind(GLuint unit=0);
};

RenderTexture::RenderTexture(GLuint p_width, GLuint p_height, GLuint format, bool p_hasDepthBuffer) : width(p_width), height(p_height), hasDepthBuffer(p_hasDepthBuffer) {
	//  allocate the texture that we will render into
	glGenTextures( 1, &texID );
	glBindTexture(GL_TEXTURE_2D, texID);
	if(format==GL_DEPTH_COMPONENT)
		glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
	else
		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);

	//  allocate a framebuffer object
	glGenFramebuffersEXT(1, &fbo);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

	if(hasDepthBuffer) {
		//  allocate a renderbuffer for our depth buffer the same size as our texture
		glGenRenderbuffersEXT(1, &depthBuffer);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);
		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height);
		//  attach the renderbuffer to our framebuffer
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);
	}


	//  attach the framebuffer to our texture, which may be a depth texture
	if(format==GL_DEPTH_COMPONENT) {
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texID, 0);
		//  disable drawing to any buffers, we only want the depth
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);
	} else
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texID, 0);

	//  you can check to see if the framebuffer is 'complete' with no errors
	if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)!=GL_FRAMEBUFFER_COMPLETE_EXT)
		//  error!  might want to handle this somehow
		;
}

RenderTexture::~RenderTexture() {
	//  free our texture
	glDeleteTextures( 1, &texID );
	//  free our framebuffer
	glDeleteFramebuffersEXT( 1, &fbo );
	//  free our renderbuffer
	if(hasDepthBuffer) glDeleteRenderbuffersEXT(1, &depthBuffer);
}
...