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. 流程控制语句continue

    continue语句 用于结束当前循环,进入下一次循环,同样通常与if分支结构一起使用 (这边和前面的break可以结合在一起与C中的一样的理解) 注意这个不是终止整个循环只是终止当前循环进行下一次循 ...

  2. Python 元类详解

    一.Type介绍 在Python中一切皆对象,类它也是对象,而元类其实就是用来创建类的对象(由于一切皆对象,所以元类其实也是一个对象). 先来看这几个例子: 例1: In [1]: type(12) ...

  3. Linux上安装java

    1,输入命令,查看是否已经安装了Openjdk:rpm -qa | grep java 如果有已经安装的java版本或者版本低于1.7,卸载该jdk:rpm -e 软件包名字 如果不能卸载,可以加上 ...

  4. 别梦依稀咒逝川,Ruby二十八年前|M1芯片Mac os系统配置Ruby(3.0.0) on Rails(6.1.1)开发环境(2021最新攻略)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_188 在每个开发者心里,都会有一门"最好"的语言,在这个世界的某个深处,在一些矫矫不群的人们心中,这门语言的名 ...

  5. Vue 监视数据总结 && 表单控件使用总结

    1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8" /> 5 & ...

  6. 万答#21,如何查看 MySQL 数据库一段时间内的连接情况

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 查看方式 已知至少有两种方式可以实现 1.开启 general_log 就可以观察到 开启命令 mysql> set ...

  7. Git 06 分支

    参考源 https://www.bilibili.com/video/BV1FE411P7B3?spm_id_from=333.999.0.0 版本 本文章基于 Git 2.35.1.2 概述 分支用 ...

  8. 管理 MongoDB 用户和权限

    创建用户 创建用户的函数是:db.createUser(). 创建用户时,需要为该用户添加权限.可添加的权限以及说明: 权限 作用 read 允许用户读取指定数据库. readWrite 允许用户读写 ...

  9. df空间满,du找不到文件的问题

    最近看了一下问题: df -h Filesystem Size Used Avail Use% Mounted on rootfs 271G 267G 2.2G 100% / 根分区满了,du 找不到 ...

  10. Spring源码环境搭建

    Spring源码在github上,地址是https://github.com/spring-projects/spring-framework/,选择5.3.x版本,直接从github上克隆项目网速很 ...