OpenGL Framebuffer Objects
Prev: Picking | Next: Atomic Counters |
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*.
[stextbox]void glGenFramebuffers(GLsizei n, GLuint *ids);
Parameters:
n
: the number of names to generate,ids
: an array where the names will be stored.
[/stextbox][stextbox]void glBindFramebuffer(GLenum target, GLuint framebuffer);
Parameters:
target
: can beGL_READ_FRAMEBUFFER
,GL_DRAW_FRAMEBUFFER
, orGL_FRAMEBUFFER
. The last option sets the framebuffer for both reading and drawing.framebuffer
: the name of the framebuffer object.
[/stextbox][stextbox]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.
[/stextbox]
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.
For instance to create a framebuffer object we could write:
GLuint fbo; // generate a framebuffer glGenFramebuffers(1, &fbo); // bind it as the target for rendering commands glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
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:
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 512); glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, 512); glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES, 4);
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
.
[stextbox]void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
Parameters:
target
:GL_RENDERBUFFER
internalformat
: a renderable formatwidth
: the width of the imageheight
: the height of the image
[/stextbox]
The color accepted formats for a renderbuffer are a subset of the possible texture formats. Some examples are GL_RGBA8, GL_RGBA32, GL_R32. All depth and stencil texture formats are accepted.
Finally we need to attach the renderbuffer to the bound framebuffer. We can achieve this with the following function:
[stextbox]void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
Parameters:
target
: specifies to which bound framebuffer the render buffer will be attached to:GL_DRAW_FRAMEBUFFER
,GL_READ_FRAMEBUFFER
, orGL_FRAMEBUFFER
.GL_FRAMEBUFFER
is equivalent toGL_DRAW_FRAMEBUFFER
;attachment
:GL_COLOR_ATTACHMENTi
,GL_DEPTH_ATTACHMENT
,GL_STENCIL_ATTACHMENT
orGL_DEPTH_STENCIL_ATTACHMMENT
;renderbuffertarget
:GL_RENDERBUFFER
;renderbuffer
: the name of the renderbuffer to attach.
[/stextbox]
The above function will attach the render buffer to the bound framebuffer with the same target.
The following code snippet shows how to create, and attach, a render buffer for the depth attachment to the framebuffer object currently bound for drawing:
GLuint fb; glGenRenderbuffers(1, &fb); glBindRenderbuffer(GL_RENDERBUFFER, fb); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb);
To delete a renderbuffer we call glDeleteRenderbuffers
.
[stextbox]void glDeleteRenderbuffers(GLsizei n, GLuint *renderbuffers);
Parameters:
n
: the number of renderbuffers to delete;renderbuffers
: An array with the names of the renderbuffers to delete.
[/stextbox]
Attaching Textures to Framebuffer Objects
Considering textures, there are two scenarios for each attachment point:
- attaching a single image, for instance a specific mipmap level of a 2D texture
- attaching a set of images, where each will represent a layer in the geometry/fragment shader.
Examples of the later are cube map textures and texture arrays.
The functions to perform the attachments are as follows:
[stextbox]void glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level);
void glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
void glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer);
void glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
Parameters:
target
:GL_DRAW_FRAMEBUFFER
,GL_READ_FRAMEBUFFER
, orGL_FRAMEBUFFER
.GL_FRAMEBUFFER
is equivalent toGL_DRAW_FRAMEBUFFER
;attachment
:GL_COLOR_ATTACHMENTi
,GL_DEPTH_ATTACHMENT
,GL_STENCIL_ATTACHMENT
orGL_DEPTH_STENCIL_ATTACHMMENT
;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.
[/stextbox]
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.
GLuint colorTex, depthTex, fbo; // create a RGBA color texture glGenTextures(1, colorTex); glBindTexture(GL_TEXTURE_2D, colorTex); glTexImage2D(,GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // create a depth texture glGenTextures(1, depthTex); glBindTexture(GL_TEXTURE_2D, depthTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); // create the framebuffer object glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); // attach color glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTex, 0); glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTex, 0);
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.
To have multiple color attachments we can write:
GLuint colorTex[n], depthTex, fbo; // create n RGBA color textures glGenTextures(n, colorTex); for (int i = 0; i < n; ++i) { glBindTexture(GL_TEXTURE_2D, colorTex[i]); glTexImage2D(...); } // create a depth texture glGenTextures(1, depthTex); glBindTexture(GL_TEXTURE_2D, depthTex); glTexImage2D(...); // create the framebuffer object glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); // attach colors for (int i = 0; i < n; ++i) { glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i , colorTex[i], 0); } glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTex, 0);
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
:
[stextbox]void glDrawBuffers(GLsizei n, const GLenum * bufs);
Parameters:
n
: the number of color attachments to render tobufs
: an array with the color attachments to use
[/stextbox]
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:
// bind the framebuffer as the output framebuffer glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); // define the index array for the outputs GLuint attachments[2] = { GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(2, attachments);
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:
... layout (location = 0) out vec4 normalOut; layout (location = 1) out vec4 texCoordOut; ...
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
:
[stextbox]void glReadBuffer(GLenum mode);
Parameter:
mode
:GL_COLOR_ATTACHMENTi
[/stextbox]
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.
[stextbox]void glBlitFramebuffer(
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask,
GLenum filter);
Parameters:
srcX0, srcY0, srcX1, srcY1
: the source window coordinates;dstX0, dstY0, dstX1, dstY1
:the destination window coordinates;mask
: a bitwise OR combination of the buffers to be copied. The available flags are:GL_COLOR_BUFFER
,GL_DEPTH_BUFFER
, andGL_STENCIL_BUFFER
;filter
: When the source area is different from the destination the image will be streched. As in regular textures two filters are available:GL_NEAREST
andGL_LINEAR
.
Note that GL_LINEAR
can only be used for color attachments.
[/stextbox]
Here goes an example:
// bind the source framebuffer and select a color attachment to copy from glBindFramebuffer(GL_READ_FRAMEBUFFER, fboS); glReadBuffers(GL_COLOR_ATTACHMENT2); // bind the destination framebuffer and select the color attachments to copy to glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboD); GLuint attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, attachments); // copy glBlitFramebuffer(0,0,1024, 1024, 0,0,512,512, GL_COLOR_BUFFER_BIT, GL_LINEAR);
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.
void printFramebufferLimits() { int res; glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &res); printf("Max Color Attachments: %d\n", res); glGetIntegerv(GL_MAX_FRAMEBUFFER_WIDTH, &res); printf("Max Framebuffer Width: %d\n", res); glGetIntegerv(GL_MAX_FRAMEBUFFER_HEIGHT, &res); printf("Max Framebuffer Height: %d\n", res); glGetIntegerv(GL_MAX_FRAMEBUFFER_SAMPLES, &res); printf("Max Framebuffer Samples: %d\n", res); glGetIntegerv(GL_MAX_FRAMEBUFFER_LAYERS, &res); printf("Max Framebuffer Layers: %d\n", res); }
To find out which are the currently bound framebuffers we also use glGetIntegerv
, this time with parameters GL_DRAW_FRAMEBUFFER_BINDING
and GL_READ_FRAMEBUFFER_BINDING
.
To find out which color attachment has been set with glDrawBuffers
we can use the param GL_DRAW_BUFFERi
.
To query information related to a specific attachment we can use glGetFramebufferAttachmentParameter
. A small example, with some of the possible queries is now presented:
void printFramebufferInfo(GLenum target, GLuint fbo) { int res, i = 0; GLint buffer; glBindFramebuffer(target,fbo); do { glGetIntegerv(GL_DRAW_BUFFER0+i, &buffer); if (buffer != GL_NONE) { printf("Shader Output Location %d - color attachment %d\n", i, buffer - GL_COLOR_ATTACHMENT0); glGetFramebufferAttachmentParameteriv(target, buffer, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &res); printf("\tAttachment Type: %s\n", res==GL_TEXTURE?"Texture":"Render Buffer"); glGetFramebufferAttachmentParameteriv(target, buffer, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &res); printf("\tAttachment object name: %d\n",res); } ++i; } while (buffer != GL_NONE); }
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.
Prev: Picking | Next: Atomic Counters |
6 Responses to “OpenGL Framebuffer Objects”
Leave a Reply Cancel reply
This site uses Akismet to reduce spam. Learn how your comment data is processed.
I read some fbo tutorials on the internet, but it’s the only one make me clearly in a few line. This is what am I looking for, thank you very much.
When you quote the documentation you used the name “glGenBuffers”, but in the rest of the article, you use “glGenFramebuffers”. You should probably fix that 😉
Done. Thanks.
Should Framebuffer be created in draw callback. Or can it be created outside, like in main( )
Hi,
The framebuffer objects can be created in the setup phase, or initialization.
It’s very awesome. I like it.