Opengl ES系列学习–顶点属性、顶点数组和缓冲区对象

  • Post author:
  • Post category:其他


本节我们继续来看一下《OPENGL ES 3.0编程指南 原书第2版(中文版)》书中第6章的内容,PDF下载地址:

OPENGL ES 3.0编程指南 原书第2版(中文版)

,代码下载地址:

Opengl ES Source Code

。本书中第3、4、5章讲解的是Opengl ES着色器语言的语法知识,没有实例,不过这些语法也是我们掌握Opengl ES的硬功底,万丈高楼平地起,只有基础扎实,才能往上实现出更炫丽的效果。

第6章一共两个实例,分别对应代码中Example63Renderer、Example66Renderer两个类,先来看一下实现效果。

效果很简单,就是两个三角形。我们先来看一下Example63Renderer类,该小节主要讲的是通过Opengl ES 3.0的新语法指定顶点属性,然后通过API赋值,其他方法就不看了,主要是onDrawFrame方法,源码如下:

    public void onDrawFrame(GL10 glUnused) {
        // Set the viewport
        GLES30.glViewport(0, 0, mWidth, mHeight);

        // Clear the color buffer
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        // Use the program object
        GLES30.glUseProgram(mProgramObject);

        // Set the vertex color to red
        GLES30.glVertexAttrib4f(0, 1.0f, 0.0f, 0.0f, 1.0f);

        // Load the vertex position
//        mVertices.position(0);
        GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT,
                false,
                0, mVertices);

        GLES30.glEnableVertexAttribArray(1);
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);
        GLES30.glDisableVertexAttribArray(1);
    }

该方法中主要有两句:GLES30.glVertexAttrib4f(0, 1.0f, 0.0f, 0.0f, 1.0f)、GLES30.glVertexAttribPointer(1, 3, GLES30.GL_FLOAT, false, 0, mVertices),这两句代码用来给着色器中的顶点数据赋值,第一个参数就是要赋值的目标对象的ID,为什么分别是0和1呢?上一节我们已经讲过,可以通过调用GLES30.glGetAttribLocation来获取顶点属性的ID值,这就是3.0的新语法,我们来看一下app\src\main\assets\chapter63目录下的顶点着色器,源码如下:

#version 300 es
layout(location = 0) in vec4 a_color;
layout(location = 1) in vec4 a_position;
out vec4 v_color;
void main()
{
    v_color = a_color;
    gl_Position = a_position;
}

第二行、第三行是通过location来定义a_color、a_position两个属性的,所以我们在Application中才可以通过0、1来给顶点属性赋值,其他的代码都基本相同,我们就不分析了。

继续看一下Example66Renderer,源码如下:

public class Example66Renderer implements GLSurfaceView.Renderer {

    private static final String TAG = Example66Renderer.class.getSimpleName();
    private Context mContext;

    ///
    // Constructor
    //
    public Example66Renderer(Context context) {
        mContext = context;
        mVertices = ByteBuffer.allocateDirect(mVerticesData.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mVertices.put(mVerticesData).position(0);

        mColors = ByteBuffer.allocateDirect(mColorData.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mColors.put(mColorData).position(0);

        mIndices = ByteBuffer.allocateDirect(mIndicesData.length * 2)
                .order(ByteOrder.nativeOrder()).asShortBuffer();
        mIndices.put(mIndicesData).position(0);
    }

    ///
    // Initialize the shader and program object
    //
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        String vShaderStr = ESShader.readShader(mContext, "chapter66/vertexShader.vert");
        String fShaderStr = ESShader.readShader(mContext, "chapter66/fragmentShader.frag");

        // Load the shaders and get a linked program object
        mProgramObject = ESShader.loadProgram(vShaderStr, fShaderStr);

        mVBOIds[0] = 0;
        mVBOIds[1] = 0;
        mVBOIds[2] = 0;

        GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    }

    // /
    // Draw a triangle using the shader pair created in onSurfaceCreated()
    //
    public void onDrawFrame(GL10 glUnused) {
        // Set the viewport
        GLES30.glViewport(0, 0, mWidth, mHeight);

        // Clear the color buffer
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);

        // Use the program object
        GLES30.glUseProgram(mProgramObject);

        drawPrimitiveWithVBOs();
    }

    private void drawPrimitiveWithVBOs() {
        int numVertices = 3;
        int numIndices = 3;

        // mVBOIds[0] - used to store vertex position
        // mVBOIds[1] - used to store vertex color
        // mVBOIds[2] - used to store element indices
        if (mVBOIds[0] == 0 && mVBOIds[1] == 0 && mVBOIds[2] == 0) {
            // Only allocate on the first draw
            GLES30.glGenBuffers(3, mVBOIds, 0);
            Log.e(TAG, "0: " + mVBOIds[0] + ", 1: " + mVBOIds[1] + ", 2: " + mVBOIds[2]);

            mVertices.position(0);
            GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[0]);
            GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vtxStrides[0] * numVertices,
                    mVertices, GLES30.GL_STATIC_DRAW);

            mColors.position(0);
            GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[1]);
            GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vtxStrides[1] * 3,
                    mColors, GLES30.GL_STATIC_DRAW);

            mIndices.position(0);
            GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);
            GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER, 2 * numIndices,
                    mIndices, GLES30.GL_STATIC_DRAW);
        }

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[0]);

        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDX);
        GLES30.glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
                GLES30.GL_FLOAT, false, vtxStrides[0], 0);

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBOIds[1]);

        GLES30.glEnableVertexAttribArray(VERTEX_COLOR_INDX);
        GLES30.glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
                GLES30.GL_FLOAT, false, vtxStrides[1], 0);

        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mVBOIds[2]);

        GLES30.glDrawElements(GLES30.GL_TRIANGLES, numIndices,
                GLES30.GL_UNSIGNED_SHORT, 0);

        GLES30.glDisableVertexAttribArray(VERTEX_POS_INDX);
        GLES30.glDisableVertexAttribArray(VERTEX_COLOR_INDX);

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);

    }

    ///
    // Handle surface changes
    //
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {
        mWidth = width;
        mHeight = height;
    }

    // Handle to a program object
    private int mProgramObject;

    // Additional member variables
    private int mWidth;
    private int mHeight;
    private FloatBuffer mVertices;
    private FloatBuffer mColors;
    private ShortBuffer mIndices;

    // VertexBufferObject Ids
    private int[] mVBOIds = new int[3];

    // 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
    private final float[] mVerticesData =
            {
                    0.0f, 0.5f, 0.0f, // v0
                    -0.5f, -0.5f, 0.0f, // v1
                    0.5f, -0.5f, 0.0f  // v2
            };

    private final short[] mIndicesData =
            {
                    0, 1, 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
            };

    final int VERTEX_POS_SIZE = 3; // x, y and z
    final int VERTEX_COLOR_SIZE = 4; // r, g, b, and a

    final int VERTEX_POS_INDX = 0;
    final int VERTEX_COLOR_INDX = 1;

    private int vtxStrides[] =
            {
                    VERTEX_POS_SIZE * 4,
                    VERTEX_COLOR_SIZE * 4
            };
}

先来看成员变量:mProgramObject表示编译链接完成的program id;mWidth、mHeight表示视窗口的宽度和高度;mVertices存储顶点位置数据;mColors存储顶点颜色数据;mIndices存储顶点的索引数据;mVBOIds存储顶点缓冲区分配到的ID值,因为我们要使用顶点缓冲区、颜色缓冲区、索引缓冲区三个,所以它的size为3;mVerticesData存储的三角形三个顶点的位置数据,v0(0.0f, 0.5f, 0.0f)位于Y轴正方向上0.5距离处,也就是最上面那个点,v1(-0.5f, -0.5f, 0.0f)位于X、Y轴都是负方向0.5距离处,就是数学上所说的第三象限,也就是左下角那个点,v2(0.5f, -0.5f, 0.0f)位于X轴正方向0.5距离、Y轴负方向0.5距离处,也就是第四象限,右下角那个点;mIndicesData存储索引数据,一共只有三个点,所以就只有0、1、2了,索引值我们在

Opengl ES系列学习–增加地形

一节已经详细讲解过了,如果还有疑问,请回头先搞清楚;mColorData存储了三个顶点的颜色值,分别是红、绿、蓝三元色,透明度全部为1,而所有中间的片段都是经过插值计算出来的,所以大家就会看到一个渐变过度的彩色三角形了,我们在

Opengl ES系列学习–序

一节中利用插值画了一个很简单的线条,也是用的这样的原理;VERTEX_POS_SIZE表示描述顶点位置需要3个size,分别对应X、Y、Z;VERTEX_COLOR_SIZE表示描述顶点颜色需要4个size,分别对应R、G、B、A;VERTEX_POS_INDX表示着色器中定义的顶点位置属性的ID值为0,我们后面看到顶点着色器的代码就可以看到了,不过总是感觉这样的方式不友好,代码可读性差,个人建议大家不要使用这样的方式;VERTEX_COLOR_INDX表示着色器中定义的顶点颜色属性的ID值为1,和上一个位置属性相同,都是在着色器程序中通过location关键字来指定的;vtxStrides定义了位置属性、颜色属性的跨距,这个我们应该已经非常熟悉了。

6-6小节主要讲解的就是如何使用顶点缓冲区,代码实现和

Opengl ES系列学习–增加地形

中的顶点缓冲区基本是一样的,主要就是在drawPrimitiveWithVBOs方法当中了,根据if (mVBOIds[0] == 0 && mVBOIds[1] == 0 && mVBOIds[2] == 0)判断条件,如果之前没有分配过,那就执行一次分配,分配时调用GLES30.glGenBuffers(3, mVBOIds, 0),分配成功的三个缓冲区ID会存储在mVBOIds数组当中,接下来调用GLES30.glBindBuffer、GLES30.glBufferData系统API给缓冲区填充数据就可以了,最后不要忘了调用GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)、GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)分别解除绑定。

6-6小节的两个着色器程序和6-3完全相同,我们就不分析了,相信大家都能理解着色器的代码原理。

回头来总结一下,本节主要就是介绍如何使用缓冲区的概念来实现一个效果,缓冲区能很好的提高我们程序的执行效率,也是Opengl ES的基本操作,以后我们要实现什么效果的话,也要基于缓冲区来实现,而不要再使用顶点数组了,那样效率太低,而且浪费内存。使用的流程都是非常规范的API调用操作,也没有什么难度,大家直接照着复制就可以了。

好了,让我们继续开车!!



版权声明:本文为sinat_22657459原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。