Buscar..


Introducción a las matrices.

Cuando estés programando en OpenGL o en cualquier otra api de gráficos, chocarás contra una pared de ladrillos cuando no seas tan bueno en matemáticas. Aquí explicaré con código de ejemplo cómo puede lograr movimiento / escalado y muchas otras cosas interesantes con su objeto 3D.

Tomemos un caso de la vida real ... Has creado un cubo increíble (tridimensional) en OpenGL y quieres moverlo en cualquier dirección.

glUseProgram(cubeProgram)
glBindVertexArray(cubeVAO)
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

En los motores de juego como Unity3d esto sería fácil. Simplemente llamaría transform.Translate () y terminaría con él, pero OpenGL no incluye una biblioteca matemática.

Una buena biblioteca de matemáticas es glm, pero para transmitir mi punto codificaré todos los (importantes) métodos matemáticos para que salgas.

Primero debemos entender que un objeto 3d en OpenGL contiene mucha información, hay muchas variables que dependen unas de otras. Una forma inteligente de gestionar todas estas variables es mediante el uso de matrices.

Una matriz es una colección de variables escritas en columnas y filas. Una matriz puede ser 1x1, 2x4 o cualquier número arbitrario.

[1|2|3]
[4|5|6]
[7|8|9] //A 3x3 matrix

Puedes hacer cosas realmente geniales con ellos ... pero, ¿cómo pueden ayudarme a mover mi cubo? Para entender esto, primero necesitamos saber varias cosas.

  • ¿Cómo se hace una matriz desde una posición?
  • ¿Cómo se traduce una matriz?
  • ¿Cómo se pasa a OpenGL?

Hagamos una clase que contenga todos nuestros datos y métodos de matriz importantes (escritos en c ++)

template<typename T>
//Very simple vector containing 4 variables
struct Vector4{
    T x, y, z, w;
    Vector4(T x, T y, T z, T w) : x(x), y(y), z(z), w(w){}
    Vector4(){}

    Vector4<T>& operator=(Vector4<T> other){
        this->x = other.x;
        this->y = other.y;
        this->z = other.z;
        this->w = other.w;
        return *this;
    }
}

template<typename T>
struct Matrix4x4{
     /*!
     *  You see there are columns and rows like this
     */
    Vector4<T> row1,row2,row3,row4;

    /*!
     *  Initializes the matrix with a identity matrix. (all zeroes except the ones diagonal)
     */
    Matrix4x4(){
        row1 = Vector4<T>(1,0,0,0);
        row2 = Vector4<T>(0,1,0,0);
        row3 = Vector4<T>(0,0,1,0);
        row4 = Vector4<T>(0,0,0,1);
    }

    static Matrix4x4<T> identityMatrix(){
        return Matrix4x4<T>(
                        Vector4<T>(1,0,0,0),
                        Vector4<T>(0,1,0,0),
                        Vector4<T>(0,0,1,0),
                        Vector4<T>(0,0,0,1));
    }



    Matrix4x4(const Matrix4x4<T>& other){
        this->row1 = other.row1;
        this->row2 = other.row2;
        this->row3 = other.row3;
        this->row4 = other.row4;
    }

    Matrix4x4(Vector4<T> r1, Vector4<T> r2, Vector4<T> r3, Vector4<T> r4){
        this->row1 = r1;
        this->row2 = r2;
        this->row3 = r3;
        this->row4 = r4;
    }

      /*!
     *  Get all the data in an Vector
     *  @return rawData The vector with all the row data
     */
    std::vector<T> getRawData() const{
        return{
            row1.x,row1.y,row1.z,row1.w,
            row2.x,row2.y,row2.z,row2.w,
            row3.x,row3.y,row3.z,row3.w,
            row4.x,row4.y,row4.z,row4.w
        };
    }

}

Primero notamos algo muy peculiar en el constructor por defecto de una matriz de 4 por 4. Cuando se llama, no comienza todo en cero, pero como:

[1|0|0|0]
[0|1|0|0]
[0|0|1|0]
[0|0|0|1] //A identity 4 by 4 matrix

Todas las matrices deben comenzar con unas en diagonal. (solo porque>. <)

Bien, entonces declaremos en nuestro cubo épico una matriz de 4 por 4.

glUseProgram(cubeProgram)
Matrix4x4<float> position;
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, cubeData);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

Ahora que tenemos todas nuestras variables, ¡finalmente podemos comenzar a hacer algunos cálculos! Vamos a hacer la traducción. Si ha programado en Unity3d, puede recordar una función Transform.Translate. Vamos a implementarlo en nuestra propia clase de matriz.

 /*!
 *  Translates the matrix to
 *  @param vector, The vector you wish to translate to
 */
static Matrix4x4<T> translate(Matrix4x4<T> mat, T x, T y, T z){
    Matrix4x4<T> result(mat);
    result.row1.w += x;
    result.row2.w += y;
    result.row3.w += z;
    return result;
}

Esta es toda la matemática necesaria para mover el cubo (no la rotación ni la escala), funciona en todos los ángulos. Implementemos esto en nuestro escenario de la vida real.

glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

Nuestro shader necesita usar nuestra maravillosa matriz.

#version 410 core
uniform mat4 mv_matrix;
layout(location = 0) in vec4 position;

void main(void){
    gl_Position = v_matrix * position;
}

Y debería funcionar ... pero parece que ya tenemos un error en nuestro programa. Cuando te mueves a lo largo del eje z, tu objeto parece desaparecer en el aire. Esto es porque no tenemos una matriz de proyección. Para resolver este error necesitamos saber dos cosas:

  1. ¿Cómo se ve una matriz de proyección?
  2. ¿Cómo podemos combinarlo con nuestra matriz de posición?

Bueno, podemos hacer una perspectiva (estamos usando tres dimensiones después de todo) matriz El código

template<typename T>
Matrix4x4<T> perspective(T fovy, T aspect, T near, T far){
    
    T q = 1.0f / tan((0.5f * fovy) * (3.14 / 180));
    T A = q / aspect;
    T B = (near + far) / (near - far);
    T C = (2.0f * near * far) / (near - far);
    
    return Matrix4x4<T>(
        Vector4<T>(A,0,0,0),
        Vector4<T>(0,q,0,0),
        Vector4<T>(0,0,B,-1),
        Vector4<T>(0,0,C,0));
}

Parece aterrador, pero este método en realidad calculará una matriz de cuán lejos desea mirar en la distancia (y qué tan cerca) y su campo de visión.

Ahora tenemos una matriz de proyección y una matriz de posición ... ¿Pero cómo los combinamos? Bueno, lo divertido es que en realidad podemos multiplicar dos matrices entre sí.

/*!
 *  Multiplies a matrix with an other matrix
 *  @param other, the matrix you wish to multiply with
 */
static Matrix4x4<T> multiply(const Matrix4x4<T>& first,const Matrix4x4<T>& other){
    //generate temporary matrix
    Matrix4x4<T> result;
    //Row 1
    result.row1.x = first.row1.x * other.row1.x + first.row1.y * other.row2.x + first.row1.z * other.row3.x + first.row1.w * other.row4.x;
    result.row1.y = first.row1.x * other.row1.y + first.row1.y * other.row2.y + first.row1.z * other.row3.y + first.row1.w * other.row4.y;
    result.row1.z = first.row1.x * other.row1.z + first.row1.y * other.row2.z + first.row1.z * other.row3.z + first.row1.w * other.row4.z;
    result.row1.w = first.row1.x * other.row1.w + first.row1.y * other.row2.w + first.row1.z * other.row3.w + first.row1.w * other.row4.w;
    
    //Row2
    result.row2.x = first.row2.x * other.row1.x + first.row2.y * other.row2.x + first.row2.z * other.row3.x + first.row2.w * other.row4.x;
    result.row2.y = first.row2.x * other.row1.y + first.row2.y * other.row2.y + first.row2.z * other.row3.y + first.row2.w * other.row4.y;
    result.row2.z = first.row2.x * other.row1.z + first.row2.y * other.row2.z + first.row2.z * other.row3.z + first.row2.w * other.row4.z;
    result.row2.w = first.row2.x * other.row1.w + first.row2.y * other.row2.w + first.row2.z * other.row3.w + first.row2.w * other.row4.w;
    
    //Row3
    result.row3.x = first.row3.x * other.row1.x + first.row3.y * other.row2.x + first.row3.z * other.row3.x + first.row3.w * other.row4.x;
    result.row3.y = first.row3.x * other.row1.y + first.row3.y * other.row2.y + first.row3.z * other.row3.y + first.row3.w * other.row4.y;
    result.row3.z = first.row3.x * other.row1.z + first.row3.y * other.row2.z + first.row3.z * other.row3.z + first.row3.w * other.row4.z;
    result.row3.w = first.row3.x * other.row1.w + first.row3.y * other.row2.w + first.row3.z * other.row3.w + first.row3.w * other.row4.w;
    
    //Row4
    result.row4.x = first.row4.x * other.row1.x + first.row4.y * other.row2.x + first.row4.z * other.row3.x + first.row4.w * other.row4.x;
    result.row4.y = first.row4.x * other.row1.y + first.row4.y * other.row2.y + first.row4.z * other.row3.y + first.row4.w * other.row4.y;
    result.row4.z = first.row4.x * other.row1.z + first.row4.y * other.row2.z + first.row4.z * other.row3.z + first.row4.w * other.row4.z;
    result.row4.w = first.row4.x * other.row1.w + first.row4.y * other.row2.w + first.row4.z * other.row3.w + first.row4.w * other.row4.w;
    
    return result;
}

Ooef ... eso es un montón de código que en realidad parece más aterrador de lo que realmente parece. Se puede hacer en un bucle for, pero (probablemente erróneamente) pensé que esto sería más claro para las personas que nunca han trabajado con matrices.

Mira el código y observa un patrón que se repite. Multiplique la columna por la fila, agréguela y continúe (esto es lo mismo para cualquier matriz de tamaño)

* Tenga en cuenta que la multiplicación con matrices no es como la multiplicación normal. AXB! = B x A *

Ahora que sabemos cómo proyectar y agregar esto a nuestra matriz de posición, nuestro código de la vida real probablemente se verá así:

glUseProgram(cubeProgram)
Matrix4x4<float> position;
position = Matrix4x4<float>::translate(position, 1,0,0);
position = Matrix4x4<float>::multiply(Matrix<float>::perspective<float>(50, 1 , 0.1f, 100000.0f), position);
glBindVertexArray(cubeVAO)
glUniformMatrix4fv(shaderRef, 1, GL_TRUE, &position.getRawData()[0]);
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0,cubeVerticesSize)

Ahora nuestro bicho está aplastado y nuestro cubo parece bastante épico en la distancia. Si desea escalar su cubo, la fórmula es la siguiente:

 /*!
 *  Scales the matrix with given vector
 *  @param s The vector you wish to scale with
 */
static Matrix4x4<T> scale(const Matrix4x4<T>& mat, T x, T y, T z){
    Matrix4x4<T> tmp(mat);
    tmp.row1.x *= x;
    tmp.row2.y *= y;
    tmp.row3.z *= z;
    return tmp;
}

Solo necesitas ajustar las variables diagonales.

Para la rotación necesitas echar un vistazo más de cerca a los Cuaterniones.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow