VBO(Vertex Buffer Object)頂點緩衝區對象,是OpenGL ES 2.0之後一個可使用的功能。表示存在於顯存中的一個對象,用於存儲頂點座標、紋理座標等相關信息。如果不使用VBO則是將頂點、紋理座標等數據存放在內存中,在每次繪製之前通過通IO將其傳遞到顯存中,這樣做的缺點是顯而易見的,每次傳遞的數據相同還要花費同樣的IO開銷,所以繪製這些數據不發生變化的物體儘量使用VBO方式,提升性能。下面是使用傳統方法繪製的代碼
首先獲取頂點座標數據
public void initVertexData(float[] vertices) {
// 頂點座標數據的初始化
mCount = vertices.length / 3;
// 創建頂點座標數據緩衝區 vertices.length*4是因爲一個整數四個字節
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder()); //設置字節順序
mVertexBuffer = vbb.asFloatBuffer(); //轉換爲Float型緩衝
mVertexBuffer.put(vertices); //向緩衝區中放入頂點座標數據
mVertexBuffer.position(0); //設置緩衝區起始位置
}
每次繪製一幀該物體時調用
public void draw()
{
// 制定使用某套着色器程序
GLES20.glUseProgram(mProgram);
// 將最終變換矩陣傳入着色器程序
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
// 將頂點位置數據傳入渲染管線
GLES20.glVertexAttribPointer
(
mPositionHandle,
3,
GLES30.GL_FLOAT,
false,
3 * 4,
mVertexBuffer
);
// 啓用頂點位置數據
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 繪製加載的物體
GLES20.glDrawArrays(GLES30.GL_TRIANGLES, 0, mCount);
}
可以看到,每次繪製時都需要將頂點位置數據傳入渲染管線
如果使用VBO來存放這些數據(頂點座標、紋理座標、法線等),這些數據將存放在顯存中,這樣做每次需要繪製物體時,只需要將顯存中的數據信息進行綁定就可以完成繪製,通俗的來說,就是告訴繪製管線,這塊顯存裏面放的頂點位置數據,那塊放的法線數據,這樣就減少了IO消耗,下面是具體的實現代碼:
獲取頂點座標數據
// 初始化頂點數據的方法
public void initVertexData(float[] vertices, float[] normals, float texCoors[])
{
// 緩衝id數組
int[] buffIds = new int[3];
// 生成3個緩衝id
GLES20.glGenBuffers(3, buffIds, 0);
// 頂點座標數據緩衝 id
mVertexBufferId = buffIds[0];
// 頂點座標數據的初始化
mCount = vertices.length / 3;
// 創建頂點座標數據緩衝 vertices.length * 4 是因爲一個整數四個字節
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());//設置字節順序
mVertexBuffer = vbb.asFloatBuffer();//轉換爲Float型緩衝
mVertexBuffer.put(vertices);//向緩衝區中放入頂點座標數據
mVertexBuffer.position(0);//設置緩衝區起始位置
// 特別提示:由於不同平臺字節順序不同數據單元不是字節的一定要經過ByteBuffer
// 轉換,關鍵是要通過ByteOrder設置nativeOrder(),否則有可能會出問題
// 綁定到頂點座標數據緩衝
GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVertexBufferId);
// 向頂點座標數據緩衝送入數據
GLES20.glBufferData(GLES30.GL_ARRAY_BUFFER, vertices.length * 4, mVertexBuffer, GLES20.GL_STATIC_DRAW);
// 頂點座標數據的初始化
// 此處省略了頂點法向量數據和頂點紋理座標數據的操作。
// 綁定到系統默認緩衝
GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0);
}
每繪製一幀該物體時調用
public void draw(int texId)
{
// 指定使用某套着色器程序
GLES20.glUseProgram(mProgram);
// 將最終變換矩陣傳入渲染管線
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
// 啓用頂點位置數據
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 綁定到頂點座標數據緩衝
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferId);
// 將頂點位置數據送入渲染管線
GLES20.glVertexAttribPointer
(
mPositionHandle,
3,
GLES20.GL_FLOAT,
false,
3 * 4,
0
);
// 省略了頂點法線和頂點紋理座標的操作
// 綁定到系統默認緩衝
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
// 繪製加載的物體
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mCount);
}
這樣雖然快了不少,但是還有一個問題就是每次還需要將指定的顯存數據與使用它的類型綁定,也就是說這一塊顯存存放的是頂點位置、那一塊放的是紋理數據,這些必須在每次繪製時綁定,這樣每次都需要綁定也很不方便,所以就有了VAO(Vertex Array Object)頂點數組對象,VAO使用一個數組存儲每個VBO存儲的數據、類型,每次繪製時就不需要那樣一個一個的傳遞了,下面是VAO的代碼
public void initVAO()
{
int[] vaoIds = new int[1];
// 生成VAO
GLES20.glGenVertexArrays(1, vaoIds, 0);
mVaoId = vaoIds[0];
// 綁定VAO
GLES20.glBindVertexArray(mVaoId);
// 啓用頂點位置、法向量、紋理座標數據
GLES20.glEnableVertexAttribArray(mPositionHandle);
// 綁定到頂點座標數據緩衝
GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVertexBufferId);
// 將頂點位置數據送入渲染管線
GLES20.glVertexAttribPointer
(
mPositionHandle,
3,
GLES20.GL_FLOAT,
false,
3 * 4,
0
);
// 綁定到系統默認緩衝
GLES20.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
GLES20.glBindVertexArray(0);
}
使用VAO之後的每次繪製
public void draw(int texId)
{
// 指定使用某套着色器程序
GLES20.glUseProgram(mProgram);
// 將最終變換矩陣傳入渲染管線
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
GLES20.glBindVertexArray(mVaoId);
// 繪製加載的物體
GLES20.glDrawArrays(GLES30.GL_TRIANGLES, 0, mCount);
GLES20.glBindVertexArray(0);
}