矩陣簡介

當你使用 OpenGL 或任何其他圖形 API 進行程式設計時,如果你的數學不是很好,那麼你將會遇到困難。在這裡,我將用示例程式碼解釋如何使用 3d 物件實現移動/縮放和許多其他很酷的東西。

讓我們來看一個真實的案例……你在 OpenGL 中製作了一個很棒的(三維)立方體,你想把它移到任何方向。

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

在像 Unity3d 這樣的遊戲引擎中,這很容易。你只需呼叫 transform.Translate() 並完成它,但 OpenGL 不包含數學庫。

一個好的數學庫是 glm 但是為了得到我的觀點,我會為你編寫所有(重要的)數學方法。

首先,我們必須瞭解 OpenGL 中的 3d 物件包含大量資訊,有許多變數相互依賴。管理所有這些變數的一種聰明方法是使用矩陣。

矩陣是用列和行寫的變數的集合。矩陣可以是 1x1,2x4 或任意數字。

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

你可以用它們做很酷的東西……但是他們怎麼能幫助我移動我的立方體?要真正理解這一點,我們首先需要了解一些事情。

  • 你如何從一個位置製作矩陣?
  • 你如何翻譯矩陣?
  • 你如何將它傳遞給 OpenGL?

讓我們建立一個包含所有重要矩陣資料和方法的類(用 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
        };
    }

}

首先,我們注意到 4×4 矩陣的預設建構函式中有一個非常奇怪的東西。當被呼叫時,它不會從零開始,但是像:

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

所有矩陣都應該從對角線上的矩陣開始。 (只是因為>。<)

好吧,讓我們在史詩立方體上宣告一個 4 乘 4 的矩陣。

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

現在我們實際上已經擁有了所有變數,我們終於可以開始做一些數學了! 我們做翻譯吧。如果你已經在 Unity3d 中程式設計,你可能還記得 Transform.Translate 函式。讓我們在我們自己的矩陣類中實現它

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

這是移動立方體所需的所有數學運算(不是旋轉或縮放),它適用於所有角度。讓我們在現實生活中實現這一點

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)

我們的著色器需要使用我們的奇妙矩陣

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

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

它應該工作….但似乎我們的程式中已經有一個錯誤。當你沿 z 軸移動時,你的物體似乎消失在空氣中。這是因為我們沒有投影矩陣。要解決這個 bug,我們需要知道兩件事:

  1. 投影矩陣如何?
  2. 我們如何將它與我們的位置矩陣結合起來?

那麼我們可以製作一個視角(我們畢竟使用三個維度)矩陣程式碼

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

它看起來很可怕,但是這個方法實際上會計算一個矩陣,表示你希望檢視距離(以及接近距離)和你的視野的距離。

現在我們有一個投影矩陣和一個位置矩陣。但是我們如何將它們結合起來呢?好玩的是,我們實際上可以相互增加兩個矩陣。

/*!
 *  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 ..這是很多程式碼,實際上看起來更加可怕。它可以在 for 迴圈中完成,但我(可能是錯誤地)認為這對於從未使用矩陣的人來說會更清楚。

檢視程式碼並注意重複模式。將列與行相乘,將其新增並繼續。(對於任何大小的矩陣,這都是相同的)

*請注意,與矩陣的乘法與正常乘法不同。AXB != B x 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)

現在我們的小蟲被壓扁了,我們的立方體在遠處看起來非常史詩。如果你想擴充套件你的立方體公式是這樣的:

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

你只需要調整對角線變數。

如需輪換,你需要仔細檢視四元數。