FBO介绍

FBO帧缓冲对象,它的主要作用一般就是用作离屏渲染,例如做Camera相机图像采集进行后期处理时就可能会用到FBO。假如相机出图的是OES纹理,为了方便后期处理,
一般先将OES纹理通过FBO转换成普通的2D纹理,然后再通过FBO等增加美颜等其他各种特效滤镜,最后将FBO一路流送进编码器进行编码,另外一路渲染到屏幕上进行预览显示。

FBO总结起来就是可以暂时将未处理完的帧不直接渲染到屏幕上,而是渲染到离屏Buffer中缓存起来,在恰当的时机再取出来渲染到屏幕。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLg73qy1-1664416288414)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/FBO%E6%8C%82%E8%BD%BD%E7%82%B9.png)]

从上图可以看出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等
}

按照之前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后的贴图再渲染到屏幕时,居然图片是倒置的,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lVq37Lcq-1664416288415)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/FBO%E8%B4%B4%E5%9B%BE%E5%80%92%E7%BD%AE.png)]

这是什么为什么呢?

默认情况下,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
};

运行结果如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JlNObzbP-1664416288416)(https://flyer-blog.oss-cn-shenzhen.aliyuncs.com/FBO%E6%AD%A3%E7%A1%AE%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.png)]

往期系列

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

关注我,一起进步,人生不止coding!!!

Opengl ES之FBO相关推荐

  1. Opengl ES之PBO

    叨叨一句几句 关于Opengl的系列已经有较长的一段时间没有更新了,然而这个系列还远没有到完毕地步,后续至少还有关于Opengl矩阵变换.YUV与RGB互转.Opengl水印贴图.Opengl转场动画 ...

  2. Opengl ES之RGB转NV21

    前言 在上一篇理论文章中我们介绍了YUV到RGB之间转换的几种公式与一些优化算法,今天我们再来介绍一下RGB到YUV的转换,顺便使用Opengl ES做个实践,将一张RGB的图片通过Shader 的方 ...

  3. OpenGL ES之离屏渲染的帧缓冲区对象FBO的说明和使用

    一.什么是 FBO ? FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO). FBO 本身不能用于渲染,只有添加 ...

  4. OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏渲染,解决透明冲突,画中画)

    OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏录制,解决透明冲突) 1.水印签名罢工了? 不知道大家有没注意到,之前我们使用MediaCodec录制的视频,水印签名那部分区 ...

  5. OpenGL ES VAO、VBO、EBO、FBO、PBO、TBO、UBO

    面试中经常被问到的 OpenGL ES 对象,你知道的有哪些? 该原创文章首发于微信公众号:字节流动 VBO 和 EBO VBO(Vertex Buffer Object)是指顶点缓冲区对象,而 EB ...

  6. 【OpenGL ES】帧缓冲区对象FBO

    1.FBO 使用OpenGL ES,一般要通过EGL来配置本地窗口系统,关于EGL的介绍可参照"[OpenGL ES]EGL简介"http://blog.csdn.net/ieea ...

  7. 以OpenGL/ES视角介绍gfx-hal(Vulkan) Framebuffer接口使用

    文档列表见:Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录) 草稿状态 以OpenGL/ES Framebuffer角度看,如果用gfx-hal(Vulkan)接口实现类似OpenGL/ES ...

  8. Android下Opengl ES实现单屏幕双眼显示

    http://blog.csdn.net/u011371324/article/details/68946779 默认情况下,Opengl ES使用系统提供的帧缓冲区作为绘图表面,一般情况下,如果只在 ...

  9. 与context的关系_你还不知道 OpenGL ES 和 EGL 的关系?

    什么是 EGL EGL 是 OpenGL ES 和本地窗口系统(Native Window System)之间的通信接口,它的主要作用: 与设备的原生窗口系统通信: 查询绘图表面的可用类型和配置: 创 ...

  10. 浅谈OpenGL、OpenGL ES

    最近研究安卓系统屏幕绘制这部分时,可谓是绞尽脑汁- 偶然得到家人的指点有了思路上的突破,实现了想要的功能.本篇文章结合<OpenGL编程指南>书籍的基础上,简单介绍一下OpenGL的结构. ...

最新文章

  1. 科普丨深度学习引擎的终极形态是什么?
  2. linux命令查看cpu架构,Linux下如何查看CPU信息
  3. Linux Shell编程入门(zz)
  4. 用 TS + Vue 写了一个在 Chrome 中运行 Prettier 格式化的扩展程序
  5. QTP的那些事--终极项目脚本设计思路及其测试查询功能的一些实际项目体会
  6. 李国杰(1943-),男,博士,中国工程院院士。
  7. 简单介绍信用卡分销系统
  8. IDEA快捷键大全(超详细!)
  9. 误删数据?如何快速恢复Oracle数据库
  10. 思科 计算机网络 测试
  11. vc植物大战僵尸修改器简单源代码
  12. 为什么我的echarts字体样式这么丑?Echarts 柱状图、饼图 等标签、字体、样式调整
  13. 2019广州大学城第二届“论客杯”青年公益微创投暨南大学校际公开赛决赛圆满落幕
  14. php 色彩空间转换,PHP Imagemagick将灰度转换为RGB
  15. 3dsMax Biped骨骼缩放
  16. php mysql弹幕_PHP开发弹幕系统
  17. 程序员一定要学好的几门技术
  18. 2703 奶牛代理商 XII
  19. 9个最有趣的代码注释
  20. [大模型] LLaMA系列大模型调研与整理-llama/alpaca/lora(部分)

热门文章

  1. 适用于遥感图像处理的神经网络
  2. linux使用光盘镜像(ISO)作为软件源安装软件
  3. C++语言风格流变史(转)
  4. 性能优化之mysql优化_性能优化之MySQL优化
  5. 图片rar 加密文件
  6. 当红10大女明星PK座驾(车靓人更美)
  7. 新建SpringCloud电商后台项目
  8. 如何使用 Zend Expressive 建立 NASA 图片库?
  9. CGAL Arrangements and Their Applications: A Step-By-Step Guide
  10. android本地视频 投屏,小米投屏神器