Szukaj…


Wprowadzenie do matryc

Kiedy programujesz w OpenGL lub innym interfejsie graficznym, uderzysz w ścianę z cegieł, gdy nie jesteś zbyt dobry z matematyki. Tutaj wyjaśnię przykładowym kodem, w jaki sposób można osiągnąć ruch / skalowanie i wiele innych fajnych rzeczy za pomocą obiektu 3D.

Weźmy przypadek z prawdziwego życia ... Stworzyłeś niesamowitą (trójwymiarową) kostkę w OpenGL i chcesz przenieść ją w dowolnym kierunku.

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

W silnikach gier takich jak Unity3d byłoby to łatwe. Po prostu wywołaj transform.Translate () i gotowe, ale OpenGL nie zawiera biblioteki matematycznej.

Dobra biblioteka matematyczna to glm, ale aby uzyskać punkt, koduję wszystkie (ważne) metody matematyczne dla ciebie.

Najpierw musimy zrozumieć, że obiekt 3d w OpenGL zawiera wiele informacji, istnieje wiele zmiennych, które zależą od siebie. Sprytnym sposobem zarządzania wszystkimi tymi zmiennymi jest użycie macierzy.

Macierz to zbiór zmiennych zapisanych w kolumnach i wierszach. Macierz może być 1x1, 2x4 lub dowolną dowolną liczbą.

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

Możesz robić z nimi naprawdę fajne rzeczy ... ale jak mogą mi pomóc w przenoszeniu mojej kostki? Aby to zrozumieć, najpierw musimy wiedzieć kilka rzeczy.

  • Jak zrobić matrycę z pozycji?
  • Jak tłumaczysz matrycę?
  • Jak przekazać go do OpenGL?

Stwórzmy klasę zawierającą wszystkie nasze ważne dane macierzowe i metody (napisane w 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
        };
    }

}

Najpierw zauważamy bardzo osobliwą rzecz w domyślnym konstruktorze macierzy 4 na 4. Po wywołaniu nie zaczyna się od zera, ale:

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

Wszystkie macierze powinny zaczynać się od tych na przekątnej. (tylko dlatego, że>. <)

W porządku, więc zadeklarujmy na naszej epickiej kostce macierz 4 na 4.

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

Teraz właściwie mamy wszystkie nasze zmienne, możemy wreszcie zacząć matematykę! Zróbmy tłumaczenie. Jeśli programowałeś w Unity3d, możesz pamiętać funkcję Transform.Translate. Zaimplementujmy to w naszej własnej klasie macierzy

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

To jest cała matematyka potrzebna do przesuwania kostki wokół (nie obracanie się ani skalowanie cię). Działa pod każdym kątem. Zaimplementujmy to w naszym prawdziwym scenariuszu

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)

Nasz moduł cieniujący musi korzystać z naszej cudownej matrycy

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

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

I powinno działać .... ale wygląda na to, że mamy już błąd w naszym programie. Gdy poruszasz się wzdłuż osi Z, obiekt wydaje się znikać w powietrzu. Jest tak, ponieważ nie mamy matrycy projekcyjnej. Aby rozwiązać ten problem, musimy wiedzieć dwie rzeczy:

  1. Jak wygląda matryca projekcyjna?
  2. Jak możemy to połączyć z naszą macierzą pozycji?

Możemy stworzyć perspektywę (w końcu używamy trzech wymiarów) macierz Kod

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

Wygląda to przerażająco, ale ta metoda faktycznie obliczy macierz tego, jak daleko chcesz spojrzeć w dal (i jak blisko) i twoje pole widzenia.

Teraz mamy macierz projekcji i macierz pozycji. Ale jak je połączyć? Zabawne jest to, że możemy pomnożyć ze sobą dwie macierze.

/*!
 *  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 ... to dużo kodu, który wygląda na bardziej przerażający niż na to wygląda. Można to zrobić w pętli for, ale (prawdopodobnie błędnie) pomyślałem, że będzie to jaśniejsze dla osób, które nigdy nie pracowały z macierzami.

Spójrz na kod i zauważ powtarzający się wzorzec. Pomnóż kolumnę przez wiersz, dodaj ją i kontynuuj (to samo dla dowolnej macierzy rozmiarów)

* Pamiętaj, że mnożenie przez macierze nie jest jak zwykłe mnożenie. AXB! = B x A *

Teraz wiemy, jak wyświetlać i dodawać to do naszej matrycy pozycji, nasz kod z życia będzie prawdopodobnie wyglądał następująco:

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)

Teraz nasz błąd jest zgnieciony, a nasza kostka wygląda dość imponująco w oddali. Jeśli chcesz skalować swoją kostkę, formuła jest następująca:

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

Musisz tylko dostosować zmienne ukośne.

W celu rotacji należy przyjrzeć się bliżej Czwartorzędom.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow