FBO介绍

FBO帧缓冲对象,它的次要作用个别就是用作离屏渲染,例如做Camera相机图像采集进行前期解决时就可能会用到FBO。如果相机出图的是OES纹理,为了不便前期解决,
个别先将OES纹理通过FBO转换成一般的2D纹理,而后再通过FBO等减少美颜等其余各种特效滤镜,最初将FBO一路流送进编码器进行编码,另外一路渲染到屏幕上进行预览显示。

FBO总结起来就是能够临时将未解决完的帧不间接渲染到屏幕上,而是渲染到离屏Buffer中缓存起来,在失当的机会再取出来渲染到屏幕。

FBO(Frame Buffer Object)帧缓冲对象提供了与色彩缓冲区(color buffer)、深度缓冲区(depth buffer)和模版缓冲区(stencil buffer) ,但并不会间接为这些缓冲区调配空间,而只是为这些缓冲区提供一个或多个挂接点。咱们须要别离为各个缓冲区创建对象,申请空间,而后挂接到相应的挂接点上。

从上图能够看出FBO中蕴含了:

  1. 多个色彩附着点(GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1...)
  2. 一个深度附着点(GL_DEPTH_ATTACHMENT)
  3. 一个模板附着点(GL_STENCIL_ATTACHMENT)

所谓的色彩附着(纹理附着)就是用于将色彩渲染到纹理中去的意思。前面咱们次要介绍FBO的色彩附着。

如何应用FBO

  1. 应用函数glGenFramebuffers生成一个FBO对象,保留对象ID。
  2. 应用函数glBindFramebuffer绑定FBO。
  3. 应用函数glFramebufferTexture2D关联纹理和FBO,并执行渲染步骤。后续如果须要应用FBO的成果时只须要操作与FBO绑定的纹理即可。
  4. 应用函数glBindFramebuffer解绑FBO,个别在Opengl中ID参数传递0就是解绑。
  5. 应用函数glDeleteFramebuffers删除FBO。

当挂接实现之后,咱们在执行FBO上面的操作之前,能够检查一下FBO的状态,应用函数GLenum glCheckFramebufferStatus(GLenum target)查看。

本着学以致用的准则,咱们将联合之前的文章,例如纹理贴图、VBO/VAO、EBO等相干知识点,应用这些知识点联合FBO绘制做一个实际的例子:首先将纹理渲染到FBO下来,而后再将FBO的纹理渲染到屏幕上。

插个话。。。总有人盗用不贴原文链接,看看是谁。。。

首先上代码,而后咱们挑重要的略微解读一下:
FBOOpengl.h

class FBOOpengl:public BaseOpengl{public:    FBOOpengl();    void onFboDraw();    virtual ~FBOOpengl();    // override要么就都写,要么就都不写,不要一个虚函数写override,而另外一个虚函数不写override,不然可能编译不过    virtual void onDraw() override;    virtual void setPixel(void *data, int width, int height, int length) override;private:    void fboPrepare();    GLint positionHandle{-1};    GLint textureHandle{-1};    GLuint vbo{0};    GLuint vao{0};    GLuint ebo{0};    // 自身图像纹理id    GLuint imageTextureId{0};    // fbo纹理id    GLuint fboTextureId{0};    GLint textureSampler{-1};    GLuint fboId{0};    // 用于fbo的vbo和vao  也能够用数组的模式,这里为了不便了解先独立开来    GLuint fboVbo{0};    GLuint fboVao{0};    int imageWidth{0};    int imageHeight{0};};

留神:override作为古代C++的一个关键字,应用的时候须要留神一点,要么就整个类的虚函数都用,要么整个类的虚函数都不必,不要一个虚函数用override润饰,另外一个虚函数又不必override关键字润饰,不然很有可能会编译不过的。

在FBOOpengl中为了辨别屏幕渲染和FBO离屏渲染,咱们申明了两套VAO和VBO。

FBOOpengl.cpp

#include "FBOOpengl.h"#include "../utils/Log.h"// 顶点着色器static const char *ver = "#version 300 es\n"                         "in vec4 aPosition;\n"                         "in vec2 aTexCoord;\n"                         "out vec2 TexCoord;\n"                         "void main() {\n"                         "  TexCoord = aTexCoord;\n"                         "  gl_Position = aPosition;\n"                         "}";// 片元着色器static const char *fragment = "#version 300 es\n"                              "precision mediump float;\n"                              "out vec4 FragColor;\n"                              "in vec2 TexCoord;\n"                              "uniform sampler2D ourTexture;\n"                              "void main()\n"                              "{\n"                              "    FragColor = texture(ourTexture, TexCoord);\n"                              "}";const static GLfloat VERTICES_AND_TEXTURE[] = {        0.5f, -0.5f, // 右下        // 纹理坐标        1.0f,1.0f,        0.5f, 0.5f, // 右上        // 纹理坐标        1.0f,0.0f,        -0.5f, -0.5f, // 左下        // 纹理坐标        0.0f,1.0f,        -0.5f, 0.5f, // 左上        // 纹理坐标        0.0f,0.0f};// 纹理坐标原点在图片的左上角    又是倒置的?什么鬼?纳闷吧?//const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {//        1.0f, -1.0f, // 右下//        // 纹理坐标//        1.0f,1.0f,//        1.0f, 1.0f, // 右上//        // 纹理坐标//        1.0f,0.0f,//        -1.0f, -1.0f, // 左下//        // 纹理坐标//        0.0f,1.0f,//        -1.0f, 1.0f, // 左上//        // 纹理坐标//        0.0f,0.0f//};// 真正的纹理坐标在图片的左下角const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {        1.0f, -1.0f, // 右下        // 纹理坐标        1.0f,0.0f,        1.0f, 1.0f, // 右上        // 纹理坐标        1.0f,1.0f,        -1.0f, -1.0f, // 左下        // 纹理坐标        0.0f,0.0f,        -1.0f, 1.0f, // 左上        // 纹理坐标        0.0f,1.0f};// 应用byte类型比应用short或者int类型节约内存const static uint8_t indices[] = {        // 留神索引从0开始!        // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,        // 这样能够由下标代表顶点组合成矩形        0, 1, 2, // 第一个三角形        1, 2, 3  // 第二个三角形};FBOOpengl::FBOOpengl() {    initGlProgram(ver,fragment);    positionHandle = glGetAttribLocation(program,"aPosition");    textureHandle = glGetAttribLocation(program,"aTexCoord");    textureSampler = glGetUniformLocation(program,"ourTexture");    LOGD("program:%d",program);    LOGD("positionHandle:%d",positionHandle);    LOGD("textureHandle:%d",textureHandle);    LOGD("textureSample:%d",textureSampler);    // VAO    glGenVertexArrays(1, &vao);    glBindVertexArray(vao);    // vbo    glGenBuffers(1, &vbo);    glBindBuffer(GL_ARRAY_BUFFER, vbo);    glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE, GL_STATIC_DRAW);    // stride 步长 每个顶点坐标之间相隔4个数据点,数据类型是float    glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);    // 启用顶点数据    glEnableVertexAttribArray(positionHandle);    // stride 步长 每个色彩坐标之间相隔4个数据点,数据类型是float,色彩坐标索引从2开始    glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),                          (void *) (2 * sizeof(float)));    // 启用纹理坐标数组    glEnableVertexAttribArray(textureHandle);    // EBO    glGenBuffers(1,&ebo);    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ebo);    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);    // 这个程序不能乱啊,先解除vao,再解除其余的,不然在绘制的时候可能会不起作用,须要从新glBindBuffer才失效    // vao解除    glBindVertexArray(0);    // 解除绑定    glBindBuffer(GL_ARRAY_BUFFER, 0);    // 解除绑定    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);    LOGD("program:%d", program);    LOGD("positionHandle:%d", positionHandle);    LOGD("colorHandle:%d", textureHandle);}void FBOOpengl::setPixel(void *data, int width, int height, int length) {    LOGD("texture setPixel");    imageWidth = width;    imageHeight = height;    glGenTextures(1, &imageTextureId);    // 激活纹理,留神以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个    // 默认是0,如果不是0的话,须要在onDraw的时候从新激活一下?//    glActiveTexture(GL_TEXTURE0);//    glUniform1i(textureSampler, 0);// 例如,一样的    glActiveTexture(GL_TEXTURE2);    glUniform1i(textureSampler, 2);    // 绑定纹理    glBindTexture(GL_TEXTURE_2D, imageTextureId);    // 为以后绑定的纹理对象设置盘绕、过滤形式    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);    // 生成mip贴图    glGenerateMipmap(GL_TEXTURE_2D);    // 解绑定    glBindTexture(GL_TEXTURE_2D, 0);}void FBOOpengl::fboPrepare(){    // VAO    glGenVertexArrays(1, &fboVao);    glBindVertexArray(fboVao);    // vbo    glGenBuffers(1, &fboVbo);    glBindBuffer(GL_ARRAY_BUFFER, fboVbo);    glBufferData(GL_ARRAY_BUFFER, sizeof(FBO_VERTICES_AND_TEXTURE), FBO_VERTICES_AND_TEXTURE, GL_STATIC_DRAW);    // stride 步长 每个顶点坐标之间相隔4个数据点,数据类型是float    glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);    // 启用顶点数据    glEnableVertexAttribArray(positionHandle);    // stride 步长 每个色彩坐标之间相隔4个数据点,数据类型是float,色彩坐标索引从2开始    glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),                          (void *) (2 * sizeof(float)));    // 启用纹理坐标数组    glEnableVertexAttribArray(textureHandle);    // EBO    glGenBuffers(1,&ebo);    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ebo);    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);    // 这个程序不能乱啊,先解除vao,再解除其余的,不然在绘制的时候可能会不起作用,须要从新glBindBuffer才失效    // vao解除    glBindVertexArray(0);    // 解除绑定    glBindBuffer(GL_ARRAY_BUFFER, 0);    // 解除绑定    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);    glGenTextures(1, &fboTextureId);    // 绑定纹理    glBindTexture(GL_TEXTURE_2D, fboTextureId);    // 为以后绑定的纹理对象设置盘绕、过滤形式    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    glBindTexture(GL_TEXTURE_2D, GL_NONE);    glGenFramebuffers(1,&fboId);    glBindFramebuffer(GL_FRAMEBUFFER,fboId);    // 绑定纹理    glBindTexture(GL_TEXTURE_2D,fboTextureId);    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTextureId, 0);    // 这个纹理是多大的?    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);    // 查看FBO状态    if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) {        LOGE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE");    }    // 解绑    glBindTexture(GL_TEXTURE_2D, GL_NONE);    glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);}void FBOOpengl::onFboDraw() {    fboPrepare();    glBindFramebuffer(GL_FRAMEBUFFER, fboId);    // 次要这个的大小要与FBO绑定时的纹理的glTexImage2D 设置的大小统一呀    glViewport(0,0,imageWidth,imageHeight);     // FBO绘制    // 清屏    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);    glClear(GL_COLOR_BUFFER_BIT);    glUseProgram(program);    // 激活纹理    glActiveTexture(GL_TEXTURE1);    glUniform1i(textureSampler, 1);    // 绑定纹理    glBindTexture(GL_TEXTURE_2D, imageTextureId);    // VBO与VAO配合绘制    // 应用vao    glBindVertexArray(fboVao);    // 应用EBO// 应用byte类型节俭内存    glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_BYTE,(void *)0);    glUseProgram(0);    // vao解除绑定    glBindVertexArray(0);    if (nullptr != eglHelper) {        eglHelper->swapBuffers();    }    glBindTexture(GL_TEXTURE_2D, 0);    glBindFramebuffer(GL_FRAMEBUFFER, 0);}void FBOOpengl::onDraw() {    // 先在FBO离屏渲染    onFboDraw();    // 复原绘制屏幕宽高    glViewport(0,0,eglHelper->viewWidth,eglHelper->viewHeight);    // 绘制到屏幕    // 清屏    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);    glClear(GL_COLOR_BUFFER_BIT);    glUseProgram(program);    // 激活纹理    glActiveTexture(GL_TEXTURE2);    glUniform1i(textureSampler, 2);    // 绑定纹理    glBindTexture(GL_TEXTURE_2D, fboTextureId);    // VBO与VAO配合绘制    // 应用vao    glBindVertexArray(vao);    // 应用EBO// 应用byte类型节俭内存    glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_BYTE,(void *)0);    glUseProgram(0);    // vao解除绑定    glBindVertexArray(0);    // 禁用顶点    glDisableVertexAttribArray(positionHandle);    if (nullptr != eglHelper) {        eglHelper->swapBuffers();    }    glBindTexture(GL_TEXTURE_2D, 0);}FBOOpengl::~FBOOpengl() noexcept {    glDeleteBuffers(1,&ebo);    glDeleteBuffers(1,&vbo);    glDeleteVertexArrays(1,&vao);    // ... 删除其余,例如fbo等}

依照之前Opengl ES之纹理贴图 一文所说的,在Opengl ES中进行纹理贴图时间接以图片的左上角为(0,0)原点进行贴图,以纠正纹理贴图倒置的问题,那么这次在绑定FBO之后之后咱们就这么干,
应用以下的顶点坐标和纹理坐标:

// 纹理坐标原点在图片的左上角    又是倒置的?什么鬼?纳闷吧?const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {        1.0f, -1.0f, // 右下        // 纹理坐标        1.0f,1.0f,        1.0f, 1.0f, // 右上        // 纹理坐标        1.0f,0.0f,        -1.0f, -1.0f, // 左下        // 纹理坐标        0.0f,1.0f,        -1.0f, 1.0f, // 左上        // 纹理坐标        0.0f,0.0f};

一运行,咱们惊喜地发现,理论状况竟然和 Opengl ES之纹理贴图 一文所说的不一样了,通过FBO后的贴图再渲染到屏幕时,竟然图片是倒置的,如下图:

这是什么为什么呢?

默认状况下,OpenGL ES 通过绘制到窗口零碎提供的帧缓冲区,也就是屏幕自身就是一个默认的FBO,而应用FBO进行纹理贴图的时候须要以真正的纹理坐标(原点0,0在图片的左下角)为基准进行贴图。因而如果间接应用屏幕进行纹理贴图,其实是应该细分成两个
过程的,先以左下角为纹理坐标原点进行贴图,而后将贴图后的屏幕默认FBO旋转绕X轴旋转180度与屏幕坐标(左上角是坐标原点)重合,然而这两个细分的过程能够做个取巧就是间接以左上角为纹理坐标原点进行贴图,失去的后果是一样的。

然而咱们在独自应用FBO时,仍应该遵循以左下角为纹理坐标原点的准则进行纹理贴图。因而咱们只需批改一下顶点坐标和纹理坐标,以左下角为纹理坐标作为原点进行FBO贴图,而后再将FBO缭绕到屏幕上即可:

// 真正的纹理坐标在图片的左下角const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {        1.0f, -1.0f, // 右下        // 纹理坐标        1.0f,0.0f,        1.0f, 1.0f, // 右上        // 纹理坐标        1.0f,1.0f,        -1.0f, -1.0f, // 左下        // 纹理坐标        0.0f,0.0f,        -1.0f, 1.0f, // 左上        // 纹理坐标        0.0f,1.0f};

运行后果如图:

往期系列

Opengl ES之EGL环境搭建
Opengl ES之着色器
Opengl ES之三角形绘制
Opengl ES之四边形绘制
Opengl ES之纹理贴图
Opengl ES之VBO和VAO
Opengl ES之EBO

关注我,一起提高,人生不止coding!!!