Frame Buffer Objects
Frame buffer objects (FBOs) are
OpenGL objects that hold texture data which can be filled by rendering off
screen, or as one of my course’s TA calls it rendering in Magic Land. FBOs are
really important for graphics because they can be used to apply post processing
effects, which is the act of applying an effect to an image or texture. A FBO
will hold user defined textures and you can have more than one texture or even
no textures attached. To apply a post process you have to render a full screen
quad and run a shader that does the screen process. In the end you will have a
2D texture that is essentially a screen picture of that frame of your
application. Then you just render another full screen quad and attach that
texture to it too display it to the screen.
Setting up an FBO is pretty easy.
You just have to create a handle then set some parameters and check if the FBO
has correctly been made by quarrying openGL. Below I will go over a bare bone
Create FBO function that has the capability to set up a multiple render targets
(MRTs). So this function is pretty bad in the sense that it has very little
flexibility and may not do what you’d want it to do. The function call accepts
a Boolean to tell it which FBO attachment types to make. Although there are
only 2 types the function can make, depth and colour, the normal option is
really just another colour frame buffer. The function accepts pointers to
texture handles and the width and height of the screen. The rest of the code
below will be explained through the comments in the code. For explanations on
some of the openGL code used you can just Google them for their full break
down.
GLuint
Game::CreateFBO(bool useColour, bool useDepth, bool useNormal, GLuint
*colourTextureOut, GLuint
*depthTextureOut, GLuint *normalTextureOut, unsigned int width, unsigned int
height)
{
//this is a simple check
to see if we are creating any of the frame buffers and if we are not //return 0.
if (!useColour && !useDepth
&& !useNormal)
return 0;
//this is our fbo handle which is
much like a texture handle.
unsigned int fboHandle;
// generate FBO in graphics memory
glGenFramebuffers(1,
&fboHandle);
glBindFramebuffer(GL_FRAMEBUFFER,
fboHandle);
// create a texture for colour
storage
if (useColour &&
colourTextureOut)
{
//create and bind fbo
glGenTextures(1,
colourTextureOut);
glBindTexture(GL_TEXTURE_2D,
*colourTextureOut);
//Create texture notice
how we made it using GL_RGBA8 which is basically for color as //well as GL_RGBA
as parameters.
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
0);
//set up parameters
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//Notice how we use the
color attachment 0 colour attachments are important for //using MRT as each
frame buffer must use its own colour attachment.
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *colourTextureOut, 0);
}
// create a texture for depth
storage
if (useDepth &&
depthTextureOut)
{
//Create fbo
glGenTextures(1,
depthTextureOut);
glBindTexture(GL_TEXTURE_2D,
*depthTextureOut);
//Create texture
glTexImage2D(GL_TEXTURE_2D,
0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT,
0);
//set up parameters
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_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);
// we use the depth
attachment for depth
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, *depthTextureOut, 0);
}
if (useNormal && normalTextureOut)
{
//create and bind fbo
glGenTextures(1,
normalTextureOut);
glBindTexture(GL_TEXTURE_2D,
*normalTextureOut);
//Create texture
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
//set up parameters
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// as the color
attachment 0 has been used we need to use the next one.
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, *normalTextureOut, 0);
}
// so now that we created the FBO we
need to check the status of it
int status =
glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status !=
GL_FRAMEBUFFER_COMPLETE)
{ //if
the FBO fails we need to delete it and all the frame buffers created
std::cout<<"FBO
Failure" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER,
0);
glBindTexture(GL_TEXTURE_2D,
0);
glDeleteFramebuffers(1,
&fboHandle);
glDeleteTextures(1,
colourTextureOut);
glDeleteTextures(1,
depthTextureOut);
glDeleteTextures(1,
normalTextureOut);
*colourTextureOut = 0;
*depthTextureOut = 0;
*normalTextureOut = 0;
return 0;
}
//disable some stuff
glBindTexture(GL_TEXTURE_2D, NULL);
glBindFramebuffer(GL_FRAMEBUFFER,
NULL);
return fboHandle;
}
Now that you know how to make an FBO, let me
show you really quickly how you would use one. It’s simple, just like most
openGL code you have to tell openGL what you’re doing. First off the GLenum is
an enumeration of the color attachments that the FBO has attached. From above
you’ll see that we had the color attachments 0 and 1. The next thing we do is
tell openGL that we are binding a Frame Buffer and then pass it the type and
the FBO handle created by the function above. Next we tell openGL that we are
drawing to buffers and tell it how many by passing it the GLenum we just
created. The next step is to draw the scene. Once done, you can unbind the
frame buffer by binding with NULL.
void
Game::DeferredFirstPass(float elapsed, float time)
{
GLenum Buffers[] =
{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glBindFramebuffer(GL_FRAMEBUFFER, GbufferFBO);
glDrawBuffers(2, Buffers);
glViewport(0, 0, windowWidth,
windowHeight);
glClear(GL_DEPTH_BUFFER_BIT |
GL_COLOR_BUFFER_BIT);
this->RenderScene(0.0f, 0.0f);
glBindFramebuffer(GL_FRAMEBUFFER,
NULL);
glViewport(0, 0, windowWidth,
windowHeight);
}
The
Last smidgen of information I will give is how to use the MRT s in the shader.
This is extremely easy and it really is just making your out colour an array
like the code below shows. Out putting to a different index will output to a
different texture in the current FBO.
#version
140
uniform
sampler2D TextureData;
in vec2
texcoord;
in vec3
normal;
out vec4
outColor[2];
void
main ()
{ //render to two targets save packed
normals in one and textured object in the other
outColor[1] = vec4(normal * 0.5f +
0.5f ,1.0f);
outColor[0] = texture(TextureData, texcoord.st);
}
The
code snippets have been from a tech demo I’ve made that shows off deferred
lighting, just one of the many post-processing effects that use FBOs and MTRs.
Below is a screen shot of my tech demo.