Wednesday, February 27, 2013

Intro to FBOs


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.

No comments:

Post a Comment