3D游戲引擎中常用的矩陣變換

在數學中,矩陣(Matrix)是一個按照長方陣列排列的復數或實數集合,最早來自于方程組的系數及常數所構成的方陣。這一概念由19世紀英國數學家凱利首先提出。

基本運算

矩陣相乘

  • 只有當左側矩陣的列數與右側矩陣的行數相等,兩個矩陣才能相乘。
  • 矩陣相乘不遵守交換律(Commutative),也就是說A ? B ≠ B ? A


縮放位移矩陣

glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(Sx, Sy, Sz));
trans = glm::scale(trans, glm::vec3(Tx, Ty, Tz)); 

旋轉矩陣

  • 歐拉角
    • Y 偏航角(yaw)
      繞y軸旋轉,類似我們的搖頭動作


    • X 俯仰角(pitch)
      繞x軸旋轉,類似我們的點頭動作


    • Z 滾轉角(roll)
      繞z軸旋轉,你可以想象飛船在做360°翻轉動作


  • 示例
    矩陣乘法是不遵守交換律的,所以它們的順序很重要。
    當矩陣相乘時,在最右邊的矩陣是第一個與向量相乘的,所以你應該從右向左閱讀。
// 在代碼中我們先位移再旋轉,實際的變換卻是先應用旋轉再是位移
glm::mat4 trans;
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
  • 萬向節鎖
    利用旋轉矩陣不可避免會產生萬向節鎖問題,有關萬象節鎖的研究可以看看這個視頻:《歐拉旋轉》
  • 四元數
    真正要避免萬向節鎖問題需要使用四元數,有關四元數的研究可以看看這篇博客:《理解四元數》

正射投影矩陣

將OpenGL坐標系映射到指定寬高的屏幕坐標


圖片轉自https://learnopengl.com

// 參數:左邊界,右邊界,下邊界,上邊界,近平面,遠平面
glm::ortho(float L, float R, float B, float T, float N, float F);
// 示例
glm::ortho(0.0f, width, 0.0f, height, 0.0f, 100.0f)
  • 近平面和遠平面構成的長方體箱子(平截頭體)之外的內容都會被裁剪
  • 屏幕上的點會被縮小寬高比例,并移動(以x軸為例)- (R + L) / (R - L),即將非平截頭體內的內容移出
  • 因為OpenGL是右手坐標系,近平面1.0f,遠平面-1.0f,為了轉化為我們常見遠近認知(左手坐標系),z軸進行了負縮放

透視投影矩陣

3D游戲中的透視效果,離的越遠的東西看起來越小

圖片轉自https://learnopengl.com
// 參數:視野弧度,寬高比,近平面,遠平面
glm::perspective(float F, float A, float N, float F)
// 示例
glm::perspective(glm::radius(45.0f), width / height, 0.1f, 100.0f);
  • 近平面和遠平面構成的不規則箱子(平截頭體)之外的內容都會被裁剪
  • 視野越大,看到的東西越多,通常為glm::radius(45.0f)
  • 近平面不能為0,這樣整個屏幕都會繪制到一個點上,通常為0.1f
  • 近平面設置太大,表現為物體太靠近時會穿過去(消失)

觀察矩陣

顧名思義,是一個看著目標物體的矩陣,模擬出一個攝像機

// 上圖中的f,s,u分別對應以下變量
glm::vec3 f = glm::normalize(center - eye)
glm::vec3 s = glm::normalize(glm::cross(f, up))
glm::vec3 u = glm::cross(s, f)
// 參數:攝像機位置,目標點,上向量
glm::lookAt(glm::vec3 &eye, glm::vec3 &center, glm::vec3 &up)
// 示例
glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f); // 攝像機位置向量
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); // 攝像機方向向量
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f); // 上向量
// 攝像機位置向量 + 攝像機方向向量 = 目標點向量
glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
  • 設置攝像機位置時,注意是右手坐標系
  • 通過攝像機位置向量 + 攝像機方向向量,可以得到攝像機方向
  • 通過上向量叉乘攝像機方向可以得到右向量(兩個向量叉乘的結果會同時垂直于兩向量)

在一個3D游戲,頂點著色器最終都會乘上三個矩陣

// 透視矩陣 * 觀察矩陣 * 模型變幻矩陣 * 頂點信息
gl_Position = projection * view * model * vec4(a_position, 1);

攝像機變換

依托觀察矩陣,我們可以很方便的模擬出一個攝像機,并實現相關的變換。

攝像機移動

操作觀察矩陣中的cameraPos變量

  • 前進(+) / 后退(-):攝像機方向 * 移動距離
cameraPos += cameraFront * distance;
  • 右移(+) / 左移(-):攝像機方向叉乘上向量 * 移動距離
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * distance;
  • 上移(+) / 下移(-): 兩次叉乘得到上向量,我們稱為格拉姆—施密特正交化(Gram-Schmidt Process)
cameraPos += glm::normalize(glm::cross(glm::cross(cameraFront, cameraUp), cameraFront)) * distance;

攝像機旋轉

操作觀察矩陣中的cameraFront變量

float pitch = glm::radians(45.0f); // 俯仰角
float yaw = glm::radians(30.0f); // 偏航角

cameraFront.y = sinf(pitch); // 攝像機方向y
float newRadius = cosf(pitch); // 俯仰后偏航半徑變化
cameraFront.x = newRadius * sinf(yaw); // 攝像機方向x
cameraFront.z = -newRadius * cosf(yaw); // 攝像機方向z
cameraFront = glm::normalize(cameraFront); // 單位化

后記:
依賴游戲引擎,你可能并不需要實現這些,但是這些原理是你有必要掌握的。
3D的世界很精彩,我們要學習的也很多,希望能在這條路上越走越遠。
與君共勉!


原創文章,僅發布于我的簡書我的博客中,禁止轉載!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 變換(Transformations) 我們可以嘗試著在每一幀改變物體的頂點并且重設緩沖區從而使他們移動,但這太繁...
    IceMJ閱讀 4,199評論 0 1
  • 一前言 特征值 奇異值 二奇異值計算 三PCA 1)數據的向量表示及降維問題 2)向量的表示及基變換 3)基向量 ...
    Arya鑫閱讀 10,651評論 2 43
  • 1 前言 OpenGL渲染3D模型離不開空間幾何的數學理論知識,而本篇文章的目的就是對空間幾何進行簡單的介紹,并對...
    RichardJieChen閱讀 7,154評論 1 11
  • 我的女兒已經快要十一歲了,至今我都記得她出生的時刻,那一刻是我們期盼已久、激動萬分的時刻。從一個只有45厘米...
    花小果閱讀 606評論 1 5
  • 密碼qq2397245095
    愚人_9db7閱讀 296評論 1 0