[教學視頻]計算機圖形學基礎 -在線學習教程
*原創文章轉載請註明出處*
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); |
爲了將頂點數據和索引數據能放到對應的緩存中,這裏定義了一個保存兩個緩存id的BufferName數組。然後使用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中提供了glVertexPointer和glNormalPointer分別來管理不同的數據。於是我們看了上面這樣的代碼。
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中可以使用glGenList,glNewList和glCallList函數,在同樣環境下運行程序,渲染幀數有了明顯改善,達到平均16FPS的水平,雖然和採用glVertex的方法比性能提高了16倍,但是仍然達不到實時渲染的要求。最後採用VBO的方法,在相同環境下運行程序,這次幀數到達了60FPS,約爲採用Display List方法的4倍,完全可以達到實時渲染的要求。從試驗中可以看到,採用VBO能夠明顯提高性能。
*原創文章轉載請註明出處*