一般電腦繪圖的流程大致是這樣:算出投影矩陣、產生模型的幾何資料、設定貼圖、打上光源、最後由硬體產生一張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範例程式