在Android中使用OpenGL ES进行开发第(二)课:绘制图形

一、前期基础知识储备

笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点:

①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分

②使用OpenGLES绘制2D/3D图形的第一步:定义图形;——运用部分

③使用OpenGLES绘制出②步骤中定义好的图形:——运用部分,难点所在

通过这三篇文章的分析,就像给万丈高楼垫定了基石,万丈高楼平地起,后面利用OpenGLES做各种效果,各种变换都是建立在这三步的图形编程理解之上的。

今天开始第三讲——绘制图形部分的分析,重难点所在!

在前面的两篇文章《在Android中使用OpenGL ES进行开发第(一)课:概念先行》《在Android中使用OpenGL ES进行开发第(二)课:定义图形》中,笔者详细分析了OpenGL ES2.0相关的重要概念和实现了一个三角形的顶点坐标的定义,那么接下来,本节文章就来讲第二篇文章中定义好的图形具体绘制出来,这也是笔者这三篇文章的重难点所在。

正如第一篇文章中提及的,使用OpenGL ES2.0的时候,尽管写最简单的程式(如基本作图、三角形、矩形或动作translate、rotate、scale等),一定要写shader(着色器/渲染器)才能运作,原本这些在OpenGL ES1.x时是系统做的事,现在要人力进行实现,因而提高了开发的难度

二、上代码,具体实现

第一步:渲染器类中初始化图形—简单;

public class MyGLRenderer2 implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // initialize a triangle
        mTriangle = new Triangle();
    }
    ...
}

第二步:为draw()方法做准备—创建着色器对象(shader),放入程式(Program)中,将程式链接至GLES—复杂

着色器相关概念(重点):我们在第一篇文章中花了大量笔墨来介绍着色器(shader),那么着色器到底是什么?

①顶点着色器(Vertex Shader):用来渲染图形顶点的 OpenGL ES 代码;

②片元着色器(Fragment Shader):使用颜色纹理(texture)渲染图形表面的 OpenGL ES 代码;

③程式(Program):一个OpenGL ES 对象,包含了你希望用来绘制图形所要用到的着色器,最后顶点着色器和片元着色器都要放入到程式中,然后才能使用。

以上三个,你需要至少一个顶点着色器(Vertex Shader)来定义一个图形顶点,以及一个片元着色器(Fragment Shader)为该图形上色。这些着色器必须被编译然后再添加到一个OpenGLES Program当中,并利用这个 progrem 来绘制形状。

这段代码在原本的OpenGL ES1.X中是由系统实现的,所以写起来1.x的代码比较简单,但是在2.x中我们采用人力的方式实现,虽然有些复杂,但是,通过编写顶点及片元着色器程序,来完成一些顶点变换和纹理颜色计算工作,可以实现更加灵活精细化的计算与渲染。

熟悉着色器概念之后,那么我们代码具体实现分三步走:

①图形类中,创建两个GLSL代码段——顶点着色器代码段+片元着色器代码

public class Triangle {

   /**
     * 顶点着色器代码
     * attribute变量(属性变量)只能用于顶点着色器中
     * uniforms变量(一致变量)用来将数据值从应用程其序传递到顶点着色器或者片元着色器。 。
     * varying变量(易变变量)是从顶点着色器传递到片元着色器的数据变量。
     * gl_Position (必须)为内建变量,表示变换后点的空间位置。 
     */
    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +  // 应用程序传入顶点着色器的顶点位置
                    "void main() {" +
                    "  gl_Position = vPosition;" + // 设置此次绘制此顶点位置
                    "}";

    /**
     * 片元着色器代码
     */
    private final String fragmentShaderCode =
            "precision mediump float;" +  // 设置工作精度
                    "uniform vec4 vColor;" +  // 应用程序传入着色器的颜色变量
                    "void main() {" +
                    "  gl_FragColor = vColor;" + // 颜色值传给 gl_FragColor内建变量,完成片元的着色
                    "}";
   ...
}

更多着色器代码解析内容,感兴趣的读者可以参考1文章2文章3文章

②渲染器类中,创建辅助方法,用于编译①中创建的两个代码段。

该辅助方法中,我们传入两个参数,第一个参数是着色器的类型,包含两个顶点着色器(GLES20.GL_VERTEX_SHADER)和片元着色器(GLES20.GL_FRAGMENT_SHADER),第二个参数就是①中定义好的两个着色器代码段。

public class MyGLRenderer2 implements GLSurfaceView.Renderer 
    ...

    /**
     * 加载并编译着色器代码
     * 渲染器类型type={GLES20.GL_VERTEX_SHADER, GLES20.GL_FRAGMENT_SHADER}
     * 渲染器代码 GLSL
     */
    public static int loadShader(int type, String shaderCode){

        int shader = GLES20.glCreateShader(type);

        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}

③图形类中,我们传入具体的参数到②中定义的方法里面,得到两个着色器的对象,然后将两个着色器对象放入到程式(Program)中。最后将程式与GLES链接好。

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...
        
        // 加载编译顶点渲染器
        int vertexShader = MyGLRenderer2.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);

        // 加载编译片元渲染器
        int fragmentShader = MyGLRenderer2.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

做完以上两个步骤,就为图形类中定义draw()方法做好了准备—draw()方法真实千呼万唤始出来啊!

第三步:图形类中创建draw()方法,拿到链接至GLES的程式(Program),设置形状的顶点位置和表面的颜色值—复杂

下面的代码为形状的顶点着色器和形状着色器设置了位置颜色值,然后执行绘制函数:

public class Triangle {

    // 绘制形状的顶点数量
    private static final int COORDS_PER_VERTEX = 3;

    ...

    private int mPositionHandle;
    private int mColorHandle;

    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

        public void draw() {
        // Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram);

        // get handle to vertex shader's vPosition member
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // Set color for drawing the triangle
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

draw()方法里面的代码是不是也非常复杂,没关系,我们捋一捋就可以看清楚,整段代码实际上可以分为三部分:

①1行代码-在draw()方法中拿到链接至GLES中的程式(Program);

②3行代码-从程式中取出顶点着色器,开始对顶点的位置进行设置;

③3行代码-从程式中取出片元着色器,开始对图形表面颜色进行设置。

第四步:渲染器类中调用图形类中定义好的draw()方法,进行具体绘制

public class MyGLRenderer2 implements GLSurfaceView.Renderer {

    @Override
    public void onDrawFrame(GL10 gl) {
        ...
        mTriangle.draw();
    }
}

总结:到此为止,笔者的OpenGL ES2.0的入门三课就已经结束了,覆盖面是有的,但是并没有仔细深入,其实原本打算也是通过这三篇文章来普及一下基础的OpenGL ES2.0用法,后续还会继续写提高篇的文章。(写着写着,发现给挖的坑蛮多!囧囧)

最后,附上前两篇文章链接:《在Android中使用OpenGL ES进行开发第(一)课:概念先行》

《在Android中使用OpenGL ES进行开发第(二)课:定义图形》

猜你喜欢

转载自blog.csdn.net/weixin_41101173/article/details/80040718