渲染缓冲对象——高效帧缓冲附件

2024-09-10 20:36:05 浏览数 (2)

1. 引言

在上一章节讲解FBO时,使用纹理来存储颜色缓存附件、深度缓存附件、模板缓存附件,但纹理并不是唯一的选择。尤其是针对深度缓存附件、模板缓存附件这类不需要在着色器中读取的缓存数据,OpenGL 还提供了另一种更加高效的缓存区附件——渲染缓冲对象(Renderbuffer Object, RBO)附件,用于存储渲染结果。

2. 渲染缓冲对象?

渲染缓冲对象(RBO)是 OpenGL 提供的一种存储渲染结果的帧缓冲对象(FrameBuffer Object,FBO)附件,与帧缓冲对象(FBO)配合使用。

与可以在着色器中采样的纹理附件不同,渲染缓冲对象的不能被直接读取。由于其不可被直接读取的特性,给了OpenGL很多优化空间:RBO直接存储渲染数据,无需进行额外的向纹理特定格式的转换,从而减少了内存带宽的占用。而深度缓冲区和模板缓冲区这类不需要在后续的着色器阶段中被读取和处理的数据,正是RBO的绝佳应用场景。

由于RBO不能被直接读取,所以无法像操作纹理一样从 RBO 中直接获取(采样)数据。但这并不意味着不可以读取RBO中缓存数据,可以借助 glReadPixels接口获得指定区域内的数据,该接口的详细叙述如下:

代码语言:javascript复制
 //// 从帧缓冲区中读取像素数据
 /// x: 从帧缓冲区读取的像素的左下角 x 坐标
 /// y: 从帧缓冲区读取的像素的左下角 y 坐标
 /// width: 从帧缓冲区读取的像素的宽度
 /// height: 从帧缓冲区读取的像素的高度
 /// format: 像素数据的格式,GL_STENCIL_INDEX, 
 ///        GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, 
 ///          GL_BGR, GL_RGBA, and GL_BGRA, etc
 /// type: 像素数据的类型,GL_UNSIGNED_BYTE, GL_BYTE, 
 ///       GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, 
 ///       GL_INT,
void glReadPixels(GLint x, GLint y, 
        GLsizei width, 
        GLsizei height, 
        GLenum format, 
        GLenum type, 
          void *pixels);

尽管渲染缓冲对象和纹理都能作为 FBO 的附件,用于存储渲染结果,但它们的功能和性能有所不同。

  • 功能差异:纹理可以被采样,可以在着色器中读取和操作;而 RBO 则只能用于渲染,无法直接读取。这使得 RBO通常用于那些只需要存储但不需要处理的缓冲数据。
  • 性能差异:由于 RBO 不需要执行采样和读取操作,因此在存储如深度缓冲或模板缓冲等临时渲染数据时,它能提供比纹理更好的性能表现。这种性能提升对于实时渲染(如游戏或图形应用程序)尤其重要,因为减少内存带宽占用能够使渲染管线更流畅。
  • 内存占用:RBO 的内存占用通常比纹理要少,因为它们不需要存储额外的纹理元数据(如 mipmap 层级、纹理坐标等)。这对于内存资源有限的设备(如移动设备)来说是一个重要的优势。
  • 应用场景:RBO 通常用于存储深度缓冲和模板缓冲等不需要在后续阶段中被读取和处理的数据。而纹理则更适合用于存储需要被采样的颜色缓冲数据,或者需要被多次使用的图像数据。纹理也能用来存储深度缓冲和模板缓冲。所以RBO的应用场景相对局限。

3. 相关接口

RBO的相关接口涉及到其创建、绑定、分配存储空间、附加到FBO等操作,下面是相关接口的详细说明:

代码语言:javascript复制
 // 创建渲染缓冲对象
// n: 要创建的 RBO 数量
// renderbuffers: 返回的 RBO ID 数组
void glGenRenderbuffers(GLsizei n, GLuint *renderbuffers);

// 绑定渲染缓冲对象
// target: 要绑定的目标,必须是 GL_RENDERBUFFER
// renderbuffer: 要绑定的 RBO ID
void glBindRenderbuffer(GLenum target, GLuint renderbuffer);


// 删除渲染缓冲对象
// n: 要删除的 RBO 数量
// renderbuffers: 要删除的 RBO ID 数组
void glDeleteRenderbuffers(GLsizei n, const GLuint *renderbuffers);

  
  // 为 RBO 分配存储空间
 // target: 要绑定的目标,必须是 GL_RENDERBUFFER
 // internalformat: RBO 的内部格式,例如 GL_DEPTH_COMPONENT, 
 //                 GL_DEPTH24_STENCIL8, GL_RGBA8 等
 // width: RBO 的宽度
 // height: RBO 的高度
void glRenderbufferStorage(GLenum target, GLenum internalformat, 
            GLsizei width, GLsizei height);
            


 // 将 RBO 附加到帧缓冲对象
 // target: 要绑定的目标,必须是 GL_FRAMEBUFFER
 // attachment: 要附加的附件类型,例如 GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT, GL_DEPTH_STENCIL_ATTACHMENT 等
 // renderbuffertarget: RBO 的目标类型,必须是 GL_RENDERBUFFER
 // renderbuffer: 要附加的 RBO ID
void glFramebufferRenderbuffer(GLenum target, GLenum attachment, 
            GLenum renderbuffertarget, GLuint renderbuffer);

相应的代码实操案例如下:

代码语言:javascript复制
void CreateAndBindFBO(GLuint& fbo,GLuint &colorTexture, GLuint &rbo, int width, int height)
{

  // 创建 FBO
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

  // 创建并绑定颜色缓冲纹理
    glGenTextures(1, &colorTexture);
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);

  // 创建并绑定深度模板纹理
    glGenRenderbuffers(1,&rbo);
    glBindRenderbuffer(GL_RENDERBUFFER,rbo);
    glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,width,height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rbo);

  // 检查 FBO 是否完整
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

  // 解除绑定 FBO
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

在这个例子中,首先我们创建了一个渲染缓冲对象 `rbo`,并为它分配了深度-模板格式的存储空间。然后,我们将这个 RBO 附加到帧缓冲对象的深度-模板附件上。

4. 总结

本文在前一章节(帧缓冲)的基础上,介绍了渲染缓冲对象,并通过对比渲染缓冲对象附件和纹理附件,详细说明了它们的区别和适用场景。然后介绍了渲染缓冲对象的相关接口,并给出了相应的代码实操案例。

0 人点赞