Frame Buffer Objects and Render to Texture

Last Updated 8/09/12
The buffers on the screen aren't the only ones you can render to. You can also generate additional frame buffer objects, and use them for other rendering operations like rendering to a texture.From LUtil.cpp
//Rotating texture
LTexture gOpenGLTexture;
GLfloat gAngle = 0.f;
//Framebuffer
GLuint gFBO = NULL;
LTexture gFBOTexture;
For this tutorial, we're going to render a scene using a texture and 4 quads and in the background we're going to have a rotating snap shot of the screen on a texture.
First, we have the texture we're going to render and the rotation angle we're going to use. Then we have the variable "gFBO". Just like an VBO and IBO have integer names, an FBO has an integer name. Lastly, we have the texture the FBO is going to render to.
First, we have the texture we're going to render and the rotation angle we're going to use. Then we have the variable "gFBO". Just like an VBO and IBO have integer names, an FBO has an integer name. Lastly, we have the texture the FBO is going to render to.
From LUtil.cpp
bool initGL()
{
//Initialize GLEW
GLenum glewError = glewInit();
if( glewError != GLEW_OK )
{
printf( "Error initializing GLEW! %s\n", glewGetErrorString( glewError ) );
return false;
}
//Make sure OpenGL 2.1 is supported
if( !GLEW_VERSION_2_1 )
{
printf( "OpenGL 2.1 not supported!\n" );
return false;
}
//Set the viewport
glViewport( 0.f, 0.f, SCREEN_WIDTH, SCREEN_HEIGHT );
//Initialize Projection Matrix
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 0.0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0, 1.0, -1.0 );
//Initialize Modelview Matrix
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
//Initialize clear color
glClearColor( 0.f, 0.f, 0.f, 1.f );
//Enable texturing
glEnable( GL_TEXTURE_2D );
//Set blending
glEnable( GL_BLEND );
glDisable( GL_DEPTH_TEST );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
//Generate framebuffer name
glGenFramebuffers( 1, &gFBO );
//Check for error
GLenum error = glGetError();
if( error != GL_NO_ERROR )
{
printf( "Error initializing OpenGL! %s\n", gluErrorString( error ) );
return false;
}
//Initialize DevIL and DevILU
ilInit();
iluInit();
ilClearColour( 255, 255, 255, 000 );
//Check for error
ILenum ilError = ilGetError();
if( ilError != IL_NO_ERROR )
{
printf( "Error initializing DevIL! %s\n", iluErrorString( ilError ) );
return false;
}
return true;
}
In initGL(), we generate a FBO name using glGenFramebuffers().
Now we're cheating a little bit here. Frame Buffer Objects didn't become part of the OpenGL core until version 3.0. They were a commonly available extension in version 2.1. As an extension, the function glGenFramebuffers() is called glGenFramebuffersEXT(). When binding a FBO, you didn't use "GL_FRAMEBUFFER" but "GL_FRAMEBUFFER_EXT". Fortunately GLEW gives us the non-extension names even with the OpenGL 2.1 context so we don't have to add EXT to all of our function calls.
Now we're cheating a little bit here. Frame Buffer Objects didn't become part of the OpenGL core until version 3.0. They were a commonly available extension in version 2.1. As an extension, the function glGenFramebuffers() is called glGenFramebuffersEXT(). When binding a FBO, you didn't use "GL_FRAMEBUFFER" but "GL_FRAMEBUFFER_EXT". Fortunately GLEW gives us the non-extension names even with the OpenGL 2.1 context so we don't have to add EXT to all of our function calls.
From LUtil.cpp
bool loadMedia()
{
//Load texture
if( !gOpenGLTexture.loadTextureFromFile32( "27_frame_buffer_objects_and_render_to_texture/opengl.png" ) )
{
printf( "Unable to load texture!\n" );
return false;
}
return true;
}
void update()
{
//Update rotation
gAngle += 6.f;
}
In loadMedia() we load the texture and in update() we update the rotation angle.
From LUtil.cpp
void renderScene()
{
//Render texture
glLoadIdentity();
glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
glRotatef( gAngle, 0.f, 0.f, 1.f );
glTranslatef( gOpenGLTexture.imageWidth() / -2.f, gOpenGLTexture.imageHeight() / -2.f, 0.f );
glColor3f( 1.f, 1.f, 1.f );
gOpenGLTexture.render( 0.f, 0.f );
//Unbind texture for geometry
glBindTexture( GL_TEXTURE_2D, NULL );
//Red quad
glLoadIdentity();
glTranslatef( SCREEN_WIDTH * 1.f / 4.f, SCREEN_HEIGHT * 1.f / 4.f, 0.f );
glRotatef( gAngle, 0.f, 0.f, 1.f );
glBegin( GL_QUADS );
glColor3f( 1.f, 0.f, 0.f );
glVertex2f( -SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glVertex2f( -SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glEnd();
//Green quad
glLoadIdentity();
glTranslatef( SCREEN_WIDTH * 3.f / 4.f, SCREEN_HEIGHT * 1.f / 4.f, 0.f );
glRotatef( gAngle, 0.f, 0.f, 1.f );
glBegin( GL_QUADS );
glColor3f( 0.f, 1.f, 0.f );
glVertex2f( -SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glVertex2f( -SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glEnd();
//Blue quad
glLoadIdentity();
glTranslatef( SCREEN_WIDTH * 1.f / 4.f, SCREEN_HEIGHT * 3.f / 4.f, 0.f );
glRotatef( gAngle, 0.f, 0.f, 1.f );
glBegin( GL_QUADS );
glColor3f( 0.f, 0.f, 1.f );
glVertex2f( -SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glVertex2f( -SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glEnd();
//Yellow quad
glLoadIdentity();
glTranslatef( SCREEN_WIDTH * 3.f / 4.f, SCREEN_HEIGHT * 3.f / 4.f, 0.f );
glRotatef( gAngle, 0.f, 0.f, 1.f );
glBegin( GL_QUADS );
glColor3f( 1.f, 1.f, 0.f );
glVertex2f( -SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, -SCREEN_HEIGHT / 16.f );
glVertex2f( SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glVertex2f( -SCREEN_WIDTH / 16.f, SCREEN_HEIGHT / 16.f );
glEnd();
}
Now we have a new function called renderScene() which renders the scene in a way that can be used by both the FBO and the screen. The only real difference is renderScene()
doesn't make a call to glutSwapBuffers().
What this function renders is a rotating texture and 4 different colored quads. We'll be rendering this both to the screen and to the FBO.
What this function renders is a rotating texture and 4 different colored quads. We'll be rendering this both to the screen and to the FBO.
From LUtil.cpp
void handleKeys( unsigned char key, int x, int y )
{
//If the user presses q
if( key == 'q' )
{
//Bind framebuffer for use
glBindFramebuffer( GL_FRAMEBUFFER, gFBO );
In this tutorial, we're going to be rendering the to the FBO when the user presses 'q'. We're going to be taking a snapshot of the current scene and remdering to the texture.
First thing we have to do is bind our frame buffer with glBindFramebuffer().
First thing we have to do is bind our frame buffer with glBindFramebuffer().
From LUtil.cpp
//If FBO texture doesn't exist
if( gFBOTexture.getTextureID() == 0 )
{
//Create it
gFBOTexture.createPixels32( SCREEN_WIDTH, SCREEN_HEIGHT );
gFBOTexture.padPixels32();
gFBOTexture.loadTextureFromPixels32();
}
//Bind texture
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gFBOTexture.getTextureID(), 0 );
In order for a FBO to render to a texture, the texture needs to be big enough to hold the screen's content. If "gFBOTexture" doesn't have a texture, we generate one big enough to
hold the screen.
Then we have the FBO use the texture with glFramebufferTexture2D(). This function will render an FBO to the first color attachment "GL_COLOR_ATTACHMENT0" to a 2D texture using "gFBOTexture".
Then we have the FBO use the texture with glFramebufferTexture2D(). This function will render an FBO to the first color attachment "GL_COLOR_ATTACHMENT0" to a 2D texture using "gFBOTexture".
From LUtil.cpp
//Clear framebuffer
glClear( GL_COLOR_BUFFER_BIT );
//Render scene to framebuffer
renderScene();
//Unbind framebuffer
glBindFramebuffer( GL_FRAMEBUFFER, NULL );
}
}
With our FBO bound using our texture, we clear it's color buffer. Remember since "gFBO" is bound, this call to glClear() operates on the FBO not the screen. With the FBO cleared
we render the scene to it. Then we unbind the FBO with glBindFramebuffer() so now our rendering calls will be done to the screen.
From LUtil.cpp
void render()
{
//Clear color and buffer
glClear( GL_COLOR_BUFFER_BIT );
//Render FBO texture
if( gFBOTexture.getTextureID() != 0 )
{
glLoadIdentity();
glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
glRotatef( -gAngle, 0.f, 0.f, 1.f );
glTranslatef( gFBOTexture.imageWidth() / -2.f, gFBOTexture.imageHeight() / -2.f, 0.f );
glColor3f( 1.f, 1.f, 1.f );
gFBOTexture.render( 0.f, 0.f );
}
//Render scene
renderScene();
//Update screen
glutSwapBuffers();
}
In render(), we clear the screen as usual and render the spinning snap shot of the screen if it exists. Then we render the scene normally on top of it.
If you're wondering why the snap shot doesn't show up in next snap shot, it's because you shouldn't render with a texture you're rendering to.
If you're wondering why the snap shot doesn't show up in next snap shot, it's because you shouldn't render with a texture you're rendering to.