http://blog.csdn.net/shuaihj/article/details/7244320
介绍
Frame Buffer Object(FBO)扩展,被推荐用于把数据渲染到纹理对像。相对于其它同类技术,如数据拷贝或交换缓冲区等,使用FBO技术会更高效并且更容易实现。 建立和OpenGL中的其它对像一样,如纹理对像(texture object), 像素缓冲对像(pixel buffer objects) , 顶点缓冲对像(vertex buffer object)等,在使用一个FBO对像之前,你必须先要生成该对像,并取得一个有效的对像标识。 GLuint fbo; glGenFramebuffersEXT(1, &fbo); 要对一个FBO进行任何的操作,你必须先要对它进行绑定。这一步骤与我们平时使用VBO或者纹理的过程很像。绑定对像后,我们便可以对FBO进行各种操作了,以下代码演示如何进行绑定。 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); 第一个参数是“目标(target)”,指的是你要把FBO与哪个帧缓冲区进行绑定,目前来说,我个参数就只有一些预定义的选择(GL_FRAMEBUFFER_EXT),但将来扩展的发展,可能会来现其它的选择,让你把FBO与其它的目标进行绑定。整型变量fbo,是用来保存FBO对像标识的,这个标识我们已在前面生成了。要实现任何与FBO有关的操作,我们必须有一个FBO被绑定,否则调用就会出错 |
加入一个深度缓存(Depth Buffer)一个FBO它本身其实没有多大用处,要想让它能被更有效的利用,我们需要把它与一些可被渲染的缓冲区绑定在一起,这样的缓冲区可以是纹理,也可以是下面我们将要介绍的渲染缓冲区(renderbuffers)。 一个渲染缓冲区,其实就是一个用来支持离屏渲染的缓冲区。通常是帧缓冲区的一部份,一般不具有纹理格式。常见的模版缓冲和深度缓冲就是这样一类对像。 在这里,我们要为我们的FBO指定一个渲染缓冲区。这样,当我们渲染的时候,我们便把这个渲染缓冲区作为FBO的一个深度缓存来使用。 和FBO的生成一样,我们首先也要为渲染缓冲区指定一个有效的标识。 GLuint depthbuffer; glGenRenderbuffersEXT(1, &depthbuffer); 成功完成上面一步之后,我们就要对该缓冲区进行绑定,让它成为当前渲染缓冲,下面是实现代码。 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer); 和FBO的绑定函数一样,第一个参数是“目标(target)”,指的是你要与哪个目标进行绑定,目前来说,只能是一些预定义好的目标。变量dephtbuffer用来保存对像标识。 这里有一个关键的地方,也就是我们生成的渲染缓冲对像,它本身并不会自动分配内存空间。因此我们要调用OpenGL的函数来给它分配指定大小的内存空间,在这里,我们分配一个固定大小的深度缓显空间。 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height); 上面这一函数成功运行之后,OpenGL将会为我们分配好一个大小为width x height的深度缓冲区。注意的是,这里用了GL_DEPTH_COMPONENT,就是指我们的空间是用来保存深度值的,但除了这个之外,渲染缓冲区 还可以用来保存普通的RGB/RGBA格式的数据或者是模板缓冲的信息。 准被好了深度缓存的显存空间后,接下来要做的工作就是把它与前面我们准备好了的FBO对像绑定在一起。 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer); 这个函数看起来有点复杂,但其实它很好理解的。它要做的全部工作就是把把前面我们生成的深度缓存对像与当前的FBO对像进行绑定,当然我们要注意一个FBO有多个不同绑定点,这里是要绑定在FBO的深度缓冲绑定点上。 |
加入用于渲染的纹理到现在为止,我们还没有办法往FBO中写入颜色信息。这也是我们接下来正要讨论的,我们有以下两种方法来实现它:
前者在某些地方会用到,后面的章节我们会深入讨论。现在我们先来说说第二种方法。 在你想要把纹理与一个FBO进行绑定之前,我们得先要生成这个纹理。这个生成纹理的过程种我们平时见到的纹理生成没什么区别。 GLuint img; glGenTextures(1, &img); glBindTexture(GL_TEXTURE_2D, img); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 这个实例中,我们生成一个普通的RGBA图像,大小是width x height,与前面我们生成的渲染缓冲区的大小是一样的,这一点很重要,也就是FBO中所有的绑定对像,都必须要有相同的宽度和高度。还有要注意的就是:这里我们没有上传任何的数据,只是让OpenGL保留分配好的空间,稍后我们将会用到。 生成好纹理之后,接下来的工作就是把这个纹理与FBO绑定在一起,以便我们可以把数据渲染到纹理空间中去。 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0); 这里再次看到这个看起来非常可怕的函数,当然它也并没有我们想像中那么难理解。参数GL_COLOR_ATTACHMENT0_EXT是告诉OpenGL把纹理对像绑定到FBO的0号绑定点(一个FBO在同一个时间内可以绑定多个颜色缓冲区,每个对应FBO的一个绑定点),参数GL_TEXTURE_2D是指定纹理的格式,img保存的是纹理标识,指向一个之前就准备好了的纹理对像。纹理可以是多重映射的图像,最后一个参数指定级级为0,指的是使用原图像。 最后还有一步要做的工作,就是检查一下FBO的准备工作是否全部完成,是否以经能被正确使用了。 这个测试工作由下面一个函数来完成,它会返回一个当前绑定的FBO是否正确的状态信息。 GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); 如果所有工作都已经做好,那么返回的状态值是GL_FRAMEBUFFER_COMPLETE_EXT,也就是说你的FBO已经准备好,并可以用来作为渲染对像了。否则就会返回其它一个错误码,通过查找定义文档,可以找到相关的错误信息,从而了角错误大概是在哪一步骤中产生的。 |
渲染到纹理所有困难的工作就是前面建立FBO环境的部份,剩下来的工作就相当简单了,相关的事情就只是调用一下以下这个函数:glBindFramebufferEXT(). 当我们要把数据渲染并输出到FBO的时候,我们只需要用这个函数来把一个FBO对像进行绑定。当我们要停止输出到FBO,我们只要把参数设为0,再重新调用一次该函数就可以了。当然,停止向FBO输出,这也是很重要的,当我们完成了FBO的工作,就得停止FBO,让图像可以在屏幕上正确输出。 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0,0,width, height);
// Render as normal here
// output goes to the FBO and it's attached buffers
glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
上面另外三行代码glPushAttrib/glPopAttrib 及 glViewport,是用来确保在你跳出FBO渲染的时候可以返回原正常的渲染路径。glViewport在这里的调用是十分必要的,我们不要常试把数据渲染到一个大于或小于FBO大小的区域。 函数glPushAtrrib 和 glPopAttrib 是用来快速保存视口信息。这一步也是必要的,因为FBO会共享主上下文的所有信息。任何的变动,都会同时影响到FBO及主上下文,当然也就会直接影响到你的正常屏幕渲染。 这里一个重要信息,你可能也注意到了,我们只是在绘制的时候绑定或解除FBO,但是我们没有重新绑定纹理或渲染缓冲区,这里因为在FBO中会一直保存了这种绑定关系,除非你要把它们分开或FBO对像被销毁了。 |
使用已渲染出来的纹理来到这里,我们已经把屏幕的数据渲染到了一个图像纹理上。现在我们来看一看如何来使用这张已经渲染好了的图像纹理。这个操作的本身其实是很简单的,我们只要把这张图像纹理当作普通纹理一样,绑定为当前纹理就可以了。 glBindTexture(GL_TEXTURE_2D, img); 以上这一函数调用完成之后,这张图像纹理就成了一个在绘图的时候用于被读取的普通纹理。 根据你在初始化时所指定的不同纹理滤波方式,你也许会希望为该纹理生成多重映像(mipmap)信息。如果要建立多重映像信息,多数的人都是在上传纹理数据的时候,通过调用函数gluBuild2DMipmaps()来实现,当然有些朋友可能会知道如何使用自动生成多重映像的扩展,但是在FBO扩展中,我们增加了第三种生成映像的方法,也就是使用GenerateMipmapEXT()函数。 这个函数的作用就是让OpenGL帮你自动创建多重映像信息。中间实现的过程,根据不同的显卡会有所不同,我们只关心它们最终的结果是一样就行了。值得注意的是:对于这种通过FBO渲染出来的纹理,要实现多重映像的话,只有这一种方法是正确的,这里你不可以使用自动生成函数来生成多重映像,这其中的原因有很多,如果你想深入了解的话,可以查看一下技术文档。 使用这一函数使方便,你所要做的就是先把该纹理对像绑定为当前纹理,然后调用一次该函数就可以了。 glGenerateMipmapEXT(GL_TEXTURE_2D); OpenGL将会自动为我们生成所需要的全部信息,到现在我们的纹理便可以正常使用了。 一个重点要注意的地方:如果你打算使用多重映像(如 GL_LINEAR_MIPMAP_LINEAR),该函数glGenerateMipmapEXT()必须要在执行渲染到纹理之前调用。 在创建纹理的时候,我们可以按以下代码来做。 glGenTextures(1, &img); glBindTexture(GL_TEXTURE_2D, img); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmapEXT(GL_TEXTURE_2D); 到现在,这张纹理和普通纹理没什么区别,我们就按处理普通纹理的方法来使用就可以了。 |
清理最后,当你完成了所有的FBO操作之后,请别忘了要清理或删除掉那些不要了的FBO对像,和清理纹理对像相似,这一步只要以下一个函数就可以完成: glDeleteFramebuffersEXT(1, &fbo); 同样的,你如果分配了渲染缓冲对像,也别忘了要把它清理掉。本实例中我们分配的是深度缓存渲染对像,我们用以下函数来清除它: glDeleteRenderbuffersEXT(1, &depthbuffer); 到这里,所有的FBO对像及渲染缓冲都被释放掉了,我们的清理工作也就完成了。 最后的思考这一篇文章只是对FBO扩展的一个初步介绍,希望对你有所帮助,更多详细的知识,可以查看一下FBO spec ,或者看一下《More OpenGL Game Programming》这本书中关于扩展部分的章节。 问题的返溃及相关技术的讨论,可以登陆物理开发网的GPGPU/CUDA论坛进行交流。 在文档结束之前,我要说一下在使用FBO来写程序的过程中,一些值得我们去注意的地方:
本文示例程序中要注意的地方对应这篇文章所讨论的内容,我们写了一个相应的程序,其功能就是给FBO加入一个深度缓冲对像及一个纹理对像。我们发现,在ATI的显卡中有一个bug,也就是当我们给同时FBO加入一个深度缓冲及一个纹理的时候,就会出现严重的冲突。从这里也告诉我们,当我们在写好一个FBO相关的程序的时候,一定要在不同的硬件及不同的驱动下进行广泛的测试,直到没有任何渲染问题为止。 I'd also like to put out a big thanks to Rick Appleton for helping me test out and debug the code on NVIDA hardware, couldn't have done it without you mate :) 本程序需要有GLUT函数库的支持才能正确运行,我使用的是FreeGLUT. FBO_Example.zip程序下载 本译文可以自由转载,要求保留原作者信息并注明文章出自物理开发网:http://www.physdev.com/ 参考GPU深度发掘 |
概要
在OpenGL的渲染管线中,几何数据和纹理通过一系列变换和测试,最终被渲染成屏幕上的二维像素。那些用于存储颜色值和测试结果的二维数组的几何被称为帧缓冲区(frame buffer)。这些二维数组按用途划分,可分为颜色缓冲区(color buffer),深度缓冲区(depth buffer),模版缓冲区(stencil buffer)和累加缓冲区(accumulation buffer)。当我们创建了一个可供OpenGL绘制用的窗体后,窗体系统会为我们生成一个默认的帧缓冲区,这个帧缓冲区完全是由窗体系统来管理的,且仅用于将渲染后的图像输出到窗口的显示区域。
然而,我们可以使用OpenGL提供的GL_EXT_framebuffer_object扩展功能来创建额外的帧缓冲区。GL_EXT_framebuffer_object扩展功能中,提出了帧缓冲区对象(framebuffer object,缩写为FBO,下文中将使用FBO来代表帧缓冲区对象)的概念,用于对帧缓冲区进行建模。这样一来,无论是窗体系统创建的帧缓冲区,还是用于屏外渲染的帧缓冲区,都是FBO的实例。有了FBO,程序员就可以重定向渲染目标到其他的存储空间,比如将渲染目标重定向到纹理空间,实现渲染到纹理功能(Render to Texture)。
上文提到过帧缓冲区中包含的二维数组按用途划分,可分为颜色缓冲区(color buffer),深度缓冲区(depth buffer),模版缓冲区(stencil buffer)和累加缓冲区(accumulation buffer)。FBO中也提供了与颜色缓冲区、深度缓冲区和模版缓冲区相对应的功能(注意,FBO没有提供与累加缓冲区对应的功能)。但是,这并不意味着FBO会直接为这些缓冲区分配空间。FBO只是为这些缓冲区提供一个或多个挂接点。我们需要分别为各个缓冲区创建对象,申请空间,然后挂接到相应的挂接点上。FBO提供的挂接点如下图所示。
可以看出,FBO提供了多个颜色缓冲区挂接点GL_COLOR_ATTACHMENT0_EXT ... GL_COLOR_ATTACHMENTn_EXT、一个深度缓冲区挂接点GL_DEPTH_ATTACHMENT_EXT和一个模板缓冲区挂接点GL_STENCIL_ATTACHMENT_EXT。颜色缓冲区挂接点的个数是因不同厂商和不同型号的显卡而异的。你可以通过GL_MAX_COLOR_ATTACHMENTS_EXT查询当前显卡所支持的颜色缓冲区挂接点的最大个数。FBO提供多个颜色缓冲区挂接点的用意是,允许程序员进行多目标渲染(使用GL_ARB_draw_buffers扩展功能)。
能够与FBO挂接的对象有两种,一种是纹理对象(texture object),另一种是渲染缓冲区对象(renderbuffer object)。纹理对象,就是我们平日为模型设置纹理贴图时使用的对象;而渲染缓冲区对象可以用作不具有纹理格式的缓冲区,如深度缓冲区和模板缓冲区。当然,渲染缓冲区对象也可以用来渲染场景。
前面提到过,窗体系统创建的帧缓冲区也有对应的FBO的实例。但是,这个FBO与其他通过手工创建的FBO相比有许多不同。第一,通过手工创建的FBO不能用于将渲染结果直接显示到窗口输出区,通过手工创建的FBO只能用于屏外渲染(Off-screen Rendering);第二,窗体系统生成的FBO在创建的时候就拥有颜色缓冲区,深度缓冲区,模版缓冲区,且创建后就为这些缓冲区分配了空间。而手工创建的FBO需要手动为其添加各个缓冲区,并为其申请空间。窗体系统创建的FBO中的各个缓冲区对象不能与手动创建的FBO的挂接点挂接,反之亦然。
创建、绑定和删除一个FBO
我们可以使用glGenFramebuffersEXT()来向OpenGL申请一个或者多个闲置的FBO的ID。注意,就算成功地申请到了闲置的ID,OpenGL也不会马上为其创建实例。只用当调用glBindFramebufferEXT ()绑定FBO的时候OpenGL才会真正的创建一个FBO实例(这和其他glBind*函数极为相似)。在FBO被绑定之后,这个FBO就会被OpenGL当作当前的操作对象,后续的操作都被视为对被绑定的FBO进行的操作。窗体系统创建的FBO的ID默认为0。我们可以通过调用glDeleteFramebuffersEXT()函数来释放FBO的实例,如果要删除的FBO实例正在被使用,则OpenGL会自动绑定窗口系统创建的FBO(ID为0)。
渲染缓冲区对象
FBO创建完成后,还不能对其进行什么实质性的操作。因为,FBO的各个挂接点上还没有挂接实际的存储对象。我们需要手动创建这些对象,并将其与既存的FBO对象进行挂接。上文提到过,能够与FBO挂接的对象有两种,一种是纹理对象(texture object),另一种是渲染缓冲区对象(renderbuffer object)。读者可能对纹理对象比较熟悉,因为在为模型进行纹理贴图的时候,经常要使用这种对象。然而,对于渲染缓冲对象,读者可能会不太熟悉,因为这是OpenGL扩展中新引入的功能。下文将着重介绍渲染缓冲区对象的内容。
渲染缓冲区对象主要是为了实现屏外渲染(Off-screen Rendering)而设计的。渲染缓冲区对象主要用做FBO的深度缓冲区和模板缓冲区。可以使用glGenRenderbuffersEXT()函数来申请一个或多个闲置的渲染缓冲区对象ID(非负整数)。ID 0被OpenGL所保留。注意,申请了闲置ID之后,OpenGL并没有创建实际的对象,需要调用glBindRenderbufferEXT()函数来绑定并创建实际的对象。如果绑定ID 0,OpenGL会解除先前设定的渲染缓冲区对象。
上文提到过,渲染缓冲区对象实际上是某种二维数组的抽象。在绑定了一个渲染缓冲区对象之后,需要使用glRenderbufferStorage()函数为其分配二维数组存储空间。注意,同一个FBO中的各个二维数组空间的行数(或列数)应该相同。
同FBO类似,可以使用glDeleteRenderbuffersEXT()函数来删除一个渲染缓冲区对象。
挂接
可以使用glFramebufferRenderbufferEXT()函数将渲染缓冲区对象挂接到FBO上;使用glFramebufferTexture2D()。如果挂接的ID为0,则OpenGL将解除先前的绑定。当被绑定纹理对象或渲染缓冲区对象被删除,则他们会被自动从当前正在使用的FBO上解除挂接。如果纹理对象或渲染缓冲区对象被挂接到多个FBO上,他们被删除的时候,只会从当前被绑定的FBO上解除挂接,而不会从未被绑定的FBO上解除绑定。
FBO的完整性
在向FBO输出渲染结果之前,需要测试FBO的完整性。如果FBO不完整,任何渲染操作都会失败。我们可以使用glCheckFramebufferStatusEXT()函数来测试FBO的完整性(此函数不能在glBegin()和glEnd()函数之间调用)。FBO完整性的判别法则如下:
- 与FBO挂接的二维数组对象的长度和宽度必须不能为0。
- 如果一个二维数组对象被挂接到FBO的颜色缓冲区挂接点时,二维数组必须具有内部颜色格式(GL_RGBA, GL_DEPTH_COMPONENT, GL_LUMINANCE等)。
- 如果一个二维数组对象被挂接到FBO的深度缓冲区挂接点时,二维数组必须具有内部深度格式(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_EXT等)。
- 如果一个二维数组对象被挂接到FBO的模板缓冲区挂接点时,二维数组必须具有内部模板格式(GL_STENCIL_INDEX, GL_STENCIL_INDEX8_EXT等)。
- FBO至少挂接有一个二维数组缓冲区对象。
- 同一个FBO上挂接的二维数组对象必须拥有相同的长度和宽度。
- 所有的颜色缓冲区挂接点上挂接的二维数组对象必须具有相同的内部格式。
FBO的使用
当所有上述的准备工作都完成之后,就可以调用glBindFramebufferEXT()来绑定一个FBO。随后,就可一像操作窗体系统提供的帧缓冲区一样操作当前绑定的FBO了。日常的3D渲染操作这里不再赘述。这里主要强调像素操作的使用。OpenGL提供了glBlitFramebufferEXT()函数进行像素操作。
示例代码
示例代码使用FBO实现渲染到纹理(Render to texture)功能。编译代码需要链接GLEW和SDL两个库。
概要
在OpenGL的渲染管线中,几何数据和纹理通过一系列变换和测试,最终被渲染成屏幕上的二维像素。那些用于存储颜色值和测试结果的二维数组的几何被称为帧缓冲区(frame buffer)。这些二维数组按用途划分,可分为颜色缓冲区(color buffer),深度缓冲区(depth buffer),模版缓冲区(stencil buffer)和累加缓冲区(accumulation buffer)。当我们创建了一个可供OpenGL绘制用的窗体后,窗体系统会为我们生成一个默认的帧缓冲区,这个帧缓冲区完全是由窗体系统来管理的,且仅用于将渲染后的图像输出到窗口的显示区域。
然而,我们可以使用OpenGL提供的GL_EXT_framebuffer_object扩展功能来创建额外的帧缓冲区。GL_EXT_framebuffer_object扩展功能中,提出了帧缓冲区对象(framebuffer object,缩写为FBO,下文中将使用FBO来代表帧缓冲区对象)的概念,用于对帧缓冲区进行建模。这样一来,无论是窗体系统创建的帧缓冲区,还是用于屏外渲染的帧缓冲区,都是FBO的实例。有了FBO,程序员就可以重定向渲染目标到其他的存储空间,比如将渲染目标重定向到纹理空间,实现渲染到纹理功能(Render to Texture)。
上文提到过帧缓冲区中包含的二维数组按用途划分,可分为颜色缓冲区(color buffer),深度缓冲区(depth buffer),模版缓冲区(stencil buffer)和累加缓冲区(accumulation buffer)。FBO中也提供了与颜色缓冲区、深度缓冲区和模版缓冲区相对应的功能(注意,FBO没有提供与累加缓冲区对应的功能)。但是,这并不意味着FBO会直接为这些缓冲区分配空间。FBO只是为这些缓冲区提供一个或多个挂接点。我们需要分别为各个缓冲区创建对象,申请空间,然后挂接到相应的挂接点上。FBO提供的挂接点如下图所示。
可以看出,FBO提供了多个颜色缓冲区挂接点GL_COLOR_ATTACHMENT0_EXT ... GL_COLOR_ATTACHMENTn_EXT、一个深度缓冲区挂接点GL_DEPTH_ATTACHMENT_EXT和一个模板缓冲区挂接点GL_STENCIL_ATTACHMENT_EXT。颜色缓冲区挂接点的个数是因不同厂商和不同型号的显卡而异的。你可以通过GL_MAX_COLOR_ATTACHMENTS_EXT查询当前显卡所支持的颜色缓冲区挂接点的最大个数。FBO提供多个颜色缓冲区挂接点的用意是,允许程序员进行多目标渲染(使用GL_ARB_draw_buffers扩展功能)。
能够与FBO挂接的对象有两种,一种是纹理对象(texture object),另一种是渲染缓冲区对象(renderbuffer object)。纹理对象,就是我们平日为模型设置纹理贴图时使用的对象;而渲染缓冲区对象可以用作不具有纹理格式的缓冲区,如深度缓冲区和模板缓冲区。当然,渲染缓冲区对象也可以用来渲染场景。
前面提到过,窗体系统创建的帧缓冲区也有对应的FBO的实例。但是,这个FBO与其他通过手工创建的FBO相比有许多不同。第一,通过手工创建的FBO不能用于将渲染结果直接显示到窗口输出区,通过手工创建的FBO只能用于屏外渲染(Off-screen Rendering);第二,窗体系统生成的FBO在创建的时候就拥有颜色缓冲区,深度缓冲区,模版缓冲区,且创建后就为这些缓冲区分配了空间。而手工创建的FBO需要手动为其添加各个缓冲区,并为其申请空间。窗体系统创建的FBO中的各个缓冲区对象不能与手动创建的FBO的挂接点挂接,反之亦然。
创建、绑定和删除一个FBO
我们可以使用glGenFramebuffersEXT()来向OpenGL申请一个或者多个闲置的FBO的ID。注意,就算成功地申请到了闲置的ID,OpenGL也不会马上为其创建实例。只用当调用glBindFramebufferEXT ()绑定FBO的时候OpenGL才会真正的创建一个FBO实例(这和其他glBind*函数极为相似)。在FBO被绑定之后,这个FBO就会被OpenGL当作当前的操作对象,后续的操作都被视为对被绑定的FBO进行的操作。窗体系统创建的FBO的ID默认为0。我们可以通过调用glDeleteFramebuffersEXT()函数来释放FBO的实例,如果要删除的FBO实例正在被使用,则OpenGL会自动绑定窗口系统创建的FBO(ID为0)。
渲染缓冲区对象
FBO创建完成后,还不能对其进行什么实质性的操作。因为,FBO的各个挂接点上还没有挂接实际的存储对象。我们需要手动创建这些对象,并将其与既存的FBO对象进行挂接。上文提到过,能够与FBO挂接的对象有两种,一种是纹理对象(texture object),另一种是渲染缓冲区对象(renderbuffer object)。读者可能对纹理对象比较熟悉,因为在为模型进行纹理贴图的时候,经常要使用这种对象。然而,对于渲染缓冲对象,读者可能会不太熟悉,因为这是OpenGL扩展中新引入的功能。下文将着重介绍渲染缓冲区对象的内容。
渲染缓冲区对象主要是为了实现屏外渲染(Off-screen Rendering)而设计的。渲染缓冲区对象主要用做FBO的深度缓冲区和模板缓冲区。可以使用glGenRenderbuffersEXT()函数来申请一个或多个闲置的渲染缓冲区对象ID(非负整数)。ID 0被OpenGL所保留。注意,申请了闲置ID之后,OpenGL并没有创建实际的对象,需要调用glBindRenderbufferEXT()函数来绑定并创建实际的对象。如果绑定ID 0,OpenGL会解除先前设定的渲染缓冲区对象。
上文提到过,渲染缓冲区对象实际上是某种二维数组的抽象。在绑定了一个渲染缓冲区对象之后,需要使用glRenderbufferStorage()函数为其分配二维数组存储空间。注意,同一个FBO中的各个二维数组空间的行数(或列数)应该相同。
同FBO类似,可以使用glDeleteRenderbuffersEXT()函数来删除一个渲染缓冲区对象。
挂接
可以使用glFramebufferRenderbufferEXT()函数将渲染缓冲区对象挂接到FBO上;使用glFramebufferTexture2D()。如果挂接的ID为0,则OpenGL将解除先前的绑定。当被绑定纹理对象或渲染缓冲区对象被删除,则他们会被自动从当前正在使用的FBO上解除挂接。如果纹理对象或渲染缓冲区对象被挂接到多个FBO上,他们被删除的时候,只会从当前被绑定的FBO上解除挂接,而不会从未被绑定的FBO上解除绑定。
FBO的完整性
在向FBO输出渲染结果之前,需要测试FBO的完整性。如果FBO不完整,任何渲染操作都会失败。我们可以使用glCheckFramebufferStatusEXT()函数来测试FBO的完整性(此函数不能在glBegin()和glEnd()函数之间调用)。FBO完整性的判别法则如下:
- 与FBO挂接的二维数组对象的长度和宽度必须不能为0。
- 如果一个二维数组对象被挂接到FBO的颜色缓冲区挂接点时,二维数组必须具有内部颜色格式(GL_RGBA, GL_DEPTH_COMPONENT, GL_LUMINANCE等)。
- 如果一个二维数组对象被挂接到FBO的深度缓冲区挂接点时,二维数组必须具有内部深度格式(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_EXT等)。
- 如果一个二维数组对象被挂接到FBO的模板缓冲区挂接点时,二维数组必须具有内部模板格式(GL_STENCIL_INDEX, GL_STENCIL_INDEX8_EXT等)。
- FBO至少挂接有一个二维数组缓冲区对象。
- 同一个FBO上挂接的二维数组对象必须拥有相同的长度和宽度。
- 所有的颜色缓冲区挂接点上挂接的二维数组对象必须具有相同的内部格式。
FBO的使用
当所有上述的准备工作都完成之后,就可以调用glBindFramebufferEXT()来绑定一个FBO。随后,就可一像操作窗体系统提供的帧缓冲区一样操作当前绑定的FBO了。日常的3D渲染操作这里不再赘述。这里主要强调像素操作的使用。OpenGL提供了glBlitFramebufferEXT()函数进行像素操作。
示例代码
示例代码使用FBO实现渲染到纹理(Render to texture)功能。编译代码需要链接GLEW和SDL两个库。
函数原型:
void glGenTextures(GLsizei n, GLuint *textures)
参数说明:
- n:用来生成纹理的数量
- textures:存储纹理索引的
glGenTextures函数根据纹理参数返回n个纹理索引。纹理名称集合不必是一个连续的整数集合。 (glGenTextures就是用来产生你要操作的纹理对象的索引的,比如你告诉OpenGL,我需要5个纹理对象,它会从没有用到的整数里返回5个给你)
glBindTexture实际上是改变了OpenGL的这个状态,它告诉OpenGL下面对纹理的任何操作都是对它所绑定的纹理对象的,比如glBindTexture(GL_TEXTURE_2D,1)告诉OpenGL下面代码中对2D纹理的任何设置都是针对索引为1的纹理的。
产生纹理函数假定目标纹理的面积是由glBindTexture函数限制的。先前调用glGenTextures产生的纹理索引集不会由后面调用的glGenTextures得到,除非他们首先被glDeleteTextures删除。你不可以在显示列表中包含glGenTextures。
概要
在OpenGL的渲染管线中,几何数据和纹理通过一系列变换和测试,最终被渲染成屏幕上的二维像素。那些用于存储颜色值和测试结果的二维数组的几何被称为帧缓冲区(frame buffer)。这些二维数组按用途划分,可分为颜色缓冲区(color buffer),深度缓冲区(depth buffer),模版缓冲区(stencil buffer)和累加缓冲区(accumulation buffer)。当我们创建了一个可供OpenGL绘制用的窗体后,窗体系统会为我们生成一个默认的帧缓冲区,这个帧缓冲区完全是由窗体系统来管理的,且仅用于将渲染后的图像输出到窗口的显示区域。
然而,我们可以使用OpenGL提供的GL_EXT_framebuffer_object扩展功能来创建额外的帧缓冲区。GL_EXT_framebuffer_object扩展功能中,提出了帧缓冲区对象(framebuffer object,缩写为FBO,下文中将使用FBO来代表帧缓冲区对象)的概念,用于对帧缓冲区进行建模。这样一来,无论是窗体系统创建的帧缓冲区,还是用于屏外渲染的帧缓冲区,都是FBO的实例。有了FBO,程序员就可以重定向渲染目标到其他的存储空间,比如将渲染目标重定向到纹理空间,实现渲染到纹理功能(Render to Texture)。
上文提到过帧缓冲区中包含的二维数组按用途划分,可分为颜色缓冲区(color buffer),深度缓冲区(depth buffer),模版缓冲区(stencil buffer)和累加缓冲区(accumulation buffer)。FBO中也提供了与颜色缓冲区、深度缓冲区和模版缓冲区相对应的功能(注意,FBO没有提供与累加缓冲区对应的功能)。但是,这并不意味着FBO会直接为这些缓冲区分配空间。FBO只是为这些缓冲区提供一个或多个挂接点。我们需要分别为各个缓冲区创建对象,申请空间,然后挂接到相应的挂接点上。FBO提供的挂接点如下图所示。
可以看出,FBO提供了多个颜色缓冲区挂接点GL_COLOR_ATTACHMENT0_EXT ... GL_COLOR_ATTACHMENTn_EXT、一个深度缓冲区挂接点GL_DEPTH_ATTACHMENT_EXT和一个模板缓冲区挂接点GL_STENCIL_ATTACHMENT_EXT。颜色缓冲区挂接点的个数是因不同厂商和不同型号的显卡而异的。你可以通过GL_MAX_COLOR_ATTACHMENTS_EXT查询当前显卡所支持的颜色缓冲区挂接点的最大个数。FBO提供多个颜色缓冲区挂接点的用意是,允许程序员进行多目标渲染(使用GL_ARB_draw_buffers扩展功能)。
能够与FBO挂接的对象有两种,一种是纹理对象(texture object),另一种是渲染缓冲区对象(renderbuffer object)。纹理对象,就是我们平日为模型设置纹理贴图时使用的对象;而渲染缓冲区对象可以用作不具有纹理格式的缓冲区,如深度缓冲区和模板缓冲区。当然,渲染缓冲区对象也可以用来渲染场景。
前面提到过,窗体系统创建的帧缓冲区也有对应的FBO的实例。但是,这个FBO与其他通过手工创建的FBO相比有许多不同。第一,通过手工创建的FBO不能用于将渲染结果直接显示到窗口输出区,通过手工创建的FBO只能用于屏外渲染(Off-screen Rendering);第二,窗体系统生成的FBO在创建的时候就拥有颜色缓冲区,深度缓冲区,模版缓冲区,且创建后就为这些缓冲区分配了空间。而手工创建的FBO需要手动为其添加各个缓冲区,并为其申请空间。窗体系统创建的FBO中的各个缓冲区对象不能与手动创建的FBO的挂接点挂接,反之亦然。
创建、绑定和删除一个FBO
我们可以使用glGenFramebuffersEXT()来向OpenGL申请一个或者多个闲置的FBO的ID。注意,就算成功地申请到了闲置的ID,OpenGL也不会马上为其创建实例。只用当调用glBindFramebufferEXT ()绑定FBO的时候OpenGL才会真正的创建一个FBO实例(这和其他glBind*函数极为相似)。在FBO被绑定之后,这个FBO就会被OpenGL当作当前的操作对象,后续的操作都被视为对被绑定的FBO进行的操作。窗体系统创建的FBO的ID默认为0。我们可以通过调用glDeleteFramebuffersEXT()函数来释放FBO的实例,如果要删除的FBO实例正在被使用,则OpenGL会自动绑定窗口系统创建的FBO(ID为0)。
渲染缓冲区对象
FBO创建完成后,还不能对其进行什么实质性的操作。因为,FBO的各个挂接点上还没有挂接实际的存储对象。我们需要手动创建这些对象,并将其与既存的FBO对象进行挂接。上文提到过,能够与FBO挂接的对象有两种,一种是纹理对象(texture object),另一种是渲染缓冲区对象(renderbuffer object)。读者可能对纹理对象比较熟悉,因为在为模型进行纹理贴图的时候,经常要使用这种对象。然而,对于渲染缓冲对象,读者可能会不太熟悉,因为这是OpenGL扩展中新引入的功能。下文将着重介绍渲染缓冲区对象的内容。
渲染缓冲区对象主要是为了实现屏外渲染(Off-screen Rendering)而设计的。渲染缓冲区对象主要用做FBO的深度缓冲区和模板缓冲区。可以使用glGenRenderbuffersEXT()函数来申请一个或多个闲置的渲染缓冲区对象ID(非负整数)。ID 0被OpenGL所保留。注意,申请了闲置ID之后,OpenGL并没有创建实际的对象,需要调用glBindRenderbufferEXT()函数来绑定并创建实际的对象。如果绑定ID 0,OpenGL会解除先前设定的渲染缓冲区对象。
上文提到过,渲染缓冲区对象实际上是某种二维数组的抽象。在绑定了一个渲染缓冲区对象之后,需要使用glRenderbufferStorage()函数为其分配二维数组存储空间。注意,同一个FBO中的各个二维数组空间的行数(或列数)应该相同。
同FBO类似,可以使用glDeleteRenderbuffersEXT()函数来删除一个渲染缓冲区对象。
挂接
可以使用glFramebufferRenderbufferEXT()函数将渲染缓冲区对象挂接到FBO上;使用glFramebufferTexture2D()。如果挂接的ID为0,则OpenGL将解除先前的绑定。当被绑定纹理对象或渲染缓冲区对象被删除,则他们会被自动从当前正在使用的FBO上解除挂接。如果纹理对象或渲染缓冲区对象被挂接到多个FBO上,他们被删除的时候,只会从当前被绑定的FBO上解除挂接,而不会从未被绑定的FBO上解除绑定。
FBO的完整性
在向FBO输出渲染结果之前,需要测试FBO的完整性。如果FBO不完整,任何渲染操作都会失败。我们可以使用glCheckFramebufferStatusEXT()函数来测试FBO的完整性(此函数不能在glBegin()和glEnd()函数之间调用)。FBO完整性的判别法则如下:
- 与FBO挂接的二维数组对象的长度和宽度必须不能为0。
- 如果一个二维数组对象被挂接到FBO的颜色缓冲区挂接点时,二维数组必须具有内部颜色格式(GL_RGBA, GL_DEPTH_COMPONENT, GL_LUMINANCE等)。
- 如果一个二维数组对象被挂接到FBO的深度缓冲区挂接点时,二维数组必须具有内部深度格式(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_EXT等)。
- 如果一个二维数组对象被挂接到FBO的模板缓冲区挂接点时,二维数组必须具有内部模板格式(GL_STENCIL_INDEX, GL_STENCIL_INDEX8_EXT等)。
- FBO至少挂接有一个二维数组缓冲区对象。
- 同一个FBO上挂接的二维数组对象必须拥有相同的长度和宽度。
- 所有的颜色缓冲区挂接点上挂接的二维数组对象必须具有相同的内部格式。
FBO的使用
当所有上述的准备工作都完成之后,就可以调用glBindFramebufferEXT()来绑定一个FBO。随后,就可一像操作窗体系统提供的帧缓冲区一样操作当前绑定的FBO了。日常的3D渲染操作这里不再赘述。这里主要强调像素操作的使用。OpenGL提供了glBlitFramebufferEXT()函数进行像素操作。
示例代码
示例代码使用FBO实现渲染到纹理(Render to texture)功能。编译代码需要链接GLEW和SDL两个库。