OpenGL/OpenGL ES 入門:基礎變換 - 初識向量/矩陣

佐籩發表於2019-05-19

系列推薦文章:
OpenGL/OpenGL ES入門:圖形API以及專業名詞解析
OpenGL/OpenGL ES入門:渲染流程以及固定儲存著色器
OpenGL/OpenGL ES入門:影象渲染實現以及渲染問題
OpenGL/OpenGL ES入門:基礎變換 - 初識向量/矩陣
OpenGL/OpenGL ES入門:紋理初探 - 常用API解析
OpenGL/OpenGL ES入門:紋理應用 - 紋理座標及案例解析(金字塔)

確定物件位置和方向的能力對於任何3D圖形程式設計人員來說都是非常重要的,正如我們將要看到的,圍繞著原點來描述物件的維度,再將物件變換到需要的位置實際上是非常方便的。

向量

為空間中(任意選擇)的一個點,以及空間中從座標系原點到這個點座標的一條帶箭頭的線段,這個帶箭頭的線段可以視為一個向量

向量

向量能夠代表的第一個量就是方向,第二個量就是數量。 方向:比如X軸就是向量(1,0,0)。在X方向為+1,而在Y方向和Z方向則為0; 數量:一個向量的數量就是這個向量的長度。對於上面X軸的向量(1,0,0)來說,向量的長度為1。我們把長度為1的向量稱為單位向量

math3d庫有兩個資料型別,能夠表示一個三維或四維向量:M3DVertor3f可以表示一個三維向量(X,Y,Z),而M3DVertor4f則可以表示一個四維向量(X,Y,Z,W),其中W為縮放因子,一般情況下為1.

// 簡單的宣告
M3DVector3f vVector;
M3DVector4f vVertex = { 0.0f, 0.0f, 1.0f, 1.0f};

// 宣告一個三分量頂點陣列
M3DVector3f vVerts[] = {
    -0.5f, 0.0f, 0.0f,
    0.5f, 0.0f, 0.0f,
    0.0f, 0.5f, 0.0f };
複製程式碼

點乘

兩個單位向量之間的點乘運算將得到一個標量(只有一個值),它表示兩個向量之間的夾角。要進行這種運算,這兩個向量必須為單位向量,返回的結果將在-1~1之間,實際上就是這兩個向量之間夾角的餘弦值

向量的點乘

math3d庫中也包含一些有用的函式使用點乘操作。

m3dDotProduct3函式來實際獲得兩個向量之間點乘結果

// 兩個單位向量夾角的餘弦值 float m3dDotProduct3(const M3DVector3f u, const M3DVector3f v);

m3dGetAngleBetweenVectors3函式返回夾角的弧度值 // 返回這個夾角的弧度值 float m3dGetAngleBetweenVectors3(const M3DVector3f u, const M3DVector3f v);

叉乘

兩個向量之間叉乘所得的結果是另外一個向量,這個新向量與原來兩個向量定義的平面垂直。要進行叉乘,這兩個向量都不必為單位向量。 與點乘還有一個不同之處是叉乘不符合交換定律即 V1 X V2 != V2 X V1.

向量的叉乘

math3d庫中也有一個函式m3dCrossProduct3對兩個向量進行叉乘並返回運算得到的結果向量

void m3dCrossProduct3(M3DVector3f result, const M3DVector3f u, const M3DVector3f v);

矩陣

它是一種功能非常強大的數學工具,大大簡化了求解變數之間有複雜關係的方程或方程組的過程。其中一個具有普遍性的例子和圖形程式設計人員密切相關,就是座標變換。 例如,如果在空間中有一個點,由x,y和z座標定義,將它圍繞任意點沿任意方向選擇一定角度後,我門需要知道這個點現在的位置,就要用到矩陣。為什麼呢?因為新的x座標不僅與原來的x座標和其他旋轉引數有關,還與原y和z座標值有關。這種變數與解之間的相關性就是矩陣最擅長解決的問題。

在我們進行3D程式設計工作是,我們將使用的幾乎全部是兩種維度的矩陣,即3 x 3 和 4 x 4矩陣。在math3d庫中,也有這兩種維度的矩陣資料型別。

typedef float M3DMatrix33f[9]; typedef float M3DMatrix44f[16];

理解變換

在我們指定頂點和這些頂點出現在螢幕上之間的這段時間裡,可能會發生3中型別的幾何變換: 檢視變換、模型變換和投影變換

變換的術語概念

視覺座標

視覺座標是相對於觀察者的視角而言的,無論可能進行何種變換,我們都可以將它們視為“絕對的”螢幕座標。

視覺座標系
在左邊的座標系1中,視覺座標系是以場景的觀察者的角度(也就是垂直於顯示器的方向)。 在右邊的座標系2中,視覺座標系稍稍進行了選擇,這樣就可以更好的觀察Z軸的位置關係。

從觀察者的角度來看,x軸和y軸的正方形分別指向右方和上方。z軸的正方形從原點指向使用者,而z軸的負方向則從觀察者只想螢幕內部。

檢視變換

檢視變換是應用到場景中的第一種變換。他用來確定場景中的遊離位置。在預設情況下,透視投影中的觀察點位於原點(0,0,0),並沿著z軸的負方向進行觀察(向顯示器內部看)。觀察點相對於視覺座標系進行移動,來提供特定的有利位置。當觀察點位於原點(0,0,0)時,就像在透視投影中一樣,繪製在z座標為正的位置的物件則位於觀察者背後。 在正投影中,觀察者被認為是在z軸正方向無窮遠的位置,能夠看到視景體中的任何東西。 檢視變換允許我們把觀察點放在所希望的任何位置,並允許在任何方向上觀察場景,確定檢視變換就想在場景中放置照相機並讓它指向某個方向。

模型變換

下圖展示了3種最普遍的模型變換:
平移: 物件沿著給定的軸進行移動
旋轉: 物件圍繞著一條座標軸進行旋轉
縮放: 物件的大小進行了指定數量的放大或縮小。縮放可以是不均勻的,即不同維度可以進行不同程度的縮放。

平移 旋轉 縮放

場景或物件的最終外觀可能在很大程度上取決於應用的模型變換順序。 如下圖,模型變換先旋轉後平移與先平移後旋轉,結果是不同的。

模型變換

模型檢視的二元性

實際上,檢視和模型變換按照它們內部效果和對場景的最終外觀來說是一樣的。將這兩者分開是為了我們(程式設計師)方便。比如將物件向後移動和將參考座標系向前移動在視覺上沒有區別,如下圖:

OpenGL/OpenGL ES 入門:基礎變換 - 初識向量/矩陣

模型檢視是指這兩種變換在變換管線中進行組合,成為一個單獨的矩陣。即模型檢視矩陣。

投影變換

投影變換將在模型檢視變換之後應用到頂點上。這種投影實際上定義了視景體並建立了裁剪平面。 更具體的說,投影變換指定一個完成的場景(所有模型變換都已完成)是如何投影到螢幕上的最終影象。

  • 在正投影中,所有多邊形都是精確的按照指定的相對大小來在螢幕上繪製的
  • 透視投影所顯示的場景與現實生活更加接近,透視投影的特點就是透視縮短,這種特性似的遠處的物體看起來比近處同樣大小的物體更小一些。

視口變換

當上述都完成後,就得到一個場景的二維投影,它將被對映到螢幕上某處的視窗上。這種到物理視窗的對映是我們最後要做的變換,即視口變換

這個過程圖形硬體會為我們做好,所以不必太操心這個過程。

模型檢視矩陣

單位矩陣

將一個向量乘以一個單位矩陣,就相當於用這個向量乘以1,不會發生任何變化。 單位矩陣中除了對角線上的一組元素為1之外,其他元素均為0.

單元矩陣

可以在OpenGL中這樣生成一個單位矩陣:

GLFloat m[] = { 1.0f,0.0f,0.0f,0.0f,
                0.0f,1.0f,0.0f,0.0f,
                0.0f.0.0f,1.0f,0.0f,
                0.0f,0.0f,0.0f,1.0f };
                
或者使用`math3d`的`M3DMatrix44f`型別:
M3DMatrix44f m = {  1.0f,0.0f,0.0f,0.0f,
                    0.0f,1.0f,0.0f,0.0f,
                    0.0f.0.0f,1.0f,0.0f,
                    0.0f,0.0f,0.0f,1.0f };
複製程式碼

math3d庫中,還有一個快捷函式m3dLoadIdentity44,這個函式初始化一個單位矩陣。

void m3dLoadIdentity44(M3DMatrix44f m);

平移

一個平移矩陣僅僅是將我們的頂點沿著3個座標軸重的一個或多個進行平移。

可以呼叫math3d庫中的m3dTranslationMatrix44函式來使用變換矩陣

void m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z);

旋轉

將一個物件沿著3個座標軸中的一個或任意向量進行旋轉,需要一個旋轉矩陣。

void m3dRotationMatrix44(M3DMatrix44f m, float x, float y, float z);

縮放

縮放矩陣可以沿著3個座標軸方向按照指定因子放大或縮小所有頂點,以改變物件的大小。 縮放不一定是一致的,我們可以在不同的方向同時使用它來進行伸展和壓縮。

M3DMatrix44f m;
void m3dScaleMatrix44(M3DMatrix44f m, float xScale, float yScale, float zScale);

綜合變換

為了將物件移動道想要的位置,我們可能需要先將它平移到指定位置,然後在旋轉得到想要的結果。又因為4 x 4變換矩陣包含一個位置和一個方向,那麼一個矩陣就可以完成這兩種轉換。只需將兩個矩陣相乘,結果得到的矩陣包含結合道一起的轉換,都在一個矩陣中。 在math3d庫中,m3dMatrixMultiply44用來將兩個矩陣相乘並返回運算結果。

void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);

頂點變換管線

矩陣堆疊的使用

初始化

// GLMatrixStack類 這個類的建構函式允許指定堆疊的最大深度,預設的堆疊深度為64.
// 同時這個矩陣堆疊在初始化時,已經在堆疊中包含了單位矩陣。

GLMatrixStack::GLMatrixStack(int iStackDepth = 64);

// 在堆疊頂部載入一個單元矩陣
void GLMatrixStack::LoadIdentity(void);

// 在堆疊頂部載入任何矩陣
// 引數:4x4矩陣
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);

// 矩陣乘以矩陣堆疊頂部矩陣,相乘結果儲存到堆疊的頂部
void GLMatrixStack::MultMatrix(const M3DMatrix44f);

// 獲取矩陣堆疊頂部的值 GetMatrix 函式
// 為了適應GLShaderManager的使用,或者獲取頂部矩陣的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3Datrix44f mMatrix);

複製程式碼

壓棧、出棧

一個矩陣的真正價值在於通過壓棧操作儲存一個狀態,然後通過出棧操作恢復這個狀態。 通過GLMatrixStack類,我們可以使用PushMatrix函式將矩陣壓入堆疊來儲存當前矩陣的值。而PopMatrix將移除頂部矩陣,並恢復它下面的值。

// 將當前矩陣壓入堆疊(棧頂矩陣copy一份到棧頂)
void GLMatrixStack::PushMatrix(void);

// 將M3DMatrix44f矩陣物件壓入當前矩陣堆疊
void PushMatrix(const M3DMatrix44f mMatrix);

// 將GLFrame物件壓入矩陣物件
void PushMatrix(GLFrame &frame);

// 出棧(出棧指的是移除頂部的矩陣物件)
void GLMatrixStack::PopMatrix(void);
複製程式碼

具體過程參照下面流程圖

壓棧 出棧

仿射變換

GLMatrixStack類也內建了對建立旋轉、平移和縮放矩陣的支援。如下函式:

// 平移
void MatrixStack::Translate(GLfloat x, GLfloat y, GLfloat z);

// 旋轉 引數angle是傳遞的度數, 而不是弧度
void MatrixStack::Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);

// 縮放
void MatrixStack::Scale(GLfloat x, GLfloat y, GLfloat z);
複製程式碼

相關文章