OpenGL Vertex Buffer Objects(VBOs)

[教學視頻]計算機圖形學基礎 -在線學習教程


*原創文章轉載請註明出處*

 

OpenGL Vertex Buffer Objects(VBOs)

 

Vertex Buffer Objects(VBOs)是一組保存在顯存中的數據,這些數據可以是頂點,頂點顏色,頂點法線,頂點索引或貼圖座標等等。由於這些數據都是保存在顯存中的,而且可以隨時修改數據或整塊替換數據,這樣就極大的提高了顯卡的工作效率和渲染的速度。VBO的概念類似於D3D中的頂點緩衝和索引緩衝的概念。這篇文章將通過實際的例子來說明在openGL中如何使用VBOs,並且和使用傳統的glVertex()函數定義頂點和使用glCallList()函數進行比較。

 

現在要渲染一個模型,首先定義該模型的頂點數組。

 

struct myVertex

{

      GLfloat x,y,z;      // vertex

      GLfloat nx,ny,nz;   // noraml

};

 

這裏定義了一個結構體數據,包括頂點和法線,它們都是浮點型的,所以該結構體一共佔用4*6=24字節的空間。有了頂點結構體後,爲了提供頂點間連接的信息,我們還需要定義索引數組。

 

myVertex *vertexData;

GLuint *indexData;

 

這裏使用無符號的整型定義了一個定點數組的指針,myVertex *vertexData是用我們定義的頂點結構體定義了一個頂點數據指針。在使用VBO之前,我們先將數據初始化到頂點數組和索引數組中。假設現在頂點數據和索引數組都有數據,現在就可以使用VBO了。使用VBO和使用其他openGL的一些對象差不多,使用前都要先申請和創建。使用VBO也要先創建對象。

 

GLuint BufferName[2];

glGenBuffers(2, BufferName);

 

爲了將頂點數據和索引數據能放到對應的緩存中,這裏定義了一個保存兩個緩存idBufferName數組。然後使用glGenBuffers()函數申請2個緩存id。申請到id後立即爲要使用的緩存分配空間和初始化。

 

glBindBuffer(GL_ARRAY_BUFFER, BufferName[0]);

glBufferData(GL_ARRAY_BUFFER, vertexSize, vertexData, GL_STATIC_DRAW);

 

glVertexPointer(3, GL_FLOAT,24,0);

glNormalPointer(GL_FLOAT, 24, (GLvoid*)12);

 

上面的代碼中,glBindBuffer表示綁定一個要使用的buffer對象,該函數有2個參數,該函數的原型爲

 

void glBindBuffer(GLenum target, GLuint buffer);

 

 

參數target表示buffer的類型,參數buffer表示id。第一個buffer裏要保存頂點數據,所以指定爲GL_ARRAY_BUFFER爲即可。綁定完一個buffer對象後,然後讓這個buffer關聯到數據上。這裏使用函數glBufferData該函數爲綁定的buffer指定要放入的數據,它的原型爲

 

void glBufferData(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);

 

同樣參數target表示使用的buffer的類型, size的類型是GLsizeiptr,該類型表示一個指向size的一個指針,在使用之前,先要求得放入buffer中數據的大小。

 

GLsizeiptr vertexSize = number_of_vertex * sizeof(myVertex);

 

可以通過上面的代碼簡單的求出頂點數據共佔用的顯存空間。data表示要放到該buffer中的頂點數組的指針,usage表示用法,這裏指定爲GL_STATIC_DRAW表示該buffer只能修改一次,但可多次讀取。

 

現在已經在頂點緩存中放入了頂點數據,但是顯卡是不知道這些數據中哪些是頂點,哪些是法線等等。於是我們還要告訴顯卡哪些數據是用來幹什麼的。同樣openGL中提供了glVertexPointerglNormalPointer分別來管理不同的數據。於是我們看了上面這樣的代碼。

 

glVertexPointer(3, GL_FLOAT,24,0);

glNormalPointer(GL_FLOAT, 24, (GLvoid*)12);

 

函數glVertexPointer中,第一個參數表示頂點的維數,比如2維,3維或4維。第二個參數表示頂點的類型,第三個參數表示每隔多少字節頂點數據開始重複,最後一個參數表示頂點開始位置的偏移。由於我們頂點每個是24字節,並且連續保存在顯存中,於是每隔24字節就開始重複。函數glNormalPointer中,第一個參數表示法線的數據類型,第二參數還是表示每隔多少字節開始重複,最後一個參數表示法線開始位置的偏移量。由於在頂點數據中前12字節是頂點座標,後12個字節纔是法線,於是法線數據開發的偏移量就是12個字節。我們可以通過下面的圖清楚的看到這些數據之間的關係。

 

 

Fig1 頂點數據在顯存中的存儲

 

頂點數據處理完後,接下來就是頂點索引數據了。和頂點數據一樣,要使用索引緩存,也要先綁定索引到buffer中。

 

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, BufferName[1]);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize, indexData, GL_STATIC_DRAW);

 

使用索引緩存的話,在glBindBuffer函數中,target就要選擇GL_ELEMENT_ARRAY_BUFFER,然後綁定到第二個buffer。之後同樣用函數glBufferData將索引數據放到buffer中。這裏的indexSize也是Glsizeiptr類型。

 

GLsizeiptr indexSize = number_of_face*3*sizeof(GLuint);

 

上面的代碼可以求出索引緩存的大小,由於mesh的一個三角形使用3個頂點索引,所以索引緩存的大小是三角形的個數乘以3再乘以索引數據類型所佔的字節數。

有了所有這些數據後,最後在渲染的時候,我們就可以使用函數glDrawElements繪製對象了。

 

glEnableClientState(GL_VERTEX_ARRAY);

     

glDrawElements(GL_TRIANGLES, number_of_face*3, GL_UNSIGNED_INT,  0);

 

glDisableClientState(GL_VERTEX_ARRAY);

 

繪製前開開啓客戶端處理功能。函數glDrawElements中,第一個參數表示索引那種圖元來連接。第二個參數表示要渲染多少個這種圖元,第三個參數表示索引的數據類型,最後一個參數表示開始索引開始位置的偏移量。

 

Fig2 渲染的模型

 

Fig2中可以看到渲染的一個模型,該模型的頂點數爲219483個,三角形數爲435667個。要渲染這樣一個頂點數有20萬,三角形數有40萬的模型來說,如果用傳統的glVertex函數來設置頂點的話,渲染一幀的畫面就要大約調用該函數3*40=120萬次,如果要達到30FPS的話,那麼每秒要調用函數大約3*40*30 =3600萬次,這樣多的函數調用次數相當耗時。

 

Fig3 各種渲染方法幀數對比

 

現在爲了對比進行試驗,試驗用電腦配置採用Inter Core2 6600處理器,2G內存和NVIDIA GeForce8600GT顯卡。 試驗的結果可以從Fig3中看到,縱軸表示幀數。 實際試驗中發現,使用glVertex函數渲染方法,平均只能達到1FPS,這遠遠低於實時渲染的要求。爲了提高性能,也可以使用Display List,在openGL中可以使用glGenListglNewListglCallList函數,在同樣環境下運行程序,渲染幀數有了明顯改善,達到平均16FPS的水平,雖然和採用glVertex的方法比性能提高了16倍,但是仍然達不到實時渲染的要求。最後採用VBO的方法,在相同環境下運行程序,這次幀數到達了60FPS,約爲採用Display List方法的4倍,完全可以達到實時渲染的要求。從試驗中可以看到,採用VBO能夠明顯提高性能。

 

 

*原創文章轉載請註明出處*

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