大家好,这是我的OpenGL ES
高级进阶系列文章,在我的github
上有一个与本系列文章对应的项目,欢迎关注,链接:github.com/kenneycode/…
今天给大家介绍VBO(Vertex Buffer Object)
和IBO(Index Buffer Object)
,让我们先从一段代码开始,逐步介绍它们:
// 将三角形顶点数据放入buffer中
// Put the triangle vertex data into the vertexDataBuffer
vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
// 启动对应位置的参数,这里直接使用LOCATION_ATTRIBUTE_POSITION,而无需像OpenGL 2.0那样需要先获取参数的location
// Enable the parameter of the location. Here we can simply use LOCATION_ATTRIBUTE_POSITION, while in OpenGL 2.0 we have to query the location of the parameter
GLES30.glEnableVertexAttribArray(LOCATION_ATTRIBUTE_POSITION)
// 指定a_position所使用的顶点数据
// Specify the data of a_position
GLES30.glVertexAttribPointer(LOCATION_ATTRIBUTE_POSITION, VERTEX_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertexDataBuffer)
复制代码
这段代码大家应该很熟悉了,它的作用就是将顶点数据指定给vertex shader
中的attribute
变量,这里有个细节之前没提到过,就是glVertexAttribPointer()
这个方法指定了顶点数据,但它不会将顶点数据一直存储在显存中,而我们渲染的时候,所有要访问的数据必需在显存中,因此,每次渲染时,OpenGL
会有一个将这些顶点数据从内存复制到显存的操作,这样会带来一些问题:
-
一个是因此每次渲染都要复制一次,因此内存中的顶点数据要一直留着,不然复制的时候就没有数据来复制了。
-
另一个是如果顶点数据量大的时候,每次渲染都做这样的一次复制,性能上会有问题,我们的例子中,顶点算是非常少的,那什么时候顶点会多呢?例如做一些形变效果时,往往会划分网格,一般来说划分得越多,效果越细腻,这时顶点就会很多,另外,在做3D渲染时,通常顶点也很多,单个3D模型甚至可以有上万个顶点。
那有什么办法来避免复制?这时就要用到VBO
,它可以和顶点数据绑定,绑定后的顶点数据是一直存储在显存中的,当需要用这些顶点数据的时候,直接绑定这个VBO
就行了,不会有复制过程。
那IBO
又是什么呢?它和VBO
作用很类似,VBO
是为了避免顶点数据的复制,IBO
是则是为了避免顶点索引数据的复制,什么是顶点索引呢?我们先来看一份顶点数据:
// 三角形顶点数据
// The vertex data of a triangle
private val vertexData = floatArrayOf(
-1f, -1f, // 左下角
-1f, 1f, // 左上角
1f, 1f, // 右上角
-1f, -1f, // 左下角
1f, 1f, // 右上角
1f, -1f // 右下角
)
复制代码
这是一份很普通的顶点数据,在我们的教程中多次用到过,它用2个三角形组成了一个矩形,对应用GL_TRIANGLES
的绘制模式进行渲染(绘制模式可参考我的一篇文章《Android OpenGL ES 2.0 手把手教学(5)- 绘制模式》),我们可以很容易地看到,这6个顶点是有重复的,一个矩形只需要4个顶点就行了,有些点是不同三角形之间共用的,那么如何让不同三角形之间共用?这就要用到顶点索引,它能让我们用索引的方法告诉OpenGL
我们的顶点,而不是每个点都用坐标的方式给出,这样可以减少我们的顶点数据量,这在面片数量较大时很有用。
那么下面我们来看看具体如何使用VBO
和IBO
,先来看看顶点数据和顶点索引数据:
// 三角形顶点、纹理数据
// The vertex data and texture coordinate data of triangles
private val vertexData = floatArrayOf(
-1f, -1f, 0f, 1f, // x, y, u, v
-1f, 1f, 0f, 0f,
1f, 1f, 1f, 0f,
1f, -1f, 1f, 1f
)
复制代码
这里我们将顶点和纹理坐标组合越来,这也是配合VBO
和IBO
的常规优化用法,它的好处让顶点和纹理坐标在存储上靠近,利于OpenGL
取数据,提高性能,特别是在3D渲染时,数据一般都是这样组织的。
接下来用glGenBuffers
创建VBO
和IBO
:
// 创建VBO和IBO
// Create VBO and IBO
val buffers = IntArray(2)
GLES30.glGenBuffers(buffers.size, buffers, 0)
vbo = buffers[0]
ibo = buffers[1]
复制代码
我们可以看到,在创建的时候其实并没有VBO
和IBO
的区别,都是叫buffer
,它们是在真正用的时候才能体现出区别。
下面将顶点的纹理数据加载到VBO
中:
// 将顶点数据载入VBO
// Load vertex data into VBO
val vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo)
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexDataBuffer.capacity() * java.lang.Float.SIZE / 8, vertexDataBuffer, GLES30.GL_STATIC_DRAW)
GLES30.glEnableVertexAttribArray(LOCATION_ATTRIBUTE_POSITION)
GLES30.glEnableVertexAttribArray(LOCATION_ATTRIBUTE_TEXTURE_COORDINATE)
GLES30.glVertexAttribPointer(LOCATION_ATTRIBUTE_POSITION, 2, GLES30.GL_FLOAT, false, 16, 0)
GLES30.glVertexAttribPointer(LOCATION_ATTRIBUTE_TEXTURE_COORDINATE, 2, GLES30.GL_FLOAT, false, 16, 8)
复制代码
主要的关键点是先用glBindBuffer
绑定我们在操作的buffer
,这一点和操作texture
和frame buffer
时很像,操作前都先绑定。接着用glBufferData
给它喂数据,这里最后一个参数用于提示OpenGL
以便于它做一些优化,例如这里我们传了GL_STATIC_DRAW
,即我们的数据是不会变的,还有一些其它的可以设置,如GL_DYNAMIC_DRAW
则提示OpenGL
这个buffer
的数据是会变的。
在glVertexAttribPointer
指定顶点和纹理数据时,我们不再像之前那样,直接把数据的传进来,因为这时我们的数据已经在VBO
中了,这里不需要再传,这里重点关注最后两个参数,倒数第二个参数是指定stride
,即OpenGL
去取一份数据时的跨度,这里因为我们把顶点和纹理数据组合在了一起,因此一份数据是2个顶点和2个纹理坐标,即4个float
,16个字节。倒数第一个参数是指定这个数据在一份数据中的开始位置,因为我们在一份数据中是先放顶点再放纹理坐标,因此对于顶点,开始位置是0,对于纹理坐标,开始位置是第8个字节。
这样我们就设置好了VBO
和IBO
,在渲染的时候,直接绑定VBO
和IBO
就可以使用对应的顶点和纹理数据了:
override fun onDrawFrame(gl: GL10?) {
// 设置清屏颜色
// Set the color which the screen will be cleared to
GLES30.glClearColor(0.9f, 0.9f, 0.9f, 1f)
// 清屏
// Clear the screen
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
// 设置视口,这里设置为整个GLSurfaceView区域
// Set the viewport to the full GLSurfaceView
GLES30.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
// 设置好状态,准备渲染
// Set the status before rendering
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo)
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ibo)
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, imageTexture)
// 调用draw方法用TRIANGLES的方式执行渲染,顶点数量为3个
// Call the draw method with GL_TRIANGLES to render 3 vertices
GLES30.glDrawElements(GLES30.GL_TRIANGLES, indexData.size, GLES30.GL_UNSIGNED_INT, 0)
}
复制代码
来看看效果,就是渲染出一张图来,从效果上看不出什么区别哈:
代码在我github
的OpenGLESPro
项目中,本文对应的是SampleVBOAndIBO
,项目链接:github.com/kenneycode/…
感谢阅读!