Ricerca…


Introduzione alle matrici

Quando stai programmando in OpenGL o in qualsiasi altra API grafica, colpisci un muro di mattoni quando non sei bravo in matematica. Qui spiegherò con codice di esempio come puoi ottenere movimento / ridimensionamento e molte altre cose interessanti con il tuo oggetto 3d.

Prendiamo un caso di vita reale ... Hai creato un cubo fantastico (tridimensionale) in OpenGL e vuoi spostarlo in qualsiasi direzione.

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

Nei motori di gioco come Unity3d questo sarebbe facile. Chiameresti transform.Translate () e avresti finito con esso, ma OpenGL non include una libreria matematica.

Una buona libreria di matematica è glaciale, ma per ottenere il mio punto di vista, codificherò tutti i (importanti) metodi matematici per te.

Per prima cosa dobbiamo capire che un oggetto 3d in OpenGL contiene molte informazioni, ci sono molte variabili che dipendono l'una dall'altra. Un modo intelligente per gestire tutte queste variabili è usando le matrici.

Una matrice è una raccolta di variabili scritte in colonne e righe. Una matrice può essere 1x1, 2x4 o qualsiasi numero arbitrario.

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

Puoi fare cose davvero interessanti con loro ... ma come possono aiutarmi a spostare il mio cubo? Per capirlo davvero, prima dobbiamo conoscere diverse cose.

  • Come si crea una matrice da una posizione?
  • Come si traduce una matrice?
  • Come lo passi a OpenGL?

Facciamo una classe contenente tutti i nostri importanti dati e metodi di matrice (scritti in 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
        };
    }

}

Innanzitutto notiamo una cosa molto particolare nel costruttore predefinito di una matrice 4 x 4. Quando chiamato non inizia tutto su zero ma come:

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

Tutte le matrici dovrebbero iniziare con quelle sulla diagonale. (solo perché>. <)

Bene, dichiariamo al nostro cubo epico una matrice 4 x 4.

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

Ora abbiamo davvero tutte le nostre variabili che possiamo finalmente iniziare a fare un po 'di matematica! Facciamo la traduzione. Se hai programmato in Unity3d potresti ricordare una funzione Transform.Translate. Implementiamolo nella nostra classe 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;
}

Questa è tutta la matematica necessaria per muovere il cubo (non ruotare o ridimensionare la mente) Funziona a tutti gli angoli. Implementiamo questo nel nostro scenario di vita reale

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)

Il nostro shader deve usare la nostra meravigliosa matrice

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

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

E dovrebbe funzionare ... ma sembra che abbiamo già un bug nel nostro programma. Quando ti sposti lungo l'asse z, il tuo oggetto sembra scomparire nel nulla. Questo perché non abbiamo una matrice di proiezione. Per risolvere questo bug abbiamo bisogno di sapere due cose:

  1. Come appare una matrice di proiezione?
  2. Come possiamo combinarlo con la nostra matrice di posizioni?

Bene possiamo creare una prospettiva (stiamo usando tre dimensioni dopotutto) la matrice Il codice

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

Sembra spaventoso, ma questo metodo calcolerà in realtà una matrice di quanto lontano desideri guardare in lontananza (e quanto vicino) e il tuo campo visivo.

Ora abbiamo una matrice di proiezione e una matrice di posizione .. Ma come li combiniamo? La cosa divertente è che possiamo effettivamente moltiplicare due matrici l'una con l'altra.

/*!
 *  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 .. questo è un sacco di codice che in realtà sembra più spaventoso di quanto effettivamente sembra. Può essere fatto in un ciclo for, ma io (probabilmente erroneamente) ho pensato che sarebbe stato più chiaro per le persone che non hanno mai lavorato con le matrici.

Guarda il codice e nota uno schema ripetuto. Moltiplicare la colonna con la riga aggiungerla e continuare. (Questo è lo stesso per qualsiasi matrice di dimensioni)

* Nota che la moltiplicazione con le matrici non è come la normale moltiplicazione. AXB! = B x A *

Ora sappiamo come proiettare e aggiungere alla nostra matrice di posizioni il nostro codice di vita reale probabilmente sarà simile a:

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)

Ora il nostro bug è schiacciato e il nostro cubo sembra piuttosto epico in lontananza. Se desideri ridimensionare il cubo, la formula è questa:

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

Hai solo bisogno di regolare le variabili diagonali.

Per la rotazione è necessario dare un'occhiata più da vicino ai Quaternioni.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow