一般电脑绘图的流程大致是这样:算出投影矩阵、产生模型的几何资料、设定贴图、打上光源、最后由硬体产生一张2D平面的影像。 而这个2D的影像会放在一块记忆体中,称之为Frame buffer。
Frame Buffer 是一块直接对应萤幕的记忆体,所以通常是放在显示卡里。 为了让连续画面显示流畅,我们都会将Frame Buffer设为前景跟背景两个。 先对背景Frame buffer进行绘图,等确定画完整个场景后,再将前景跟背景进行对调(Swap)。 如此就可把刚才画完的场景显示在萤幕上,而我们可以继续在背景Frame buffer画下一个场景。
问题来了,我们能不能把Frame buffer读出来? 因为我们可能会将还没送出萤幕的影像做一些加工处理。 比如将场景影像当成贴图贴在某个模型,像镜子这样的效果。
早期OpenGL只有提供glReadPixels()这样的函式可以读出Frame buffer的资料,但是这会将资料读在主记忆体中,而我们又要当成贴图放在显示记忆体中,这样一来一往就浪费了不少时间。 后来改用glCopyTexImage(), glCopyTexSubImage()直接在显示记忆体中存取后,速度上就改善了许多。 但仍然有使用上的不便性,因为就只是对一块背景Frame buffer进行存取,换另一张贴图则整个Frame buffer就必须重画。
直到2002年OpenGL 提供了一套解决方法,称为PBuffer( Pixel Buffer)。 这是让使用者自行建立一块颣似Frame Buffer的记忆体,差别是PBuffer是不能直接显示在萤幕上的。 我们可以先对PBuffer进行绘图,然后再将PBuffer当成贴图来用。 这个功能一般的显示卡都有支援,它是建立在wgl extension中,所以我们在程式开头要先含入wglext.h档。 要知道您的显示卡是否支援PBuffer,可以用这个方法来确认:
#include <gl/wglext.h>
/* 在此之前要先设定好OpenGL的基本环境*/
wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
char *ext = NULL;
if( wglGetExtensionsStringARB ){
ext = (char*)wglGetExtensionsStringARB( wglGetCurrentDC() );
if( strstr( ext, "WGL_ARB_pbuffer" ) == NULL ){
// 不支援PBuffer!
}
}
接下来要介绍如何使用PBuffer。 整个使用流程大致是这样:设定好OpenGL的基本环境、建立PBuffer、启动PBuffer、对PBuffer绘图、将PBuffer当成贴图、启动原来的Frame Buffer、对Frame Buffer画图、解除贴图的连结、删除PBuffer。
1. 设定OpenGL的基本环境:请参考我之前的文章-- "在VC++ .NET framework 使用OpenGL",这边不多介绍。 这个步骤主要是取得HDC及HGLRC。
2. 建立PBuffer:
a. 取得WGL extension 的函式位置,我们会需要这些函式:wglMakeContextCurrentARB , wglChoosePixelFormatARB , wglCreatePbufferARB , wglDestroyPbufferARB , wglGetPbufferDCARB , wglReleasePbufferDCARB , wglReleasePbufferDCARB , wglQueryPbufferARB , wglBindTexImageARB , wglReleaseTexImageARB , wglSetPbufferAttribARB .
b. 设定PBuffer每个Pixel的格式,用wglChoosePixelFormatARB
c. 建立PBuffer,用wglCreatePbufferARB
d. 取得PBuffer的环境DC,用wglGetPbufferDCARB及wglCreateContext
3. 启动PBuffer:
wglMakeContextCurrentARB(hPBufDC, hPBufDC, hPBufRC);
4. 对PBuffer绘图:这个步骤有一点需要特别注意,就是在PBuffer的绘图环境是与Frame Buffer完全独立的。 也就是说投影矩阵、几何资讯、光源、贴图...等,都需要重新设定!
5. 启动原来的Frame Buffer:
wglMakeContextCurrentARB(hGLDC, hGLDC, hGLRC);
6. 将PBuffer当成贴图:注意! 这个步骤一定要Frame Buffer的绘图环境下执行!
GLuint nPbufTexID;
glGenTextures(1, &nPbufTexID);
glBindTexture (GL_TEXTURE_2D, nPbufTexID);
glTexImage2D( .... );
glTexParameterf(....);
wglBindTexImageARB( hPBuf, WGL_FRONT_LEFT_ARB);
7. 对Frame Buffer画图:即一般的绘图流程,要注意的是所有绘图环境需重新设定。
8. 解除贴图的连结:
wglReleaseTexImageARB(hPBuf, WGL_FRONT_LEFT_ARB);
9. 删除PBuffer:这个步骤是当PBuffer不再使用的情况下才执行。
wglReleaseTexImageARB(hPBuf, WGL_FRONT_LEFT_ARB);
glDeleteTextures (1, &nPbufTexID);
wglDeleteContext(hPBufRC);
wglReleasePbufferDCARB(hPBuf, hPBufDC);
wglDestroyPbufferARB(hPBuf);
对直接在Texture上作画,PBuffer是目前比较常用的方法,但不是最佳的方法! 因为它仍有许多限制,如它只能在视窗化的作业系统上运作(如Microsoft Windows、XWindow、Mac OS...),以及两个PBuffer无法直接分享资源等。 于是OpenGL又在2003年提供了一个更好的方法-- Frame Buffer Object(FBO),这也是目前GPGPU(General Purpose GPU, 绘图晶片泛用运算)所必须的基本功能。 我将在下一篇文章为各位介绍。
参考资料:
[1] Christopher Oat, "Rendering to an off-screen buffer with WGL_ARB_pbuffer", ATI Research, Inc.
[2] PBuffer范例程式