学习OpenGL ES for Android(五)

上篇我们学习了对平面的变换,这篇我们将要学习对立体图形的变换。立体图形也可以称为3D图形,立体图形是由平面图形组合而来的,我先看下一个立方体的结构

一个立方体有8个顶点,6个面,可以用12个三角形组合而成。我们可以使用36个(或24个)顶点的数据和glDrawArrays的方式来绘制,但是这样照成了浪费,有许多顶点都是可以复用的,所以我们使用8个顶点的数据和glDrawElements的方式来绘制立方体。

首先我们定义顶点的坐标,在平面图形上我们的z坐标都是0,而在立体图形上的z坐标就需要定义为不同的值了。我们把立方体的中心视为(0,0,0)中心点,那么顶点0的坐标我们设置为(-0.5f, 0.5f, 0.5f),如果觉得比较抽象,可以拿一个盒子来参照下,我们得出8个顶点数据如下,

    /**
     * 立方体的8个顶点
     */
    private float[] CubeCoords = new float[]{
            -0.5f, 0.5f, 0.5f, // 上左前顶点
            0.5f, 0.5f, 0.5f, // 上右前顶点
            -0.5f, 0.5f, -0.5f, // 上左后顶点
            0.5f, 0.5f, -0.5f, // 上右后顶点

            -0.5f, -0.5f, 0.5f, // 下左前顶点
            0.5f, -0.5f, 0.5f, // 下右前顶点
            -0.5f, -0.5f, -0.5f, // 下左后顶点
            0.5f, -0.5f, -0.5f, // 下右后顶点
    };

然后我们设置索引,立方体的上面是由0,1,2,3这四个顶点组合而成的,那么这两个三角形就是:0,1,2和1,2,3,同理我们可以得到其他面的索引如下,

    /**
     * 索引
     */
    private short[] indices = new short[]{
            0, 1, 2, 1, 2, 3, // 上面
            4, 5, 6, 5, 6, 7,// 下面
            0, 1, 4, 1, 4, 5, // 前面
            2, 3, 6, 3, 6, 7, // 后面
            0, 2, 4, 2, 4, 6, // 左面
            1, 3, 5, 3, 5, 7 // 右面
    };

然后我们设置下各个顶点的颜色值,

    /**
     * 颜色
     */
    private float[] colors = {
            0f, 0f, 0f, 1f,
            0f, 0f, 1f, 1f,
            0f, 1f, 0f, 1f,
            0f, 1f, 1f, 1f,
            1f, 0f, 0f, 1f,
            1f, 0f, 1f, 1f,
            1f, 1f, 0f, 1f,
            1f, 1f, 1f, 1f,
            1f, 0f, 0f, 1f,
            0f, 1f, 0f, 1f,
            0f, 0f, 1f, 1f,
            1f, 0f, 1f, 1f
    };

这时候我们可以绘制出来的就是一个长方体了,需要结合缩放或者透视投影变为立方体。 但是我们只能看到一面,其他的面是无法看到的。最后我们需要结合旋转变换和观察点来观察我们的立方体,使我们能看到另外几个面。

着色器的代码如下

        vertexShaderCode =
                "uniform mat4 uMVPMatrix;" +
                        "attribute vec4 aPosition;" +
                        "attribute vec4 aColor;" +
                        "varying vec4 ourColor;" +
                        "void main() {" +
                        "  gl_Position = uMVPMatrix * aPosition;" +
                        "  ourColor = aColor;" + // 将ourColor设置为我们输入的颜色
                        "}";
        fragmentShaderCode =
                "precision mediump float;" + //预定义的全局默认精度
                        "varying vec4 ourColor;" +
                        "void main() {" +
                        "  gl_FragColor = ourColor;" +
                        "}"; // 动态改变颜色

在onSurfaceChanged中使用投影矩阵

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        super.onSurfaceChanged(gl, width, height);
        Matrix.setIdentityM(projectionMatrix, 0);
        float ratio = (float) width / height;

        // 设置透视投影矩阵,近点是3,远点是7
        Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

最后在onDrawFrame中绘制顶点,设置颜色,并开始不停的循转

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        int shaderProgram = OpenGLUtil.createProgram(vertexShaderCode, fragmentShaderCode);
        GLES20.glUseProgram(shaderProgram);

        int positionHandle = GLES20.glGetAttribLocation(shaderProgram, "aPosition");
        GLES20.glEnableVertexAttribArray(positionHandle);
        FloatBuffer vertexBuffer = OpenGLUtil.createFloatBuffer(CubeCoords);
        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT,
                false, 3 * 4, vertexBuffer);

        int colorHandle = GLES20.glGetAttribLocation(shaderProgram, "aColor");
        GLES20.glEnableVertexAttribArray(colorHandle);
        FloatBuffer colorBuffer = OpenGLUtil.createFloatBuffer(colors);
        GLES20.glVertexAttribPointer(colorHandle, 4, GLES20.GL_FLOAT,
                false, 4 * 4, colorBuffer);

        // 得到形状的变换矩阵的句柄
        int mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");
        // 创建一个旋转矩阵
        Matrix.setRotateM(rotationMatrix, 0, angle, 0, 1, 0);
        // 设置观察点,当eyeZ是3时最大,是7时最小,超过这个范围时不可见
        Matrix.setLookAtM(viewMatrix, 0, 0, 0, 4f,
                0f, 0f, 0f,
                0f, 1.0f, 0.0f);
        // 计算
        Matrix.multiplyMM(tempMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        // 计算
        Matrix.multiplyMM(vPMatrix, 0, tempMatrix, 0, rotationMatrix, 0);

        // 将视图转换传递给着色器
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, vPMatrix, 0);
        ShortBuffer indexBuffer = OpenGLUtil.createShortBuffer(indices);
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length,
                GLES20.GL_UNSIGNED_SHORT, indexBuffer);
        GLES20.glDisableVertexAttribArray(positionHandle);
        angle += 2;
    }

这时候我们就能看到一个不停旋转的立方体了。

我们用的是GL_TRIANGLES绘制的三角形,但是我们可以使用GL_TRIANGLE_STRIP来绘制面,减少索引的大小。在使用这两种方式时,需要先转为一个平面,然后再确定索引,如向上立方体转为平面图形时如下,

让我们确定开始点和结束点后确定索引的顺序:2, 3, 0, 1, 5, 3, 7, 2, 6, 0, 4, 5, 6, 7

注:立方体展开图有14种之多,可以自己展开后来确定索引。

如果以GL_TRIANGLE_FAN的方式绘制,则需要先以0顶点绘制三个面,再以7顶点绘制三个面。

源码地址https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/view/window/CubeView.java

总的来说以GL_TRIANGLE_STRIP方式绘制比较难理解,但是索引最少;以GL_TRIANGLE_FAN绘制需要的索引次之,也便于理解;以GL_TRIANGLES的方式绘制最好理解,但是索引量最大。

发布了53 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/jklwan/article/details/103513757