实践项目:https://github.com/JackieLong/OpenGL/tree/main/project_framebuffer_test

由以下几个缓冲组成:

  • 颜色缓冲(Color Buffer)
  • 深度缓冲(Depth Buffer)
  • 模板缓冲(Stencil Buffer)

    一、创建和使用

    ```cpp

// * // *第一步:创建,和其他OpenGL对象一样创建 // * GLuint fbo;
glGenFrameBuffers(1, &fbo); // 创建FrameBuffer对象。

glBindFrameBuffer(GL_FRAMEBUFFER, fbo); // 读取帧缓冲操作都将作用在这个fbo上,一般就是用这个。 glBindFrameBuffer(GL_DRAW_FRAMEBUFFER, fbo); // 写操作(渲染、清除)作用在这个fbo上。 glBindFrameBuffer(GL_READ_FRAMEBUFFER, fbo); // 读操作作用在这个fbo上。

// * // *第二步:创建附件,并绑定到帧缓冲中,下面会讲 // * ……; // 创建附件并绑定到帧缓冲上。

// * // *第三步:检查帧缓冲完整性 // * if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE ) { cout << “ERROR::FRAMEBUFFER:: FRAMEBUFFER is not complete” << endl; }

// * // *第四步:使用 // * else { // do some junk }

// * // *第五步:结尾 // * glBindFramebuffer(GL_FRAMEBUFFER, 0); // 记得恢复成默认缓冲,这步不是必须,但是你得知道它的意义 // OpenGL只会从默认帧缓冲,读取显示画面。

glDeleteFrameBuffer(1, &fbo); // 如果不再使用了,别忘记删除,和其他openGL对象一样。

// * // * 函数解释 // * GLenum result = glCheckFramebufferStatus(GL_FRAMEBUFFER); // 检查帧缓冲是否完整 // result可能取值如下: // GL_FRAMEBUFFER_COMPLETE: 帧缓冲是完整的,可以正常使用。 // GL_FRAMEBUFFER_UNDEFINED:
// if target is the default framebuffer, but the default framebuffer does not exist. // GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: // if any of the framebuffer attachment points are framebuffer incomplete. // GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: // if the framebuffer does not have at least one image attached to it. // GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER // if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment // point( s ) named by GL_DRAW_BUFFERi. // GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER // if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is // GL_NONE for the color attachment point named by GL_READ_BUFFER. // GL_FRAMEBUFFER_UNSUPPORTED // if the combination of internal formats of the attached images violates an implementation - // dependent set of restrictions. // GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE // if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if // the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or , if the attached // images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match // the value of GL_TEXTURE_SAMPLES. // also if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or , if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures. // GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS // if any framebuffer attachment is layered, and any populated attachment // is not layered, or if all populated color attachments are not from textures of the same target. // 0 // an error is occurs.

  1. 离屏渲染(Off-screen Rendering):当前帧缓冲不是默认帧缓存,渲染指令会将结果输出到当前帧缓冲中,窗口不会有任何视觉输出。
  2. ```cpp
  3. glBindFrameBuffer(GL_FRAMEBUFFER, 0); // 激活默认帧缓冲,窗口才会输出渲染结果。

二、附件

在完整性检查执行之前,我们需要给帧缓冲附加一个附件。
附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:

  • 纹理类型
  • 渲染缓冲对象(Renderbuffer Object)类型

附件使用过程如下,以纹理类型附件为例:

  1. // ***********************************************
  2. // ***********第一步:创建(纹理类型)附件
  3. // ***********************************************
  4. GLuint texture;
  5. glGenTextures(1, &texture);
  6. glBindTexture(GL_TEXTURE_2D, texture); // 暂时绑定为当前激活纹理
  7. // 创建纹理,不同的附件类型(颜色也叫纹理、深度、模板),设置纹理的格式参数有不同,这里以创建纹理附件为例
  8. // 本质上都是开辟一块内存,只不过内存格式有不同。
  9. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, // 和创建纹理一样的参数
  10. 800, 600, // 设置为一个指定大小(比如屏幕大小)
  11. 0, // 一直为0
  12. GL_RGB, // 和创建纹理一样的参数
  13. GL_UNSIGNED_BYTE, // 和创建纹理一样的参数
  14. NULL); // 空纹理,创建纹理,这里是纹理内存数据
  15. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  16. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  17. // ***********************************************
  18. // ***********第二步:绑定
  19. // ***********************************************
  20. // 例子:绑定一个颜色纹理附件
  21. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
  22. glBindTexture(GL_TEXTURE_2D, 0); // 恢复成默认绑定是好习惯。

glFrameBufferTexture2D

  1. void glFramebufferTexture2D(GLenum target, GLenum attachment,GLenum textarget, GLuint texture, GLint level);
  2. // 为target帧缓冲绑定纹理类型附件。
  3. // target: 帧缓冲目标,GL_FRAMEBUFFER、GL_DRAW_FRAMEBUFFER、GL_READ_FRAMEBUFFER
  4. // attachment: 我们想要附加的附件类型。
  5. // GL_COLOR_ATTACHMENTi 颜色附件
  6. // GL_DEPTH_ATTACHMENT 深度附件
  7. // GL_STENCIL_ATTACHMENT 模板附件
  8. // GL_DEPTH_STENCIL_ATTACHMENT 深度和模板在一个附件上。
  9. // textarget: 希望附加的纹理类型,GL_TEXTURE_2D
  10. // texture: 纹理id
  11. // level: 多级渐远纹理的级别。我们将它保留为0。

纹理类型附件

颜色附件

不同纹理附件,创建过程基本相似,只不过参数不太一样。

  1. GLuint texture;
  2. glGenTextures(1, &texture);
  3. glBindTexture(GL_TEXTURE_2D, texture);
  4. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, // 和创建纹理一样的参数
  5. 800, 600, // 设置为一个指定大小(比如屏幕大小)
  6. 0,
  7. GL_RGB, GL_UNSIGNED_BYTE, // 和创建纹理一样的参数
  8. NULL); // 空纹理,创建纹理,这里是纹理内存数据
  9. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  10. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  11. glBindTexture(GL_TEXTURE_2D, 0); // 好习惯
  12. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

深度附件

  1. GLuint texture;
  2. glGenTextures(1, &texture);
  3. glBindTexture(GL_TEXTURE_2D, texture);
  4. glTexImage2D(GL_TEXTURE_2D, 0,
  5. GL_DEPTH_COMPONENT, // format
  6. 800, 600,
  7. 0,
  8. GL_DEPTH_COMPONENT, // interal format
  9. GL_UNSIGNED_INT, // 一个深度值大小
  10. NULL);
  11. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  12. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  13. glBindTexture(GL_TEXTURE_2D, 0); // 好习惯
  14. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture, 0);

模板附件

  1. GLuint texture;
  2. glGenTextures(1, &texture);
  3. glBindTexture(GL_TEXTURE_2D, texture);
  4. glTexImage2D(GL_TEXTURE_2D, 0,
  5. GL_STENCIL_INDEX, // format
  6. 800, 600,
  7. 0,
  8. GL_STENCIL_INDEX, // interal format
  9. GL_UNSIGNED_BYTE, // 一个模板值大小
  10. NULL);
  11. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  12. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  13. glBindTexture(GL_TEXTURE_2D, 0); // 好习惯
  14. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);

深度/模板附件

深度值和模板(一般)都保存在一个附件上。

  1. GLuint texture;
  2. glGenTextures(1, &texture);
  3. glBindTexture(GL_TEXTURE_2D, texture);
  4. glTexImage2D(GL_TEXTURE_2D, 0,
  5. GL_DEPTH24_STENCIL8, // format,24bit深度值,8bit模板值
  6. 800, 600,
  7. 0,
  8. GL_DEPTH_STENCIL, // interal format
  9. GL_UNSIGNED_INT_24_8, //
  10. NULL);
  11. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  12. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  13. glBindTexture(GL_TEXTURE_2D, 0); // 好习惯
  14. glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);

渲染缓冲类型附件

RenderBuffer Object,在纹理之后引入。在处理离屏渲染上,渲染缓冲比纹理更高效。
渲染缓冲直接存储所有的渲染数据,不做任何纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。
因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。我们在每个渲染迭代最后使用的glfwSwapBuffers,也可以通过渲染缓冲对象实现。
渲染缓冲对象的创建和使用如下:

  1. // ***********************************************
  2. // ***********第一步:创建
  3. // ***********************************************
  4. GLuint rbo;
  5. glGenRenderbuffers(1, &rbo);
  6. glBindRenderbuffer(GL_RENDERBUFFER, rbo); // 暂时绑定为当前渲染缓冲对象。
  7. // 创建深度和模板渲染缓冲对象:开辟内存
  8. // 一般情况深度、模板缓冲都无需采样读取,因此非常适合实用渲染类型缓冲。
  9. glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); // 下面有解释
  10. // ***********************************************
  11. // ***********第二步:绑定
  12. // ***********************************************
  13. // 绑定渲染缓冲对象到帧缓冲,这里是创建了一个深度模板渲染缓冲对象。下面有解释:
  14. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
  15. glBindRenderbuffer(GL_RENDERBUFFER, 0); // 好习惯

glRenderbufferStorage

  1. // 开辟渲染缓冲内存,类比于glTexImage2D
  2. void glRenderbufferStorage(
  3. GLenum target, // 必须是GL_RENDERBUFFER
  4. GLenum internalformat, // internal format,存储格式
  5. GLsizei width, // 渲染缓冲的像素宽高。
  6. GLsizei height);

glFrameBufferRenderbuffer

  1. // 绑定渲染缓冲到帧缓冲
  2. void glFramebufferRenderbuffer( GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
  3. // target: 帧缓冲对象,可能值如下:
  4. // GL_DRAW_FRAMEBUFFER
  5. // GL_READ_FRAMEBUFFER
  6. // GL_FRAMEBUFFER,等同于GL_DRAW_FRAMEBUFFER
  7. // attachment: 我们想要附加的附件类型。
  8. // GL_COLOR_ATTACHMENTi 颜色附件
  9. // GL_DEPTH_ATTACHMENT 深度附件
  10. // GL_STENCIL_ATTACHMENT 模板附件
  11. // GL_DEPTH_STENCIL_ATTACHMENT 深度和模板在一个附件上。
  12. // renderbuffertarget: 必须是GL_RENDERBUFFER
  13. // renderbuffer: 渲染缓冲id,由glBindRenderbuffer得到

如何选择附件

如果不需要从缓冲中采样(读取)数据,选择渲染缓冲对象,比如深度值、模板值一般不会去读取。
如果需要从缓冲中采样颜色、深度值等,则选择纹理附件,性能方面它不会产生非常大的影响的。

三、帧缓冲例子

  1. GLuint createFrameBuffer()
  2. {
  3. // ***************************************************
  4. // ******* 第一步,创建帧缓冲
  5. // ***************************************************
  6. GLuint frameBuffer;
  7. glGenFramebuffers( 1, &frameBuffer ); // 创建自己的帧缓冲
  8. glBindFramebuffer( GL_FRAMEBUFFER, frameBuffer ); // 暂时绑定到当前帧缓冲(代替默认帧缓冲),目的是保证后续的附件绑定能生效,在最后别忘记恢复到默认的帧缓冲
  9. // ***************************************************
  10. // ******* 第二步,绑定颜色附件,创建为纹理类型,原因是后续我们需要进行采样(读取数据)。
  11. // ***************************************************
  12. glGenTextures( 1, &textureColorBuffer );
  13. glBindTexture( GL_TEXTURE_2D, textureColorBuffer ); // 暂时绑定到当前激活纹理单元,目的是保证后面开辟纹理内存能生效,最后别忘了恢复成默认
  14. // 开辟纹理内存
  15. glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, // 和纹理一样的参数类型
  16. ScreenWidth, ScreenHeight, // 设置为一个指定大小(比如屏幕大小)
  17. 0, // 总是0
  18. GL_RGB, GL_UNSIGNED_BYTE, // 和纹理一样的参数类型
  19. NULL ); // 空纹理数据,暂不填充
  20. // 和普通纹理一样,设置纹理参数
  21. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  22. glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  23. glBindTexture( GL_TEXTURE_2D, 0 ); // 别忘了恢复成默认的,到这一步,纹理附件已经创建完成
  24. // 绑定到我们创建的帧缓冲中
  25. glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorBuffer, 0 );
  26. // ***************************************************
  27. // ******* 第三步,绑定一个深度和模板附件(深度和模板数据都保存在一块内存中),使用的渲染缓冲类型,原因是我们只会写入数据,不进行采样读取,使用渲染缓冲,性能会非常好。
  28. // ***************************************************
  29. GLuint renderbuffer;
  30. glGenRenderbuffers( 1, &renderbuffer );
  31. glBindRenderbuffer( GL_RENDERBUFFER, renderbuffer ); // 暂时绑定,目的保证下面的开辟内存能生效
  32. glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, ScreenWidth, ScreenHeight );
  33. glBindRenderbuffer( GL_RENDERBUFFER, 0 ); // 恢复成默认绑定
  34. glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, renderbuffer ); // 绑定渲染缓冲
  35. // ***************************************************
  36. // ******* 第四步:检查帧缓冲的完整性,是否所有必需的工作都做完了,绑定附件就是必须的。
  37. // ***************************************************
  38. if( glCheckFramebufferStatus( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE )
  39. {
  40. cout << "ERROR::FRAMEBUFFER:: FRAMEBUFFER is not complete" << endl;
  41. }
  42. glBindFramebuffer( GL_FRAMEBUFFER, 0 ); // 恢复到默认的帧缓冲,后续有需要,随时绑定使用。
  43. return frameBuffer;
  44. }