[Android] OpenGL ES 绘制矩形以及加载 Bitmap 图片

本文通过介绍绘制矩形以及加载图片来介绍 OpenGL ES 的一些特性。

绘制矩形

OpenGL ES 所有的多边形图形都是由多个三角形拼成的,所以我们在上一篇文章的基础上进行矩形的绘制。矩形即为两个直角三角形构成的,我们可以通过定义四个顶点然后按一定顺序去进行两个三角形的绘制。最简单的方法就是跟绘制三角形一样定义好顶点我们通过 glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, int offset) 载入顶点然后通过 glDrawArrays(int mode, int first, int count)  绘制。接下来我们介绍另一个方式,使用索引来定义绘制的顺序:

    // 顶点坐标
    static float triangleCoords[] = {
            -0.5f, 0.5f, 0.0f, // top left
            -0.5f, -0.5f, 0.0f, // bottom left
            0.5f, -0.5f, 0.0f, // bottom right
            0.5f, 0.5f, 0.0f  // top right
    };

    // 绘制索引
    static short index[] = {
            0, 1, 2, 0, 3, 2
    };

然后我们也需要将索引放入 Native,所以用一个 ByteBuffer 来存放索引数据:

        ByteBuffer cc = ByteBuffer.allocateDirect(index.length * 2);
        cc.order(ByteOrder.nativeOrder());
        indexBuffer = cc.asShortBuffer();
        indexBuffer.put(index);
        indexBuffer.position(0);

顶点着色器的 GLSL 还是跟三角形一样,然后我们可以在 GLSurfaceView 的 onDrawFram 将绘制三角形的 GLES20.glDrawArrays 改成 GLES20.glDrawElements,具体代码为:

        //将程序加入到OpenGLES2.0环境
        GLES20.glUseProgram(mProgram);
        //获取顶点着色器的vPosition成员句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //启用三角形顶点的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //准备三角形的坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //索引法绘制矩形
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, index.length, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);

至此,一个矩形就绘制完毕了。其他图形类似,都可用三角形拼出来。

 

加载 Bitmap

其实把 Bitmap 加载进去就是把上面的颜色(通过片段着色器)改成一个纹理画上去而已,此时我们来修改一下我们的着色器语言:

    private static final String vertexMatrixShaderCode =
            "attribute vec4 vPosition;\n" +
            "attribute vec2 vCoordinate;\n" +
            "varying vec2 aCoordinate;\n" +
            "void main(){\n" +
            "    gl_Position=vPosition;\n" +
            "    aCoordinate=vCoordinate;\n" +
            "}";


    private static final String fragmentShaderCode =
            "precision mediump float;\n" +
            "uniform sampler2D vTexture;\n" +
            "varying vec2 aCoordinate;\n" +
            "void main(){\n" +
            "    gl_FragColor=texture2D(vTexture,aCoordinate);\n" +
            "}";

然后我们先来创建一个纹理,代码如下:

    private int createTexture() {
        int[] texture = new int[1];
        if (mBitmap != null && !mBitmap.isRecycled()) {
            //生成纹理
            GLES20.glGenTextures(1, texture, 0);
            //生成纹理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
            //设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
            //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
            //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
            GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
            //根据以上指定的参数,生成一个2D纹理
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
            return texture[0];
        }
        return 0;
    }

然后我们来定义纹理的坐标,纹理坐标又有不同的理解:首先,顶点坐标是确定的,就是之前一直提到的三角形顶点或矩形顶点,然后就是纹理坐标了,它的坐标系以纹理左下角为坐标原点,向右为x正轴方向,向上为y轴正轴方向。他的总长度是1。即纹理图片的四个角的坐标分别是:(0,0)、(1,0)、(0,1)、(1,1),分别对应左下、右下、左上、右上四个顶点。我们定义纹理坐标如下:

    private static final float[] sCoord = {
            0.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 0.0f,
            1.0f, 1.0f,
    };

然后也需要通过 ByteBuffer 将其放入 Native:

        ByteBuffer cc = ByteBuffer.allocateDirect(sCoord.length * 4);
        cc.order(ByteOrder.nativeOrder());
        bCoord = cc.asFloatBuffer();
        bCoord.put(sCoord);
        bCoord.position(0);

然后我们在 GLSurfaceView 中的 onDrawFrame() 中进行绘制:

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        GLES20.glUseProgram(mProgram);
        GLES20.glEnableVertexAttribArray(glHPosition);
        GLES20.glEnableVertexAttribArray(glHCoordinate);
        GLES20.glUniform1i(glHTexture, 0);
        textureId = createTexture();
        //传入顶点坐标
        GLES20.glVertexAttribPointer(glHPosition, 2, GLES20.GL_FLOAT, false, 0, bPos);
        //传入纹理坐标
        GLES20.glVertexAttribPointer(glHCoordinate, 2, GLES20.GL_FLOAT, false, 0, bCoord);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

此时,一个 Bitmap 就绘制在了 GLSurfaceView 中了。

上面提到了纹理坐标系,于是乎裁剪图片就出来了吧~

发表评论

电子邮件地址不会被公开。 必填项已用*标注