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。

Opengl ES系列学习--glDrawArrays API使用_第1张图片

     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方式来绘制,想要绘制出如下的形状。

Opengl ES系列学习--glDrawArrays API使用_第2张图片

     绘制该形状,我们在明白了GL_TRIANGLES序列的基础上就很清楚了,每三个点绘制成一个三角形,那么它应该如下构成。

Opengl ES系列学习--glDrawArrays API使用_第3张图片

     按照顺序:012、023、034、045、051组成五个三角形,就拼成我们看到的形状了。绘制的时候注意一下逆时针顺序,其实这个顺序根本不影响效果,但是基于以前的经验,我现在一直都习惯在任何地方都统一使用逆时针的顺序,就是怕有时候出错,查起来很麻烦。

     我们再来看一下GL_TRIANGLE_STRIP,我们使用代码中定义的mVerticesTrianglesStrip数组中的顶点来绘制,调用glDrawArrays(type, 0, 7)最后一个参数传入7,表示要绘制7个顶点,这样实现的效果如下:

Opengl ES系列学习--glDrawArrays API使用_第4张图片

     仔细对比它的定义就可以看出,它是跟顶点的序号相关的,奇数和偶数就是按照定义的效果实现的。用它来画条形的图形应该更好看一些,画这样封闭的就麻烦了。

     最后我们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);

Opengl ES系列学习--glDrawArrays API使用_第5张图片

     最后一个三角形相当于是045三个顶点组成的,少了一块051,这也跟我们的预期是相同的,好了,到这里大家应该已经掌握glDrawArrays API的使用方法了,我们下节继续。

你可能感兴趣的:(android,framework,Opengl,ES,Android,View视图)