Code-Highlight

Saturday 4 February 2012

#some tutorial on FBOs

The final render target of OpenGL is a framebuffer. External framebuffers can be used to render to textures. OpenGL provides an interface to create these framebuffers, also known as Frame Buffer Objects (FBO).

To illustrate how FBOs are used and what they can do, here is an approach to implement rendered textures:

You start by creating the texture and setting the parameters of the texture.

 
GLuint texture = 0; 
glGenTextures(1, &texture); 
glBindTexture(GL_TEXTURE_2D, texture); 
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 
             0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 
                GL_NEAREST); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
                GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, 
                GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 
                GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);

This is straight-forward and similar to the usual way, you set up a texture using OpenGL, but something people don't notice is, that the last line, which is unbinding the texture is really important!
To be able to attach a texture to an FBO it needs to be unbinded at that time, as well as when something is rendered into the FBO.

The next step is to setup a depthbuffer for depth-testing, may wonder at this point why setup the texture and depthbuffer before the FBO, there is no real reason, but what is important is that textures, buffers attached to a FBO have the same size as the FBO!

 
glGenRenderbuffersEXT(1, &depth);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, 
                         GL_DEPTH_COMPONENT, 
                         width, height);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

Again it is important to unbind before attaching something to the FBO.
Now finally create the FBO:

 
glGenFramebuffersEXT(1, &fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, 
                          GL_COLOR_ATTACHMENT0_EXT, 
                          GL_TEXTURE_2D, texture, 0);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, 
                             GL_DEPTH_ATTACHMENT, 
                             GL_RENDERBUFFER_EXT, 
                             depth); 

After creating the FBO the only last thing left to do is, checking if everything worked fine and then unbind the framebuffer:

 
GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (GL_FRAMEBUFFER_COMPLETE != err) 
    std::cout << glewGetErrorString(err) << endl;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

The depth-buffer is optional for a rendered texture, but it is quite useful to know about it for other applications. Now everything is setup to render to the texture simply bind the fbo and then render what should be in the texture, but it is important, that the viewport is not bigger than the buffer, so if you want to render to the full size of the texture you would do it like this:

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); 
glPushAttrib(GL_VIEWPORT_BIT); 
glViewport(0,0,width,height); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// RENDER STUFF IN THE TEXTURE...
glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

If you want to use the texture now you can do it the usual way, nothing special about that...


Some more references:
#more infos and speccs on FBOs
#interesting question on how to utilize FBOs