OpenGL ES 2.0 —— 帧缓冲 FBO

 

前言

上一篇文章学习了 VBO 顶点缓冲, 它解决了多次向 GPU 内存拷贝顶点数据带来性能损耗的问题

这里思考另一个问题, 当数据通过渲染管线输出到屏幕时, 我们如何拿到输出的数据呢?

带着这个问题, 我们了解一下 FBO 的相关知识

一. 什么是帧缓冲?

帧缓冲 (Framebuffer Object, 简称 FBO) 即用于存放渲染管线输出数据帧的缓冲

帧缓冲结构

一个帧缓冲由颜色附件, 深度附件模板附件组成

  • 纹理可以作为颜色附件和深度附件使用
  • 渲染缓冲可以作为深度附件和模板附件使用

Android 自带的帧缓冲使用 Surface/SurfaceTexture 描述, 对应一个 NativeWindow

二. 使用场景

  • 在图像输出到屏幕之前, 需要对图像进行加工
    • 滤镜
    • 水印
  • 不希望数据直接输出到屏幕, 希望能够拦截输出的数据
    • 将数据发送到 MediaCodec 进行硬编

三. 操作流程

一) 创建缓冲帧

// 创建 fbo
int[] fboIds = new int[1];
GLES20.glGenBuffers(1, fboIds, 0);
int fboId = fboIds[0];

通过 glGenBuffers 函数, 便可以创建一个缓冲帧的对象, 通过传出参数 fboIds 输出

二) 绑定附件

1. 纹理附件

将纹理附件添加到缓冲帧上时, 所有渲染命令都会输出到纹理中, 所有的渲染结果都会存储为纹理图像

创建纹理对象
// 创建纹理
GLES20.glTexImage2D(
        GLES20.GL_TEXTURE_2D,
        0,
        GLES20.GL_RGBA,
        // 这里传入画布的宽高
        width, height,
        0,
        GLES20.GL_RGBA,
        GLES20.GL_UNSIGNED_BYTE,
        null
);
添加纹理附件
// 为 fbo 添加附件
GLES20.glFramebufferTexture2D(
        GLES20.GL_FRAMEBUFFER,
        GLES20.GL_COLOR_ATTACHMENT0,  // 颜色附件
        GLES20.GL_TEXTURE_2D,
        mTextureId,
        0
);
  • target: 一般为 GLES20.GL_FRAMEBUFFER
  • attachment: 附件类型有如下三种
    • 颜色附件: GL_COLOR_ATTACHMENT0
    • 深度附件: GL_DEPTH_ATTACHMENT
    • 模板附件: GL_STENCIL_ATTACHMENT
  • textarget: 纹理的类型
  • texture: 纹理的 id
  • level: Mipmap level, 一般传 0

2. 渲染缓冲附件

除了可以绑定纹理附件之外, OpenGL 还引入了渲染缓冲对象, 它以原生渲染格式存储数据, 不会进行针对特定纹理格式的转换

因为存储的是原生数据, 因此进行 glSwapBuffers, 进行数据交换时比纹理附件更加高效

创建渲染缓冲对象
int[] renderbuffers = new int[1];
GLES20.glGenRenderbuffers(1, renderbuffers, 0);
int renderbuffer = renderbuffers[0];
添加渲染缓冲附件
// 绑定到当前渲染缓冲对象
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderbufferId);
// 初始化渲染数据存储
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_ATTACHMENT, width, height);
// 将渲染缓冲添加到 FBO 上
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderbufferId);

三) 绘制到缓冲帧

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebufferId);

...... // 在此之间的代码将绘制到缓冲帧上面去

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

可以看到绘制到缓冲帧的方式非常简单, 只需要进行绑定 fboId 和解绑即可, 在其之间的代码都将渲染到缓冲帧上

渲染到缓冲帧上之后, 我们可以根据自己的场景自行判断是否需要输出到屏幕上, 因此即使没有屏幕作为交换介质, 依旧可以获取渲染的数据, 帧缓冲又称之为离屏渲染(off-screen rendering)

总结

通过对 FBO 的学习, 让我们简单的了解到了帧缓冲的使用方式, 以及使用场景

  • 它的工作流程类似于一个 Hook, 相当于在输出到屏幕之前先将准备好的数据 Hook 到 FBO 上, 由它进行加工处理

FBO 的使用频率是非常高的, 我们可以通过 fbo 实现对纹理添加水印这样子类似的功能, 可以在之前的纹理绘制的基础上进行拓展

参考

  • https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/05%20Framebuffers/
  • https://glumes.com/post/opengl/opengl-framebuffer-object-usage/