本节我们来看一下glDrawArrays API的使用,我们就讲一些常量的知识,生僻少用的就不看了。
所有实例均有提供源码,下载地址:Opengl ES Source Code。
API中文说明:GLES2.0中文API-glDrawArrays。
Opengl提供的两类绘制API就是glDrawArrays、glDrawElements,绘制三角形序列的三种方式:GL_TRIANGLES、GL_TRIANGLE_STRIP和GL_TRIANGLE_FAN。
GL_TRIANGLES是以每三个顶点绘制一个三角形,第一个三角形使用顶点v0,v1,v2,第二个使用v3,v4,v5,以此类推。如果顶点的个数n不是3的倍数,那么最后的1个或者2个顶点会被忽略。
GL_TRIANGLE_STRIP则稍微有点复杂。其规律是:构建当前三角形的顶点的连接顺序依赖于要和前面已经出现过的2个顶点组成三角形的当前顶点的序号的奇偶性(序号从0开始):如果当前顶点是奇数,组成三角形的顶点排列顺序:T = [n-1 n-2 n];如果当前顶点是偶数,组成三角形的顶点排列顺序:T = [n-2 n-1 n];如上图中,第一个三角形,顶点v2序号是2,是偶数,则顶点排列顺序是v0,v1,v2。第二个三角形,顶点v3序号是3,是奇数,则顶点排列顺序是v2,v1,v3;第三个三角形,顶点v4序号是4,是偶数,则顶点排列顺序是v2,v3,v4,以此类推。我们从自己实现的形状也可以看到实际效果。这个顺序是为了保证所有的三角形都是按照相同的方向绘制的,使这个三角形串能够正确形成表面的一部分。对于某些操作,维持方向是很重要的,比如剔除。注意:顶点个数n至少要大于3,否则不能绘制任何三角形。
GL_TRIANGLE_FAN是类似弧形的绘制,以这种方式画出来的三角形也是连接在一起的,但是区别于Triangle的是它们有一个共同的顶点。这个顶点称为它们的中心顶点。按顺序前三个点组成一个三角形。而后保留该组三角形的最后一个顶点我们暂且记为last,依次按照中心点、last和下一个点组成下一个三角形。并重复该过程。我们可以看到,因为有一个共用的中心点,其他点将以它为中心点依次构建三角形。所以给定点数目为N(N>=3),则所画出的三角形个数为(N - 3 + 1)个三角形。也就是(N - 2)个三角形。结合我们本节要实现的实例来看一下,绘制的形状完全一样,但是按照GL_TRIANGLES方式绘制,一共五个三角形,每个三角形三个顶点,所以一共需要15个顶点;但是如果用GL_TRIANGLE_FAN方式绘制,(N - 2 = 5),所以N等于7,也就是只需要7个顶点就可以绘制出来了,记得不要忘了v1要闭合,否则就会缺一角,可以参考本节最后的图形。
明白了三种绘制序列,我们来自己实现一下,本节的代码对应OpenGL\learn\src\main\java\com\opengl\learn\GlDrawArraysRender.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 javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_COMPILE_STATUS;
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_TRIANGLES;
import static android.opengl.GLES20.GL_TRIANGLE_FAN;
import static android.opengl.GLES20.GL_TRIANGLE_STRIP;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
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.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
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 GlDrawArraysRender implements GLSurfaceView.Renderer {
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
};
private final float[] mVerticesTrianglesStrip =
{
0.0f, 0.0f, 0.0f, // v0
0.0f, 0.5f, 0.0f, // v1
-0.5f, 0.0f, 0.0f, // v2
-0.5f, -0.5f, 0.0f, // v3
0.5f, -0.5f, 0.0f, // v4
0.5f, 0.0f, 0.0f, // v5
0.0f, 0.5f, 0.0f, // v1
};
private final float[] mVerticesTrianglesFan =
{
0.0f, 0.0f, 0.0f, // v0
0.0f, 0.5f, 0.0f, // v1
-0.5f, 0.0f, 0.0f, // v2
-0.5f, -0.5f, 0.0f, // v3
0.5f, -0.5f, 0.0f, // v4
0.5f, 0.0f, 0.0f, // v5
// 0.0f, 0.5f, 0.0f, // v1
};
private static final String TAG = GlDrawArraysRender.class.getSimpleName();
private static final int BYTES_PER_FLOAT = 4;
private static final int POSITION_COMPONENT_SIZE = 3;
private Context mContext;
private int mProgramObject;
private int vPosition;
private FloatBuffer mVertices, mVerticesStrip, mVerticesFan;
private int mWidth, mHeight;
public GlDrawArraysRender(Context context) {
mContext = context;
mVertices = ByteBuffer.allocateDirect(mVerticesTriangles.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertices.put(mVerticesTriangles).position(0);
mVerticesStrip = ByteBuffer.allocateDirect(mVerticesTrianglesStrip.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVerticesStrip.put(mVerticesTrianglesStrip).position(0);
mVerticesFan = ByteBuffer.allocateDirect(mVerticesTrianglesFan.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVerticesFan.put(mVerticesTrianglesFan).position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
loadProgram();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void onDrawFrame(GL10 gl) {
// drawShape(GL_TRIANGLES);
// drawShape(GL_TRIANGLE_STRIP);
drawShape(GL_TRIANGLE_FAN);
}
private void loadProgram() {
String vShaderStr = ESShader.readShader(mContext, "drawarrays_vertexShader.glsl");
String fShaderStr = ESShader.readShader(mContext, "drawarrays_fragmentShader.glsl");
int vertexShader;
int fragmentShader;
int programObject;
int[] linked = new int[1];
// Load the vertex/fragment shaders
vertexShader = loadShader(GL_VERTEX_SHADER, vShaderStr);
fragmentShader = loadShader(GL_FRAGMENT_SHADER, fShaderStr);
// Create the program object
programObject = glCreateProgram();
if (programObject == 0) {
return;
}
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
// Link the program
glLinkProgram(programObject);
// Check the link status
glGetProgramiv(programObject, GL_LINK_STATUS, linked, 0);
if (linked[0] == 0) {
Log.e(TAG, "Error linking program:");
Log.e(TAG, glGetProgramInfoLog(programObject));
glDeleteProgram(programObject);
return;
}
// Store the program object
mProgramObject = programObject;
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
vPosition = glGetAttribLocation(mProgramObject, "vPosition");
Log.i(TAG, "vPosition: " + vPosition);
}
private int loadShader(int type, String shaderSrc) {
int shader;
int[] compiled = new int[1];
// Create the shader object
shader = glCreateShader(type);
if (shader == 0) {
return 0;
}
// Load the shader source
glShaderSource(shader, shaderSrc);
// Compile the shader
glCompileShader(shader);
// Check the compile status
glGetShaderiv(shader, GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "compile shader error: " + glGetShaderInfoLog(shader));
glGetError();
glDeleteShader(shader);
return 0;
}
Log.i(TAG, "load " + type + " shader result: " + shader);
return shader;
}
private void drawShape(int type) {
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, mWidth, mHeight);
// Use the program object
glUseProgram(mProgramObject);
if (GL_TRIANGLES == type) {
mVertices.position(0);
// Load the vertex data
glVertexAttribPointer(vPosition, POSITION_COMPONENT_SIZE, GL_FLOAT, false, 0, mVertices);
glEnableVertexAttribArray(vPosition);
glDrawArrays(type, 0, 15);
} else if (GL_TRIANGLE_STRIP == type) {
mVertices.position(0);
// Load the vertex data
glVertexAttribPointer(vPosition, POSITION_COMPONENT_SIZE, GL_FLOAT, false, 0, mVerticesStrip);
glEnableVertexAttribArray(vPosition);
glDrawArrays(type, 0, 7);
} else if (GL_TRIANGLE_FAN == type) {
mVertices.position(0);
// Load the vertex data
glVertexAttribPointer(vPosition, POSITION_COMPONENT_SIZE, GL_FLOAT, false, 0, mVerticesFan);
glEnableVertexAttribArray(vPosition);
glDrawArrays(type, 0, 6);
}
glEnableVertexAttribArray(vPosition);
}
}
我们首先使用GL_TRIANGLES方式来绘制,想要绘制出如下的形状。
绘制该形状,我们在明白了GL_TRIANGLES序列的基础上就很清楚了,每三个点绘制成一个三角形,那么它应该如下构成。
按照顺序:012、023、034、045、051组成五个三角形,就拼成我们看到的形状了。绘制的时候注意一下逆时针顺序,其实这个顺序根本不影响效果,但是基于以前的经验,我现在一直都习惯在任何地方都统一使用逆时针的顺序,就是怕有时候出错,查起来很麻烦。
我们再来看一下GL_TRIANGLE_STRIP,我们使用代码中定义的mVerticesTrianglesStrip数组中的顶点来绘制,调用glDrawArrays(type, 0, 7)最后一个参数传入7,表示要绘制7个顶点,这样实现的效果如下:
仔细对比它的定义就可以看出,它是跟顶点的序号相关的,奇数和偶数就是按照定义的效果实现的。用它来画条形的图形应该更好看一些,画这样封闭的就麻烦了。
最后我们GL_TRIANGLE_FAN,FAN的意思就是弧形的意思,围绕一个中心点画一圈,我们要画圆的话也要使用它。而按照一圈的顶点定义,最后一个应该要封闭,所以我们定义如下的顶点数组,就可以使用GL_TRIANGLE_FAN完成一个封闭图形的绘制。
private final float[] mVerticesTrianglesFan =
{
0.0f, 0.0f, 0.0f, // v0
0.0f, 0.5f, 0.0f, // v1
-0.5f, 0.0f, 0.0f, // v2
-0.5f, -0.5f, 0.0f, // v3
0.5f, -0.5f, 0.0f, // v4
0.5f, 0.0f, 0.0f, // v5
0.0f, 0.5f, 0.0f, // v1
};
我们可以把最后一个点去掉,或者只绘制6个点来看一下是什么效果。mVerticesTrianglesFan当中定义的七个顶点仍然不变,我们把要绘制的顶点个数变为6,结果就成为如下的样子。
glDrawArrays(type, 0, 6);
最后一个三角形相当于是045三个顶点组成的,少了一块051,这也跟我们的预期是相同的,好了,到这里大家应该已经掌握glDrawArrays API的使用方法了,我们下节继续。