OpenGL的烘托管线中,几许数据和纹路经过一系列改换和测验,最终被烘托成屏幕上的二维像素。那些用于存储色彩值和测验成果的二维数组的几许被称为帧缓冲区(frame buffer)

用于写入色彩值的色彩缓冲、用于写入深度信息的深度缓冲和允许咱们根据一些条件丢弃特定片段的模板缓冲,这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中

咱们现在所做的全部操作都是在默许帧缓冲的烘托缓冲上进行的。默许的帧缓冲是在你创立窗口的时分生成和装备的

OpenGL允许咱们界说咱们自己的帧缓冲,也就是说咱们能够界说咱们自己的色彩缓冲,甚至是深度缓冲和模板缓冲。

帧缓冲,便是允许开发将烘托内容制作到另一个缓冲上,不影响当时屏幕显现,需求显现上述帧缓冲时再取出显现即可。

1、帧缓冲理解

OpenGL ES教程——帧缓冲

如上图,帧缓冲由以下三种缓冲组成:

  • 色彩缓冲,能够有多个色彩缓冲
  • 深度缓冲,只要一个
  • 模板缓冲,只要一个

从上图看,这些缓冲并不是由帧缓冲创立供给,需求其它来attach。一般来说,色彩缓冲由纹路附件供给,而深度缓冲、模板缓冲由烘托缓冲目标供给。

那么纹路附件和烘托缓冲目标有什么区别呢?

假如你不需求从一个缓冲中采样数据,那么对这个缓冲运用烘托缓冲目标会是正确的挑选。假如你需求从缓冲中采样色彩或深度值等数据,那么你应该挑选纹路附件。功能方面它不会发生非常大的影响的。

浅显来说,深度及模板缓冲用烘托缓冲目标,色彩缓冲用纹路附件

2、帧缓冲运用

这里不得不提一句机遇问题,android中创立帧缓冲假如机遇不对,会失败。

GLSurfaceView.Render有三个接口:

  • onSurfaceCreated,suface刚创立,此刻创立帧缓冲会失败
  • onSurfaceChanged,能够创立帧缓冲
  • onDrawFrame,能够创立帧缓冲,不过这个接口可能会被调用很多次,导致会重复创立很多次帧缓冲,冗余调用,于功能有损,不建议在此刻操作

整体代码如下:

    //先生成帧缓冲id
    glGenFramebuffers(1, &mFrameBufferId);
    //再绑定帧缓冲id
    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferId);
    //生成纹路
    glGenTextures(1, &mFrameTextureId);
    //绑定纹路
    glBindTexture(GL_TEXTURE_2D, mFrameTextureId);
    //给纹路指定数据,确定内存大小,注意给纹路赋值的buf为null
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MyGlRenderContext::getInstance()->getWidth(),
                 MyGlRenderContext::getInstance()->getHeight(),
                 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);
    //绑定纹路到对应的色彩缓冲,attach
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFrameTextureId, 0);
    GLuint rbo;
    //生成烘托缓冲目标id
    glGenRenderbuffers(1, &rbo);
    //绑定烘托缓冲目标
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    //创立一个深度和模板烘托缓冲目标
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
                          MyGlRenderContext::getInstance()->getWidth(),
                          MyGlRenderContext::getInstance()->getHeight());
    //绑定到帧缓冲中,attach操作
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
    //查看帧缓冲状态是否异常
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        LOGI("error framebuffer");
    }
    //修改帧缓冲为默许的,即屏幕
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

注意,在指定烘托缓冲目标格式时,是GL_DEPTH24_STENCIL8,它封装了24位的深度和8位的模板缓冲。

也会有同学有疑问,为什么纹路赋值的buf为null呢?

因为当咱们往当时帧缓冲上烘托时,烘托的全部数据就会主动填充到帧缓冲相关的纹路上了,有点类似于android中的bitmap和canvas之间的联系,假如canvas相关了一个bitmap,调用canvas制作的任何东西都会保存在bitmap中。

所以如何使用帧缓冲制作呢?通常是烘托到帧缓冲结束时,拿与帧缓冲相关的纹路来制作即可。且因为帧缓冲也有深度和模板测验,所以再次制作纹路时,需求封闭深度测验、模板测验。

而且咱们拿到这个纹路,能够改动片段着色器,完成很多不一样的作用,比如反相、灰度等。

3、反相的完成

完成反相分为三步:

  • 制作东西到帧缓冲中
  • 切换为默许帧缓冲
  • 取与帧缓冲相关的纹路id,制作

具体代码如下:

void FrameBufferSample::draw() {
    //绑定
    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferId);
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //制作两个正常的箱子到帧缓冲中
    frameShader.use();
    auto rat = MyGlRenderContext::getInstance()->getWidth() * 1.0f /
               MyGlRenderContext::getInstance()->getHeight();
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), rat, 0.1f, 100.0f);
    glm::mat4 view = camera.getViewMatrix();
    glm::mat4 model = glm::mat4(1.0f);
    frameShader.setMat4("view", view);
    frameShader.setMat4("projection", projection);
    glBindVertexArray(mCubeVao);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mCubeId);
    model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
    frameShader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
    frameShader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    //制作地板到帧缓冲中
    glBindVertexArray(mFloorVao);
    glBindTexture(GL_TEXTURE_2D, mFloorId);
    frameShader.setMat4("model", glm::mat4(1.0f));
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindVertexArray(0);
    //切换为默许帧缓冲并封闭深度测验
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDisable(GL_DEPTH_TEST);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    //制作与帧缓冲相相关的纹路
    screenShader.use();
    glBindVertexArray(mScreenVao);
    glBindTexture(GL_TEXTURE_2D, mFrameTextureId);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

到现在为止,咱们只是把本来想要制作的箱子地板等东西制作到了帧缓冲上,再直接取帧缓冲内容制作到默许帧缓冲上(即当时屏幕),那说好的反相呢?怎样让整体色彩值取反呢?

在制作与帧缓冲相关纹路时,咱们在它的片段着色器上修改点东西,就能够完成作用:

void main() {
    FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

反相作用如图所示:

OpenGL ES教程——帧缓冲

正常作用:

OpenGL ES教程——帧缓冲

这里也不得不敬服opengl,做一些相应作用太简单了,假如用c++做,往往得遍历像素,一个个像素处理,而opengl只需求运用1.0减去当时色彩值即可