OpenGL Framebuffer Objects

OpenGL renders to framebuffers. By default OpenGL renders to screen, the default framebuffer that commonly contains a color and a depth buffer. This is great for many purposes where a pipeline consists of a single pass, a pass being a sequence of shaders. For instance a simple pass can have only a vertex and a fragment shader.

For more complex graphical effects or techniques, such as shadows or deferred rendering, multiple passes are often required, where the outputs of a pass are inputs of the following pass, for instance as textures. In this context, instead of rendering to screen, and then copying the result to a texture it would be much nicer to render to texture directly. The figure shows a two pass pipeline, where the first produces three textures that are used in the second pass to compose the final image. This is one of the advantages of framebuffer objects, we can render to multiple outputs in a single pass.

Besides, rendering to screen requires the outputs to be of a displayable format, which is not always the case in a multipass pipeline. Sometimes the textures produced by a pass need to have a floating point format which does not translate directly to colors, for instance the speed of a particle in meters per second.

In this short tutorial we will see how a framebuffer object can be created, and used with shaders. A demo is also provided with full source code, and a VS 2010 solution.

Creating a Framebuffer Object

OpenGL framebuffer objects allows us to create versatile framebuffer configurations, exposing all texture formats. As any OpenGL object, framebuffers are created with a glGen* function, bound with glBind*, and deleted with glDelete*.

void glGenFramebuffers(GLsizei n, GLuint *ids);

Parameters:

n: the number of names to generate,

ids: an array where the names will be stored.

void glBindFramebuffer(GLenum target, GLuint framebuffer);

Parameters:

target: can be GL_READ_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER, or GL_FRAMEBUFFER. The last option sets the framebuffer for both reading and drawing.

framebuffer: the name of the framebuffer object.

void glDeleteFramebuffers(GLsizei n, GLuint *framebuffers);

Parameters:

n: the number of framebuffers to delete;

framebuffers: An array with the names of the framebuffers to delete.

At any time, in OpenGL we have a framebuffer for drawing operations, and another for reading operations. By default these are the same, i.e. the default framebuffer is used for both purposes. When we use a framebuffer object we can bind it for drawing, reading, or both. When draw commands are issued they are directed to the framebuffer bound as GL_DRAW_FRAMEBUFFER. On the other hand, read back operations, such as glReadPixels target the framebuffer bound as GL_READ_FRAMEBUFFER. A framebuffer bound as GL_FRAMEBUFFER is targeted by both types of commands.

When creating a framebuffer object, and bind it, we have to state the type of framebuffer as draw or read. In the above example we bound the framebuffer object as drawing. Nonetheless, this does not imply that later we can not use the same framebuffer object for reading. A framebuffer can be later used for reading and/or drawing, regardless of the binding at creation time.

Right now we have an empty framebuffer object, and, as of OpenGL 4.3 or with ARB_framebuffer_no_attachment extension, that is perfectly valid! As long as we don’t need to output any fragments we can use it. For instance we can have a fragment shader that reads and writes arbitrarily from images, or it just wants to count something with atomic counters.

Nonetheless, rasterization works as usual, and this means that the framebuffer must have a size specified. In this particular case i.e. an empty framebuffer object, the size, and other attributes such as multisampling and layered rendering, can be defined with the function glFramebufferParameteri.

To set the size of an empty framebuffer to 512×512, with 4 samples, we could write:

This is probably not the typical scenario, though. So, we probably want to add some color output, or any other supported texture format, as well as a Z-buffer.

In OpenGL terminology, we attach these items to the framebuffer. The items can be render buffers or textures. From a flexibility point of view, textures are more versatile, as they allow for the same output types as render buffers, and they are ready to be fed as a texture for the next pass. Render buffers were designed specifically to be used with framebuffer objects hence, performance wise they may be superior, but on the other hand a copy is required to get their contents.

There are three types of attachments points in OpenGL:

color: the outputs written with the output variables from the fragment shader

depth: this works as the Z buffer for the framebuffer object

stencil: the stencil buffer

There may be multiple color attachments, or render targets, but only a single depth and stencil. The process is the same, the attachment type being a parameter of the function that associates, or attaches, the texture or a renderbuffer to a framebuffer object.

Creating and Attaching Renderbuffers to Framebuffer Objects

A renderbuffer object represents an image. These objects follow the same procedure as other objects: they are created with glGen* and are bound with glBind functions.

The only new relevant function, as far as creation goes, is glRenderBufferStorage.

textarget: the type of texture to attatch, can be the face of a cube map;

texture: the texture name. If zero then the previously attached texture is detached;

level: the selected mipmap level;

layer: the selected layer.

When the texture is a cube map, or a texture array, on a single color attachment then we’re attaching a layered texture, where each image in the set will be a layer. Note that in this case the depth attachment must match the color attachment, i.e. if the color attachment is a cube map then the depth attachment must also be a cube map with depth textures.

Let’s start with a simple example, creating a framebuffer with RGBA color and depth attachments.

We can build a framebuffer object without any color attachments, for instance to build a shadow map. And we can also build a framebuffer without a depth attachment, i.e. without a Z-buffer.

The requirements to successfully build a framebuffer object are quite loose. For instance we could have different dimensions for each attachment. In this case OpenGL considers the minimum value in each dimension.

Although in the above example all color attachments have the same internal type, this is not required. Each color attachment can have a different texture format. For instance, we could have a color attachment with an RGBA format, and another with R32F.

We can also mix textures and renderbuffers in the same framebuffer object.

Checking the Framebuffer Status

OpenGL provides a function to check the currently bound framebuffer, glCheckFramebufferStatus.

// check if everything is OK
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
GLenum e = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
if (e != GL_FRAMEBUFFER_COMPLETE)
printf("There is a problem with the FBO\n");

This function tests attachments, and their coherency. For instance, if using multisampling, the number of samples must be the same for all texture attachments and render buffer attachments. Layered rendering also implies that all attachments must also be layered. For all the gritty details see the OpenGL wiki page.

OpenGL Rendering

To use a framebuffer object, created as described above, several steps must be performed.

In the rendering stage, first we call glBindFramebuffer, with parameter GL_DRAW_FRAMEBUFFER to set the framebuffer object the target for rendering operations.

We must also define which color attachments will be used for rendering, and in which order.

Let’s assume we have a framebuffer object with n color attachments. To actually be able to render to all, or some, of these targets simultaneously we must enable them in the OpenGL application with function glDrawBuffers:

void glDrawBuffers(GLsizei n, const GLenum * bufs);

Parameters:

n: the number of color attachments to render to

bufs: an array with the color attachments to use

For instance, considering a framebuffer fbo with 3 color attachments (GL_COLOR_ATTACHMENT0..GL_COLOR_ATTACHMENT2), the following example enables the last two color attachments of the framebuffer:

In the above example the first color attachment would be left untouched, i.e. it will preserve its previous contents when a draw call is executed.

Note: the buffer selection in glDrawBuffers is part of the framebuffer object state. Therefore, if this setting is constant, this function can be called only once when creating the framebuffer.

After these steps we just do the rendering bit as usual, and in the end the result will be in the bound draw framebuffer object. To return to the default framebuffer just call glBindFramebuffer with 0 as the last parameter.

GLSL details

When setting up the shaders in the application we must bind each fragment’s output to a location. The fragment shader must declare an output variable for each render target and it can declare an explicit location as follows:

Alternatively, instead of specifying a location in the fragment shader, the variables can be bound with glBindFragDataLocation.

The location is not related to the color attachment index, i.e., location 0 does not necessarily output to color attachment 0. Instead, the location is related to the indexes used as an argument in glDrawBuffers. For instance, considering the above example for glDrawBuffers, location 0 will output to color attachment 1, and location 1 will output to color attachment 2.

When we write to a particular output variable we will be writing in the associated attachment (through glDrawBuffers).

Copying between framebuffer objects

Framebuffer copying is indeed a nice feature, and it even includes filtering! However, only one color attachment is considered from the source framebuffer. If the source framebuffer has multiple color attachments, then by default only color attachment 0 is copied. We can select which color attachment to use as a source from the framebuffer bound as GL_READ_FRAMEBUFFER calling glReadBuffer:

void glReadBuffer(GLenum mode);

Parameter:

mode: GL_COLOR_ATTACHMENTi

Again, the destination color attachments are defined with glDrawBuffers. The copying operation will copy the color attachment selected with glReadBuffer, from the source framebuffer, to the color attachments selected with glDrawBuffers of the destination framebuffer.

The above example copies the contents of color attachment 2 from framebuffer fboS to color attachments 0 and 1 from framebuffer fboD, shrinking the source image from 1024×1024 to 512×512 using linear filtering.

There are some restrictions though:

The data type of the attachments must match: floats with floats, ints with ints, unsigneds with unsigneds;

If both framebuffers are multisampled, then the number of samples must be equal in both;

If copying depth and/or stencil attachments, then the formats of the read and draw framebuffers must match, and the filtering must be set to GL_NEAREST.

Querying the OpenGL state and limits

There are of course limits to how many color attachments we can have in a framebuffer object. This can be queried with glGetIntegerv with parameter GL_MAX_COLOR_ATTACHMENTS.

The maximum width, height, number of layers, and samples, can be queried with parameters GL_MAX_FRAMEBUFFER_WIDTH, GL_MAX_FRAMEBUFFER_HEIGHT, GL_MAX_FRAMEBUFFER_LAYERS, GL_MAX_FRAMEBUFFER_SAMPLES, respectively.

Framebuffer Objects Demo

Download the Lighthouse3D Demo – FBO, with full source code and a VS 2010 solution. It includes everything required to run the demo (I hope). It renders a rotating model, a cow, to a framebuffer object, and then it uses it as a texture in a plane on the left side of the window, and on a cube on the right side.