iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 13
1
自我挑戰組

寫遊戲初體驗系列 第 13

Day 13 [OpenGL] Transformations

  • 分享至 

  • xImage
  •  

Transformations

我們已經知道了如何創建一個物體並上色或貼上紋理,給他們一些細節的表現,但是這些都只是靜態的表現。我們可以嘗試在每個 frame 都重新配置 VBO 使他們動起來,但是這太繁瑣且耗效能。更好的方法就是用使用(多個)矩陣(Matrix) 將座標 轉換(Transform) 過去。

注意以下有較多的數學

向量 Vector

向量最基本的定義就是一個方向。或者更正式的說,向量有一個方向(Direction)和大小(Magnitude,也叫做長度)。

向量可以在任意維度(Dimension)上,但是我們通常只使用2至4維。如果一個向量有2個維度,它表示一個平面的方向(想像一下2D的圖像),當它有3個維度的時候它可以表達一個3D世界的方向。

由於向量表示的是方向,起始於何處並不會改變它的值。下圖我們可以看到w向量v向量是相等的,儘管他們的起始點不同:

由於向量是一個方向,我們通常設定這個方向的原點為(0, 0, 0),然後指向一個方向,對應一個點,使其變為位置向量(Position Vector)。比如說位置向量(3, 5)在圖像中的起點會是(0, 0),並會指向(3, 5)。我們可以使用向量在2D或3D空間中表示方向與位置。

向量運算

  • 標量 Scalar

    • 不能反過來
  • 反向

  • 向量加法

  • 向量減法

  • 長度

  • 單位向量

    • 長度 = 1
    • 標準化 (Normalize)
    • 任意向量除以其長度 = 單位向量

內積 (Dot product)

  • 定義

  • 一些性質

    • 單位向量內積:

      • 可以求兩向量夾角
  • 投影

    • a 純量投影投影到 b 上:

  • 計算
    • 把每個分量相乘

    • e.g.

    • 用反餘弦 $\cos^{-1}$ 可以求得向量夾角

外積 (Cross product)

用來求與兩向量垂直的向量之方向,在計算三維空間中平面的垂直方向。

  • 計算

矩陣 Matrix

$m\times n$ 的矩形陣列,裏頭的元素可以是數字、符號或算式。

  • 加法、減法

    • Scalar (glm)

    • 矩陣加減

  • Scalar 標量乘法

  • 矩陣乘法

    • 只有 b = c 才可以相乘,產出

    • 沒有交換律,

  • 運算規則

    • 交換律

    • 分配律

  • 單位矩陣 Identity Matrix

    • 除了對角線之外都是 0

  • 縮放 Scale
    • 稱作均勻縮放(Uniform Scale)

  • 位移 Translate
    • 原始向量加上另一個向量,移動了原始向量
    • 位移向量

所以的位移值()都會乘上 w,w 稱作齊次座標(Homogeneous Coordinates),它讓我們可以用矩陣乘法來表示向量位移

旋轉 (Rotation)

旋轉會有角(Angle),而角可以用角度(Degree)及弧度(Radian)表示,角度比較直觀,但電腦計算通常都用弧度

  • 互換
    • 弧度轉角度
      • 角度 = 弧度 * (180.0f / PI)
    • 角度轉弧度
      • 弧度 = 角度 * (PI / 180.0f)

在 3D 空間中旋轉需要:角(Angle)跟旋轉軸(Rotation Axis)

  • 沿 x 軸旋轉

  • 沿 y 軸旋轉

  • 沿 z 軸旋轉

我們可以把三軸的旋轉組合起來,達成任意的旋轉,但這會產生:萬象鎖(Gimbal Lock)的問題產生

  • 當第二個旋轉軸是 90 度時,會使得第一個旋轉軸與第三個旋轉軸等價,丟失了一個維度

這裡有很好的解釋

矩陣組合

多個變換矩陣可以用乘法融合到一個矩陣中。例如,先縮放兩倍、再位移$(1,2,3)$
矩陣乘法是從最右邊開始,依序往左乘,所以是最右邊的操作最先發生。

不同的操作之間可能相互影響,依照縮放、旋轉、位移的順序組合矩陣,否則操作間會相互影響

GLM


GLM是OpenGL Mathematics的縮寫,它是一個只有頭文件的庫,也就是說我們只需包含對應的頭文件就行了,不用鏈接和編譯。

我們需要的GLM的大多數功能都可以從下面這3個頭文件中找到:

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

我們來看看是否可以利用我們剛學的變換知識把一個向量$(1, 0, 0)$位移$(1, 1, 0)$個單位

glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);

glm::mat4 trans(1.0f);
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
printf("%d %d %d\n", vec.x, vec.y, vec.z);

縮放 0.5 倍,逆時針選轉 90 度

glm::mat4 trans;
trans = glm::rotate(trans, glm::radian(90.f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));

接下來我們要更改我們的 Shader。

我們在前面簡單提到過 GLSL 裡也有一個mat4類型。所以我們將修改頂點著色器讓其接收一個mat4uniform變量,然後再用矩陣uniform乘以位置向量:

#version 450 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aVertColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 VertColor;
out vec2 TexCoord;

uniform mat4 vTransform;

void main()
{
    gl_Position = vTransform * vec4(aPos, 1.0);
    TexCoord = aTexCoord;
    VertColor = aVertColor;
}

然後將矩陣傳給 Shader。

int transformLoc = glGetUniformLocation(program, "vTransform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));

如果以上的程式都沒有錯的話會跑出以下的結果

接著我們試試看能不能隨著時間旋轉他

glm::mat4 trans(1.0f); // identity matrix
static float rotate_degree = 90.f;
rotate_degree = fmod(mixClk.getElapsedTime().asMilliseconds()/10, 360);
trans = glm::rotate(trans, glm::radians(rotate_degree), glm::vec3(0.0, 0.0, 1.0));

完整的code在這裡

參考資料

https://learnopengl.com/Getting-started/Transformations


上一篇
Day 12 [OpenGL] Textures
下一篇
Day 14 [OpenGL] Coordinate
系列文
寫遊戲初體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言