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. Solution -「树上杂题?」专练

    主要是记录思路,不要被刚开始错误方向带偏了 www 「CF1110F」Nearest Leaf 特殊性质:先序遍历即为 \(1 \to n\),可得出:叶子节点编号递增或可在不改变树形态的基础上调整为 ...

  2. 牛牛与后缀表达式_via牛客网

    题目 链接:https://ac.nowcoder.com/acm/contest/28537/B 来源:牛客网 时间限制:C/C++ 3秒,其他语言6秒 空间限制:C/C++ 262144K,其他语 ...

  3. Menci的序列

    题目大意 一个长度为n的字符串s,只包含+和×. 选出一个子序列,然后你有一个ret,初始为0,按顺序扫你选出的这个子序列. 如果碰到的是+,ret+1,否则ret*2. 最大化ret%2^k. 首先 ...

  4. Kafka与Spark案例实践

    1.概述 Kafka系统的灵活多变,让它拥有丰富的拓展性,可以与第三方套件很方便的对接.例如,实时计算引擎Spark.接下来通过一个完整案例,运用Kafka和Spark来合理完成. 2.内容 2.1 ...

  5. 羽夏看Linux内核——环境搭建

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作.如想转载,请把我的转载信息附在文章后面,并 ...

  6. MySQL 的prepare使用中的bug解析过程

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 一.问题发现 二.问题调查过程 三.问题解决方案 四.问题总结 一.问题发现 在一次开发中使用 MySQL PREP ...

  7. Apache DolphinScheduler 3.0.0 正式版发布!

    ​  点亮 ️ Star · 照亮开源之路 GitHub:https://github.com/apache/dolphinscheduler   ​ 版本发布 2022/8/10 2022 年 8 ...

  8. 从 Delta 2.0 开始聊聊我们需要怎样的数据湖

    盘点行业内近期发生的大事,Delta 2.0 的开源是最让人津津乐道的,尤其在 Databricks 官宣 delta2.0 时抛出了下面这张性能对比,颇有些引战的味道. 虽然 Databricks ...

  9. 在Kubernetes上部署k6的详细步骤

    k6介绍 k6是一款使用go语言编写的开源测试工具,支持用户编写测试脚本,解决了JMeter不易代码化的缺点.它的主要特点有 提供了友好的 CLI 工具 使用 JavaScript 代码编写测试用例 ...

  10. java-前端之js

    js: js的三种形式: <!-- 事件:就是用户的操作或者动作,就是js被调用的时机:如:单机事件,双击事件 --> <!-- 1.事件定义式:在定义事件时直接写js --> ...