不知道在這里講VBO&VAO是否合適硕并,我的想法是早一點了解這一塊明吩,后面的例子中我們都運用上以加深印象與熟練度。
首先我們知道OpenGL ES部分運行在CPU上哗魂,部分運行在GPU上勇婴,所以O(shè)penGL ES橫跨在兩個處理器之間忱嘹,協(xié)調(diào)兩個內(nèi)存區(qū)域之間的數(shù)據(jù)交換。
對于渲染速度耕渴,最快的數(shù)據(jù)交換方式是沒有數(shù)據(jù)交換拘悦。從一個內(nèi)存區(qū)域賦值數(shù)據(jù)到另一個內(nèi)存速度是相對較慢的。最致命的是橱脸,在內(nèi)存復(fù)制發(fā)生的時候GPU和CPU都不能把內(nèi)存另作它用础米。因此內(nèi)存區(qū)域之間的數(shù)據(jù)交換需要盡量避免。
其次添诉,所有的內(nèi)存訪問都是相對較慢的屁桑,現(xiàn)在的嵌入式CPU可以很容易完成大約每秒一億次的運算,但是它只能每秒讀寫內(nèi)存200萬次吻商。這意味著掏颊,除非CPU能夠在每次從內(nèi)存讀取一塊數(shù)據(jù)后有效地運行五個或者更多個運算,否則處理器的性能就處于次優(yōu)狀態(tài)艾帐,這種狀態(tài)叫做"數(shù)據(jù)饑餓"乌叶。這種情況對于CPU來說更明顯,在理想條件下柒爸,GPU能夠每秒執(zhí)行數(shù)億次運算准浴,但是卻只能每秒訪問內(nèi)存2億次。GPU幾乎總是受限于內(nèi)存訪問的性能捎稚,并且通常需要在每塊數(shù)據(jù)上執(zhí)行10~30次運算才不會影響整體的圖形輸出乐横。(摘自O(shè)penGL ES應(yīng)用開發(fā)實踐)
上面這段話的大概意思是:GPU的性能并不在于數(shù)據(jù)處理求橄,而是在于數(shù)據(jù)讀取上。就前一篇的例子而言葡公,如果要保持矩形一直存在于屏幕上罐农,CPU就需要不斷的發(fā)送頂點數(shù)據(jù)到GPU上,否則的話就會被設(shè)置的清除色覆蓋催什。這就對應(yīng)于激活頂點屬性涵亏,然后發(fā)送數(shù)據(jù)到屬性上,在進(jìn)行繪制的那段代碼:
static GLfloat vertices[] = {
// 第一個三角形
0.5f, 0.5f, 0.0, 1.0 , 0.0, 0.0,
0.5f, -0.5f, 0.0, 1.0 , 0.0, 0.0,
-0.5f, -0.5f, 0.0, 1.0 , 0.0, 0.0,
// 第二個三角形
-0.5, -0.5f, 0.0, 1.0 , 0.0, 0.0,
-0.5f, 0.5f, 0.0, 1.0 , 0.0, 0.0,
0.5f, 0.5f, 0.0, 1.0 , 0.0, 0.0,
};
// 啟用這個著色器程序
glUseProgram(program);
// 啟用著色器屬性和獲取位置
GLuint positionAttribLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(positionAttribLocation);
GLuint colorAttribLocation = glGetAttribLocation(program, "color");
glEnableVertexAttribArray(colorAttribLocation);
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)vertices);
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)vertices + 3 * sizeof(GLfloat));
// 繪制
glDrawArrays(GL_TRIANGLES, 0, 6);
在glkView:(GLKView *)view drawInRect:(CGRect)rect方法中會一直持續(xù)這個過程蒲凶,從CPU傳遞vertices到GPU內(nèi)存气筋,也就是每次渲染都會傳遞一次。雖然這里的繪制數(shù)據(jù)量比較小旋圆,但是到后面繪制復(fù)雜的圖形事宠默,可能會導(dǎo)致數(shù)據(jù)傳遞延遲,這就嚴(yán)重影響了繪制幀率灵巧。
VBO
VBO全稱Vertex Buffer Object -- 頂點緩存對象搀矫。OpenGL ES對于數(shù)據(jù)傳遞效率低下問題提供的辦法是提前傳遞數(shù)據(jù)到GPU內(nèi)存緩存區(qū),繪制的時候GPU就可以非澈⒌龋快速地讀取自己內(nèi)存區(qū)域的數(shù)據(jù)艾君,這樣就可以減少數(shù)據(jù)傳遞次數(shù),避免數(shù)據(jù)傳遞延遲造成的繪制幀率降低問題肄方。而VBO就可以使用頂點緩存能夠大大較少了CPU到GPU 之間的數(shù)據(jù)拷貝開銷,因此顯著地提升了程序運行的效率蹬癌。下面是創(chuàng)建代碼:
// 使用glGenBuffers函數(shù)和一個緩沖ID生成一個VBO對象
glGenBuffers(1, &vertexID);
/*
使用glBindBuffer函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER目標(biāo)上
參數(shù):
target :指定綁定的目標(biāo)权她,取值為 GL_ARRAY_BUFFER(用于頂點數(shù)據(jù)) 或 GL_ELEMENT_ARRAY_BUFFER(用于索引數(shù)據(jù))
buffer :頂點緩存對象句柄
*/
glBindBuffer(GL_ARRAY_BUFFER, vertexID);
/*
調(diào)用glBufferData函數(shù),它會把之前定義的頂點數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中
參數(shù):
target:與 glBindBuffer 中的參數(shù) target 相同逝薪;
size :指定頂點緩存區(qū)的大小隅要,以字節(jié)為單位計數(shù);
data :用于初始化頂點緩存區(qū)的數(shù)據(jù)
usage:表示該緩存區(qū)域?qū)蝗绾问褂枚茫闹饕康氖怯糜趯υ摼彺鎱^(qū)域做何種程度的優(yōu)化
GL_STATIC_DRAW:表示該緩存區(qū)不會被修改
GL_DYNAMIC_DRAW:表示該緩存區(qū)會被周期性更改
GL_STREAM_DRAW:表示該緩存區(qū)會被頻繁更改
*/
glBufferData(GL_ARRAY_BUFFER, 36 * sizeof(GLfloat), vertices, GL_STATIC_DRAW);
數(shù)據(jù)提前緩存到GPU內(nèi)存之后步清,glkView:(GLKView *)view drawInRect:(CGRect)rect方法中就需要進(jìn)行修改了,不能再每次繪制都向GPU發(fā)送頂點數(shù)據(jù)虏肾。因為我們的數(shù)據(jù)已經(jīng)發(fā)送到GPU內(nèi)存中廓啊,現(xiàn)在就是需要告訴GPU如果去識別那些數(shù)據(jù)以及去使用了。
VAO
VAO被稱為頂點數(shù)組對象封豪,它并不存儲數(shù)據(jù)谴轮。它可以像頂點緩沖對象那樣被綁定,任何隨后的頂點屬性調(diào)用都會儲存在這個VAO中吹埠。這樣的好處就是第步,當(dāng)配置頂點屬性指針時疮装,你只需要將那些調(diào)用執(zhí)行一次,之后再繪制物體的時候只需要綁定相應(yīng)的VAO就行了粘都。這使在不同頂點數(shù)據(jù)和屬性配置之間切換變得非常簡單廓推,只需要綁定不同的VAO就行了。剛剛設(shè)置的所有狀態(tài)都將存儲在VAO中翩隧。
一個頂點數(shù)組對象會儲存以下這些內(nèi)容:
- glEnableVertexAttribArray和glDisableVertexAttribArray的調(diào)用樊展。
- 通過glVertexAttribPointer設(shè)置的頂點屬性配置。
- 通過glVertexAttribPointer調(diào)用與頂點屬性關(guān)聯(lián)的頂點緩沖對象鸽心。
下面通過代碼創(chuàng)建一個VAO:
glGenVertexArraysOES(1, &vao);
// 綁定vao
glBindVertexArrayOES(vao);
// 把頂點數(shù)組復(fù)制到緩沖中供OpenGL ES使用
glBindBuffer(GL_ARRAY_BUFFER, vertexID);
GLuint positionAttribLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(positionAttribLocation);
GLuint colorAttribLocation = glGetAttribLocation(program, "color");
glEnableVertexAttribArray(colorAttribLocation);
glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), NULL);
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), NULL + 3 * sizeof(GLfloat));
glBindVertexArrayOES(0);
接下來的繪制就簡單了滚局,通過glBindVertexArrayOES啟用VAO就可以完成繪制。
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1.0, 1.0, 1.0, 1.0);
// 啟用這個著色器程序
glUseProgram(program);
glBindVertexArrayOES(vao);
// 繪制
glDrawArrays(GL_TRIANGLES, 0,6);
現(xiàn)在glkView:(GLKView *)view drawInRect:(CGRect)rect中就這么簡單了顽频。
這里有一點需要說明藤肢,我們現(xiàn)在是在OpenGL ES 2.0上使用VAO,這里就得使用蘋果擴(kuò)展的相關(guān)API糯景,既帶有'OES'的函數(shù):
GLvoid glGenVertexArraysOES(GLsizei n, GLuint *arrays)
GLvoid glBindVertexArrayOES(GLuint array)
GLvoid glDeleteVertexArraysOES(GLsizei n, const GLuint *arrays)
GLboolean glIsVertexArrayOES(GLuint array);
如果我們使用的是ES3嘁圈,那么就就是對應(yīng)的這些函數(shù),照樣可以完成繪制:
GLvoid glGenVertexArrays(GLsizei n, GLuint *arrays)
GLvoid glBindVertexArray(GLuint array)
GLvoid glDeleteVertexArrays(GLsizei n, const GLuint *arrays)
GLboolean glIsVertexArray(GLuint array);
這樣的話蟀淮,頂點數(shù)據(jù)經(jīng)過VBO和VAO的優(yōu)化最住,就不需要每次繪制前進(jìn)行數(shù)據(jù)傳遞和屬性綁定了,大大提高了繪制速度怠惶。
最后的效果圖: