Opengl ES系列學習--glDrawElements API使用

     本節我們來看一下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;
}

     好了,本節的內容就到這裏了,後邊我們還要繼續學習!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章