GPU深度發掘(四):: Render to Vertexbuffer in OpenGL 作者:華文廣 更新:2007/5/10 www.physdev.com
要想實作GPU程式設計,需要比較好的相關理論基礎才行。如果你以前沒有這方面的基礎,請先學習一下相關的知識,推薦看一下文章《GPGPU::數學基礎教程》
概貌:
PBO: Pixel buffer object
FBO: Frame buffer object
VBO: Vertex buffer object
以下介紹兩種不同的實作方法,随着顯示卡的不斷發展,可能會有更好的解決方法,但以下兩種方法是目前比較常用的。
方法 1: 在頂點着色其間讀取紋理,也就是(Vertex Texture Fetch). 可以看一下以下論文: http://developer.nvidia.com/object/using_vertex_textures.html
優點:
- 不用拷貝到VBO。
- 靈活的紋理資料讀取
- 消耗比較少的顯存資源
缺點:
- 隻有NVidia的N3.0以上的顯示卡才支援
- 隻支援少量的浮點紋理格式(GL_RGBA_FLOAT32_ATI)
- 老的顯示卡(GFX系列之前)不支援,GF6以後的才可以。
- 稍為要慢一點,GF6600GT的讀取紋理的速度是30M/秒,如果該紋理要先經過FBO作處理的話,則會更慢一些。當然,這裡的慢是相對于後面将要紹介的第二種方法而言,但如果與把紋理讀回到CPU記憶體的速度相比,這個方法要快得多。
方法 2: 拷貝到像素緩沖對像(PBO)
PBO指的是: pixel buffer object,PBO能直接轉換為VBO,用作頂點數組的渲染。
優點:
- 支技多種PBO的資料格式,而不緊緊是浮點的RGBA。
- 更快。在GF6600GT顯示卡上,渲染FBO + 拷貝FBO到PBO + 渲染VBO,速度是58M/秒。據說現在新的G8顯示卡,可以省去拷貝這一步,也就是直接渲染到VBO,那速度就更快了。
缺點:
- 比較高的顯存消耗。
- 須要額外的拷貝動作。
- 使用多個FBO渲染對像時,每個對像必許用同一種資料格式。例如:如果我們想在一個渲染通道中,把position資訊渲染到浮點RGB緩沖區塊,然後把normal資訊渲染到BYTE-RGB格式的緩沖區,這樣做是不行的。
注意:老的GFX顯示卡不支援多渲染對像。
實作:
方法 1: 在頂點着色其間讀取紋理.
步驟 1: 生成 FBO
生成一個FBO,然後把一個用來儲存頂點資料的紋理綁定到這個FBO上。
步驟 2: 生成 VBO
生成一個頂點緩沖對像(VBO),用來保儲存紋理坐标資訊,主要的作用就是為了在頂點着色期間能正确定位并讀取到FBO紋理中的資料。
Create a vertex buffer object (VBO) holding texture coordinates for
referencing the FBO texture (e.g. a position VBO with
2 float-coordinates per vertex)
( see further down the text how to create )
步驟 3: 渲染 FBO
( 詳細說明請看後面的内容 )
步驟 4: 渲染 VBO
這裡,我們先從VBO得到頂點坐标,然後用該坐标來通路FBO紋理,并取得紋理資料。
以下例子是一個頂點程式的代碼:
Code:
// vertex.position is our
// index to the real vertex array
!!ARBvp1.0
OPTION NV_vertex_program3;
PARAM mvp[4] = { state.matrix.mvp };
TEMP real_position;
TEX real_position, vertex.position, texture[0], 2D;
DP4 result.position.x, mvp[0], real_position;
DP4 result.position.y, mvp[1], real_position;
DP4 result.position.z, mvp[2], real_position;
DP4 result.position.w, mvp[3], real_position; END ;
以下列出的是一引些可以被支援的紋理格式。
vertex shader:
Quote:
Originally Posted by Nvidia Documentation 頂點紋理的調用會有許多的限制,必須使用GL_TEXTURE_2D的紋理對像,目前隻支援GL_LUMINANCE_FLOAT32_ATI 和 GL_RGBA_FLOAT32_ATI 兩種資料格式,這兩種格式都表示隻支援32-bit的浮點資料,前者是單通道,後者是四通道。值得注意的是:如果使用其它的紋理格式,或用了一些不被支援的過濾模式,會造成一些問題,顯示卡驅動可能會退回到軟體模式下進行頂點處理。 以下是一個正确的代碼寫法。 GLuint vertex_texture; glGenTextures(1, &vertex_texture); glBindTexture(GL_TEXTURE_2D, vertex_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_FLOAT32_ATI, width, height, 0,GL_LUMINANCE, GL_FLOAT, data); |
方法2. 拷貝到像素緩沖對像 (PBO).
步驟 1: 生成一個用作像素緩沖對像的VBO:
示例:
Code:
GLuint vbo_points_handle; glGenBuffersARB(1, &vbo_vertices_handle); glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, vbo_vertices_handle); glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, vbo_points.size()*4*
sizeof(
float ),
NULL, GL_DYNAMIC_DRAW_ARB );
步驟 2: 生成一個 FBO.
多個渲染對像可以幫助我們實作同一時間寫入頂點/法線/副法線。以下是一個生成FBO的示例:
Code:
GLuint fb_handle; glGenFramebuffersEXT(1,&fb_handle); fbo_tex_vertices = NewFloatTex(tex_width,tex_height,0); fbo_tex_normals = NewFloatTex(tex_width,tex_height,0);
這段代碼是示範如何生成浮點資料的紋理。
Code:
/** * Sets up a floating point texture with NEAREST filtering.
* (mipmaps etc. are unsupported for floating point textures)
*/
void setupTexture (
const GLuint texID,
int texSize_w,
int texSize_h) {
// make active and bind
glBindTexture(textureParameters.texTarget,texID);
// turn off filtering and wrap modes
glTexParameteri(textureParameters.texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(textureParameters.texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(textureParameters.texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(textureParameters.texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
// define texture with floating point format
glTexImage2D(textureParameters.texTarget,0,textureParameters.texInternalFormat,
texSize_w,texSize_h,0,textureParameters.texFormat,GL_FLOAT,0);
// check if that worked
if (glGetError() != GL_NO_ERROR)
{
printf("glTexImage2D(): [FAIL] ");
// PAUSE();
exit (ERROR_TEXTURE);
}
else if (mode == 0)
{
printf("glTexImage2D(): [PASS] ");
}
// printf("Created a %i by %i floating point texture. ",texSize,texSize);
}
注意:即使我們可以生成RGB的紋理,但它内部格式可能還是RGBA的,用glReadPixels()來讀取資料時由于要進行格式轉換,可能會減慢運作的速度。是以,多數情況下,我們盡量使用RGBA的格式。
步驟 3: 渲染 FBO
輸入紋理中包含有必要的資料(如:頂點位置、法線等),經過運算之後,把資料儲存到輸出紋理中。
下面代碼是綁定FBO緩沖區:
Code:
//glBindFramebufferEXT(GL_attach two textures to FBO
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachmentpoints[0],
textureParameters.texTarget, outTexID, 0);
// check if that worked
if (!checkFramebufferStatus())
{
printf("glFramebufferTexture2DEXT(): [FAIL] ");
// PAUSE();
exit (ERROR_FBOTEXTURE);
}
else if (mode == 0)
{ printf("glFramebufferTexture2DEXT(): [PASS] ");
}
下面代碼通過渲染一個四邊形來觸發FBO的運算。
Code:
// make quad filled to hit every pixel/texel
// (should be default but we never know)
glPolygonMode(GL_FRONT,GL_FILL);
if (textureParameters.texTarget == GL_TEXTURE_2D)
{
// render with normalized texcoords
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex2f(outTexSizeW, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex2f(outTexSizeW, outTexSizeH);
glTexCoord2f(0.0, 1.0);
glVertex2f(0.0, outTexSizeH);
glEnd();
}
else
{
// render with unnormalized texcoords
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(outTexSizeW, 0.0);
glVertex2f(outTexSizeW, 0.0);
glTexCoord2f(outTexSizeW, outTexSizeH);
glVertex2f(outTexSizeW, outTexSizeH);
glTexCoord2f(0.0, outTexSizeH);
glVertex2f(0.0, outTexSizeH);
glEnd();
}
gluOrtho2D是必須要有的! 如果沒有, glReadPixels運作時會出錯
Code:
/**
* Creates framebuffer object, binds it to reroute rendering operations
* from the traditional framebuffer to the offscreen buffer
*/
void initFBO(void)
{
// create FBO (off-screen framebuffer)
glGetIntegerv(GL_DRAW_BUFFER, &_currentDrawbuf);
// Save the current Draw buffer
glGenFramebuffersEXT(1, &fb);
// bind offscreen framebuffer (that is, skip the window-specific render target)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
// viewport for 1:1 pixel=texture mapping
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, outTexSizeW, 0.0, outTexSizeH);
glMatrixMode(GL_MODELVIEW); glLoadIdentity();
glViewport(0, 0, outTexSizeW, outTexSizeH);
}
步驟 4: 拷貝 FBO 的資料到 PBO
Example:
Code:
/**
*Copy from FBO to PBO
*
*/
void copyFromTextureToPBO(GLuint pboID,
int texSize_w,
int texSize_h)
{
glReadBuffer(attachmentpoints[0]);
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, pboID);
glReadPixels(0, 0, texSize_w,texSize_h,
textureParameters.texFormat,GL_FLOAT, 0);
glReadBuffer(GL_NONE);
glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0 );
}
( vbo_vertices.size() == tex_width * tex_height )
步驟 5: 渲染 VBO:
Code:
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo_vertices_handle);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer ( 4, GL_FLOAT,4*
sizeof(
float), (
char *) 0);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo_normals_handle);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 4*
sizeof(
float), (
char *) 0 );
glDrawArrays( GL_TRIANGLES, 0,vbo_vertices.size() );
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
示例說明
本文章所帶的例子,實作了在GPU中計算B樣條曲線的功能,用到的技術有:VBO,FBO,Render to vertex,CG,B-spline.實作過程如圖所示:

主要分為三個階段:
第一階段:GPU片段着色運算,生成FBO頂點資料。
- 把樣條控制點的資料發送到GPU的一個輸入紋理(控制點紋理)。
-
在片段處理單元中讀取控“制點紋理”中的資料,使用B樣條插值函數,計算插值頂點,把結果儲存到FBO所綁定的輸出紋理(插值紋理)中。
第二階段:FBO拷貝到PBO
把插值紋理通過使用glReadPixels()函數,拷貝到PBO中。
第三階段:渲染VBO
使用glDrawArrays(); 來渲染樣條曲線。當然這裡我們要把前面生成的PBO資料指定為一個VBO對像。
整個過程的插值運算及資料拷貝,都是在GPU中進行,最終的頂點資料直接用頂點數組來作渲染,資料沒有傳回到CPU中是以速度會非常快。
結論
聲明:
本譯文可以自由轉載,要求保留原作者資訊并注明文章出自實體開發網:www.physdev.com
本文所提供的代碼在NV6600的顯示卡測試通過,如果如果你在其它顯示卡上測試有問題,可以到實體開發網(www.physdev.com)的GPGPU/CUDA論壇上發表留言進行反潰。
下一步計劃将要寫一個GUP粒子系統示例,GUP布料系統示例,GPU頭發系統示例,敬請關注。
-
參考
http://oss.sgi.com/projects/ogl-sample/registry/EXT/pixel_buffer_object.txt
http://developer.nvidia.com/object/using_vertex_textures.html
http://wiki.delphigl.com/index.php/GLSL_Partikel
http://www.mathematik.uni-dortmund.de/~goeddeke/gpgpu/tutorial.html#arrays3
http://download.developer.nvidia.com/developer/SDK/Individual_Samples/samples.html
示例代碼下載下傳:http://www.physdev.com/phpbb/viewtopic.php?t=144