OpenGL ES 顶点缓冲区和布局(3)
OpenGL ES 顶点缓冲区和布局(3)
简述
顶点缓冲区的本质就是一段GPU上的显存,我们通过绑定顶点缓冲区的方式来将数据从CPU传到GPU。
我们之前在绘制三角形的例子中,我们往顶点缓冲区只传入了坐标,但是其实顶点是可以包含很多数据的,比如纹理索引,颜色等,因为它的本质其实就是一个Buffer,所以我们可以使用它做非常多的事情。
我们前面说了,顶点缓冲区只是一个一段内存,但是GPU并不知道这段内存里的这些数是什么意思,每个数据的含义是什么。所以我们需要告诉GPU顶点缓冲区布局,定义每个数据是什么意思。
配置布局接口
glVertexAttribPointer
- 第一个参数是index,表示这个数据要传递给shader哪一个属性,这个我们在shader对篇章会详细介绍。
- 第二个参数是size,表示有几个数
- 第三个参数是type,表示数据类型
- 第四个参数是normalized,表示参数是否需要归一化
- 第五个参数是stride,每个顶点的步长,基本可以理解为一个顶点的长度
- 第六个参数是offset,表示当前属性在当前顶点的偏移
绑定BufferData
glBufferData
- 第一个参数,数据类型,顶点缓冲区一般用GL_ARRAY_BUFFER,后续我们还会介绍索引缓冲区
- 第二个参数,size,我们需要分配缓冲区的大小。
- 第三个参数,data,我们定义的数据。
- 第四个参数,缓冲区用于使用的场景,GL_STREAM_DRAW/GL_STATIC_DRAW/GL_DYNAMIC_DRAW。
GL_STREAM_DRAW:缓冲区数据代码设置,仅渲染一次,使用很少。
GL_STATIC_DRAW:缓冲区数据是静态的,不会修改的。
GL_DYNAMIC_DRAW:缓冲区数据会动态变化,常常需要更新数据。
实现渐变颜色三角形
我们这里通过顶点缓冲区传递数据,实现一个渐变三角形。这个demo会有助于我们对顶点缓冲区数据传递,同时还能对顶点着色器属性有一些了解。
我们基于之前那个三角形的案例来修改。
修改顶点缓冲区Buffer
我们之前顶点缓冲区只有9个数,三个顶点,每个顶点三个坐标,修改后每个顶点有7个数,前三个点依旧是坐标,和之前相同,而后面新增的四个顶点是坐标,即RGBA。
我们之前提过,着色器会对每个像素都调用一次,而顶点着色器则会根据顶点坐标渐变,对每个像素都进行调用。
这里实现的三个角的颜色分别是红黄蓝,然后向中间渐变。
private float[] vertexArray = new float[] {
-0.5f, -0.5f, 0.0f, 1f, 0f, 0f, 1f,
0.5f, -0.5f, 0.0f, 0f, 1f, 0f, 1f,
0.0f, 0.5f, 0.0f, 0f, 0f, 1f, 1f
};
修改shader
添加了属性attribute vec4 inputColor,用于接收顶点缓冲区新增的四维用于表示颜色的数组,顶点缓冲区传递给vertexColor,通过varying传递给片段着色器。
片段着色器直接使用varying vec4 vertexColor作为gl_FragColor返回。
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec4 inputColor;" +
"varying vec4 vertexColor;" +
"void main() {" +
" vertexColor = inputColor;" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"varying vec4 vertexColor;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vertexColor;" +
"}";
顶点缓冲区创建/数据填充
创建绑定顶点缓冲区,后使用glBufferData填充数据,这里和之前创建三角形一样,基本没有修改。
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 清除颜色
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 创建顶点缓冲区
int[] idBuffer = new int[1];
GLES30.glGenBuffers(1, idBuffer, 0);
vertexBufferId = idBuffer[0];
// 顶点缓冲区数据填充
FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(vertexArray);
vertexBuffer.position(0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
// 填充数据
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertexArray.length * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
// shader
shaderProgramId = initShaderProgram(vertexShaderCode, fragmentShaderCode);
}
配置顶点缓冲区布局
通过这里的glVertexAttribPointer使用,配合上面的api介绍,我们大概就能理解这个布局接口是怎么使用的。
public void onDrawFrame(GL10 gl) {
// 清除屏幕
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
// 使能着色器程序
GLES30.glUseProgram(shaderProgramId);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);
int positionLocation = GLES30.glGetAttribLocation(shaderProgramId, "vPosition");
GLES30.glEnableVertexAttribArray(positionLocation);
// 主要改动部分在这里
// 这里的步长为7个顶点 * sizeof(float), 这里index是传给vPosition,type还是GL_FLOAT,size为3个顶点
GLES30.glVertexAttribPointer(positionLocation, 3, GLES30.GL_FLOAT, false, 7 * 4, 0);
// 步长和上面一样, 这里index是传给inputColor,type还是GL_FLOAT,size为4个顶点
int colorInputLocation = GLES30.glGetAttribLocation(shaderProgramId, "inputColor");
GLES30.glEnableVertexAttribArray(colorInputLocation);
GLES30.glVertexAttribPointer(colorInputLocation, 4, GLES30.GL_FLOAT, false, 7 * 4, 3 * 4);
// 调用DrawCall绘制三角形
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);
// 清除配置
GLES30.glDisableVertexAttribArray(positionLocation);
GLES30.glDisableVertexAttribArray(colorInputLocation);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
GLES30.glUseProgram(0);
}
效果
顶点数组
还有一个概念是顶点数组,我们简称VAO,相关的接口glGenVertexArrays,这个接口在GLES20上没有,但是在GLES30上有。
顶点数组的本质的作用是用于记录顶点缓冲区和顶点布局的关系,如果有多个缓冲区,顶点数组可以记录这些,可以减少调用绑定的次数。
相信大家会有一个问题,为什么GLES20上没有这个api,而且我们之前绘制三角形的demo中也没有使用这个顶点数组,为什么还可以正常绘制呢。
按照技术上的作用,这个顶点数组是必要的,前面这个问题的答案其实是因为OpenGL默认配置下就会有一个顶点数组。
这个目前了解一下即可,后续我们绘制多个对象的时候会再仔细介绍顶点数组。
小结
本节介绍了顶点缓冲区和布局,且通过实现一个渐变色的三角形demo来展示了怎么使用顶点缓冲区和布局。
顶点缓冲区的数据可以是位置,颜色,纹理等等,本质这里就是一个内存缓冲区,这里展示了颜色的用法。
配置顶点布局的接口使用比较简单,按照文档的使用即可。