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

Opengl ES之FBO的更多相关文章

  1. OpenGL ES 3.0之Fragment buffer objects(FBO)详解(二)

    我们可以使用帧缓冲对象来实现离屏渲染.帧缓冲对象支持下列操作 1.只使用OpenGL ES 函数创建帧缓冲区对象. 2.使用EGL context创建多个FBO. 3.创建离屏颜色.深度.模板渲染缓冲 ...

  2. OpenGL ES 3.0之Fragment buffer objects(FBO)详解(一)

    片段操作图 这篇文章将介绍从写入帧缓冲和读取帧缓冲的方式. Buffers(缓冲) OpenGL ES支持三种缓冲: OpenGL ES •• Color buffer颜色缓冲 •• Depth bu ...

  3. OpenGL ES 3.0之Fragment buffer objects(FBO)详解 (转)

    http://www.cnblogs.com/salam/p/4957250.html 片段操作图 这篇文章将介绍从写入帧缓冲和读取帧缓冲的方式. Buffers(缓冲) OpenGL ES支持三种缓 ...

  4. OpenGL ES无法获取贴图数据原因

    最近在做一个项目,要从贴图中获取图像数据,查了很多资料,也琢磨很久,获取到的数据都是0.终于在一次偶然的机会,发现了端倪,成功了. 不得不说这"一分灵感"真的很重要 以下是在获取贴 ...

  5. 如何使用Android中的OpenGL ES媒体效果

    引自:http://www.2cto.com/kf/201506/404366.html Android的媒体效果框架允许开发者可以很容易的应用多种令人印象深刻的视觉效果到照片或视频之上.作为这个媒体 ...

  6. OpenGL ES中MRT应用

    Demo涵盖了OpenGL ES 3.0 的一系列新特性: 1.VAO和VBO 2.帧缓冲对象 3.MRT 效果: 代码: //yf's version #define STB_IMAGE_IMPLE ...

  7. OpenGL ES 3.0 帧缓冲区对象基础知识

    最近在帧缓冲区对象这里卡了一下,不过前面已经了解了相关的OpenGL ES的知识,现在再去了解就感觉轻松多了.现在就进行总结. 基础知识 我们知道,在应用程序调用任何的OpenGL ES命令之前,需要 ...

  8. Faster Alternatives to glReadPixels and glTexImage2D in OpenGL ES

    In the development of Shou, I’ve been using GLSL with NEON to manipulate image rotation, scaling and ...

  9. 深度剖析OpenGL ES中的多线程和多窗口渲染技术

    由 创新网小编 于 星期五, 2014-04-11 14:56 发表 移动设备中的CPU和GPU已经变得很强大,到处都是配备一个或多个高分辨率屏幕的设备,需要使用带有图形驱动器的复杂交互也日益增加.在 ...

随机推荐

  1. Kubernetes组件介绍

    一.api-server   基本概念 该端口默认值为6443,可通过启动参数"--secure-port"的值来修改默认值. 默认IP地址为非本地(Non-Localhost)网 ...

  2. 算法竞赛进阶指南 0x43 线段树

    目录 线段树简介 线段树的简单代码实现 建树代码 修改操作 查询操作 线段树的查询操作的时间复杂度分析: AcWing245. 你能回答这些问题吗 思路 代码[时间复杂度:\(O( \space(N+ ...

  3. 企业运维实践-还不会部署高可用的kubernetes集群?使用kubeadm方式安装高可用k8s集群v1.23.7

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 文章目录: 0x00 前言简述 ...

  4. CentOS删除桌面环境

    公司有几台虚拟机安装的是CentOS7的桌面环境,平时也是用终端访问,于是在服务器卡住需要重启时,顺便就把桌面环境给卸载了:测试了好多方法均不成功,最终找到了可行的方式,以此记录: [root@yun ...

  5. cordova 发送ajax请求的相关配置

    <access origin="*" /> <preference name="scheme" value="http" ...

  6. Python3.7+Tornado5.1.1+Celery3.1+Rabbitmq3.7.16实现异步队列任务

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_99 在之前的一篇文章中提到了用Django+Celery+Redis实现了异步任务队列,只不过消息中间件使用了redis,redi ...

  7. ubuntu 下获取Let's Encrypt免费ssl证书

    # ubuntu 下获取Let's Encrypt免费ssl证书 # 一.安装Nginx https://www.cnblogs.com/watermeloncode/p/15476317.html ...

  8. 日志(logging模块)

    1. 为什么要使用日志(作用) 在学习过程中,写一个小程序或小demo时,遇到程序出错时,我们一般会将一些信息打印到控制台进行查看,方便排查错误.这种方法在较小的程序中很实用,但是当你的程序变大,或是 ...

  9. Dart 异步编程(二):async/await

    对每一个异步任务返回的 Future 对象都使用链式操作-- then ,超过三个以上时,导致"倒三角"现象,降低代码的可阅读性. getHobbies() { post('htt ...

  10. html js 导出excel表格

    这个使用js 导出excel,可以集成其他语言,可以html,php,asp ,java 等,自己喜欢用那种语言就用哪种,使用非常方便.js是使用tableExport.js ,jquery-3.2. ...