现在的位置: 首页 > 综合 > 正文

一个很好的FBO—RenderToTexture教程

2013年08月02日 ⁄ 综合 ⁄ 共 11967字 ⁄ 字号 评论关闭

http://www.swiftless.com/tutorials/opengl/framebuffer.html

Introduction

Frame buffers are one of those mythical things that we have all heard of, but many beginner OpenGL developers avoid because there is not much information about them, and they can be confusing at first. A frame buffer can be thought of as another window that
we render to, but we don’t end up seeing.

Now you might be thinking, if we draw something, but we don’t end up seeing it, then what is the point? Have you ever heard of off-screen rendering? I might ask you this instead, have you ever seen a reflection in a game of a live scene? Well, to get this reflection,
we typically have to render the scene from the angle of the reflection first, and then we turn that render into a texture and bind it to whatever shape has the reflection attached to it. This initial rendering of the reflection is typically done in a frame
buffer for speed, as we can bind a frame buffer straight to a texture.

If you have ever heard of pixel buffers, don’t get them confused with frame buffers, they are different, but can be used together. I won’t go into pixel buffers here, I might write a tutorial on them later on, but they are highly useful for asynchronous texture
reads and writes. I once had a project, and part of it required I write to two frame buffers and read back the data, and then perform operations on this data and push it back to the GPU as a texture as quickly as possible. Using a combination of frame buffers
and pixel buffers, I went from approximately 5 frames per second doing this, to 30 frames per second, and if the demand arises, I will post a tutorial on how to do this. It was a case where offloading the information to the GPU was not possible.

You should now have an idea of what frame buffers are, in short, they are a rendering context that can be bound to a texture, so let’s start coding them and see what we come up with. I am going to aim for a quad, with a texture of a rotating cube on it. It
is a simple example, but highly useful, as you can imagine, the entire scene can be rendered into a frame buffer, and then placed onto a quad for some post processing.

Code

Frame buffer objects are stored like textures. OpenGL will store the information on the graphics card, and it will return an ID associated with that texture for us to place inside an unsigned integer. Also, as seen with textures, the frame buffer ID of 0 (zero)
is reserved by OpenGL and is used to unbind the current frame buffer. Also, frame buffers can have several buffers attached. The default buffer is the colour buffer, but you can also bind depth buffers, stencil buffers, etc. So let’s create some variables
to hold our frame buffer, frame buffers depth buffer and the texture we are going to store our frame buffer in.

  1. unsigned int fbo; // The frame buffer object  
  2. unsigned int fbo_depth; // The depth buffer for the frame buffer object  
  3. unsigned int fbo_texture; // The texture object to write our frame buffer object to  

After we have all the variables required to create and use our frame buffers (yes, we only need three variables), we are going to add some extra variables. First, we need two integer values for the width and height of our GLUT window, this is because when you
create a frame buffer, you need to specify the width and the height of the texture, and when you use the frame buffer, you need to change the viewport size to match the texture size. Because frame buffers are independent of the size of the GLUT window, you
can use them to create higher or lower resolution textures than your regular scene. Typically you would use a lower resolution texture for effects such as reflections so that it renders quicker. The last extra variable we are going to use will just hold how
much our teapot in our frame buffer scene will rotate.

  1. int window_width = 500; // The width of our window  
  2. int window_height = 500; // The height of our window  
  3.   
  4. float rotation_degree = 0.0f; // The angle of rotation in degrees for our teapot  

The next step we have to do is to create/initialize our frame buffer, the associated depth buffer, and the texture we are going to render to. Typically it is done in the following order:

  1. Create the Depth Buffer that we are going to use with our frame buffer.
  2. Create the Texture that we are going to bind our frame buffer to.
  3. Create the actual Frame Buffer.
  4. Bind the texture to the frame buffer.
  5. Bind the depth buffer to the frame buffer.
  6. Check for errors.

You can check for errors along the way if you wish, but I will just be using one check to make sure our final frame buffer is complete.

Frame Buffer – Depth Buffer

Let’s create the depth buffer we are going to use. To begin with, create a method call initFrameBufferDepthBuffer. This method is going to take no parameters and is not going to return anything. This method is going to contain all code related to the depth
buffer we are going to use.

  1. void initFrameBufferDepthBuffer(void) {  
  2.   
  3. }  

Inside of this method to create the depth buffer, the first call we need to make will create a render buffer for OpenGL to use, and is very similar to the code used to create textures.

  1. glGenRenderbuffersEXT(1, &fbo_depth); // Generate one render buffer and store the ID in fbo_depth  

This will create a render buffer, and store the ID in our fbo_depth variable for us to access.  Once the render buffer is created for the depth buffer, we then need to bind the buffer so that we can play with it and once we are finished with it, we need to
bind the null render buffer, which takes the value of 0. When we are binding a frame buffer, it is once again very similar to texturing, so let’s place our binding code inside our method to fill between.

  1. void initFrameBufferDepthBuffer(void) {  
  2. glGenRenderbuffersEXT(1, &fbo_depth); // Generate one render buffer and store the ID in fbo_depth  
  3. glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_depth); // Bind the fbo_depth render buffer  
  4. ...  
  5. glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); // Unbind the render buffer  
  6. }  

Next, we need to fill in the … between the binding of the render buffers. These next two lines are the lines that tell OpenGL that this render buffer will be used for depth. The first line tells OpenGL that we are going to be storing the depth component, and
we are going to monitor the entire size of the window. The next line then says that we are going to use fbo_depth to render the depth buffer/attachment.

  1. glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, window_width, window_height); // Set the render buffer storage to be a depth component, with a width and height of the window  
  2.   
  3. glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo_depth); // Set the render buffer of this buffer to the depth buffer  

Frame Buffer – Texture

Recapping on what we have done, we have created a render buffer that will store the depth component of the buffer that it is attached to. Now we need to create the texture that we want to store our frame buffer in. To do this, let’s create another method similar
to the one we just created, but I am going to call this one initFrameBufferTexture. Because we are only creating a texture here, I am not really going to explain this code, the code comments should outline what is going on if you don’t know about texturing
in OpenGL (in which case, you are diving in fairly deep straight out).

  1. void initFrameBufferTexture(void) {  
  2. glGenTextures(1, &fbo_texture); // Generate one texture  
  3.   
  4. glBindTexture(GL_TEXTURE_2D, fbo_texture); // Bind the texture fbo_texture  
  5.   
  6. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, window_width, window_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); // Create a standard texture with the width and height of our window  
  7.   
  8. // Setup the basic texture parameters  
  9.   
  10. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
  11. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
  12. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
  13. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  14.   
  15. // Unbind the texture  
  16. glBindTexture(GL_TEXTURE_2D, 0);  
  17. }  

Frame Buffer – Initialization

Finally, we need one more method; this one will be called initFrameBuffer and will make calls to the above methods we have created, as well as creating the frame buffer and attaching the texture and the depth buffer.

  1. void initFrameBuffer(void) {  
  2. initFrameBufferDepthBuffer(); // Initialize our frame buffer depth buffer  
  3.   
  4. initFrameBufferTexture(); // Initialize our frame buffer texture  
  5. …  
  6. }  

After making the calls to our above methods, we need to create the frame buffer, and bind it. This is extremely similar to how we created the render buffer for the depth component, and how we create textures, so the following should look vaguely familiar.

  1. glGenFramebuffersEXT(1, &fbo); // Generate one frame buffer and store the ID in fbo  
  2. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // Bind our frame buffer  
  3. …  
  4. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Unbind our frame buffer  

This code will generate one frame buffer, and then bind it so we can modify it, and then finally it will unbind it. This is fairly straight forward, and the next two calls will attach the texture and render buffer to our frame buffer. First off, let’s attach
the texture to the frame buffer.

  1. glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fbo_texture, 0); // Attach the texture fbo_texture to the color buffer in our frame buffer  

Now that we have a texture, theoretically we can use this straight out, but you won’t get any depth information. So we need to go ahead and attach the depth render buffer we created called fbo_depth, to our frame buffer.

  1. glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo_depth); // Attach the depth buffer fbo_depth to our frame buffer  

And now we have a frame buffer, which outputs to a texture and also has a depth buffer, which we can use. Well, we think we do, so first let’s check if our frame buffer is complete and there were no problems. To do this, we can make a call to glCheckFramebufferStatusEXT
while our frame buffer is bound, and this will give us back a GLenum with the status of our frame buffer. For our frame buffer to be complete, we need to check that the status is equal to GL_FRAMEBUFFER_COMPLETE_EXT. We can do this with a simple if statement.
Note that if you want the output to the console, you need to include <iostream> into your file.

  1. GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); // Check that status of our generated frame buffer  
  2.   
  3. if (status != GL_FRAMEBUFFER_COMPLETE_EXT) // If the frame buffer does not report back as complete  
  4. {  
  5. std::cout << "Couldn't create frame buffer" << std::endl; // Make sure you include <iostream>  
  6. exit(0); // Exit the application  
  7. }  

General Initialization

Because in this example, we want to use the frame buffer constantly, I am going to create an init method which will call the initFrameBuffer method, and init will be called straight after we initialize GLEW, which is straight after we call glutCreateWindow.
So here is our init method, which in this example, enables texturing, enables depth testing, and then initializes our frame buffer.

  1. void init(void) {  
  2. glEnable(GL_TEXTURE_2D); // Enable texturing so we can bind our frame buffer texture  
  3. glEnable(GL_DEPTH_TEST); // Enable depth testing  
  4.   
  5. initFrameBuffer(); // Create our frame buffer object  
  6. }  

Next up, here is an example of my main method, which calls glewInit and calls our own init method.

  1. int main (int argc, char **argv) {  
  2. …  
  3. glutCreateWindow ("Your first OpenGL Window"); // Set the title for the window  
  4.   
  5. if (GLEW_OK != glewInit()) {  
  6. std::cout << "Couldn't initialize GLEW" << std::endl;  
  7. exit(0);  
  8. }  
  9.   
  10. init();  
  11. …  
  12. }  

Frame Buffer – Usage

To use our frame buffer, I am first going to give you the display method, and whilst you could put all your code in here, I am going to make a call to a method to render our teapot scene. This display method builds upon the tutorial for creating a square.

So it looks something like the following code, which will create a square with texture coordinates, and I have placed it back 2 units so that it fills up most of the screen. Keep in mind, that I am not doing anything with the frame buffer at the moment; this
is just a standard display method.

  1. void display (void) {  
  2. keyOperations();  
  3.   
  4. glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // Clear the background of our window to red  
  5. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //Clear the colour buffer (more buffers later on)  
  6. glLoadIdentity(); // Load the Identity Matrix to reset our drawing locations  
  7.   
  8. glTranslatef(0.0f, 0.0f, -2.0f);  
  9.   
  10. glBegin(GL_QUADS);  
  11.   
  12. glTexCoord2f(0.0f, 0.0f);  
  13. glVertex3f(-1.0f, -1.0f, 0.0f); // The bottom left corner  
  14.   
  15. glTexCoord2f(0.0f, 1.0f);  
  16. glVertex3f(-1.0f, 1.0f, 0.0f); // The top left corner  
  17.   
  18. glTexCoord2f(1.0f, 1.0f);  
  19. glVertex3f(1.0f, 1.0f, 0.0f); // The top right corner  
  20.   
  21. glTexCoord2f(1.0f, 0.0f);  
  22. glVertex3f(1.0f, -1.0f, 0.0f); // The bottom right corner  
  23.   
  24. glEnd();  
  25.   

抱歉!评论已关闭.