Android开发OpenGL ES

OpenGL ES 运用投影与相机视角

2017-08-21  本文已影响216人  石先

文章中所有的代码示例都已放在 Github 上,可以去项目 OpenGL-ES-Learning 中查看 。

在前面的文章中介绍了如何绘制形状,相信你对于使用 OpenGL ES 进行绘制的流程有了大致的了解。其中包括一些基本概念:

如果你对这些概念还有些不熟悉,可以回头再看一下前面的文章,把这些简单的概念过一遍可以强化理解。

在 OpenGL ES 环境中,利用投影和相机视角可以让显示的绘图对象更加酷似于我们用肉眼看到的真实物体。该物理视角的模拟是对绘制对象坐标的进行数学变换实现的:

关于更多 OpenGL ES 投影和坐标映射的知识,可以阅读 Mapping Coordinates for Drawn Objects

本篇文章主要阐述如何创建一个投影和一个相机视角,并应用到 GLSurfaceView 中的绘制图像上。

定义一个投影

投影变换的数据是在 GLSurfaceView.Renderer 类的 onSurfaceChanged() 方法中计算出来的。

下面的代码先获取 GLSurfaceView 的高和宽,然后利用它并使用 Matrix.frustumM() 方法来填充一个投影变换矩阵(Projection Transformation Matrix):


public class MyGLRenderer3 implements GLSurfaceView.Renderer {
 
    ...
    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];

    ...

  @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        // 获取 GLSurfaceView 的宽和高的比例
        float ratio = (float) width / height;

        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method

       // 填充了一个投影矩阵:mProjectionMatrix
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }
}

Note: 在绘图对象上只应用一个投影变换时会导致显示 empty display 。所以我们在进行 projection transformation(投影变换)时通常还要进行一个相机视角转化,使得显示对象能全部出现在屏幕上。

定义一个相机视角

在渲染器中添加一个相机视角变换作为图形绘制过程的一部分,以此完成我们的绘图对象所需变换的所有步骤。下面的代码在 onDrawFrame 回调返回中使用 Matrix.setLookAtM() 方法来计算相机视角变换,然后与之前计算的投影矩阵结合起来,结合后的变换矩阵传递给绘制图像:

 @Override
    public void onDrawFrame(GL10 gl) {

        // Set the camera position (View matrix)
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

        // Draw shape
        mTriangle.draw(mMVPMatrix);
    }

因此整个渲染器包含了投影变换和相机视变换的结合,该类的全部代码如下所示:

public class MyGLRenderer3 implements GLSurfaceView.Renderer {

    private Triangle mTriangle;

    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];


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

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        float ratio = (float) width / height;

        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

        // Set the camera position (View matrix)
        // 改变摄像头在 z 轴上的位置(镜头拉伸),让视图调整到合适的大小。
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

        // Draw shape
        mTriangle.draw(mMVPMatrix);
    }
}

应用投影和相机变换

为了使用在之前章节中结合了的相机视角变换和投影变换,我们首先为之前在 Triangle 类中定义的顶点着色器添加一个 Matrix 变量:

class Triangle {

    /**
     * 顶点着色器代码
     */
    private final String vertexShaderCode =
            // This matrix member variable provides a hook to manipulate
            // the coordinates of the objects that use this vertex shader
            "uniform mat4 uMVPMatrix;" + // 添加一个 Matrix 变量
            "attribute vec4 vPosition;" +  // 应用程序传入顶点着色器的顶点位置
                    "void main() {" +
                    // the matrix must be included as a modifier of gl_Position
                    // Note that the uMVPMatrix factor *must be first* in order
                    // for the matrix multiplication product to be correct.
                    "  gl_Position = uMVPMatrix * vPosition;" + // 设置此次绘制此顶点位置,进行矩阵变换
                    "}";

 // Use to access and set the view transformation
    private int mMVPMatrixHandle;
    ...
}

再修改原有图形对象(Triangle)的 draw() 方法,使得它接收组合后的变换矩阵,并将它应用到图形上:

 public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix

        ...
        // get handle to shape's transformation matrix
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        // Pass the projection and view transformation to the shader
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

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

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

下面给出图形对象的完整代码:

class Triangle {

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

    /**
     * 顶点着色器代码
     */
    private final String vertexShaderCode =
            // This matrix member variable provides a hook to manipulate
            // the coordinates of the objects that use this vertex shader
            "uniform mat4 uMVPMatrix;" +
            "attribute vec4 vPosition;" +  // 应用程序传入顶点着色器的顶点位置
                    "void main() {" +
                    // the matrix must be included as a modifier of gl_Position
                    // Note that the uMVPMatrix factor *must be first* in order
                    // for the matrix multiplication product to be correct.
                    "  gl_Position = uMVPMatrix * vPosition;" + // 设置此次绘制此顶点位置
                    "}";

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

    /**
     * 定义三角形顶点的坐标数据的浮点型缓冲区
     */
    private FloatBuffer vertexBuffer;

    static float triangleCoords[] = {   // 以逆时针顺序;
            0.0f,  0.622008459f, 0.0f,  // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f   // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    private final int mProgram;
    
    private int mPositionHandle;
    private int mColorHandle;

    // Use to access and set the view transformation
    private int mMVPMatrixHandle;

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


    public Triangle(){
        // 初始化形状中顶点坐标数据的字节缓冲区
        // 通过 allocateDirect 方法获取到 DirectByteBuffer 实例
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(
                // 顶点坐标个数 * 坐标数据类型 float 一个是 4 bytes
                triangleCoords.length * 4
        );

        // 设置缓冲区使用设备硬件的原本字节顺序进行读取;
        byteBuffer.order(ByteOrder.nativeOrder());

        // 因为 ByteBuffer 是将数据移进移出通道的唯一方式使用,这里使用 “as” 方法从 ByteBuffer 中获得一个基本类型缓冲区(浮点缓冲区)
        vertexBuffer = byteBuffer.asFloatBuffer();
        // 把顶点坐标信息数组存储到 FloatBuffer
        vertexBuffer.put(triangleCoords);
        // 设置从缓冲区的第一个位置开始读取顶点坐标信息
        vertexBuffer.position(0);

        // 加载编译顶点渲染器
        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);
    }


    public void draw(float[] mvpMatrix) { // pass in the calculated transformation matrix
        // 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);


        // get handle to shape's transformation matrix
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        // Pass the projection and view transformation to the shader
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

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

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

一旦我们正确地计算并应用了投影变换和相机视角变换,我们的图形就会以正确的比例绘制出来,它看上去会像是这样:

效果图

文章中所有的代码示例都已放在 Github 上,可以去项目 OpenGL-ES-Learning 中查看 。

现在,应用已经可以按正确的比例显示图形了,下面就来学习为图形添加一些动作效果。

>>>>Next>>>> : OpenGL ES 为视图添加动作

上一篇下一篇

猜你喜欢

热点阅读