VBO,Vertex Buffer Array
为了加快显示速度,显卡增加了一个扩展,即VBO。它本质上是存储几何数据的缓存。它直接把顶点数据放置到显卡中的高速缓存,极大提高了绘制速度。
这个扩展用到ARB_vertex_buffer_object,它可以直接像顶点数组那样使用。唯一不同的地方在于它需要将数据载入显卡的高效缓存,因此需要占用渲染时间。
[参考文章1] 给出了一个使用VBO扩展的例子程序。[参考文章2]罗列了与VBO操作相关的函数
[参考文章3]归纳出VBO扩展的用法流程,并总结出与纹理用法流程的相似性:
初始化阶段:
1. glGenBuffersARB(1, &nVBOVertices); //生成一个句柄
2. glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVBOVertices); //声明该句柄为一个vbo句柄,并选择之
3. glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(vertices), vertices,GL_STATIC_DRAW); //将顶点集上传至server端
使用阶段:
1. glEnableClientState(GL_VERTEX_ARRAY); //开始使用vbo
2. glBindBufferARB(GL_ARRAY_BUFFER_ARB, nVBOVertices); //选择当前使用的vbo
3. glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0)); //指定vbo顶点格式
4. glDrawArrays( GL_TRIANGLES, 0, g_pMesh->m_nVertexCount ); //画吧
5. glDisableClientState(GL_VERTEX_ARRAY); //停止使用vbo
收尾阶段:
1. glDeleteBuffersARB(1,&nVBOVertices); //删除句柄,同时删除server端顶点缓冲
再来看看纹理缓冲是怎么使用的,其实差不多:
初始化阶段:
1. glGenTextures(1, &texID);//创建句柄
2. glBindTexture(GL_TEXTURE_2D, texID); //设置句柄类型
3. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->GetWidth(), img->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, raw_rgba); //上传纹理缓冲
使用阶段:
1. glEnable(GL_TEXTURE_2D); //开始使用纹理缓冲
2. glBindTexture(GL_TEXTURE_2D, texID); //选择当前使用的纹理缓冲
3. 发送顶点和纹理坐标,画吧...省略
4. glDisable(GL_TEXTURE_2D); //停止使用纹理
收尾阶段:
1. glDeleteTextures(1,&texID);//删除句柄,,同时删除server端缓冲
参考文章:
1. NEHE教程第45讲 http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=45
2. http://hi.baidu.com/excerpts/blog/item/dde4e0ceeeee8c0692457e54.html
3. OpenGL VBO顶点缓冲的使用 http://www.cppblog.com/w2001/archive/2008/05/10/49376.aspx
4. Opengl Vertex Buffer Object http://www.songho.ca/opengl/gl_vbo.html
PBO,即Pixel Buffer Object也是用于GPU的扩展(ARB_vertex_buffer_object)。这里的缓存当然就是GPU的缓存。PBO与VBO扩展类似,只不过它存储的是像素数据而不是顶点数据。PBO借用了VBO框架和所有API函数形式,并加了上两个"target"标志。这两个标识是:
这里的“pack”还是“unpack”,可分别理解为“传给”和“得到”。它们也都可以统一理解为“拷贝”,也就是像素数据的“传递”。
比如说,glReadPixel就是数据从帧缓存(framebuffer)到内存(memory),可理解为“pack”;glDrawPixel是从内存到帧缓存,可理解为“unpack”;glGetTexImage是从纹理对象到内存,可理解为“pack”;glTexImage2d从内存(memory)到纹理对象(texture object),可理解为“unpack”。
下图是PBO与Framebuffer和Text对象之间的传递。
图1 opengl PBO
使用PBO的好处是快速的像素数据传递,它采用了一种叫DMA(Direct Memory Access)的技术,无需CPU介入。另一个PBO的优点是,这种DMA是异步的。我们可以通过下面两张图来比较使用PBO的与传统的纹理传递的过程。
图2是用传统的方法从图像源(如图像文件或视频)载入图像数据到纹理对象的过程。像素数据首先存到系统内存中,接着使用glTexImage2D将数据从系统内存拷贝到纹理对象。包含的两个子过程均需要有CPU执行。而从图3中,我们可以看到像素数据直接载入到PBO中,这个过程仍需要CPU来执行,但是从数据从PBO到纹理对象的过程则由GPU来执行DMA,而不需要CPU参与。而且opengl可安排异步DMA,不必马上进行像素数据的传递。因此,相比而言,图3中的glTexImage2D立即返回而不是马上执行,这样CPU可以执行其它的操作而不需要等待像素数据传递的结束。
图2 不使用PBO的纹理载入
图3 使用PBO的纹理载入
GL_PIXEL_PACK_BUFFER_ARB用于将像素数据从opengl传递给应用程序,GL_PIXEL_UNPACK_BUFFER_ARB则是将像素数据从应用程序传递给opengl。
生成PBO
生成PBO分成3步:
1. 用glGenBuffersARB()生成缓存对象;
2. 用glBindBufferARB()绑定缓存对象;
3. 用glBufferDataARB()将像素数据拷贝到缓存对象。
如果在glBufferDataARB函数中将一个NULL的指针给源数组,PBO只会为之分配一个给定大小的内存空间。glBufferDataARB的另一个参数是关于PBO的性能参数(hint),表明缓存对象如何使用。该参数取GL_STREAM_DRAW_ARB表明是载入,GL_STREAM_READ_ARB表明是异步帧缓存读出。
映射PBO
PBO提供了一种将opengl控制的缓存对象与客户端地址空间进行内存映射的机制。所以客户端可通过glMapBufferARB()和glUnmapbufferARB就可以修改缓存对象的部分或整个数据。
void* glMapBufferARB(GLenum target, GLenum access);
GLboolean glUnmapBufferARB(GLenum target);
glMapBufferARB返回缓存对象的指针。参数target取GL_PIXEL_PACK_BUFFER_ARB或GL_PIXEL_UNPACK_BUFFER_ARB,参数access是能对映射缓存的操作,可取GL_READ_ONLY_ARB、GL_WRITE_ONLY_ARB和GL_READ_WRITE_ARB,分别表明可从PBO读、可向PBO写,可从PBO读也可向PBO写。
要注意:如果GPU正在对缓存对象进行操作,glMapBufferARB不会返回缓存对象直到GPU结束了对缓存对象的处理。为了避免等待,在调用glMapBufferARB之前,先调用glBufferDataARB(用NULL指针作为参数),这时OpenGL将丢弃老的缓存对象,为新的缓存对象分配空间。
在客户端使用PBO后,应调用glUnmapBufferARB来取消映射。glUnmapBufferARB返回GL_TRUE表明成功,否则返回GL_FALSE。
____________________________________________________
Demo
例子程序pboUnpack.zip使用了不同方式来比较将纹理传给OpenGL的模式:
通过按空格键,可以不同模式间切换。
在PBO模式下,每帧的纹理源(像素)都是在映射PBO状态下直接写进去的。再通过调用glTexSubImage2D将PBO中的像素传递给纹理对象。通过使用纹理对象可以在PBO和纹理对象间进行异步DMA传递。它能大大提高像素传递的性能。
由于glTexSubImage2D立即返回,因此CPU能够直接进行其它工作,无需等待实际的像素传递。
图4 两个PBO更新纹理
为了将像素传递的性能最大化,可以使用多个PBO对象。图4中表明同时使用了两个PBO。在glTexSubImage2D将像素数据从PBO拷贝出来的同时,另一份像素数据写进了另一个PBO。
在第n帧时,PBO1用于glTexSubImage2D,而PBO2用于生成一个新的纹理对象了。再到n+1帧时,两个PBO则互换了角色。由于异步DMA传递,像素数据的更新和拷贝过程可同时进行,即CPU将纹理源更新到PBO,同时GPU将从另一PBO中拷贝出纹理。
例子程序pboPack.zip从窗口的左边读出(pack)像素数据到PBO,在更改它的亮度后,把它在窗口的右边绘制出来。通过按空格键,可以看glReadPixels的性能。
传统使用glReadPixels将阻塞渲染管道(流水线),直到所有的像素数据传递完成,才会将控制权交还给应用程序。相反,使用PBO的glReadPixels可以调度异步DMA传递,能够立即返回而不用等待。因此,CPU可以在OpenGL(GPU)传递像素数据的时候进行其它处理。
图5 用两个PBO异步glReadPixels
例子程序也使用了两个PBO,在第n帧时,应用帧缓存读出像素数据到PBO1中,同时在PBO中对像素数据进行处理。读与写的过程可同时进行,是因为,在调用glReadPixels时立即返回了,而CPU立即处理PBO2而不会有延迟。在下一帧时,PBO1和PBO2的角色互换。
参考文章
1. http://www.songho.ca/opengl/gl_pbo.html
2. http://hacksoflife.blogspot.com/2006/10/vbos-pbos-and-fbos.html
3. http://www.paulsprojects.net/opengl/rtotex/rtotex.html
FBO, Frame Buffer Object
在OpenGL渲染流水线上,几何数据和纹理数据被多次转换、多次测试,最后以2维像素的形式显示在屏幕上。而OpenGL流水线上最后显示阶段像素所在处,称为帧缓存。帧缓存可视为2维数组,或OpenGL使用的存储区域,它包括了:颜色缓存、深度缓存、模板缓存和累积缓存。
一般情况下,帧缓存由window系统生成并管理,供OpenGL使用。这种缺省的帧缓存称之为“window系统生成”(window-system-provided)的帧缓存。
在OpenGL扩展中,GL_EXT_framebuffer_object提供了另外一种不能够显示的帧缓存接口,帧缓存对象(FBO)。这种帧缓存称之为“应用生成”帧缓存,以区别于“window系统生成”帧缓存。通过使用帧缓存对象(FBO),OpenGL应用程序可以将显示内容输出到“应用生成”帧缓存,而不是传统的“window系统生成”帧缓存。这个过程全部由OpenGL控制。
和window系统提供的帧缓存一样,FBO也有一组相应存储颜色、深度和模板(注意没有累积)数据的缓存区域。我们把FBO中存储这些数据的区域称之为“缓存关联图像”(framebuffer-attached image)。它完全由OpenGL管理控制。
缓存关联图像分为两类:纹理缓存和渲染(显示)缓存(renderbuffer)。如果纹理对象的图像数据关联到帧缓存,opengl执行的将是“渲染到纹理”(render to texture)操作。如果渲染缓存对象的图像数据关联到帧缓存,opengl执行的将是“离线渲染”(offscreen rendering)。
渲染缓存对象是GL_EXT_framebuffer_object中定义的新的一种存储类型。这用于渲染过程中存储单幅2维图像。下面的图1描述了FBO、纹理对象和渲染缓存对象之间的关系。
图1 FBO、纹理对象和渲染缓存对象之间的关系
从图中可以看出,FBO有多个颜色关联:(GL_COLOR_ATTACHMENT0_EXT,..., GL_COLOR_ATTACHMENTn_EXT), 一个深度关联(GL_DEPTH_ATTACHMENT_EXT)和一个模板关联 (GL_STENCIL_ATTACHMENT_EXT)。颜色关联的数目最少有一个,最大数目是与实体的显卡相关的,可以GL_MAX_COLOR_ATTACHMENTS_EXT查询得到。FBO采用多个颜色关联,这样可以同时将多个颜色缓存渲染(绘制)到多个FBO关联存储区,即“多渲染目标”MRT(multiple render target)。MRT可用GL_ARB_draw_buffers完成。
注意:FBO本身并没有图像数据存储区,只有多个关联。
FBO提供了一种高效的切换机制:将前一个帧缓存关联图像从FBO分离,将一个新的可关联帧缓存图像与FBO关联。FBO对帧缓存关联图像的切换要比对FBO的切换速度更快。用glFramebufferTexture2DEXT(),可以进行二维纹理对象的切换,用glFramebufferRenderbufferEXT可切换渲染缓存对象。
——————————————————————————————————————————————
FBO的生成
FBO的生成过程与VBO的生成过程类似。
glGenFramebuffersEXT()
void glGenFramebuffersEXT(GLsizei n, GLuint* ids) void glDeleteFramebuffersEXT(GLsizei n, const GLuint* ids)
glGenFramebuffersEXT需要两个参数,一个是要生成的帧缓存个数,一个是存储帧缓存ID的地址指针。如果帧缓存的ID为0,表明为缺省的帧缓存,也即window系统生成的帧缓存。
glDeleteFramebuffersExt可用于删除不再使用的FBO。
glBindFramebufferEXT()
void glBindFramebufferEXT(GLenum target, GLuint id)
一旦生成FBO,可用glBindFramebufferEXT来启用它。第一个参数target应为GL_FRAMEBUFFER_EXT,第二个参数为FBO的ID。如果不再使用FBO,应调用该函数,这时参数ID置为0。
———————————————————————————————
渲染缓存对象
渲染缓存对象是新添加的,专门用于离线渲染。它允许将一个场景直接渲染到一个渲染缓存对象中,而不是渲染到纹理对象。渲染缓存对象是用于存储单幅图像的简单存储对象。该图像是按一种可渲染的内部格式存储。可以说,渲染缓存对象是存储OpenGL的逻辑缓存,它不具有像纹理那样的格式。
_______________________________________________________________________________
glGenRenderbuffersEXT()
void glGenRenderbufferEXT(GLsizei target, GLuint* ids)
void glDeleteRenderbuffersEXT(GLsizei n, const GLuint* ids)
glGenRenderbuffersEXT一旦生成渲染缓存, 返回非零整数。
_________________________________________________________________
glBindRenderbufferEXT()
void glBindRenderbufferEXT(GLenum target, GLuint id)
和其它OpenGL对象一样,你需要在使用它之前对渲染缓存对象进行绑定。其中的target参数应为GL_RENDERBUFFER_EXT。
__________________________________________________________________________________
glRenderbufferStorageEXT()
void glBindRenderbufferEXT(GLenum target, GLuint id)
生成了渲染对象后,还需要分配空间存储数据。glRenderbufferStorageEXT用来完成这件事。该函数的第一个参数必须是GL_RENDERBUFFER_EXT。第二个参数可以是用于颜色的(如GL_RGB, GL_RGBA等等),用于深度的(GL_DEPTH_COMPOENT),用于模板的(GL_STENCIL_INDEX)。渲染缓存存储的图像其大小还是按像素的宽×高来计算,它不得大于常量GL_MAX_RENDERBUFFER_SIZE_EXT;否则会产生GL_INVALID_VALUE的错误。
_________________________________________________________________________________
glGetRenderbufferParameterivEXT()
void glGetRenderbufferParameterivEXT(GLenum target, GLenum param,
GLint* value);
用glGetRenderbufferParameterivEXT可以得到当前渲染对象的属性参数。target必须为GL_RENDERBUFFER_EXT,第二个参数param从下面的常量中取,value为得到返回值的指针。
GL_RENDERBUFFER_WIDTH_EXT
GL_RENDERBUFFER_HEIGHT_EXT
GL_RENDERBUFFER_INTERNAL_FORMAT_EXT
GL_RENDERBUFFER_RED_SIZE_EXT
GL_RENDERBUFFER_GREEN_SIZE_EXT
GL_RENDERBUFFER_BLUE_SIZE_EXT
GL_RENDERBUFFER_ALPHA_SIZE_EXT
GL_RENDERBUFFER_DEPTH_SIZE_EXT
GL_RENDERBUFFER_STENCIL_SIZE_EXT
----————————————————————————————————----
将图像与FBO关联
FBO本身并没有存储任何图像,我们只是将帧缓存可关联图像(纹理和渲染对象)与FBO进行了关联。这种机制可以让我们在FBO中进行帧缓存可关联图像的快速切换。这样就用不着不必要地拷贝,也减少了内存消耗。比如说一个纹理可与多个FBO关联,这样它的图像存储区就能够让多个FBO共享。
______________________________________________________________________________________
将一个纹理关联到FBO
glFramebufferTexture2DEXT(GLenum target,
GLenum attachmentPoint,
GLenum textureTarget,
GLuint textureId,
GLint level)
glFramebufferTexture2DEXT可将2D纹理图像关联到FBO上。第一个参数target必须为GL_FRAMEBUFFER_EXT。参数attachment为FBO上的关联点。FBO上有多个颜色关联点(GL_COLOR_ATTACHMENT0_EXT, ..., GL_COLOR_ATTACHMENTn_EXT),一个GL_DEPTH_ATTACHMENT_EXT和GL_STENCIL_ATTACHMENT_EXT。参数textureTarget大数多情况下为GL_TEXTURE_2D。参数textureId为纹理对象的ID号。参数level为关联的纹理的mipmap等级。
如果textureId取0,则当前关联的纹理将与FBO分离。如果纹理对象被删除了,还它关联着一个FBO的话,该纹理图像会自动与其分离。如果它关联的是多个FBO,它删除时只与当前的FBO分离,不会与其它FBO分离。
______________________________________________________________________________________
将一个渲染缓存图像关联到FBO
glFramebufferRenderbufferEXT(GLenum target,
GLenum attachmentPoint,
GLenum renderbufferTarget,
GLuint renderbufferId)
渲染缓存图像可用glFramebufferRenderbufferEXT,来与FBO关联。参数target和attachmentpoint和前一个函数glFramebufferTexture2DEXT一样。第三个函数必须为GL_RENDERBUFFER_EXT, 参数renderbufferId为渲染对象的Id。
如果renderbufferId设为0,当前FBO中的渲染图像将与其分离。如果渲染对象删除时还与FBO关联着,那它会自动与当前的FBO分离,但不会与其它关联的FBO分离。
----————————————————————————————————----
检查FBO状态
在图像(纹理或渲染缓冲)与FBO关联,在招待FBO的操作之前,都需要对FBO的状态进行检查。获取FBO状态函数为glCheckFramebufferStatusEXT。如果FBO不完整,那么绘制和读取的命令(glBegin(), glCopyTexImage2D()等)都会失败。
GLenum glCheckFramebufferStatusEXT(GLenum target);
glCheckFramebufferStatusEXT对当前帧缓存的参数和相关联的图像进行验证。注意该函数不能在glBegin()/glEnd()函数对中间使中。target参数必须为GL_FRAMEBUFFER_EXT。它返回一个非零的状态值。如果FBO通过了检查,则返回GL_FRAMEBUFFER_COMPLETE_EXT。
FBO必须满足以下条件:
注意,即使所有的条件都得到满足,opengl驱动程序可能也不支持某些内部格式和参数的组合。如果出现这种情况,glCheckFramebufferStatusEXT将返回GL_FRAMEBUFFER_UNSUPPORTED_EXT.
----————————————————————————————————----
示例:渲染到纹理
示例源程序:fbo.zip
有时,你可能需要生成动态的纹理,如做镜面效果、动态立方体/环境贴图、阴影等效果时。动态纹理
可以通过将渲染的场影绘制到纹理上来完成这些效果。传统解决渲染到纹理的方法是将场景绘制到普通的帧缓存上,然后用glCopyTexSubImage2D拷贝帧缓存图像到纹理上。
使用FBO,我们可以直接将场景绘制到纹理上,而用不着window系统提供的帧缓存。而且,我们可以减少数据拷贝(从帧缓存到纹理)的过程。
例程中提供了用FBO和不用FBO来完成渲染到纹理的代码,以例比较它们的性能。除了性能上的优点,如果纹理分辩率大于无FBO模式下渲染窗口的大小,窗口区域外的部分将被裁剪,但FBO不会出现这种问题。你可以生成比显示窗口大得多的帧缓存图像。
以下代码在渲染循环启动之前,先对FBO和帧缓存可关联图像进行了设置(初始化)。程序中,不仅纹理图像关联到FBO,一个渲染缓存图像也关联到FBO的深度关联点。我们并没有实际用到这个深度缓存,只不过,FBO本身需要用它来做深度测试。我们如果没有把一个深度可渲染图像关联到FBO,那么可能会因为不能进行深度测试而无法进行成功渲染。如果渲染过程中需要模板测试,也可以在程序中加上可渲染图像与GL_STENCIL_ATTACHMENT_EXT关联。
渲染到纹理的过程和普通绘制过程一样。我们只需将渲染的目的地放在FBO即可。
注意,glGenerateMipmapEXT()也是作为FBO扩展的一部分,用于在修改纹理图像的基级(base level)之后,显式生成mipmap。如果GL_GENERATE_MIPMAP设置为GL_TRUE,glTex{Sub}Image2D()和glCopyTex{Sub}Image2D()将激活自动mipmap的生成。然而,在纹理的基级修改后,FBO不会自动生成它的MIPMAP。这是因为FBO不调用glCopyTex{Sub}Image2D()来修改纹理。因此,glGenerateMipmapEXT()必须显式的调用来生成mipmap。
参考文章
http://www.gamedev.net/reference/articles/article2331.asp
http://www.physdev.com/articles/
http://www.builder.com.cn/2008/0703/963021.shtml
http://www.songho.ca/opengl/gl_fbo.html