欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL之帧缓冲

程序员文章站 2022-03-16 19:17:22
...
  • 简介
    OpenGlES中的物体绘制并不是直接在屏幕上进行的,而是预先在帧缓冲区内进行绘制的,每绘制完一帧再将绘制的结果交换到屏幕上,所以每次绘制新的一帧时,都要清除缓冲区中的相关数据,否则可能产生不正确的绘制。
  • 包括三种类型的屏幕缓冲
    • 颜色缓冲
      用于存储每个片元的颜色值,每个颜色包括RGBA4个色彩通道,应用程序看到的内容就是颜色缓冲区中的内容
    • 用于写入深度信息的深度缓冲
      用于存储每个片元的深度值,是指从片元处到观察点(相机)的距离。在启用深度测试的情况下,新片元想进入帧缓冲时需要将自己的深度值与帧缓冲中的对应位置片元的深度值进行比较,若结构为小于才有可能进入缓冲,否则丢弃。
    • 允许我们基于一些条件丢弃指定片段的模板缓冲
      存储每个片元的模板值,供 模板测试使用。
  • OpenGL给了我们自己定义帧缓冲的*,我们可以选择性的定义自己的颜色缓冲、深度和模板缓冲
    目前所做的渲染操作都是是在默认的帧缓冲之上进行的。当你创建了你的窗口的时候默认帧缓冲就被创建和配置好了(GLFW为我们做了这件事)。通过创建我们自己的帧缓冲我们能够获得一种额外的渲染方式。
    通过帧缓冲可以将你的场景渲染到一个不同的帧缓冲中,可以使我们能够在场景中创建镜子这样的效果,或者做出一些炫酷的特效。首先我们会讨论它们是如何工作的,然后我们将利用帧缓冲来实现一些炫酷的效果。
  • 创建过程
  1. 就像OpenGL中其他对象一样,我们可以使用一个叫做glGenFramebuffers的函数来创建一个帧缓冲对象(简称FBO)

    GLuint fbo;
    glGenFramebuffers(1, &fbo);
    使用glBindFramebuffer来将fbo绑定到当前帧缓冲,由于opengl采用状态机模式,绑定到GL_FRAMEBUFFER目标后,接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲。
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    也可以把帧缓冲分开绑定到读或写目标上
    GL_READ_FRAMEBUFFER
    就能执行所有读取操作,像glReadPixels这样的函数使用了
    GL_DRAW_FRAMEBUFFER
    就允许进行渲染、清空和其他的写入操作
    
  • 建构一个完整的帧缓冲必须满足以下条件:
  1. 我们必须往里面加入至少一个附件(attachments)(颜色、深度、模板缓冲)。
    其中至少有一个是颜色附件。

  2. 所有的附件都应该是已经完全做好的(已经存储在内存之中)。
    每个缓冲都应该有同样数目的样本。
    检查帧缓冲是否创建完整的方法

  3. 可以用 glCheckFramebufferStatus 带上 GL_FRAMEBUFFER这个参数来检查是否真的成功做到了。然后检查当前绑定的帧缓冲,返回了这些规范中的哪个值。如果返回的是 GL_FRAMEBUFFER_COMPLETE就对了:

    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
    
  • 离屏渲染
    后续所有渲染操作将渲染到当前绑定的帧缓冲的附加缓冲中,由于我们的帧缓冲不是默认的帧缓冲,渲染命令对窗口的视频输出不会产生任何影响。出于这个原因,它被称为离屏渲染(off-screen rendering),就是渲染到一个另外的缓冲中。为了让所有的渲染操作对主窗口产生影响我们必须通过绑定为0来使默认帧缓冲被**:

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    

    做完所有的帧缓冲操作后,删除帧缓冲对象

    glDeleteFramebuffers(1, &fbo);
    
  • 创建附加到帧缓冲上的附件
    我们需要把一个或更多的附件附加到帧缓冲上。一个附件就是一个内存地址,这个内存地址里面包含一个为帧缓冲准备的缓冲,它可以是个图像。当创建一个附件的时候我们有两种方式可以采用

    • 纹理附件(Texture attachments)
      当把一个纹理附加到帧缓冲上的时候,所有渲染命令会写入到纹理上,就像它是一个普通的颜色/深度或者模板缓冲一样。使用纹理的好处是,所有渲染操作的结果都会被储存为一个纹理图像,这样我们就可以简单的在着色器中使用了。创建一个帧缓冲的纹理和创建普通纹理差不多:区别是我们把纹理的维度设置为屏幕大小(尽管不是必须的),我们还传递NULL作为纹理的data参数。对于这个纹理,我们只分配内存,而不去填充它。纹理填充会在渲染到帧缓冲的时候去做
    1. 将创建好的纹理附加到帧缓冲上

      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, texture, 0);
      target
      我们所创建的帧缓冲类型的目标(绘制、读取或两者都有)。
      attachment
      我们所附加的附件的类型。现在我们附加的是一个颜色附件。需要注意,最后的那个0是暗示我们可以附加1个以上颜色的附件。
      textarget
      你希望附加的纹理类型。
      texture
      附加的实际纹理。
      level
      Mipmap level。我们设置为0。
      

      除颜色附件以外,我们还可以附加一个深度和一个模板纹理到帧缓冲对象上。为了附加一个深度缓冲,我们可以知道那个GL_DEPTH_ATTACHMENT作为附件类型。记住,这时纹理格式和内部格式类型(internalformat)就成了 GL_DEPTH_COMPONENT去反应深度缓冲的存储格式。附加一个模板缓冲,你要使用 GL_STENCIL_ATTACHMENT
      也可以同时附加一个深度缓冲和一个模板缓冲为一个单独的纹理。这样纹理的每32位数值就包含了24位的深度信息和8位的模板信息。为了把一个深度和模板缓冲附加到一个单独纹理上,我们使用GL_DEPTH_STENCIL_ATTACHMENT类型配置纹理格式以包含深度值和模板值的结合物。下面是一个附加了深度和模板缓冲为单一纹理的例子

      glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL );
       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
      
    • 渲染缓冲对象附件(Renderbuffer object attachments)
      和纹理图像一样,渲染缓冲对象也是一个缓冲,它可以是一堆字节、整数、像素或者其他东西。渲染缓冲对象的一大优点是,它以OpenGL原生渲染格式储存它的数据,因此在离屏渲染到帧缓冲的时候,这些数据就相当于被优化过的了。然而,渲染缓冲对象通常是只写的,不能修改它们(就像获取纹理,不能写入纹理一样)。可以用glReadPixels函数去读取,函数返回一个当前绑定的帧缓冲的特定像素区域,而不是直接返回附件本身。

      • 创建一个渲染缓冲对象
      GLuint rbo;
      glGenRenderbuffers(1, &rbo);
      绑定渲染对象
      glBindRenderbuffer(GL_RENDERBUFFER, rbo);
      

      由于渲染缓冲对象通常是只写的,它们经常作为深度和模板附件来使用,由于大多数时候,我们不需要从深度和模板缓冲中读取数据,但仍关心深度和模板测试。我们就需要有深度和模板值提供给测试,但不需要对这些值进行采样(sample),所以深度缓冲对象是完全符合的。当我们不去从这些缓冲中采样的时候,渲染缓冲对象通常很合适,因为它们等于是被优化过的。

      调用glRenderbufferStorage函数可以创建一个深度和模板渲染缓冲对象:
      glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
      渲染缓冲对象 依附到帧缓冲
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
      
    • 何时使用渲染缓冲对象,何时使用纹理
      如果你永远都不需要从特定的缓冲中进行采样,渲染缓冲对象对特定缓冲是更明智的选择。如果哪天需要从比如颜色或深度值这样的特定缓冲采样数据的话,你最好还是使用纹理附件。从执行效率角度考虑,它不会对效率有太大影响。

  • 渲染到纹理

    1. 创建一个帧缓冲对象,并绑定它

      GLuint framebuffer; glGenFramebuffers (1, &framebuffer);
      glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
      
    2. 我们创建一个纹理图像,这是我们将要附加到帧缓冲的颜色附件。我们把纹理的尺寸设置为窗口的宽度和高度,并保持数据未初始化:

      GLuint texColorBuffer;
      glGenTextures(1, &texColorBuffer);
      glBindTexture(GL_TEXTURE_2D, texColorBuffer);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glBindTexture(GL_TEXTURE_2D, 0);
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);
      
    3. 我们同样打算要让OpenGL确定可以进行深度测试(模板测试,如果你用的话)所以我们必须还要确保向帧缓冲中添加一个深度(和模板)附件。由于我们只采样颜色缓冲,并不采样其他缓冲,我们可以创建一个渲染缓冲对象来达到这个目的。记住,当你不打算从指定缓冲采样的的时候,它们是一个不错的选择。

      GLuint rbo; // render buffer object
      glGenRenderbuffers (1, &rbo);
      glBindRenderbuffer (GL_RENDERBUFFER, rbo); glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, screenWidth, screenHeight);
      glBindRenderbuffer (GL_RENDERBUFFER, 0);
      我们为渲染缓冲对象分配了足够的内存空间以后,我们可以解绑渲染缓冲。
      我们把渲染缓冲对象附加到帧缓冲的深度和模板附件上
      glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
      
    4. 然后我们要检查帧缓冲是否真的做好了,如果没有,我们就打印一个错误消息。

      if (glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
      cout << "ERROR:FRAMEBUFFER:: Framebuffer is not complete!" << endl;
      glBindFramebuffer (GL_FRAMEBUFFER, 0);
      还要保证解绑帧缓冲,这样我们才不会意外渲染到错误的帧缓冲上。