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);
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 ); }
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); } ...