应用投影和相机视图

在 OpenGL ES 环境中,投影和摄像机视图允许您以更接近于您用眼睛观察物理对象的方式显示绘制的对象。这种物理查看的模拟是通过绘制对象坐标的数学变换来完成的。

  • 投影 - 此变换根据显示绘制对象的 GLSurfaceView 的宽度和高度调整绘制对象的坐标。如果没有此计算,则 OpenGL ES 绘制的对象会因视图窗口的不等比例而发生倾斜。投影变换通常只需要在 OpenGL 视图的比例在渲染器的 onSurfaceChanged() 方法中建立或更改时进行计算。有关 OpenGL ES 投影和坐标映射的更多信息,请参阅 绘制对象的坐标映射
  • 摄像机视图 - 此变换根据虚拟摄像机位置调整绘制对象的坐标。需要注意的是,OpenGL ES 没有定义实际的摄像机对象,而是提供了模拟摄像机的方法,方法是变换绘制对象的显示。摄像机视图变换可能只在您建立 GLSurfaceView 时计算一次,或者根据用户操作或应用程序的功能动态更改。

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

定义投影

投影变换的数据是在 onSurfaceChanged() 方法中计算的,该方法位于 GLSurfaceView.Renderer 类中。以下示例代码获取 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(mProgram, "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(mProgram, "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. 应用了投影和摄像机视图的三角形。

现在您有一个以正确比例显示形状的应用程序,是时候为形状添加运动了。