乐趣区

关于android:Opengl-ES之FBO

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!!!

退出移动版