帧缓冲

我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

  1. 创建缓冲 > 帧缓冲对象(Framebuffer Object, FBO):

    • 创建帧缓冲对象
        unsigned int fbo;
        glGenFramebuffers(1, &fbo);
    • 绑定帧缓冲对象
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    在绑定到GL_FRAMEBUFFER目标之后,所有的读取和写入帧缓冲的操作将会影响当前绑定的帧缓冲。我们也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,将一个帧缓冲分别绑定到读取目标或写入目标。绑定到GL_READ_FRAMEBUFFER的帧缓冲将会使用在所有像是glReadPixels的读取操作中,而绑定到GL_DRAW_FRAMEBUFFER的帧缓冲将会被用作渲染、清除等写入操作的目标。大部分情况你都不需要区分它们,通常都会使用GL_FRAMEBUFFER,绑定到两个上。

    不幸的是,我们现在还不能使用我们的帧缓冲,因为它还不完整(Complete),一个完整的帧缓冲需要满足以下的条件:

    • 附加至少一个缓冲(颜色、深度或模板缓冲)。
    • 至少有一个颜色附件(Attachment)。
    • 所有的附件都必须是完整的(保留了内存)。
    • 每个缓冲都应该有相同的样本数。

    GL_FRAMEBUFFER为参数调用glCheckFramebufferStatus,检查帧缓冲是否完整。它将会检测当前绑定的帧缓冲,并返回规范中这些值的其中之一。如果它返回的是GL_FRAMEBUFFER_COMPLETE,帧缓冲就是完整的了。 cpp if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { // 执行胜利的舞蹈 }

    操作完成后重新设置为默认值 cpp glBindFramebuffer(GL_FRAMEBUFFER, 0);

    在所有使用完成后需要清理fbo对象 cpp glDeleteFramebuffers(1, &fbo);

  2. 纹理附件

    1. 创建一个渲染缓冲对象的代码和帧缓冲的代码很类似: cpp unsigned int rbo; glGenRenderbuffers(1, &rbo);

    2. 类似,我们需要绑定这个渲染缓冲对象,让之后所有的渲染缓冲操作影响当前的rbo: cpp glBindRenderbuffer(GL_RENDERBUFFER, rbo);

    3. 创建一个深度和模板渲染缓冲对象可以通过调用glRenderbufferStorage函数来完成: cpp glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);

    4. 最后一件事就是附加这个渲染缓冲对象: cpp glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

  3. 渲染到纹理 我们将会将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后将在一个横跨整个屏幕的四边形上绘制这个纹理。这样视觉输出和没使用帧缓冲时是完全一样的,但这次是打印到了一个四边形上。

    1. 首先要创建一个帧缓冲对象,并绑定它,这些都很直观: cpp unsigned int framebuffer; glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    2. 接下来我们需要创建一个纹理图像,我们将它作为一个颜色附件附加到帧缓冲上。我们将纹理的维度设置为窗口的宽度和高度,并且不初始化它的数据: cpp // 生成纹理 unsigned int 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. 创建一个渲染缓冲对象不是非常复杂。我们需要记住的唯一事情是,我们将它创建为一个深度和模板附件渲染缓冲对象。我们将它的内部格式设置为GL_DEPTH24_STENCIL8,对我们来说这个精度已经足够了。 cpp unsigned int rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); glBindRenderbuffer(GL_RENDERBUFFER, 0);

    4. 接下来,作为完成帧缓冲之前的最后一步,我们将渲染缓冲对象附加到帧缓冲的深度和模板附件上: cpp glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

    5. 最后,我们希望检查帧缓冲是否是完整的,如果不是,我们将打印错误信息。 cpp if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0);

    记得要解绑帧缓冲,保证我们不会不小心渲染到错误的帧缓冲上。

  4. 后处理 > 单纯重复渲染是一种,更多的用图在后处理 > 图形数学,卷积微积分高阶数学算子

    1. 反向,颜色取反

          void main()
          {
              FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
          }
    2. 灰度 rgb 平均值

          void main()
          {
              FragColor = texture(screenTexture, TexCoords);
              //float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
              float average = (FragColor.r + FragColor.g + FragColor.b) * 0.333333;
              FragColor = vec4(average, average, average, 1.0);
          }

      这已经能创造很好的结果了,但人眼会对绿色更加敏感一些,而对蓝色不那么敏感,所以为了获取物理上更精确的效果,我们需要使用加权的(Weighted)通道: r 0.2126、 g 0.7152 、 b 0.0722 cpp void main() { FragColor = texture(screenTexture, TexCoords); float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b; FragColor = vec4(average, average, average, 1.0); }

    3. 核效果

          void main()
          {
              vec2 offsets[9] = vec2[](
                  vec2(-offset,  offset), // 左上
                  vec2( 0.0f,    offset), // 正上
                  vec2( offset,  offset), // 右上
                  vec2(-offset,  0.0f),   // 左
                  vec2( 0.0f,    0.0f),   // 中
                  vec2( offset,  0.0f),   // 右
                  vec2(-offset, -offset), // 左下
                  vec2( 0.0f,   -offset), // 正下
                  vec2( offset, -offset)  // 右下
              );
      
              float kernel[9] = float[](
                  -1, -1, -1,
                  -1,  9, -1,
                  -1, -1, -1
              );
      
              vec3 sampleTex[9];
              for(int i = 0; i < 9; i++)
              {
                  sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
              }
              vec3 col = vec3(0.0);
              for(int i = 0; i < 9; i++)
                  col += sampleTex[i] * kernel[i];
      
              FragColor = vec4(col, 1.0);
          }
    4. 模糊 cpp float kernel[9] = float[]( 1.0 / 16, 2.0 / 16, 1.0 / 16, 2.0 / 16, 4.0 / 16, 2.0 / 16, 1.0 / 16, 2.0 / 16, 1.0 / 16 );

    5. 边缘检测

          float kernel[9] = float[](
              1, 1, 1,
              1, -8, 1,
              1, 1, 1  
          );

文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录