Frame Buffer Objects and Render to Texture
Last Updated: Aug 9th, 2012
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.