Recherche…


Introduction aux matrices

Lorsque vous programmez en OpenGL ou toute autre API graphique, vous frappez un mur de briques lorsque vous n'êtes pas si bon en maths. Ici, je vais expliquer avec un exemple de code comment vous pouvez réaliser un mouvement / une mise à l'échelle et bien d'autres choses intéressantes avec votre objet 3D.

Prenons un cas réel ... Vous avez créé un cube impressionnant (en trois dimensions) dans OpenGL et vous voulez le déplacer dans n'importe quelle direction.

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

Dans les moteurs de jeu comme Unity3d, ce serait facile. Vous appelez simplement transform.Translate () et vous en avez fini, mais OpenGL n'inclut pas de bibliothèque mathématique.

Une bonne bibliothèque de maths est glm mais pour vous faire comprendre, je coderai toutes les méthodes mathématiques (importantes) pour vous.

Tout d'abord, nous devons comprendre qu'un objet 3D dans OpenGL contient beaucoup d'informations, il y a beaucoup de variables qui dépendent les unes des autres. Une manière intelligente de gérer toutes ces variables consiste à utiliser des matrices.

Une matrice est une collection de variables écrites en colonnes et en lignes. Une matrice peut être 1x1, 2x4 ou n'importe quel nombre arbitraire.

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

Vous pouvez faire des choses vraiment cool avec eux ... mais comment peuvent-ils m'aider à déplacer mon cube? Pour comprendre cela, il faut d'abord connaître plusieurs choses.

  • Comment faites-vous une matrice à partir d'une position?
  • Comment traduisez-vous une matrice?
  • Comment le transmettez-vous à OpenGL?

Faisons une classe contenant toutes nos données et méthodes matricielles importantes (écrites 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
        };
    }

}

Nous remarquons d’abord une chose très particulière dans le constructeur par défaut d’une matrice 4 par 4. Lorsqu'il est appelé, il ne démarre pas tous sur zéro mais comme:

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

Toutes les matrices doivent commencer par celles de la diagonale. (juste parce que>. <)

Bon alors déclarons à notre cube épique une matrice 4 par 4.

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

Maintenant, nous avons toutes nos variables, nous pouvons enfin commencer à faire des calculs! Faisons la traduction. Si vous avez programmé dans Unity3d, vous pourriez vous souvenir d'une fonction Transform.Translate. Implémentons-le dans notre propre classe de matrice

 /*!
 *  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;
}

C'est tout le calcul nécessaire pour déplacer le cube (pas de rotation ni de mise à l'échelle). Cela fonctionne à tous les angles. Implémentons cela dans notre scénario réel

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)

Notre shader doit utiliser notre merveilleuse matrice

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

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

Et ça devrait marcher .... mais il semble que nous ayons déjà un bug dans notre programme. Lorsque vous vous déplacez le long de l'axe z, votre objet semble disparaître dans l'air. C'est parce que nous n'avons pas de matrice de projection. Pour résoudre ce problème, nous devons connaître deux choses:

  1. A quoi ressemble une matrice de projection?
  2. Comment pouvons-nous le combiner avec notre matrice de position?

Eh bien, nous pouvons faire une perspective (nous utilisons trois dimensions après tout) matrice Le code

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

Cela semble effrayant, mais cette méthode calcule en fait une matrice de la distance à parcourir (et de la distance) et de votre champ de vision.

Nous avons maintenant une matrice de projection et une matrice de position. Mais comment les combinons-nous? Ce qui est amusant, c'est que nous pouvons multiplier deux matrices les unes avec les autres.

/*!
 *  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 .. c'est beaucoup de code qui semble plus effrayant que ça. Cela peut être fait dans une boucle for, mais j'ai pensé (probablement à tort) que cela serait plus clair pour les personnes qui n'ont jamais travaillé avec des matrices.

Regardez le code et remarquez un motif répétitif. Multipliez la colonne par la ligne ajoutez-la et continuez (c'est la même chose pour toute matrice de taille)

* Notez que la multiplication avec les matrices n’est pas comme la multiplication normale. AXB! = B x A *

Maintenant que nous savons comment projeter et ajouter ceci à notre matrice de position, notre code réel ressemblera probablement à ceci:

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)

Maintenant, notre bogue est écrasé et notre cube semble assez épique au loin. Si vous souhaitez adapter votre cube, la formule est la suivante:

 /*!
 *  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;
}

Il vous suffit d'ajuster les variables diagonales.

Pour la rotation, vous devez regarder de plus près Quaternions.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow