學一學,VBO

 VBO,全稱Vertex Buffer Object,與FBO,PBO並稱,但它實際上老不少。就某種意義來說,它就是VA(Vertex Array)的升級版。——ZwqXin.com

事實上,對VBO的接觸可以追溯到當初接觸FBO之後[學一學,FBO] 。那時候還在做Shadow Volume吧,見識到FBO的強大之後,就想把VBO也學了——於是不覺得有什麼難理解和難應用的地方,就把構造Volume(多側面錐體)的任務交給它了——誰知道(應該說,想當然知道),失敗了。結果大概忘記了,反正不會不糟糕。因爲想把時間繼續放在Shadow Volume算法學習上[Shadow Volume 陰影錐技術之探Ⅴ] ,就放棄使用VBO了。 而如今再次拿起來,想學的成分和不甘心的成分都有吧,用於最近做的水效DEMO[水效果Ⅰ - 水池] 上 ,構造網格看看吧。

本文來源於 ZwqXin (http://www.zwqxin.com/), 轉載請註明
      原文地址:http://www.zwqxin.com/archives/opengl/learn-vbo.html

1.“客戶端狀態”和“服務端狀態”

從VBO的作用說起吧。VBO出現的背景是人們發現VA還有不讓人滿足的地方。一般,在OpenGL裏,提高頂點繪製速度的手法,一是把常規的glBegin() - glEnd()類代碼段放入一個顯示列表中(通常在初始化階段完成),然後每遍渲染都調用這個顯示列表;二是使用頂點數組,把頂點以及頂點屬性數據作爲數組,渲染的時候直接用一個或幾個函數調動這些數組裏的數據進行繪製,形式上是減少函數調用的次數(glVertex再見),提高繪製效率。但是這兩種方法都有缺點。在說這個之前,應該對“客戶端狀態”和“服務端狀態”兩個名詞有點了解。

OpenGL是個狀態機,我們通常見到的glEnable - glDisable函數就是通知OpenGL開啓/關閉某種狀態的,譬如光照、深度檢測等等。但是也有glEnableClientState - glDisableClientState這對。它們的區別是通知的具體對象在概念上不一樣——分別是服務端和客戶端。事實上我也無法很清楚地告訴你區別之處,反正你把你電腦上的具體程序,包括它用到的內存等等看作客戶端,把你電腦裏面的——顯卡里的OpenGL“模塊”,乃至整張擁有OpenGL流水線、硬件實現OpenGL功能的顯卡,作爲服務端。它們各自維護一些“狀態”,glEnable 等是直接維護流水線處理相關的狀態的,glEnableClientState 維護的則是進入流水線前的狀態。流水線早期的T&L階段,程序的頂點數據就被獲知而接受處理了。至於頂點是怎麼來的——是glVertex來的,還是glDrawArray來的,流水線沒必要知道——這就是客戶端的任務,所以是否使用頂點數組(作爲一種狀態是否需要被啓動)都是由客戶端決定。顯示列表的glCallList比較特殊,它繞過客戶端,直接通知服務端把之前初始化時設定的代碼段所映射的硬件設置“啓亮”,這是相當於直接把顯存的某一段佔有而隨時呼喚了,硬件對此命令沒有絲毫猶豫地接受,對該呼喚的答應變成一種“神經反射”行爲——這是最理想最高級的“繪製”。

2. VBO,存在的理由

VBO出現的背景是人們發現VA還有不讓人滿足的地方,同樣,顯示列表也是。VA(頂點數組)是在客戶端設置的,所以執行這類函數(glDrawArray,glDrawElement等等)後,客戶端還得把得到的頂點數據向服務端傳輸一次(所謂的“二次處理”),這樣一來就有了不必要的動作了,降低了效率——如果我們寫的函數能直接把頂點數據發給服務端就好了——這正是VBO的特性之一。你可能會說,既然上面說到顯示列表這麼強大,用顯示列表不就好了?顯示列表的缺點正在於它的古板——一旦設定,就不容許更改,所以它只適合對一些“固定”的東西的繪製進行包裝。(我們無辦法直接在硬件層改頂點數據,因爲這是脫離了流水線的事物。)而VBO直接把頂點數據交到流水線的第一步,與顯示列表的效率還是有差距,但它這樣就得到了操作數據的彈性——渲染階段,我們的VBO繪製函數持續把頂點數據交給流水線,在某一刻我們可以把該幀到達了流水線的頂點數據捏回客戶端修改(Vertex mapping),再提交回流水線(Vertex unmapping),(或者用glBufferData或glBufferSubData重新全部/部分提交更改了的頂點數據,)這是VBO的另一特性。

或者說,VBO結合了VA和顯示列表的這個說法不太妥當,應該說它結合了兩者的一些的特性,繪製效率在兩者之間,且擁有良好的數據更改彈性。這種折衷造就了它一直爲目前“最高”的地位。

3.VBO的使用

VBO的使用一方面跟FBO相似(應該調轉一下主謂),另一方面與VA相似,繪製部分函數的樣子根本就是同一個(畢竟叫作VA的升級版嘛)。以前VBO用不成功,現在用VBO最初也老出問題,其實不是沒掌握好VBO而是沒掌握好VA。的確,頂點數組是我學過而沒怎麼用過的OpenGL功能,一直偏向於使用容易理解的glVertex,glNormal系列,這種傾向希望從今天開始改變吧。

初始化部分:

//mVertexBufferObject爲VBO對象ID
    GLsizeiptr VertDataSize = XSCALE * ZSCALE * sizeof(CVector3);
    glGenBuffers(1, &mVertexBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, VertDataSize, Waterpos, GL_STREAM_DRAW);

    GLsizeiptr TexCoordDataSize = XSCALE * ZSCALE * sizeof(TexCoord);
    glGenBuffers(1, &mTexCoordBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, mTexCoordBufferObject);
    glBufferData(GL_ARRAY_BUFFER, TexCoordDataSize, Watertex, GL_STREAM_DRAW);
    
    GLsizeiptr NormalDataSize = XSCALE * ZSCALE * sizeof(CVector3);
    glGenBuffers(1, &mNormalBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, mNormalBufferObject);
    glBufferData(GL_ARRAY_BUFFER, NormalDataSize, Waternorm, GL_STREAM_DRAW);

跟FBO[學一學,FBO]很像吧。其他頂點屬性(包括頂點位置glVertex,頂點法線glNormal,頂點顏色glColor,頂點紋理座標glTexCoord,頂點霧座標等等,或者shader的attribute屬性變量)也是類似的。其中,Waterpos等儲存了具體的數據(注意數據存放次序呀,一般栽會栽在這裏)。GL_STATIC_DRAW,GL_STREAM_DRAW,GL_DYNAMIC_DRAW用於給OpenGL系統提醒:預期數據是一直不變、數據每幀變一次或幾幀變一次、數據每幀變兩三次以上,方便硬件內部優化吧。

渲染部分:

//
    glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferObject);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, mNormalBufferObject);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 0, 0);
          ........

       glDrawArrays(GL_QUADS, 0, VertexCount);

          .........
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

簡直就是VA!對吧(雖然我沒什麼感覺)。記得前後要對VBO進行綁定和解綁哦,跟FBO一樣的流程了。

4.數據更改的方式

其實前面也提到了,就是三種:1.重新對VBO對象進行綁定,用glBufferData把新數據交給VBO。它在當前幀執行,會把當前VBO內容清空,放入新數據後繼續;2.glBufferSubData只更新VBO中部分數據,但是如果當前數據正在進行中,它會等待數據全部發送完畢,然後待把某部分改好了再全部發送;3.glMapBuffer - glUnMapBuffer,如上所述,數據會全傳回來再傳過去。這裏是參考資料的一斷代碼,可參考:

glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);
glBufferData(GL_ARRAY_BUFFER, ColorSize, NULL, GL_STREAM_DRAW);
GLvoid* ColorBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
glBufferData(GL_ARRAY_BUFFER, PositionSize, NULL, GL_STREAM_DRAW);
GLvoid* PositionBuffer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

memcpy(ColorBuffer, ColorData, ColorSize);
memcpy(PositionBuffer, PositionData, PositionSize);

glBindBuffer(GL_ARRAY_BUFFER, BufferName[COLOR_OBJECT]);
glUnmapBuffer(GL_ARRAY_BUFFER);
glColorPointer(3, GL_UNSIGNED_BYTE, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, BufferName[POSITION_OBJECT]);
glUnmapBuffer(GL_ARRAY_BUFFER);
glVertexPointer(2, GL_FLOAT, 0, 0);

對本文有問題可在我的網站www.zwqxin.com一起討論討論?本文參考資料:OpenGL Vertex Buffer ObjectsOpenGL Vertex Buffer Object(VBO)

對VBO的初步學習先到此吧。接下來我會根據自己實際應用的經驗,談談怎麼控制頂點數據中頂點順序的問題,構造網格不能簡單使用glDrawArrays,而應該通過建立頂點索引,用GL_ELEMENT_ARRAY_BUFFER下的glDrawElements繪製;同時談談對於紋理座標這種一頂點可能存在多個的問題(多重紋理),怎樣設置VBO的問題。並可能簡單介紹Interleaved arrays 、Serialized arrays這類用法。

好吧。見下篇文章吧:[索引頂點的VBO與多重紋理下的VBO]

本文來源於 ZwqXin (http://www.zwqxin.com/), 轉載請註明
      原文地址:http://www.zwqxin.com/archives/opengl/learn-vbo.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章