本節我們來看一下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的使用方法了,我們下節繼續。