Cómo aplicar vistas de cámara y proyección

En el entorno de OpenGL ES, las vistas de proyección y cámara permiten mostrar objetos dibujados en una que se asemeja más al modo en que ves los objetos físicos con los ojos. Esta simulación de la visualización física se realiza con transformaciones matemáticas de coordenadas de objetos dibujados:

  • Proyección: Esta transformación ajusta las coordenadas de los objetos dibujados según el ancho y la altura de GLSurfaceView donde se muestran Sin este cálculo, los objetos dibujados por OpenGL ES están sesgados por las proporciones desiguales de la vista en la ventana modal. En general, una transformación de proyección solo debe calcularse cuando las proporciones de la se establecen o cambian en el método onSurfaceChanged() de tu procesador. Para obtener más información sobre las proyecciones de OpenGL ES y mapeo de coordenadas, consulta Cómo asignar coordenadas para dibujos objetos.
  • Vista de cámara: esta transformación ajusta las coordenadas de los objetos dibujados según una la posición de la cámara virtual. Es importante tener en cuenta que OpenGL ES no define un modelo sino que proporciona métodos de utilidades que simulan una cámara transformando la visualización de dibujados. Una transformación de vista de cámara se puede calcular solo una vez cuando estableces tu GLSurfaceView o puede cambiar de forma dinámica según las acciones del usuario o tu función de la aplicación.

En esta lección, se describe cómo crear una vista de cámara y proyección, y cómo aplicarla a las formas dibujadas en tu GLSurfaceView.

Cómo definir una proyección

Los datos para una transformación de proyección se calculan en onSurfaceChanged(). de tu clase GLSurfaceView.Renderer. El siguiente código de ejemplo toma la altura y el ancho de la GLSurfaceView y la usa para propagar Transformación de proyección Matrix con el método 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);
}

Este código propaga una matriz de proyección, mProjectionMatrix, que luego puedes combinar. con una transformación de vista de cámara en el método onDrawFrame(), que se muestra en la siguiente sección

Nota: Solo aplica una transformación de proyección a tu los objetos de dibujo generalmente dan como resultado una pantalla muy vacía. En general, también debes aplicar una cámara la transformación de datos para que algo aparezca en la pantalla.

Define una vista de cámara

Completa el proceso de transformación de los objetos dibujados; para ello, agrega una transformación de vista de cámara como parte del proceso de dibujo en tu renderizador. En el siguiente código de ejemplo, la vista de cámara transformación se calcula con Matrix.setLookAtM() y, luego, combinarse con la matriz de proyección calculada previamente. La combinación las matrices de transformación se pasan a la forma dibujada.

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

Cómo aplicar transformaciones de cámara y proyección

Para usar la matriz de transformación combinada de proyección y vista de cámara que se muestra en la primero, agrega una variable de matriz al sombreador de vértices definido previamente En la clase 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;

    ...
}

A continuación, modifica el método draw() de tus objetos gráficos para aceptar las combinaciones de transformación y aplícala a la siguiente forma:

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

Una vez que hayas calculado y aplicado correctamente las transformaciones de proyección y vista de cámara, tus objetos gráficos se dibujan en proporciones correctas y deberían verse de la siguiente manera:

Figura 1: Triángulo dibujado con una vista de cámara y proyección aplicada

Ahora que tienes una aplicación que muestra las formas en las proporciones correctas, es hora de agregar movimiento a tus formas.