Sök…


Introduktion till matriser

När du programmerar i OpenGL eller någon annan grafisk api kommer du att träffa en tegelvägg när du inte är så bra i matematik. Här förklarar jag med exempelkod hur du kan uppnå rörelse / skalning och många andra coola saker med ditt 3d-objekt.

Låt oss ta ett verkligt fall ... Du har skapat en fantastisk (tredimensionell) kub i OpenGL och du vill flytta den till valfri riktning.

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

I spelmotorer som Unity3d skulle detta vara enkelt. Du skulle bara ringa transform.Translate () och vara klar med det, men OpenGL inkluderar inte ett matematikbibliotek.

Ett bra matematikbibliotek är glm men för att få min poäng över kommer jag att koda alla (viktiga) matematiska metoder för dig.

Först måste vi förstå att ett 3d-objekt i OpenGL innehåller mycket information, det finns många variabler som är beroende av varandra. Ett smart sätt att hantera alla dessa variabler är att använda matriser.

En matris är en samling variabler skrivna i kolumner och rader. En matris kan vara 1x1, 2x4 eller valfritt godtyckligt tal.

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

Du kan göra riktigt coola saker med dem ... men hur kan de hjälpa mig att flytta min kub? För att förstå detta måste vi först veta flera saker.

  • Hur gör du en matris från en position?
  • Hur översätter du en matris?
  • Hur skickar du det till OpenGL?

Låt oss göra en klass som innehåller alla våra viktiga matrisdata och metoder (skrivna i 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
        };
    }

}

Först märker vi en mycket speciell sak i standardkonstruktören av en matris med 4 och 4. När det kallas startar det inte allt på noll utan gillar:

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

Alla matriser bör börja med diagonalerna. (bara för att>. <)

Okej, så låt oss förklara på vår episka kub en matris på 4 för 4.

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

Nu har vi faktiskt alla våra variabler som vi äntligen kan börja göra matematik! Låt oss göra översättning. Om du har programmerat i Unity3d kanske du kommer ihåg en Transform.Translate-funktion. Låt oss implementera det i vår egen matrisklass

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

Detta är all matematik som behövs för att flytta kuben runt (inte rotation eller skalning tänka på dig) Det fungerar i alla vinklar. Låt oss implementera detta i vårt verkliga scenario

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)

Vår shader måste använda vår fantastiska matris

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

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

Och det borde fungera .... men det verkar som om vi redan har ett fel i vårt program. När du rör dig längs z-axeln verkar objektet försvinna direkt i tunn luft. Det beror på att vi inte har en projektionsmatris. För att lösa detta fel måste vi veta två saker:

  1. Hur ser en projektionsmatris ut?
  2. Hur kan vi kombinera det med vår positionsmatris?

Vi kan väl skapa ett perspektiv (vi använder trots allt tre dimensioner) matris Koden

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

Det ser skrämmande ut, men den här metoden beräknar faktiskt en matris över hur långt du vill titta på avståndet (och hur nära) och ditt synfält.

Nu har vi en projektionsmatris och en positionsmatris .. Men hur kombinerar vi dem? Tja roligt är att vi faktiskt kan multiplicera två matriser med varandra.

/*!
 *  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 .. det är mycket kod som faktiskt ser mer utrymt ut än det verkligen ser ut. Det kan göras i en for loop men jag (förmodligen felaktigt) trodde att det skulle bli tydligare för människor som aldrig arbetat med matriser.

Titta på koden och se ett upprepande mönster. Multiplicera kolumnen med raden lägg till den och fortsätt. (Det här är samma för valfri storleksmatris)

* Observera att multiplikation med matriser inte är som normal multiplikation. AXB! = B x A *

Nu vet vi hur vi projicerar och lägger till detta i vår positionsmatris kommer vår verkliga livskod förmodligen att se ut:

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)

Nu är vårt fel krossat och vår kub ser ganska episk i fjärran. Om du vill skala din kub är formeln följande:

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

Du behöver bara justera de diagonala variablerna.

För rotation måste du titta närmare på Quaternions.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow