GL_ARB_vertex_buffer_object擴展致力於提供頂點數組與顯示列表的優點來提高OpenGL效率,同時避免它們實現上的不足。頂點緩存對象(VBO)准許頂點數組數據存放在服務端的高性能顯卡內存中,且提供高效數據傳輸。若是緩存對象用於保存像素數據,就被稱爲像素緩存對象(PBO)。html
使用頂點數組能夠下降函數調用次數與下降共享頂點的重複使用。然而,頂點數組的不足之處是頂點數組函數處在客戶端狀態中,且每次引用都須向服務端從新發送數據。數組
此外,顯示列表爲服務端函數,所以,它並不受限於數據傳輸的開銷。不過,一旦顯示列表編譯完成,顯示列表中的數據不可以修改。緩存
頂點緩存對象(VBO)在服務器端高性能內存中爲頂點屬性建立「緩存對象」,而且提供引用這些數組的訪問函數,這些函數與頂點數組中使用的函數相同,如glVertexPointer()、glNormalPointer()、glTexCoordPointer()等。安全
頂點緩存對象中的內存管理根據用戶提示(「target」與「Usage」模式)將緩存對象放置在最合適內存位置。所以,內存管理可以經過在系統、AGP與顯卡內存三種內存之間作出平衡的方式優化緩存。服務器
與顯示列表不一樣,能夠經過映射緩存到客戶端內存空間的方式讀取與更新頂點緩存對象中的數據。函數
VBO的另外一個重要優點是就像顯示列表與紋理那樣能夠在多個客戶端共享緩存數據。由於VBO處在服務器端,多個客戶端能夠經過相應的標示符訪問同一個緩存。性能
建立VBO須要3個步驟:優化
glGenBuffers()建立緩存對象而且返回緩存對象的標示符。它須要2個參數:第一個爲須要建立的緩存數量,第二個爲用於存儲單一ID或多個ID的GLuint變量或數組的地址。ui
void glGenBuffers(GLsizei n, GLuint* buffers)
當緩存對象建立以後,在使用緩存對象以前,咱們須要將緩存對象鏈接到相應的緩存上。glBindBuffer()有2個參數:target與buffer。指針
void glBindBuffer(GLenum target, GLuint buffer)
target告訴VBO該緩存對象將保存頂點數組數據仍是索引數組數據:GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。任何頂點屬性,如頂點座標、紋理座標、法線與顏色份量數組都使用GL_ARRAY_BUFFER。用於glDraw[Range]Elements()的索引數據須要使用GL_ELEMENT_ARRAY綁定。注意,target標誌幫助VBO肯定緩存對象最有效的位置,若有些系統將索引保存AGP或系統內存中,將頂點保存在顯卡內存中。
當第一次調用glBindBuffer(),VBO用0大小的內存緩存初始化該緩存,而且設置VBO的初始狀態,如用途與訪問屬性。
當緩存初始化以後,你可使用glBufferData()將數據拷貝到緩存對象。
void glBufferData(GLenum target,GLsizeiptr size, const GLvoid* data, GLenum usage);
第一個參數target能夠爲GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY。size爲待傳遞數據字節數量。第三個參數爲源數據數組指針,如data爲NULL,則VBO僅僅預留給定數據大小的內存空間。最後一個參數usage標誌位VBO的另外一個性能提示,它提供緩存對象將如何使用:static、dynamic或stream、與read、copy或draw。
VBO爲usage標誌指定9個枚舉值:
GL_STATIC_DRAW GL_STATIC_READ GL_STATIC_COPY GL_DYNAMIC_DRAW GL_DYNAMIC_READ GL_DYNAMIC_COPY GL_STREAM_DRAW GL_STREAM_READ GL_STREAM_COPY
」static「表示VBO中的數據將不會被改動(一次指定屢次使用),」dynamic「表示數據將會被頻繁改動(反覆指定與使用),」stream「表示每幀數據都要改變(一次指定一次使用)。」draw「表示數據將被髮送到GPU以待繪製(應用程序到GL),」read「表示數據將被客戶端程序讀取(GL到應用程序),」copy「表示數據可用於繪製與讀取(GL到GL)。
注意,僅僅draw標誌對VBO有用,copy與read標誌對頂點/幀緩存對象(PBO或FBO)更有意義,如GL_STATIC_DRAW與GL_STREAM_DRAW使用顯卡內存,GL_DYNAMIC使用AGP內存。_READ_相關緩存更適合在系統內存或AGP內存,由於這樣數據更易訪問。
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
與glBufferData()相似,glBufferSubData()用於向VBO中拷貝數據,不過它僅僅將從給定offset開始的必定範圍的數據替換到現存緩存中。(在使用glBufferSubData()以前,整個緩存必須由glBufferData()指定。)
void glDrawBuffers(GLsizei n, const GLenum* bufs);
在VBO再也不使用時,你可使用glDeleteBuffers()刪除一個VBO或多個VBO。在混存對象刪除以後,它的內容將丟失。
下列代碼是爲頂點座標建立單一VBO的實例。注意,在你拷貝數據到VBO以後,你能夠應用程序中爲頂點數組分配的內存。
GLuint vboId; // VBO標示符 GLfloat* vertices = new GLfloat[vCount*3]; // 建立頂點數組 ... // 建立新的VBO並獲取相關標示符 glGenBuffers(1, &vboId); // 綁定VBO以待使用 glBindBuffer(GL_ARRAY_BUFFER_ARB, vboId); // 更新數據到VBO glBufferData(GL_ARRAY_BUFFER_ARB, dataSize, vertices, GL_STATIC_DRAW_ARB); // 在拷貝數據到VBO以後,能夠安全刪除 delete [] vertices; ... // 程序終止時刪除VBO glDeleteBuffers(1, &vboId);
因爲VBO基於現有的頂點數組實現之上,渲染VBO與使用頂點數組渲染幾乎一致。僅有的不一樣是頂點數組指針如今做爲當前綁定緩存對象的偏移值。所以,繪製VBO時除了glBindBuffer()以外不需別的API。
// 爲頂點數組與索引數組綁定VBO glBindBuffer(GL_ARRAY_BUFFER_ARB, vboId1); // 頂點座標 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, vboId2); // 索引座標 // 除了指針都與頂點數組一致 glEnableClientState(GL_VERTEX_ARRAY); // 開啓頂點座標數組 glVertexPointer(3, GL_FLOAT, 0, 0); // 最後一個參數爲offset,而非ptr // 使用索引數組偏移繪製6個四邊形 glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, 0); glDisableClientState(GL_VERTEX_ARRAY); // 禁用頂點數組 // 用0綁定,所以,切換到標準指針操做 glBindBuffer(GL_ARRAY_BUFFER_ARB, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
使用0綁定緩存對象將關閉VBO操做。在使用完VBO以後,最好將之關閉,所以具備絕對指針的頂點數組操做將從新開啓。
VBO相對於顯示列表的優點在於客戶端能夠讀取與編輯緩存對象數據,而顯示列表不能這樣作。更新VBO的最簡便方法是使用glBufferData()或glBufferSubData()將新數據從新拷貝到綁定的VBO中。這種狀況下,你的應用程序須要始終擁有一個有效的頂點數組。也就是說,你必須始終擁有2份頂點數據:一份在應用程序中,另外一份在VBO中。
修改緩存對象的另外一個方式是將緩存對象映射到客戶端內存,客戶端可使用映射緩存的指針更新數據。下文描述如何將VBO映射到客戶端內存以及如何訪問映射數據。
VBO提供glMapBuffer()以將緩存對象綁定到客戶端內存。
void* glMapBuffer(GLenum target, GLenum access);
若是OpenGL可以將緩存對象映射到客戶端地址空間,glMapBuffer()返回指向緩存的指針。不然它返回NULL。
第一個參數target早在glBindBuffer()中涉及,第二個參數access標誌指定怎樣使用映射數據:讀取、改寫或二者都。
GL_READ_ONLY GL_WRITE_ONLY GL_READ_WRITE
注意,glMapBuffer()引發同步問題。若是GPU任然工做於該緩存對象,glMapBuffer()將一直等待直到GPU結束對應緩存對象上的工做。
爲了不等待,你能夠首先使用NULL調用glBufferData(),而後再調用glMapBuffer()。這樣,前一個數據將被丟棄且glMapBuffer()當即返回一個新分配的指針,即便GPU任然工做於前一個數據。
然而,因爲你丟棄了前一個數據,這種方法只有在你想更新整個數據集的時候纔有效。若是你僅僅但願更改部分數據或讀取數據,你最好不要釋放先前的數據。
GLboolean glUnmapBuffer(GLenum target)
在完成VBO數據的修改以後,必須將緩存對象從客戶端內存解除映射。若是成功,glUnmapBuffer()返回GL_TRUE。如返回GL_FALSE,綁定以後的VBO緩存內容是壞的。腐壞現象源自顯示器分辨率的改變或窗口系統的特定事件。此種狀況,數據必須重發。
下面是使用綁定方式改變VBO的實例代碼。
// 綁定而後映射該VBO glBindBuffer(GL_ARRAY_BUFFER_ARB, vboId); float* ptr = (float*)glMapBuffer(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB); // 若是指針爲空(映射後的),更新VBO if (ptr) { updateMyVBO(ptr, ...); // 修改緩存數據 glUnmapBufferARB(GL_ARRAY_BUFFER_ARB); // 使用以後解除映射 } // 你能夠繪製更新後的VBO ...
該實例程序沿法線防線建立VBO抖動。它映射VBO而且使用指向映射緩存的指針每幀更新一次頂點數據。你能夠與傳統頂點數組實現方式繼進行性能對比。
它使用2個頂點緩存:一個爲了頂點座標與法向量,另外一個僅僅保存索引數組。
下載源文件與二進制文件:vbo.zip,vboSimple.zip。
vboSimple是使用VBO與頂點數組繪製立方體的簡單例子。你能夠很容易看出VBO與VA的相同點與不一樣點。