Áp dụng phép chiếu và khung hiển thị camera

Trong môi trường OpenGL ES, các khung hiển thị phép chiếu và máy ảnh cho phép bạn hiển thị các đối tượng đã vẽ theo cách gần giống với cách bạn nhìn các đối tượng thực tế bằng mắt của mình. Việc mô phỏng hoạt động xem thực tế này được thực hiện bằng các phép biến đổi toán học của toạ độ đối tượng được vẽ:

  • Phép chiếu – Phép biến đổi này điều chỉnh toạ độ của các đối tượng được vẽ dựa trên chiều rộng và chiều cao của GLSurfaceView mà các đối tượng đó hiển thị. Nếu không có cách tính này, các đối tượng do OpenGL ES vẽ sẽ bị lệch theo tỷ lệ không bằng nhau của cửa sổ chế độ xem. Thông thường, bạn chỉ phải tính toán một phép biến đổi phép chiếu khi tỷ lệ của khung hiển thị OpenGL được thiết lập hoặc thay đổi trong phương thức onSurfaceChanged() của trình kết xuất. Để biết thêm thông tin về các phép chiếu OpenGL ES và ánh xạ toạ độ, hãy xem bài viết Liên kết toạ độ cho các đối tượng đã vẽ.
  • Camera View (Khung hiển thị máy ảnh) – Phép biến đổi này điều chỉnh toạ độ của các đối tượng được vẽ dựa trên vị trí máy ảnh ảo. Điều quan trọng cần lưu ý là OpenGL ES không xác định đối tượng máy ảnh thực tế, mà thay vào đó cung cấp các phương thức tiện ích mô phỏng máy ảnh bằng cách chuyển đổi màn hình của các đối tượng đã vẽ. Phép biến đổi khung hiển thị máy ảnh có thể chỉ được tính một lần khi bạn thiết lập GLSurfaceView hoặc có thể thay đổi linh động dựa trên thao tác của người dùng hoặc chức năng của ứng dụng.

Bài học này mô tả cách tạo một phép chiếu và khung hiển thị camera, đồng thời áp dụng cho các hình dạng được vẽ trong GLSurfaceView.

Xác định phép chiếu

Dữ liệu cho phép biến đổi phép chiếu được tính toán trong phương thức onSurfaceChanged() của lớp GLSurfaceView.Renderer. Mã ví dụ sau đây lấy chiều cao và chiều rộng của GLSurfaceView rồi dùng giá trị này để điền sẵn một phép biến đổi phép chiếu Matrix bằng phương thức Matrix.frustumM():

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);
}

Mã này sẽ điền sẵn một ma trận chiếu, mProjectionMatrix, sau đó bạn có thể kết hợp với phép biến đổi thành phần hiển thị máy ảnh trong phương thức onDrawFrame(), như minh hoạ trong phần tiếp theo.

Lưu ý: Việc chỉ áp dụng phép biến đổi phép chiếu cho các đối tượng vẽ thường dẫn đến màn hình rất trống. Nhìn chung, bạn cũng phải áp dụng phép biến đổi thành phần hiển thị máy ảnh để mọi nội dung xuất hiện trên màn hình.

Xác định chế độ xem camera

Hoàn tất quy trình chuyển đổi các đối tượng được vẽ bằng cách thêm phép biến đổi thành phần hiển thị máy ảnh trong quy trình vẽ trong trình kết xuất. Trong mã ví dụ sau, phép biến đổi khung hiển thị camera được tính toán bằng phương thức Matrix.setLookAtM(), sau đó kết hợp với ma trận chiếu đã tính toán trước đó. Sau đó, các ma trận biến đổi tổng hợp được truyền vào hình dạng đã vẽ.

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);
}

Áp dụng các phép biến đổi phép chiếu và camera

Để sử dụng ma trận biến đổi phép chiếu và chế độ xem máy ảnh kết hợp như trong phần xem trước, trước tiên hãy thêm một biến ma trận vào trình đổ bóng đỉnh đã được xác định trước đó trong lớp 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;

    ...
}

Tiếp theo, hãy sửa đổi phương thức draw() của các đối tượng đồ hoạ để chấp nhận ma trận biến đổi kết hợp và áp dụng cho hình dạng:

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);
}

Sau khi bạn tính toán và áp dụng chính xác các phép biến đổi phép chiếu và khung hiển thị máy ảnh, các đối tượng đồ hoạ của bạn sẽ được vẽ theo tỷ lệ chính xác và có dạng như sau:

Hình 1. Hình tam giác được vẽ bằng phép chiếu và chế độ xem máy ảnh được áp dụng.

Giờ đây, bạn đã có ứng dụng hiển thị hình dạng theo tỷ lệ chính xác, đã đến lúc thêm chuyển động vào hình dạng.