[OpenGL ES 06]使用VBO:頂點(diǎn)緩存
[OpenGL ES 06]使用VBO:頂點(diǎn)緩存
羅朝輝 (http://www.rzrgm.cn/kesalin/)
本文遵循“署名-非商業(yè)用途-保持一致”創(chuàng)作公用協(xié)議
這是《OpenGL ES 教程》的第六篇,前五篇請(qǐng)參考如下鏈接:
[OpenGL ES 01]iOS上OpenGL ES之初體驗(yàn)
[OpenGL ES 02]OpenGL ES渲染管線與著色器
[OpenGL ES 03]3D變換:模型,視圖,投影與Viewport
[OpenGL ES 04]3D變換實(shí)踐篇:平移,旋轉(zhuǎn),縮放
[OpenGL ES 05]相對(duì)空間變換及顏色
一,VBO簡(jiǎn)介
在前面幾篇的示例中,都是通過類似如下代碼直接從 CPU 主存中傳遞頂點(diǎn)數(shù)據(jù)到 GPU 中去進(jìn)行運(yùn)算與渲染的。
glVertexAttrib4f(_colorSlot, color[0], color[1], color[2], color[3]);
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices );
glEnableVertexAttribArray(_positionSlot);
glDrawElements(GL_LINES, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);
在上面的代碼中 vertices 和 indices 都是在主存中分配的內(nèi)存空間,當(dāng)需要進(jìn)行渲染時(shí),這些數(shù)據(jù)便通過 glDrawElements 或 glDrawArrays 從 CPU 主存中拷貝到 GPU 中去進(jìn)行運(yùn)算與渲染。這種做法需要頻繁地在 CPU 與 GPU 之間傳遞數(shù)據(jù),效率低下,因此出現(xiàn)了 VBO (Vertex Buffer object),即頂點(diǎn)緩存,它直接在 GPU 中開辟一個(gè)緩存區(qū)域來存儲(chǔ)頂點(diǎn)數(shù)據(jù),因?yàn)樗怯脕砭彺鎯?chǔ)頂點(diǎn)數(shù)據(jù),因此被稱之為頂點(diǎn)緩存。我們只會(huì)在初始化緩沖區(qū),以及在頂點(diǎn)數(shù)據(jù)有變化時(shí)才需要對(duì)該緩沖區(qū)進(jìn)行寫操作。使用頂點(diǎn)緩存能夠大大較少了CPU-GPU 之間的數(shù)據(jù)拷貝開銷,因此顯著地提升了程序運(yùn)行的效率。
今天我們就能學(xué)習(xí) VBO 在 OpenGL ES 中的運(yùn)用,示例程序演示了六種編程實(shí)現(xiàn)的物體,本文源碼:點(diǎn)此查看,其運(yùn)行效果如下:

二,API介紹
1,總覽
OpenGL ES 中通過如下函數(shù)來實(shí)現(xiàn) VBO:
| glGenBuffers | 創(chuàng)建頂點(diǎn)緩存對(duì)象 |
| glBindBuffer | 將頂點(diǎn)緩存對(duì)象設(shè)置為當(dāng)前數(shù)組緩存對(duì)象(array buffer object)或當(dāng)前元素緩存對(duì)象(element buffer object) |
| glBufferData | 為頂點(diǎn)緩存對(duì)象申請(qǐng)內(nèi)存空間,并進(jìn)行初始化(視傳入的參數(shù)而定) |
| glBufferSubData | 初始化或更新頂點(diǎn)緩存對(duì)象 |
| glDeleteBuffers | 刪除頂點(diǎn)緩存對(duì)象 |
2,創(chuàng)建頂點(diǎn)緩存對(duì)象
void glGenBuffers (GLsizei n, GLuint* buffers);
參數(shù) n : 表示需要?jiǎng)?chuàng)建頂點(diǎn)緩存對(duì)象的個(gè)數(shù);
參數(shù) buffers :用于存儲(chǔ)創(chuàng)建好的頂點(diǎn)緩存對(duì)象句柄;
同第一篇文章《[OpenGL ES 01]OpenGL ES之初體驗(yàn)》中的講的 render buffer 對(duì)象句柄一樣,在這里,頂點(diǎn)緩存對(duì)象句柄始終是大于 0 的正整數(shù),0 是 OpenGL ES 保留。該函數(shù)能夠一次產(chǎn)生多個(gè)頂點(diǎn)緩存對(duì)象。
3,將頂點(diǎn)緩存對(duì)象設(shè)置為(或曰綁定到)當(dāng)前數(shù)組緩存對(duì)象或元素緩存對(duì)象
void glBindBuffer (GLenum target, GLuint buffer);
參數(shù) target :指定綁定的目標(biāo),取值為 GL_ARRAY_BUFFER(用于頂點(diǎn)數(shù)據(jù)) 或 GL_ELEMENT_ARRAY_BUFFER(用于索引數(shù)據(jù));
參數(shù) buffer :頂點(diǎn)緩存對(duì)象句柄;
4,為頂點(diǎn)緩存對(duì)象分配空間
void glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
參數(shù) target:與 glBindBuffer 中的參數(shù) target 相同;
參數(shù) size :指定頂點(diǎn)緩存區(qū)的大小,以字節(jié)為單位計(jì)數(shù);
data :用于初始化頂點(diǎn)緩存區(qū)的數(shù)據(jù),可以為 NULL,表示只分配空間,之后再由 glBufferSubData 進(jìn)行初始化;
usage :表示該緩存區(qū)域?qū)?huì)被如何使用,它的主要目的是用于提示OpenGL該對(duì)該緩存區(qū)域做何種程度的優(yōu)化。其參數(shù)為以下三個(gè)之一:
GL_STATIC_DRAW:表示該緩存區(qū)不會(huì)被修改;
GL_DyNAMIC_DRAW:表示該緩存區(qū)會(huì)被周期性更改;
GL_STREAM_DRAW:表示該緩存區(qū)會(huì)被頻繁更改;
如果頂點(diǎn)數(shù)據(jù)一經(jīng)初始化就不會(huì)被修改,那么就應(yīng)該盡量使用 GL_STATIC_DRAW,這樣能獲得更好的性能。
5,更新頂點(diǎn)緩沖區(qū)數(shù)據(jù)
void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
參數(shù) :offset 表示需要更新的數(shù)據(jù)的起始偏移量;
參數(shù) :size 表示需要更新的數(shù)據(jù)的個(gè)數(shù),也是以字節(jié)為計(jì)數(shù)單位;
data :用于更新的數(shù)據(jù);
6,釋放頂點(diǎn)緩存
void glDeleteBuffers (GLsizei n, const GLuint* buffers);
參數(shù)與 glGenBuffers 類似,就不再累述,該函數(shù)用于刪除頂點(diǎn)緩存對(duì)象,釋放頂點(diǎn)緩存。
三,多面手:glVertexAttribPointer 和 glDrawElements
在介紹如何使用 VBO 進(jìn)行渲染之前,我們先來回顧一下之前使用頂點(diǎn)數(shù)組進(jìn)行渲染用到的函數(shù):
void glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr);
參數(shù) index :為頂點(diǎn)數(shù)據(jù)(如頂點(diǎn),顏色,法線,紋理或點(diǎn)精靈大小)在著色器程序中的槽位;
參數(shù) size :指定每一種數(shù)據(jù)的組成大小,比如頂點(diǎn)由 x, y, z 3個(gè)組成部分,紋理由 u, v 2個(gè)組成部分;
參數(shù) type :表示每一個(gè)組成部分的數(shù)據(jù)格式;
參數(shù) normalized : 表示當(dāng)數(shù)據(jù)為法線數(shù)據(jù)時(shí),是否需要將法線規(guī)范化為單位長(zhǎng)度,對(duì)于其他頂點(diǎn)數(shù)據(jù)設(shè)置為 GL_FALSE 即可。如果法線向量已經(jīng)為單位長(zhǎng)度設(shè)置為 GL_FALSE 即可,這樣可免去不必要的計(jì)算,提升效率;
stride : 表示上一個(gè)數(shù)據(jù)到下一個(gè)數(shù)據(jù)之間的間隔(同樣是以字節(jié)為單位),OpenGL ES根據(jù)該間隔來從由多個(gè)頂點(diǎn)數(shù)據(jù)混合而成的數(shù)據(jù)塊中跳躍地讀取相應(yīng)的頂點(diǎn)數(shù)據(jù);
ptr :值得注意,這個(gè)參數(shù)是個(gè)多面手。如果沒有使用 VBO,它指向 CPU 內(nèi)存中的頂點(diǎn)數(shù)據(jù)數(shù)組;如果使用 VBO 綁定到 GL_ARRAY_BUFFER,那么它表示該種類型頂點(diǎn)數(shù)據(jù)在頂點(diǎn)緩存中的起始偏移量。
那 GL_ELEMENT_ARRAY_BUFFER 表示的索引數(shù)據(jù)呢?那是由以下函數(shù)使用的:
void glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);
參數(shù) mode :表示描繪的圖元類型,如:GL_TRIANGLES,GL_LINES,GL_POINTS;
參數(shù) count : 表示索引數(shù)據(jù)的個(gè)數(shù);
參數(shù) type : 表示索引數(shù)據(jù)的格式,必須是無符號(hào)整形值;
indices :這個(gè)參數(shù)也是個(gè)多面手,如果沒有使用 VBO,它指向 CPU 內(nèi)存中的索引數(shù)據(jù)數(shù)組;如果使用 VBO 綁定到 GL_ELEMENT_ARRAY_BUFFER,那么它表示索引數(shù)據(jù)在 VBO 中的偏移量。
四,使用示例
在今天的示例中,我借用《iPhone 3D Programming》中創(chuàng)建可編程3維物體的部分代碼來創(chuàng)建3維物體的頂點(diǎn)以及索引,在這里就略去這部分的介紹,有興趣研究的同學(xué)可以查看源碼。在這里就只講與頂點(diǎn)緩存相關(guān)的部分代碼。
首先是創(chuàng)建頂點(diǎn)緩存對(duì)象,分配空間并初始化:
// Create the VBO for the vertice.
//
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, vBufSize * sizeof(GLfloat), vbuf, GL_STATIC_DRAW);
// Create the VBO for the line indice
//
GLuint lineIndexBuffer;
glGenBuffers(1, &lineIndexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lineIndexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, lineIndexCount * sizeof(GLushort), lineBuf, GL_STATIC_DRAW);
// Create the VBO for the triangle indice
//
GLuint triangleIndexBuffer;
glGenBuffers(1, &triangleIndexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangleIndexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangleIndexCount * sizeof(GLushort), triangleBuf, GL_STATIC_DRAW);
然后,使用 VBO 進(jìn)行渲染:
- (void)drawSurface
{
if (_currentVBO == nil)
return;
glBindBuffer(GL_ARRAY_BUFFER, [_currentVBO vertexBuffer]);
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, [_currentVBO vertexSize] * sizeof(GLfloat), 0);
glEnableVertexAttribArray(_positionSlot);
// Draw the red triangles.
//
glVertexAttrib4f(_colorSlot, 1.0, 0.0, 0.0, 1.0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, [_currentVBO triangleIndexBuffer]);
glDrawElements(GL_TRIANGLES, [_currentVBO triangleIndexCount], GL_UNSIGNED_SHORT, 0);
// Draw the black lines.
//
glVertexAttrib4f(_colorSlot, 0.0, 0.0, 0.0, 1.0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, [_currentVBO lineIndexBuffer]);
glDrawElements(GL_LINES, [_currentVBO lineIndexCount], GL_UNSIGNED_SHORT, 0);
glDisableVertexAttribArray(_positionSlot);
}
由于本示例可描繪 6 個(gè)3維幾何物體,因此 _currentVBO 表示當(dāng)前描繪的幾何物體,這是一個(gè) DrawableVBO 對(duì)象。DrawableVBO 類聲明如下:
@interface DrawableVBO : NSObject @property (nonatomic, assign) GLuint vertexBuffer; @property (nonatomic, assign) GLuint lineIndexBuffer; @property (nonatomic, assign) GLuint triangleIndexBuffer; @property (nonatomic, assign) int vertexSize; @property (nonatomic, assign) int lineIndexCount; @property (nonatomic, assign) int triangleIndexCount; - (void) cleanup; @end
它包含一個(gè)用于頂點(diǎn)數(shù)據(jù)的頂點(diǎn)緩存對(duì)象 vertexBuffer 和兩個(gè)用于索引數(shù)據(jù)的頂點(diǎn)緩存對(duì)象 lineIndexBuffer 和 triangleIndexBuffer,這些對(duì)象都是通過前面的創(chuàng)建頂點(diǎn)緩存對(duì)象部分代碼生成的。vertexSize 表示頂點(diǎn)數(shù)據(jù)的大小,而 lineIndexCount 和 triangleIndexCount 表示索引數(shù)據(jù)的個(gè)數(shù)。方法 cleanup 是用于清理頂點(diǎn)緩存對(duì)象,其實(shí)現(xiàn)如下:
- (void) cleanup
{
if (vertexBuffer != 0) {
glDeleteBuffers(1, &vertexBuffer);
vertexBuffer = 0;
}
if (lineIndexBuffer != 0) {
glDeleteBuffers(1, &lineIndexBuffer);
lineIndexBuffer = 0;
}
if (triangleIndexBuffer) {
glDeleteBuffers(1, &triangleIndexBuffer);
triangleIndexBuffer = 0;
}
}
五,運(yùn)行效果
本示例演示了 6 中不同形狀的可編程幾何物體,并使用 Quaternion 來響應(yīng)手指滑動(dòng)形成的旋轉(zhuǎn)操作。示例運(yùn)行效果如圖所示:

六,練習(xí)作業(yè)
在示例中,是通過編程方式來生成頂點(diǎn)數(shù)據(jù)與索引數(shù)據(jù)。那如果我想用已有頂點(diǎn)數(shù)據(jù)和索引數(shù)據(jù)來使用 VBO,那么該如何做呢?下面提供一個(gè)立方體 cube 的頂點(diǎn)數(shù)據(jù)和索引數(shù)據(jù),看聰明的你能不能修改它,加入本示例中成為第七個(gè)幾何圖形,這個(gè)作業(yè)就留個(gè)你了。
// Cube 頂點(diǎn)數(shù)據(jù)以及索引數(shù)據(jù)
const GLfloat vertices[] = {
-1.5f, -1.5f, 1.5f, -0.577350, -0.577350, 0.577350,
-1.5f, 1.5f, 1.5f, -0.577350, 0.577350, 0.577350,
1.5f, 1.5f, 1.5f, 0.577350, 0.577350, 0.577350,
1.5f, -1.5f, 1.5f, 0.577350, -0.577350, 0.577350,
1.5f, -1.5f, -1.5f, 0.577350, -0.577350, -0.577350,
1.5f, 1.5f, -1.5f, 0.577350, 0.577350, -0.577350,
-1.5f, 1.5f, -1.5f, -0.577350, 0.577350, -0.577350,
-1.5f, -1.5f, -1.5f, -0.577350, -0.577350, -0.577350
};
const GLushort indices[] = {
// Front face
3, 2, 1, 3, 1, 0,
// Back face
7, 5, 4, 7, 6, 5,
// Left face
0, 1, 7, 7, 1, 6,
// Right face
3, 4, 5, 3, 5, 2,
// Up face
1, 2, 5, 1, 5, 6,
// Down face
0, 7, 3, 3, 7, 4
};
浙公網(wǎng)安備 33010602011771號(hào)