在OpenGLES中,VBO与VAO都是一个重要的概念,以前也在书上看到过这些概念,在有了自己的理解之后对其做以总结。
VBO的全称 Vertex Buffer Object顶点缓冲区对象,这是在Opengles2.0以后一个可使用的功能。如果不使用VBO则是将顶点、纹理坐标等数据存放在内存当中,在每次绘制之前通过通IO将其传递到显存当中,这样做的缺点是显而易见的,每次传递相同的数据花费大量的IO开销,所以绘制这些数据不发生变化的物体尽量使用VBO方式,提升性能。下面是使用传统方法绘制的代码:
初始化顶点数据:
//初始化顶点坐标的方法 public void initVertexData(float[] vertices) { //顶点坐标数据的初始化================begin============================ vCount=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 drawSelf() { //制定使用某套着色器程序 GLES30.glUseProgram(mProgram); //将最终变换矩阵传入着色器程序 GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0); // 将顶点位置数据传入渲染管线 GLES30.glVertexAttribPointer ( maPositionHandle, 3, GLES30.GL_FLOAT, false, 3*4, mVertexBuffer ); //启用顶点位置数据 GLES30.glEnableVertexAttribArray(maPositionHandle); //绘制加载的物体 GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount); }这里可以看到,每次绘制时都需要将顶点位置数据传入渲染管线。
如果使用VBO来存放这些数据(顶点位置、纹理坐标、法线等),这些数据将存放在GPU当中(显存),这样做每次需要绘制物体时,只需要将显存中的数据信息进行绑定就可以完成绘制,通俗的来说,就是告诉绘制管线,这块显存里面放的顶点位置数据,那块放的法线数据,这样就减少了IO消耗,下面是具体的实现代码:
初始化顶点数据:
//初始化顶点数据的方法 public void initVertexData(float[] vertices,float[] normals,float texCoors[]) { //缓冲id数组 int[] buffIds=new int[3]; //生成3个缓冲id GLES30.glGenBuffers(3, buffIds, 0); //顶点坐标数据缓冲 id mVertexBufferId=buffIds[0]; //顶点坐标数据的初始化================begin============================ vCount=vertices.length/3; //创建顶点坐标数据缓冲 //vertices.length*4是因为一个整数四个字节 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder());//设置字节顺序 FloatBuffer mVertexBuffer = vbb.asFloatBuffer();//转换为Float型缓冲 mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据 mVertexBuffer.position(0);//设置缓冲区起始位置 //特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer //转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题 //绑定到顶点坐标数据缓冲 GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,mVertexBufferId); //向顶点坐标数据缓冲送入数据 GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertices.length*4, mVertexBuffer, GLES30.GL_STATIC_DRAW); //顶点坐标数据的初始化================end============================ //此处省略了顶点法向量数据和顶点纹理坐标数据的操作。 //绑定到系统默认缓冲 GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0); }每帧绘制该物体时调用:
public void drawSelf(int texId)
{
//指定使用某套着色器程序
GLES30.glUseProgram(mProgram);
//将最终变换矩阵传入渲染管线
GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
//启用顶点位置数据
GLES30.glEnableVertexAttribArray(maPositionHandle);
//绑定到顶点坐标数据缓冲
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,mVertexBufferId);
//将顶点位置数据送入渲染管线
GLES30.glVertexAttribPointer
(
maPositionHandle,
3,
GLES30.GL_FLOAT,
false,
3*4,
0
);
//省略了顶点法线和顶点纹理坐标的操作
//绑定到系统默认缓冲 GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0); //绘制加载的物体 GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount); }
当然这样虽然快了不少,但是还有一个问题就是每次还需要将指定的显存数据与使用它的类型绑定,也就是说这一块显存存放的是顶点位置、那一块放的是纹理数据,这些必须在每次绘制时绑定,这样每次都需要绑定也很不方便,所以就有了VAO(Vertex Array Object)顶点数组对象。
VAO使用一个数组存储每个VBO存储的数据、类型,每次绘制时就不需要那样一个一个的传递了,下面是VAO的代码:
初始化VAO:
public void initVAO() { int[] vaoIds=new int[1]; //生成VAO GLES30.glGenVertexArrays(1, vaoIds, 0); vaoId=vaoIds[0]; //绑定VAO GLES30.glBindVertexArray(vaoId); //启用顶点位置、法向量、纹理坐标数据 GLES30.glEnableVertexAttribArray(maPositionHandle); //绑定到顶点坐标数据缓冲 GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,mVertexBufferId); //将顶点位置数据送入渲染管线 GLES30.glVertexAttribPointer ( maPositionHandle, 3, GLES30.GL_FLOAT, false, 3*4, 0 ); // 绑定到系统默认缓冲 GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0); GLES30.glBindVertexArray(0); }使用VAO之后的每次绘制:
public void drawSelf(int texId) { //指定使用某套着色器程序 GLES30.glUseProgram(mProgram); //将最终变换矩阵传入渲染管线 GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0); GLES30.glBindVertexArray(vaoId); //绘制加载的物体 GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount); GLES30.glBindVertexArray(0); }上面就是我对VBO、VAO的一些理解,后面也会尝试使用WebGL来对其进行实现(WebGL中缺少一些api),如果哪里理解的有偏差,还望指正。