FBO一个最常见的应用就是:渲染到纹理(render to texture),通过这项技术可以实现发光效果,环境映射,阴影映射等很炫的效果。
OpenGL中的Frame Buffer Object(FBO)扩展,被推荐用于把数据渲染到纹理对像。相对于其它同类技术,如数据拷贝或交换缓冲区等,使用FBO技术会更高效并且更容易实现。
在OpenGL渲染管线中,几何数据和纹理最终都是以2d像素绘制到屏幕上。最后一步的渲染目标在OpenGL渲染管线中被称为帧缓存(frame buffer)。帧缓存是颜色缓存、深度缓存、模板缓存、累积缓存的集合。默认情况下, OpenGL使用的帧缓存是由窗体系统创建和管理的。
在OpenGL扩展中,GL_EXT_framebuffer_object扩展提供了一个创建额外帧缓存对象(FBO)的接口。这个帧缓存的创建和控制完全是由OpenGL完成的,有别于窗体系统创建的默认的帧缓存。与系统默认的帧缓存类似,一个FBO也是颜色缓存、深度缓存、模板缓存的集合(FBO不包括累积缓存),然后OpenGL程序就可以把渲染重定向到FBO中。
这里有一个新的概念需要注意,那就是renderbuffer object。这个对象是通过GL_EXT_framebuffer_object扩展创建。它被用来在渲染过程中为一个2D图像提供渲染目标。
下图展示了FBO和renderbuffer object与texture object之间的关系。从图中我们可以看出:多个renderbuffer object和texture object可以通过挂接点挂接到FBO上。需要主要的是FBO并没有实际存储数据的地方,它只是一个数据的壳,它只有挂接点。
一个FBO对象包含多个颜色挂接点和一个深度挂接点以及一个模板挂接点。不同的显卡支持的颜色挂接点的数目是不同的,可以通过查询GL_MAX_COLOR_ATTACHMENTS_EXT获取支持的最大的挂接点的数目。支持多个颜色挂接点的原因是FBO可以在同一时间内将颜色缓存渲染到多个目标中去,这种能力被称为MRT(multiple render targets)。通过GL_ARB_draw_buffers扩展可以实现该功能。
帧缓冲提供了一种有效的切换机制,使得挂接和卸载一个可挂接的图像非常之迅速。FBO使用glFramebufferTexture2DEXT()来进行texturebuffer对象的切换,使用glFramebufferRenderbufferEXT()来进行renderbuffer对象的切换。
让我们看一下其使用过程:
1.FBO对象的创建与销毁
void glGenFramebuffersEXT(GLsizei n, GLuint* ids);
void glDeleteFramebuffersEXT(GLsizei n, const GLuint* ids);
第一个参数是要创建的FBO的个数,第二个保存创建的FBO的ID。
2.一旦FBO对象创建完毕,即要进行绑定
void glBindFramebufferEXT(GLenum target, GLuint id);
第一个参数必须是GL_FRAMEBUFFER_EXT
第二个参数是第一步创建FBO对象中获取的id号,该id号是一个非0值,因为系统默认的FBO的id号是0,所以如果你想取消FBO的绑定,将id等于0作为id参数传递给该函数即可。
3.创建Renderbuffer Object
void glGenRenderbuffersEXT(GLsizei n, GLuint* ids);
void glDeleteRenderbuffersEXT(GLsizei n, const GLuint* ids);
4.同样的,Renderbuffer Object创建好之后,要记得绑定。
void glBindRenderbufferEXT(GLenum target, GLuint id)
5.确定Renderbuffer Object的数据格式和尺寸。
这一步很重要,因为我们只是创建了Renderbuffer Object,还没有为它提供一个存储数据的地方,也没有指定其存储数据的地方存储什么格式的数据,所以接下来就要做这个事情了。
void glRenderbufferStorageEXT(GLenum target, GLenum internalFormat, GLsizei width, GLsizei height);
第一个参数必须是GL_RENDERBUFFER_EXT
第二个参数可以是可渲染的颜色格式,如(GL_RGB, GL_RGBA, etc.),可渲染的深度格式(GL_DEPTH_COMPONENT)或者是可渲染的模板格式(GL_STENCIL_INDEX)。
最后两个参数就是以像素为单位指定数据所要占用内存的大小。
这里需要注意两点:
1。 指定的内存区域的宽和高应该都小于GL_MAX_RENDERBUFFER_SIZE_EXT,否则将产生GL_INVALID_VALUE错误。
2。 所有的texture object也好,renderbuffer object也好,其宽和高都是严格一致的。
6.指定挂接的对象
glFramebufferTexture2DEXT函数挂接一个texture图像到FBO
glFramebufferRenderbufferEXT函数挂接一个Renderbuffer图像到FBO
7.检测FBO状态
该挂接的对象都挂接好了,在使用FBO之前,必须要检查FBO的状态是否处于完成状态。如果FBO处于未完成状态,那么绘制操作就会失败。如何获取FBO的完成状态呢?使用
glCheckFramebufferStatusEXT函数。
至此,关于FBO的概念及其使用就算告一段落了。下面给出一个完整的例子,该例子是从国外一个网站上找到的,代码写得简单而漂亮,对FBO的演示也很完善。
帧缓冲是显卡内存中的一块,保存在该内存区块中的图像数据会实时地在显示器上显示出来,帧缓存中的数据最大最小值会被限定在一个范围内,也就是 [0/255; 255/255]
有一个叫 EXT_framebuffer_object 的OpenGL的扩展, 允许我们把一个离屏缓冲区作为我们渲染运算的目标。我们通常把这一技术叫FBO,也就是 Frame Buffer Object的缩写。
要使用一个离屏缓冲区作我们的渲染运算区,只要很少的几行代码便可以实现了。当使用数字0来绑定一个FBO的时候,它会还原window系统的特殊帧缓冲区。因此可以用FBO实现在GPU上进行运算
2. FBO用法
分三步: 创建,使用,停止
创建:glGenFrameBuffersEXT, glBindFrameBuffersEXT, 绑定纹理
使用:glFrameBufferTexutre2DEXT(), glFrameBuffer2DEXT(...,0)
停止:glDeleteFrameBufferEXT
3. 示例(来自http://blog.chinaunix.net/u/15845/showart_315485.html)
/**
使用GPU的计算
1. 使用glTexSubImage2D将数据放到GPU的纹理内存里
2. 使用绘制将运算结果放到FBO里
3. 使用glReadPixels将结果读出到CPU里
*/
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <GL/glut.h>
int main(int argc, char **argv) {
// 这里声明纹理的大小为:teSize;而数组的大小就必须是texSize*texSize*4
int texSize = 2;
int i;
// 生成测试数组的数据
float* data = (float*)malloc(4*texSize*texSize*sizeof(float));
float* result = (float*)malloc(4*texSize*texSize*sizeof(float));
for (i=0; i<texSize*texSize*4; i++)
data[i] = (i+1.0)*0.01F;
// 初始化OpenGL的环境
glutInit (&argc, argv);
glutCreateWindow("TEST1");
glewInit();//使用了OpenGL扩展,所以必须glewInit
// 视口的比例是 1:1 pixel=texel=data 使得三者一一对应
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// glFrustum(-1,1,-1,1,1,-1);//测试变换
gluOrtho2D(0.0,texSize,0.0,texSize);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0,0,texSize,texSize);
// 生成并绑定FBO,也就是生成一个离屏渲染对像
GLuint fb;
glGenFramebuffersEXT(1,&fb);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);
// 生成两个纹理,一个(tex)是<b>输入</b>的纹理,一个(fboTex)是<b>运算结果</b>的纹理
GLuint tex,fboTex;
glGenTextures (1, &tex);
glGenTextures (1, &fboTex);
//设置运算结果fboTex
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,fboTex);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,//申请内存
texSize,texSize,0,GL_RGBA,GL_FLOAT,0);
//设置输入tex
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,GL_DECAL);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,//申请内存
texSize,texSize,0,GL_RGBA,GL_FLOAT,0);
//绑定FBO与运算结果,开始计算
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_RECTANGLE_ARB,fboTex,0);
//Step1. 将输入存入显卡
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,0,0,0,texSize,texSize,
GL_RGBA,GL_FLOAT,data);
//--------------------begin-------------------------
//以下代码是渲染一个大小为texSize * texSize矩形,
//其作用就是把纹理中的数据,经过处理后,保存到帧缓冲中去,
//由于用到了离屏渲染,这里的帧缓冲区指的就是FBO纹理。
//在这里,只是简单地把数据从纹理直接传送到帧缓冲中,
//没有对这些流过GPU的数据作任何处理,但是如果我们会用CG、
//GLSL等高级着色语言,对显卡进行编程,便可以在GPU中
//截获这些数据,并对它们进行任何我们所想要的复杂运算。
//这就是GPGPU技术的精髓所在。
//Step2 GPU->FBO, 运算应该发生在这里, 可以Enable Cg的Profile,实现运算
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(texSize, 0.0);
glVertex2f(texSize, 0.0);
glTexCoord2f(texSize, texSize);
glVertex2f(texSize, texSize);
glTexCoord2f(0.0, texSize);
glVertex2f(0.0, texSize);
glEnd();
//--------------------end------------------------
//Step3 从显卡的FBO将数据读出到CPU
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);
//打印输出结果
printf("Data before roundtrip:\n");
for (i=0; i<texSize*texSize*4; i++)
printf("%f\n",data[i]);
printf("Data after roundtrip:\n");
for (i=0; i<texSize*texSize*4; i++)
printf("%f\n",result[i]);
// 释放本地内存
free(data);
free(result);
// 释放显卡内存
glDeleteFramebuffersEXT (1,&fb);
glDeleteTextures (1,&tex);
glDeleteTextures(1,&fboTex);
return 0;
}