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

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

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