OpenGL Frame Buffer Object 101


Original source: Rob Jones' tutorials at gamedev.net
I have just reformatted it slightly for better printing.

Introduction

The Frame Buffer Object (FBO) extension was introduced to make Render to Texture objects much more efficient and much easier to perform when compared with the copying or pbuffer alternatives.

In this little article Iím going to give you a quick over view of how to use this extension and some things to keep in mind when using it so you can add faster Render to Texture functionality to your OpenGL programs.

Setting Up

As with the other objects in OpenGL (texture object, pixel buffer objects and vertex buffer object) before you can use a FBO you have to create a valid handle to it:

GLuint fbo;
glGenFramebuffersEXT(1, &fbo);

To perform any operations on a FBO you need to bind it, much like you would a VBO or texture, so that the operations can be performed on it, this is done via the following code

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

The first parameter is the Ďtargetí you wish to bind the framebuffer to, right now the only target you can use is the one indicated above however it is possible future extensions might allow you to bind it somewhere else. The fbo variable holds the handle to the FBO we requested earlier. To perform any FBO related operations you need to have a FBO bound or the calls will fail.

Adding a Depth Buffer

A FBO on its own isnít of much use, for it to be usable you have to attach some renderable objects to it; these can be textures or the newly introduced renderbuffers.

A renderbuffer are just objects which are used to support offscreen rendering, often for sections of the framebuffer which donít have a texture format associated with them such as the stencil or depth buffer.

In this case we are going to use a renderbuffer to give our FBO a depth buffer to use while rendering.

Like the FBO we first of all have to get a handle to a valid renderbuffer:

GLuint depthbuffer;
glGenRenderbuffersEXT(1, &depthbuffer);

Having successfully done this we need to bind the renderbuffer so that it is the current renderbuffer for the following operations:

glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);

As with a FBO bind the first parameter is the Ďtargetí you wish to bind to, which right now can only be the indicated target. The depthbuffer variable holds the handle to the renderbuffer weíll be working with after this.

At this point the renderbuffer doesnít have any storage space associated with it, so we need to tell OpenGL how we want to use it and what size weíd like. In this case we are asking for a depth buffer of a certain size:

glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height);

Upon successful completion of the above code OpenGL will have allocated space for the renderbuffer to be used as a depth buffer with a given width and height. Note that renderbuffers can be used for normal RGB/RGBA storage and could be used to store stencil information.

Having reserved the space for the depth buffer the next job is to attach it to the FBO we created earlier.

glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);

While it might look a bit imposing the function is pretty easy to understand; all it is doing is attaching the depthbuffer we created earlier to the currently bound FBO to its depth buffer attachment point.

Adding a Texture To Render To

At this point we still donít have a way of writing colour information to the FBO, so that is what we are going to add now. There are two ways of going about it:

  1. Attach a colour Renderbuffer to the FBO
  2. Attach a texture to the FBO

The former does have some uses; however it is the latter we will be covering here.

Before you can attach a texture you need to create one, this hasnít changed from the normal way of using textures as youíll see:

GLuint img;
glGenTextures(1, &img);
glBindTexture(GL_TEXTURE_2D, img);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

In this instance we are creating a normal RGBA image of the same width and height as the renderbuffer we created earlier; this is important as ALL attachments to a FBO have to be the same width and height. Note that we donít upload any data, the space is just reserved by OpenGL so we can use it later.

Having created our texture the next job is to attach it to the FBO so we can render to it:

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0);

Again this might look imposing but it isnít that bad, the GL_COLOR_ATTACHMENT0_EXT tells OpenGL to attach it to the relevent attachment point (FBOs can have more than one colour buffer attached at any given time, each one to a different point), the GL_TEXTURE_2D tells OpenGL the format of the texture we are going to be attaching, img is the texture weíll be attaching and the Ď0í refers to the mipmap level of the texture you want to attach to, which you will generally want to leave as 0.

The final job to do in setup is to check that the FBO is Ďcompleteí. Completeness refers to the state of the FBO being one which, given the current OpenGL state and its attachments, all is correct for you to render to it.

This test is done via a single function which returns the status of the currently bound FBO

GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

If all has gone well then status will equal GL_FRAMEBUFFER_COMPLETE_EXT and your FBO is ready to be rendered to. Other error codes, as found in the spec, indicate other problems which might well have occurred when you tried to setup the FBO.

Rendering to Texture

With all the hard work setting things up done the usage of the FBO from here on out is in fact pretty simple and relies on just one function call: glBindFramebufferEXT().

To render to a FBO you bind it and to stop rendering to it you call the above again with Ď0í as the final parameter:

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0,0,width, height);


// Render as normal here
// output goes to the FBO and itís attached buffers

glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Three lines which probably jumped out at you right away are the glPushAttrib/glPopAttrib pair and the glViewport call. The glViewport call is needed so that we donít try to render into an area bigger than, or even smaller than, the FBO is setup for. The glPushAtrrib and glPopAttrib are used as a quick way to save the old viewport information, this is needed because the FBO shares all of its states with the main context and as such any changes made affect both the FBO and the main context you would be normally rendering to.

An important point to make here is that youíll notice that we only bound and then unbound the FBO to draw to it, we didnít reattach any textures or renderbuffers, this because they stay attached until you detach them yourself or the FBO is destroyed.

Using The Rendered To Texture

At this point our scene has been rendered to the texture and is now ready for us to use it and this operation itself is easy; we just bind the attached texture like any other texture.

glBindTexture(GL_TEXTURE_2D, img);

Having carried that out the texture is now ready to read from as normal.

Depending on the textureís filtering setup you might also want to generate mipmap information for it. Many people are used to using the gluBuild2DMipmaps() function to build mipmap information at load time and some of you might also be aware of the automatic mipmap generation extension; the FBO extension adds a third way with the GenerateMipmapEXT() function.

This function lets OpenGL build the mipmap information for you, the way itís done depends on the hardware you are running on, and is the correct way to do so for textures you have rendered to (you shouldnít use the automatic mipmap generation on a texture you are going to render to for various reasons which are covered in the spec).

To use the function all you have to do is bind a texture as above and then call:

glGenerateMipmapEXT(GL_TEXTURE_2D);

OpenGL will then generate all the required mipmap data for you so that your texture is ready to be used.

Itís important to note that if you intend on using any of the mipmap filters (GL_LINEAR_MIPMAP_LINEAR for example) then you must call glGenerateMipmapEXT() before checking the framebuffer is complete or attempting to render to it.

At setup time you can just do it as follows:

glGenTextures(1, &img);
glBindTexture(GL_TEXTURE_2D, img);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmapEXT(GL_TEXTURE_2D);

At this point your texture is complete and can be rendered to like normal.

Cleaning Up

Finally, when you are done with your FBO you need to clean things up and delete it. This, like textures, is done via a single function:

glDeleteFramebuffersEXT(1, &fbo);

You also have to clean up any renderbuffers you might have allocated, in this case the depthbuffer renderbuffer needs to deleted, which again works the same way as cleaning up a texture:

glDeleteRenderbuffersEXT(1, &depthbuffer);

At this point both the FBO and the renderbuffer have been freed and your clean up is completed.

Final Thoughts

Hopefully this quick tour of the FBO extension will be enough to get you up and running with it. For a more detailed over view you'll want to check out the FBO spec or the section on the extension in the book More OpenGL Game Programming

In way of closing, before you go and check out the example program which shows a basic usage of the FBO extension Iíd like to leave you with the following tips and notes on FBO usage.

  1. Currently you canít have a stencil attachment. There is an extension in the works to allow for a texture format which would allow depth-stencil textures and thus render to stencil, however as of yet there is a lack of support for it.
  2. Donít constantly make the destroy FBOs, instead generate what you need at load/setup time and reuse them during the program as required.
  3. Avoid modifying the texture you have rendered to via any glTexImage and related calls. Doing so can and most probably will hurt your performance.

Notes on the example program

While this article talks about adding a depth renderbuffer and then a texture to the FBO in that order it was discovered that currently ATIís drivers appear to have a bug whereby adding the depth renderbuffer and then a texture causes the application to crash. This should be kept in mind when doing any FBO related work and tested for as it is possible it could be fixed in a future driver revision thus rendering the problem non-existent.

Iíd also like to put out a big thanks to Rick Appleton for helping me test out and debug the code on NVIDA hardware, couldnít have done it without you mate :)

The example program requires some form of GLUT to be compiled and run (I used FreeGLUT)

OpenGL Frame Buffer Object 201

Introduction

In the last OpenGL Framebuffer object article we covered the basic usage of an FBO for rendering to a single texture and then applying that texture some where else. However this isnít all the FBO extension can do; indeed one of the integrated features of this extension which was touched upon briefly in the last article was that of attachment points.

In this article weíll go a little more in-depth into this aspect of the extension, first of all showing how you can use a single FBO to cycle through a number of textures to render to and finish off with using the OpenGL Shading Language to render to multiple textures at the same time via the Draw Buffers extension.

One FBO and Many Textures

In the last article we covered how to attach a texture to an FBO as a colour render target using the following function call:

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0);

As you might recall, the function attaches the texture indicated by the value held in img to the currently bound FBO. In this article the point of interest is the second parameter: GL_COLOR_ATTACHMENT0_EXT.

This parameter tells OpenGL to attach the texture to attachment point 0, however FBOs have many more colour attachment points which can be bound. The current specification allows for 16 attachment points (GL_COLOR_ATTACHMENT0_EXT to GL_COLOR_ATTACHMENT15_EXT) each of which can point to a separate texture attached to it. However, the number you can render to depends on whether you are running on hardware and drivers; this can be queried using the following code:

GLuint maxbuffers;
glGetIntergeri(GL_MAX_COLOR_ATTACHMENTS, &maxbuffers);

At this point maxbuffers holds the total number of colour attachments you can attach. On current hardware available at the time of writing the value returned will be a max of 4 buffers.

So, if we wanted to attach the texture indicated by img to the 2nd colour attachment point the above function call would become:

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, img, 0);

As you can see it is pretty easy to add textures, but how do we tell OpenGL where to render to?

Selecting The Destination

Well, in this case we go back to a function which has been around since the start of OpenGL; glDrawBuffer()

This function, and its relative glReadBuffer(), tells OpenGL where it should write data to and where it should read data from. By default both the draw and read buffers are set as GL_FRONT for single buffered contexts and GL_BACK for double buffered ones. With the advent of the FBO extension this function has been modified to allow you to select GL_COLOR_ATTACHMENTx_EXT for rendering to and reading from (where Ďxí is the attachment point number).

When you bind an FBO, the buffers are changed behind your back to GL_COLOR_ATTACHMENT0_EXT. So if you are only rendering to the default colour attachment point you donít have to make any changes, however when it comes to other buffers we have to tell OpenGL ourselves where we want it to render to.

Thus if we want to render to GL_COLOR_ATTACHMENT1_EXT we would have to bind the FBO and set the write buffer to the correct attachment point. Assuming we have attached a texture to colour attachment point 1 for the FBO held in fbo, then rendering would look as follows:

glBindFrameBuffer(GL_FRAMEBUFFER_EXT, fbo);
glPushAttrib(GL_VIEWPORT_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0,0,width, height);

// Set the render target
glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);

// Render as normal here
// output goes to the FBO and itís attached buffers


glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Note the use of glPushAttrib() to save both the viewport and colour buffer configuration before we make the changes and the use of glPopAttrib() to restore them once we are done. This is because these changes affect both the FBO and main rendering context and we donít want them active once we have completed rendering to the texture.

An important point when attaching multiple textures to an FBO is that they all have to be of the same dimension and colour depth. So, you canít attach a 512*512 32bit texture and a 256*256 16bit texture to the same FBO. However if you can stay within these limits then it is possible to use one FBO to render to multiple textures, which is faster than switching between FBOs. While this isnít an overly slow operation, avoiding unneeded operations is often good practise.

The first example

The first example program gives an example of rendering to 2 textures, one after each other, and then applying those textures to another cube. The code is based on the same example as used in the first article; there are some minor changes however.

Firstly, in the FBO setup code in the init function we create and bind a second texture to the FBO we created. Notice how we bind it to different attachment point to the first texture by using GL_COLOR_ATTACHMENT1_EXT as the bind point.

The rendering for the scene is basically the same as well, however instead of drawing the cube with its colours on once we draw it twice, the second time with the colours at half the intensity.

You should notice in the example program that when we render to the FBO we explicitly tell OpenGL to render to GL_COLOR_ATTACHMENT0_EXT and then GL_COLOR_ATTACHMENT1_EXT; this is because the FBO remembers the last buffer you told it to draw to, so when the drawing loop runs for the second time the first texture isnít updated as the drawing goes to the destination given in the last glDrawBuffer() call, which in this case is GL_COLOR_ATTACHMENT1_EXT. To see this effect comment out line 133, which has the glDrawBuffer() call in it, and you will notice the texture on the left hand cube is never updated.

Multiple Render Targets

Now we know how to attach multiple textures to an FBO, however we are still only drawing to one texture at a time by switching the draw target and, as helpful as that might be, at the start of the article it was mentioned that we would be covering how to render to multiple textures at the same time.

Well, it turns out that once you understand how to attach multiple textures at once the rest is pretty simple and all you need comes from the Draw Buffers extension and the OpenGL Shading Language (GLSL), both of which are core features of OpenGL 2.0.

The Draw Buffers Extension

The first extension, Draw Buffers, builds upon the functionality provided by glDrawBuffer(). As you recall this function allows us to specify which colour buffer we are going to write to, the Draw Buffers extension expands upon this to allow us to specify multiple colour buffers to write to. The number of buffers you can render to at once can be queried as follows:

GLuint maxbuffers;
glGetIntergeri(GL_MAX_DRAW_BUFFERS, &maxbuffers);

After which the variable maxbuffers holds the number of buffers we can render to at once (at the time of writing this value is typically 4, however the GeForce 8x00 series allows for up to 8 buffers to be drawn to).

The function used to indicated which buffers to draw to takes the same values as the glDrawBuffer() for the targets, which means we can supply it with GL_COLOR_ATTACHMENTx_EXT values in order to write to multiple attached textures at the same time.

Thus if we had textures attached to points 0 and 1, and wanted to render to both of them then we would do the following:

GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
glDrawBuffers(2, buffers);

After this function is executed OpenGL is setup to render to both colour buffers, which brings us on to how this is done.

MRT with FBO and GLSL

At this point if we were to render it using the standard Fixed Function Pipeline (as the examples have used thus far) then both textures would get the same data in them, however using GLSL we can write a fragment shader which allows us to send different data to the textures.

Normally when you write a GLSL fragment shader you would output the colour value to gl_FragColor, which would then be written to the frame buffer as normal. However there is a second way to write out colour information via the gl_FragData[] array.

This special variable allows us to direct where the data is going and maps directly to the values given to glDrawBuffers(). So, in the case of the glDrawBuffers() call above the buffers would map as follows:

glDrawBuffers value
FragData syntax
GL_COLOR_ATTACHMENT0_EXT
gl_FragData[0]
GL_COLOR_ATTACHMENT1_EXT
gl_FragData[1]

If we were to change the above function call however the mappings would change:

GLenum buffers[] = { GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT0_EXT };
glDrawBuffers(2, buffers);
glDrawBuffers value
FragData syntax
GL_COLOR_ATTACHMENT1_EXT
gl_FragData[0]
GL_COLOR_ATTACHMENT0_EXT
gl_FragData[1]

This is highlighted because it is the order the values are supplied to the glDrawBuffers() function, which dictates how they map to the gl_FragData[] array, not their values.

Lets say that for some reason we wanted to write green to one render target and blue to the other, then the GLSL code would look as follows:

#version 110

void main()
{
	gl_FragData[0] = vec4(0.0, 1.0, 0.0);
	gl_FragData[1] = vec4(0.0, 0.0, 1.0);
}

The first line says we need at least version 1.10 (OGL2.0) of the GLSL and the function body just writes green to the first buffer and blue to the second buffer.

The Second Example

The second example is a hybrid of the first example and the example from the first article. It performs the same output as the first example from this article but only draws the cube to the FBO once like the original article does; we achieve this by using a shader to control the output.

As with before, the major difference is in the initialisation code. Leaving aside the loading of a GLSL program, which is beyond the scope of this article, the part which is required to make MRT rendering work with an FBO is the following two lines:

GLenum mrt[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }
glDrawBuffers(2, mrt);

These two lines tell OpenGL that we wish to render to two buffers and what those two buffers are. Remember that an FBO remembers the last render target it was told to use, as such by doing this while the FBO is bound we can set this at startup and not have to worry about doing so during the main rendering loop.

The rendering loop should look familiar; the rendering to the FBO is the same as the original code, the only change being the call to bind and unbind the GLSL program which controls the colour output. The lower section is the same as the first example from this article, with the two cubes being rendered one with each texture on it.

The two GLSL shaders themselves require a quick mention as they are somewhat central to how MRT works in this manner.

The vertex shader, which is executed for each vertex you send to the graphics card, simply passes the colour value passed via glColor() to the fragment shader and transforms the vertex so it will be in the right place to draw the cube.

The fragment shader used is as follows:

#version 110

void main(void)
{
	gl_FragData[0] = vec4(gl_Color.r, gl_Color.g,gl_Color.b,1.0);
	gl_FragData[1] = vec4(gl_Color.r/2.0, gl_Color.g/2.0,gl_Color.b/2.0,1.0);
}

The key lines are the two gl_FragData lines, these indicate which buffer we are writing to. In this case gl_FragData[0] is the first texture and it gets a copy of the unmodified colour passed down from the vertex. On the other hand gl_FragData[1] is the second texture and gets the value of the colour passed from the vertex shader but halfed, thus giving the same output as the first example.

Final Thoughts

This article was designed to give you a quick overview and example of two more uses of the FBO extension.

The first example allows you to use the same FBO to render to multiple textures without switching FBOs, this is a useful thing to know because while FBOs are light to change when compared to pbuffers it is still much quicker to switch render targets than to switch between FBOs. As such if you can group your textures which need to be rendered to one at a time in such a way you can save some time.

The second example was to give you a feel for MRT rendering. While the example here is somewhat trivial, MRT does form a major part of various render-to-vertex buffer and post-processing techniques, as such the ability to output to multiple colour buffers is a useful one to know.

As before more details can be found in the Framebuffer Object spec and the Draw Buffers spec. More OpenGL Game Programming also has a chapter on FBOs and a chapter on GLSL, written by myself, which touches on using FBOs and MRT with GLSL some more.

Notes on the example program

The example program requires some form of GLUT to be compiled and run (I used FreeGLUT)

References

More OpenGL Game Programming
Framebuffer Object Spec
GDC 2005 Framebuffer Object pdf