应用投影和相机视图

在 OpenGL ES 环境中,通过投影和相机视图,显示的绘制对象更接近于眼睛看到的实物。这种对实物查看的模拟是通过对绘制对象坐标进行数学转换完成的:

  • 投影 - 这种转换可根据显示绘制对象的 GLSurfaceView 的宽度和高度调整绘制对象的坐标。如果不进行这种计算,OpenGL ES 绘制的对象会被不等比例的视图窗口所扭曲。通常只有在渲染程序的 onSurfaceChanged() 方法中确定或更改 OpenGL 视图的比例时,才需要计算投影转换。如需详细了解 OpenGL ES 投影和坐标映射,请参阅映射绘制对象的坐标
  • 相机视图 - 这种转换可根据虚拟相机的位置调整绘制对象的坐标。请务必注意,OpenGL ES 不会定义实际的相机对象,而是通过转换绘制对象的显示方式提供模拟相机的实用程序方法。相机视图转换可能仅在您确定 GLSurfaceView 时计算一次,也可能会根据用户操作或应用的功能动态变化。

本课程介绍了如何创建投影和相机视图,并将其应用于 GLSurfaceView 中绘制的形状。

定义投影

用于投影转换的数据使用 GLSurfaceView.RendereronSurfaceChanged() 方法计算。以下示例代码采用 GLSurfaceView 的高度和宽度,并使用它通过 Matrix.frustumM() 方法填充投影转换 Matrix

Kotlin

    // vPMatrix is an abbreviation for "Model View Projection Matrix"
    private val vPMatrix = FloatArray(16)
    private val projectionMatrix = FloatArray(16)
    private val viewMatrix = FloatArray(16)

    override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height)

        val ratio: Float = width.toFloat() / height.toFloat()

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

Java

    // vPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] vPMatrix = new float[16];
    private final float[] projectionMatrix = new float[16];
    private final float[] viewMatrix = new float[16];

    @Override
    public void onSurfaceChanged(GL10 unused, 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(projectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }
    

该代码填充了一个投影矩阵 mProjectionMatrix,您之后可以将其与 onDrawFrame() 方法中的相机视图转换合并,这在下一部分中进行介绍。

注意:仅将投影转换应用于绘制的对象通常会导致显示画面过于空旷。一般而言,要在屏幕上显示任何内容,您还必须应用相机视图转换。

定义相机视图

通过在渲染程序中添加相机视图转换作为绘制流程的一部分,完成绘制对象的转换流程。在以下示例代码中,相机视图转换使用 Matrix.setLookAtM() 方法进行计算,然后与之前计算的投影矩阵合并。之后,系统会将合并后的转换矩阵传递到绘制的形状。

Kotlin

    override fun onDrawFrame(unused: GL10) {
        ...
        // Set the camera position (View matrix)
        Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)

        // Calculate the projection and view transformation
        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0)

        // Draw shape
        triangle.draw(vPMatrix)
    

Java

    @Override
    public void onDrawFrame(GL10 unused) {
        ...
        // Set the camera position (View matrix)
        Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation
        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);

        // Draw shape
        triangle.draw(vPMatrix);
    }
    

应用投影和相机转换

为了使用预览部分中显示的合并后的投影和相机视图转换矩阵,请先将矩阵变体添加到之前在 Triangle 类中定义的顶点着色程序。

Kotlin

    class Triangle {

        private val 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;" +
                "}"

        // Use to access and set the view transformation
        private var vPMatrixHandle: Int = 0

        ...
    }
    

Java

    public 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;" +
            "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 vPMatrixHandle;

        ...
    }
    

接下来,修改图形对象的 draw() 方法以接受合并后的转换矩阵,并将其应用于形状:

Kotlin

    fun draw(mvpMatrix: FloatArray) { // pass in the calculated transformation matrix

        // get handle to shape's transformation matrix
        vPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix")

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

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

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(positionHandle)
    }
    

Java

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

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

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

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

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

正确计算并应用投影和相机视图转换之后,系统会以正确的比例绘制您的图形对象,这些对象应如下所示:

图 1. 应用了投影和相机视图后绘制的三角形。

现在,您的应用已经能够按照正确的比例显示形状,接下来该为形状添加动画了。