本节我们来看一下glDrawElements API的使用,对应的代码是OpenGL\learn\src\main\java\com\opengl\learn\GlDrawElementsRender.java文件。
所有实例均有提供源码,下载地址:Opengl ES Source Code。
API中文说明:GLES2.0中文API-glDrawElements。
和上一节类似,glDrawElements也是Opengl提供的绘制API。我们最常用的绘制三角形,第一个参数就是GL_TRIANGLES、GL_TRIANGLE_STRIP和GL_TRIANGLE_FAN,这三种绘制的顺序上一节已经详细介绍过了,大家如果还有疑问,请回头翻看:Opengl ES系列学习--glDrawArrays API使用。
那么我们具体来看一下,glDrawElements是有什么不同呢?它和我们之前介绍的绘制方式不一样,是使用索引来绘制的,一般都是结合GL_ELEMENT_ARRAY_BUFFER一起完成绘制的。索引是什么意思呢?比如上节我们调用glDrawArrays API以GL_TRIANGLES方式完成那个五边形的绘制,那么一共五个三角形,总共需要15个顶点,如果需要的三角形更多,我们定义顶点数据就麻烦了,看一下我们上节定义的数组。
private final float[] mVerticesTriangles =
{
0.0f, 0.0f, 0.0f, // v0
0.0f, 0.5f, 0.0f, // v1
-0.5f, 0.0f, 0.0f, // v2
0.0f, 0.0f, 0.0f, // v0
-0.5f, 0.0f, 0.0f, // v2
-0.5f, -0.5f, 0.0f, // v3
0.0f, 0.0f, 0.0f, // v0
-0.5f, -0.5f, 0.0f, // v3
0.5f, -0.5f, 0.0f, // v4
0.0f, 0.0f, 0.0f, // v0
0.5f, -0.5f, 0.0f, // v4
0.5f, 0.0f, 0.0f, // v5
0.0f, 0.0f, 0.0f, // v0
0.5f, 0.0f, 0.0f, // v5
0.0f, 0.5f, 0.0f // v1
};
然而实际存在的顶点只有五个,那么我们可以给它们标号,然后直接使用标号就可以了(012)、(023)、(034)、(045)、(051);另一点,我们之前的绘制方式是每次都传实际的顶点数组给着色器程序的,那样效率也较低,而我们可以提前申请一些缓冲区,每个缓冲区都有对应的标号,先把顶点数据填充到缓存区中,绘制的时候只需要传递缓冲区的标号就可以了,这样才是更高效的作法。
那么先来看一下如何使用该API,实例程序如下:
glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0);
第一个参数就是使用什么样的方式绘制三角形,和上节的glDrawArrays作用相同,第二个参数指定要绘制的个数,这里是指尖索引顶点的个数,第三个参数是索引数据的类型,因为我们的索引数据使用short,所以这里就传GL_UNSIGNED_SHORT,最后一个是offset,我们是从头开始绘制的,所以偏移量为0。
我们最后绘制的实际效果如下:
颜色看着好炫,GlDrawElementsRender.java类的所有源码如下:
package com.opengl.learn;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;
import com.lime.common.ESShader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_COMPILE_STATUS;
import static android.opengl.GLES20.GL_ELEMENT_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_LINK_STATUS;
import static android.opengl.GLES20.GL_STATIC_DRAW;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_TRIANGLE_FAN;
import static android.opengl.GLES20.GL_TRIANGLE_STRIP;
import static android.opengl.GLES20.GL_UNSIGNED_SHORT;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glBindBuffer;
import static android.opengl.GLES20.glBufferData;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glDeleteProgram;
import static android.opengl.GLES20.glDeleteShader;
import static android.opengl.GLES20.glDisableVertexAttribArray;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glDrawElements;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetError;
import static android.opengl.GLES20.glGetProgramInfoLog;
import static android.opengl.GLES20.glGetProgramiv;
import static android.opengl.GLES20.glGetShaderInfoLog;
import static android.opengl.GLES20.glGetShaderiv;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
public class GlDrawElementsRender implements GLSurfaceView.Renderer {
private static final String TAG = GlDrawElementsRender.class.getSimpleName();
private Context mContext;
private int mProgramObject;
private static final int BYTES_PER_FLOAT = 4;
private static final int BYTES_PER_SHORT = 2;
private static final int POSITION_COMPONENT_SIZE = 3;
private static final int COLOR_COMPONENT_SIZE = 4;
// Additional member variables
private int mWidth;
private int mHeight;
private FloatBuffer mVertices;
private FloatBuffer mColors;
private ShortBuffer mIndices;
private int vPosition, vColor;
private final float[] mVerticesData =
{
0.0f, 0.0f, 0.0f, // v0
0.0f, 0.5f, 0.0f, // v0
-0.5f, 0.0f, 0.0f, // v1
-0.5f, -0.5f, 0.0f, // v2
0.5f, -0.5f, 0.0f, // v2
0.5f, 0.0f, 0.0f // v2
};
private final short[] mIndicesData =
{
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 5,
0, 5, 1
};
private final float[] mColorData =
{
1.0f, 0.0f, 0.0f, 1.0f, // c0
0.0f, 1.0f, 0.0f, 1.0f, // c1
0.0f, 0.0f, 1.0f, 1.0f, // c2
0.5f, 1.0f, 0.5f, 1.0f, // c0
1.0f, 0.5f, 0.5f, 1.0f, // c1
0.5f, 1.0f, 0.5f, 1.0f // c2
};
// VertexBufferObject Ids
private int[] mVBOIds = new int[3];
public GlDrawElementsRender(Context context) {
mContext = context;
mVertices = ByteBuffer.allocateDirect(mVerticesData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(mVerticesData).position(0);
mColors = ByteBuffer.allocateDirect(mColorData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mColors.put(mColorData).position(0);
mIndices = ByteBuffer.allocateDirect(mIndicesData.length * BYTES_PER_SHORT)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mIndices.put(mIndicesData).position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
String vShaderStr = ESShader.readShader(mContext, "drawelements_vertexShader.glsl");
String fShaderStr = ESShader.readShader(mContext, "drawelements_fragmentShader.glsl");
// Load the shaders and get a linked program object
mProgramObject = ESShader.loadProgram(vShaderStr, fShaderStr);
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
vPosition = glGetAttribLocation(mProgramObject, "vPosition");
vColor = glGetAttribLocation(mProgramObject, "vColor");
glGenBuffers(3, mVBOIds, 0);
Log.e(TAG, "0: " + mVBOIds[0] + ", 1: " + mVBOIds[1] + ", 2: " + mVBOIds[2]);
// mVBOIds[0] - used to store vertex position
mVertices.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length,
mVertices, GL_STATIC_DRAW);
// mVBOIds[1] - used to store vertex color
mColors.position(0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mColorData.length,
mColors, GL_STATIC_DRAW);
// mVBOIds[2] - used to store element indices
mIndices.position(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndices, GL_STATIC_DRAW);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void onDrawFrame(GL10 gl) {
glViewport(0, 0, mWidth, mHeight);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(mProgramObject);
drawShape();
}
private void drawShape() {
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[0]);
glEnableVertexAttribArray(vPosition);
glVertexAttribPointer(vPosition, POSITION_COMPONENT_SIZE,
GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, mVBOIds[1]);
glEnableVertexAttribArray(vColor);
glVertexAttribPointer(vColor, COLOR_COMPONENT_SIZE,
GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0);
glDisableVertexAttribArray(vPosition);
glDisableVertexAttribArray(vColor);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
}
其中成员变量mVerticesData、mIndicesData、mColorData分别是顶点数组、索引数组、颜色数组,索引位置我们使用short就足够了。跟上一节相比,onSurfaceCreated方法中多了很多缓冲区的操作,先调用glGenBuffers(3, mVBOIds, 0)申请三个缓冲区,分别用来存储顶点、索引、颜色,然后依次调用glBindBuffer、glBufferData把各数组和对应的缓冲区绑定起来,注意索引缓冲区的buffer类型为GL_ELEMENT_ARRAY_BUFFER,和其他两个是不一样的。最后调用draw方法开始绘制。
绘制的时候也是一样,依次调用glBindBuffer绑定buffer、调用glEnableVertexAttribArray使能顶点属性、调用glVertexAttribPointer给顶点指定取值方式,这里一定要注意,这些API的调用是有强制的前后关系,不能乱,大家可以试下打乱这几行代码的先后顺序,就什么也绘制不出来了,所以还是规规矩矩的来。调用glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2])绑定索引缓冲区,因为索引缓冲区不存在对应的顶点属性,所以也不用使能;调用glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0)完成绘制,不要忘记绘制完成后,调用glBindBuffer(GL_ARRAY_BUFFER, 0)、glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)将两个buffer解绑,0是Opengl保留的非法buffer id,所以给它们赋值为0也就相当于解绑的意思了。
还有一点需要提一下,一直到现在,我们的顶点数组个数和颜色数组个数都是一一对应的,一个顶点对应一个颜色,那如果两个数组不对应,会发生什么情况呢?我们来试一下把颜色数据改成如下:
private final float[] mColorData =
{
1.0f, 0.0f, 0.0f, 1.0f, // c0
// 0.0f, 1.0f, 0.0f, 1.0f, // c1
// 0.0f, 0.0f, 1.0f, 1.0f, // c2
// 0.5f, 1.0f, 0.5f, 1.0f, // c0
// 1.0f, 0.5f, 0.5f, 1.0f, // c1
// 0.5f, 1.0f, 0.5f, 1.0f // c2
};
这样的实际效果图如下:
再来把颜色数据改成如下:
private final float[] mColorData =
{
1.0f, 0.0f, 0.0f, 1.0f, // c0
0.0f, 1.0f, 0.0f, 1.0f, // c1
// 0.0f, 0.0f, 1.0f, 1.0f, // c2
// 0.5f, 1.0f, 0.5f, 1.0f, // c0
// 1.0f, 0.5f, 0.5f, 1.0f, // c1
// 0.5f, 1.0f, 0.5f, 1.0f // c2
};
这样的实际效果如下:
通过两种效果的实验,不知道大家发现规律没有?从中心点到边角点的亮线也出来了,说明肯定是进行了插值处理了。那为什么会出现这样的结果呢?因为我们要绘制几个顶点,那么顶点着色器就会执行几次,而片段着色器中的色值就是顶点着色器中定义的fColor传递过去的。当出现颜色数组小于顶点数组的情况时,那么一一对应取颜色,比如我们上面试验的第一种,只有第一个顶点可以取到颜色,后面的顶点都取不到,所以只有第一个顶点显示为红色,其他顶点全部是黑色,且中间区域会进行红色和黑色的插值;而第二种试验就是只有两个颜色,那么只有前两个顶点可以取到颜色,后面的其他顶点都取不到,所以就会出现顶部的v1显示为绿色,v2、v3、v4、v5全部为黑色的现象了。
attribute vec4 vPosition;
attribute vec4 vColor;
varying vec4 fColor;
void main()
{
fColor = vColor;
gl_Position = vPosition;
}
好了,本节的内容就到这里了,后边我们还要继续学习!!