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。

     我们最后绘制的实际效果如下:

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

     颜色看着好炫,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
            };

     这样的实际效果图如下:

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

     再来把颜色数据改成如下:

    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
            };

     这样的实际效果如下:

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

     通过两种效果的实验,不知道大家发现规律没有?从中心点到边角点的亮线也出来了,说明肯定是进行了插值处理了。那为什么会出现这样的结果呢?因为我们要绘制几个顶点,那么顶点着色器就会执行几次,而片段着色器中的色值就是顶点着色器中定义的fColor传递过去的。当出现颜色数组小于顶点数组的情况时,那么一一对应取颜色,比如我们上面试验的第一种,只有第一个顶点可以取到颜色,后面的顶点都取不到,所以只有第一个顶点显示为红色,其他顶点全部是黑色,且中间区域会进行红色和黑色的插值;而第二种试验就是只有两个颜色,那么只有前两个顶点可以取到颜色,后面的其他顶点都取不到,所以就会出现顶部的v1显示为绿色,v2、v3、v4、v5全部为黑色的现象了。

attribute vec4 vPosition;
attribute vec4 vColor;

varying vec4 fColor;

void main()
{
    fColor = vColor;
    gl_Position = vPosition;
}

     好了,本节的内容就到这里了,后边我们还要继续学习!!

你可能感兴趣的:(环境搭建,Opengl,ES,android,framework)