本節我們來看一下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;
}
好了,本節的內容就到這裏了,後邊我們還要繼續學習!!