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);
}