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) ,但并不会直接为这些缓冲区分配空间,而只是为这些缓冲区提供一个或多个挂接点 。我们需要分别为各个缓冲区创建对象,申请空间,然后挂接到相应的挂接点上 。
【Opengl ES之FBO】

Opengl ES之FBO

文章插图
从上图可以看出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};// 本身图像纹理idGLuint imageTextureId{0};// fbo纹理idGLuint 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);// VAOglGenVertexArrays(1, &vao);glBindVertexArray(vao);// vboglGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE, GL_STATIC_DRAW);// stride 步长 每个顶点坐标之间相隔4个数据点,数据类型是floatglVertexAttribPointer(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);// EBOglGenBuffers(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(){// VAOglGenVertexArrays(1, &fboVao);glBindVertexArray(fboVao);// vboglGenBuffers(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个数据点,数据类型是floatglVertexAttribPointer(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);// EBOglGenBuffers(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配合绘制// 使用vaoglBindVertexArray(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配合绘制// 使用vaoglBindVertexArray(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等}

经验总结扩展阅读