02總結(jié)--010--OpenGL 基礎(chǔ)變換:向量和矩陣的深入理解【重點(diǎn)】

image

在正式進(jìn)入本章節(jié)內(nèi)容之前,先來正視幾個(gè)觀念。

1. 3D 數(shù)學(xué)在OpenGL中充當(dāng)什么角色?

對于學(xué)習(xí)OpenGL 有?個(gè)誤區(qū),就是大家認(rèn)為如果不能精通那些3D圖形數(shù)學(xué)知識,會 讓我們?步難行,其實(shí)不然。就像我們不需要懂得任何關(guān)于汽車結(jié)構(gòu)和內(nèi)燃機(jī)?面的 知識也能每天開車。但是,我們最好能對汽?車有?夠的了解,以便我們意識到什么時(shí) 候需要更換機(jī)油、定期加油、汽?常規(guī)保養(yǎng)工作。

同樣要成為一名可靠和有能力的OpenGL程序員,至少需要理解這些基礎(chǔ)知識,才知道 能作什么?以及哪些工具適合我們要做的?作。

對于初學(xué)者,經(jīng)過一段時(shí)間的實(shí)踐,就會漸漸理解矩陣和向量。并且培養(yǎng)出一種更為直觀 的能力,能夠在實(shí)踐中充分利用所學(xué)的內(nèi)容。

2. GLTools 庫中有一個(gè)組件叫Math3d

其中包含了了?量好?的OpenGL一致的3D數(shù)學(xué)和數(shù)據(jù)類型。雖然我們不必親?進(jìn)行所有的矩陣和向量的操作,但我然知道它們是什么?以及如何運(yùn)?它們.

(一)向量和矩陣

1.1 向量

向量的定義

向量=方向+標(biāo)量

  • 方向:點(diǎn)(1,0,0),在x方向?yàn)?,y和z的方向都為0
  • 標(biāo)量:可以理解為長度,同樣是點(diǎn)(1,0,0),在x方向的長度為1,y和z的為0
向量
  • 單位向量:標(biāo)量(長度)為1的向量
  • 標(biāo)準(zhǔn)化:將一個(gè)向量的長度縮放為1
  • 向量長度(模):
    image

向量的使用

math3D 庫中有兩種數(shù)據(jù)類型

  • M3DVector3f:三維向量 (x, y, z)
  • M3DVector4f:四維向量 (x, y, z, w),w表示縮放因子,通常設(shè)為1,x,y,z的值通過除以w,來進(jìn)行縮放。而除以1.0是不會改變x,y,z的值。
//三維向量/四維向量的聲明
typedef float M3DVector3f[3]; 
typedef float M3DVector4f[4];
//聲明一個(gè)三維向量 M3DVector3f:類型 vVector:變量名 
M3DVector3f vVector;
//聲明一個(gè)四維向量并初始化?個(gè)四維向量 
M3DVector4f vVertex = {0,0,1,1};
//聲明一個(gè)三分量頂點(diǎn)數(shù)組,例如?成一個(gè)三?形 
M3DVector3f vVerts[] = {
    -0.5f,0.0f,0.0f, 
    0.5f,0.0f,0.0f, 
    0.0f,0.5f,0.0f
};

向量的點(diǎn)乘

向量可以進(jìn)行 加法,減法計(jì)算. 但是向量里有?個(gè)在開發(fā)中使用價(jià)值?常?的操作,叫做“點(diǎn)乘(dot product)”。點(diǎn)乘只能發(fā)?在2個(gè)向量之間進(jìn)行。

2個(gè)(三維向量)單元向量 之間進(jìn)行點(diǎn)乘運(yùn)算將得到?個(gè)標(biāo)量(不是三維向量,是?個(gè)標(biāo)量)。它表示兩個(gè)向量之間的夾角。

image
  1. 條件: 2個(gè)向量必須為單位向;
  2. 動作: 2個(gè)三維向量之間進(jìn)?點(diǎn)乘
  3. 結(jié)果:返回一個(gè)[-1,1]范圍的值,這個(gè)值其實(shí)就是 夾?的cos值(余弦值),點(diǎn)乘結(jié)果是一個(gè)標(biāo)量
  4. 用途:游戲中計(jì)算子彈的夾角

math3d 庫中提供了關(guān)于點(diǎn)乘的API

//1.m3dDotProduct3 函數(shù)獲得2個(gè)向量之間的點(diǎn)乘結(jié)果;
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);
//2.m3dGetAngleBetweenVector3 即可獲取2個(gè)向量之間夾?的弧度值; 
float m3dGetAngleBetweenVector3(const M3DVector3f u,const
M3DVector3f v);

向量的叉乘

向量之間的叉乘(cross product) 也是在業(yè)務(wù)開發(fā)?非常有用的一個(gè)計(jì)算?式;;2個(gè)向量之間叉乘就可以得到另外一個(gè)向量,新的向量會與原來2個(gè)向量定義的平?垂直。同時(shí)進(jìn)?叉乘,不必為單位向量。

image
  1. 前提:兩個(gè)普通向量
  2. 動作:向量與向量叉乘
  3. 結(jié)果:向量(垂直于原來2個(gè)向量定義的平面),叉乘結(jié)果是一個(gè)向量
  4. 注意:這兩個(gè)向量的順序不一樣,得到的結(jié)果向量方向相反
  5. 用途:游戲求法線(垂直于平面的線)

math3d 庫中提供了關(guān)于叉乘的API

//1.m3dCrossProduct3 函數(shù)獲得2個(gè)向量之間的叉乘結(jié)果得到一個(gè)新的向量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f  u ,const
M3DVector3f v);

1.2 矩陣

矩陣的定義

  1. 矩陣的定義

假設(shè), 在空間有?個(gè)點(diǎn).使? xyz 描述它的位置. 此時(shí)讓其圍繞任意位置旋轉(zhuǎn)一定?角度
后. 我們需要知道這個(gè)點(diǎn)的新的位置. 此時(shí)需要通過矩陣進(jìn)行行計(jì)算; 為什么?
因?yàn)樾碌奈恢玫膞 單純與原來的x還和旋轉(zhuǎn)的參數(shù)有關(guān). 甚?至于y和z坐標(biāo)有關(guān)

image
  • 矩陣只有一行或者一列都是合理的。
  • 只有一行或一列的數(shù)字可以成為向量,也可以稱為矩陣。

聲明向量,用的是

  • M3DVector3f[];
  • M3DVector4f[];

聲明矩陣,用的是

  • M3DMatrix33f[];
  • M3DMatrix44f[];
//三維矩陣/四維矩陣的聲明
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];

在OpenGL中,推薦使用一維數(shù)組來定義矩陣,也可以使用二維數(shù)組

  1. 行矩陣和列矩陣
image
  • 行矩陣:行優(yōu)先矩陣排序,圖中按行讀取,A0->A1->A2->A3->...
  • 列矩陣:列優(yōu)先矩陣排序,圖中按列讀取,A0->A1->A2->A3->...
  • OpenGL 中以列優(yōu)先,在數(shù)學(xué)中,稱列矩陣為轉(zhuǎn)置矩陣

如何識別列矩陣,如下圖中的例子

  1. 矩陣的最后一行都是0
  2. 最后一個(gè)元素為1
image
  • 這16個(gè)值,每一個(gè)都對應(yīng)一個(gè)特定的值
  • 每一列分別對應(yīng)一個(gè)“軸”
  • 矩陣的最后一行都是0
  • 最后一個(gè)元素為1
  1. 單元矩陣

向量中有單位向量(長度為一的向量),矩陣中也有單元矩陣

  • 定義:
    對角線上都為1的矩陣

  • 單元矩陣初始化方式1

GLFloat m[] = {
           1,0,0,0, //X Column
           0,1,0,0, //Y Column
           0,0,1,0, //Z Column
           0,0,0,1  // Translation
           }
  • 單元矩陣初始化方式 2
M3DMatrix44f m = {
            1,0,0,0, //X Column
            0,1,0,0, //Y Column
            0,0,1,0, //Z Column
            0,0,0,1 // Translation 
            }
  • 單元矩陣初始化?式3
void m3dLoadIdentity44f(M3DMatrix44f m);

矩陣的乘法

  1. 矩陣相乘的前提條件:矩陣1 x 矩陣2,矩陣1的列 = 矩陣2的行
  2. 矩陣A x 單元矩陣 = 矩陣A
image
  • 左邊:4(行)4(列)
  • 右邊:4(行)1(列)

滿足條件,所以可以做乘法

image
  • 左邊:4(行)1(列)
  • 右邊:4(行)4(列)

不滿足條件,結(jié)果不可預(yù)測

矩陣左乘

在線性代數(shù)的維度,坐標(biāo)的計(jì)算,都是按照從左往右順序進(jìn)行計(jì)算的:

  • 變換后的頂點(diǎn)向量 = V_local * M_model * M_view * M_pro
  • 變換后的頂點(diǎn)向量 = 頂點(diǎn) x 模型矩陣 x 觀察矩陣 x 投影矩陣

為了方便記憶,我們按照 MVP 的順序來記

  • M:model,表示模型矩陣
  • V:view,表示視圖矩陣
  • P:projection,表示投影矩陣
image
  • 從上圖的過程中可以看出,實(shí)際計(jì)算中,不管多少個(gè)頂點(diǎn),都是兩個(gè)兩個(gè)逐個(gè)計(jì)算的,不會一次性的全部計(jì)算
  • 上圖中的矩陣是行矩陣,不是OpenGL的習(xí)慣

OpenGL的習(xí)慣——矩陣左乘

我們知道矩陣的乘法是有順序的,上面的是按照線性代數(shù)的習(xí)慣,進(jìn)行的乘法,而且是使用的行矩陣,但是在OpenGL中,我們習(xí)慣使用列矩陣,通過相反的順序 PVM 來進(jìn)行乘法,過程如下圖所示:

image
  • 變換后的頂點(diǎn)向量 = M_pro * M_view * M_model * V_local
  • 變換后的頂點(diǎn)向量 = 投影矩陣 x 觀察矩陣 x 模型矩陣 x 頂點(diǎn)

OpenGL ES 中的代碼片段

image

(二)理解變換以及模型視圖矩陣

投影(projection):將3D數(shù)據(jù)“壓扁”成2D數(shù)據(jù)的處理過程

前面講的正投影和透視投影只是變換中的投影變換而已,還有視圖變換、模型變換等多種變換類型。如下表:

OpenGL 變換術(shù)語概念

變換 應(yīng) 用
視圖 指定觀察者或照相機(jī)的位置
模型 在場景中移動物體
模型視圖 描述視圖和模型變換的二元性
投影 改變視景體的大小或重新設(shè)置它的形狀
視口 這是一種偽變換,只是對窗口上的最終輸出進(jìn)行縮放

上面將矩陣的時(shí)候就提到過了,有三種矩陣,MVP的名詞在變換場景中同樣適用

  • M:model,表示模型矩陣
  • V:view,表示視圖矩陣
  • P:projection,表示投影矩陣

2.1 視覺坐標(biāo)

視覺變換
  • 無論進(jìn)行何種變換,我們都可以將它們視為“絕對的”屏幕坐標(biāo);
  • 視覺坐標(biāo)表示一個(gè)虛擬的固定坐標(biāo)系,通常作為參考坐標(biāo)系使用;
  • a圖:表示我們垂直于手機(jī)屏幕視角;
  • b圖:表示我們的頭稍微歪了一點(diǎn),屏幕里面表示“最深的”地方;

2.2 視圖變換(View)

  • 透視投影:觀察點(diǎn)位于原點(diǎn)(0,0,0),并沿z軸負(fù)方向(向顯示器內(nèi)部“看進(jìn)去”)。觀察點(diǎn)相對于視覺坐標(biāo)系進(jìn)行移動,來提供特定的有利位置;
  • 正投影:觀察者被認(rèn)定在z軸正方向無窮遠(yuǎn)的位置(上帝視角),能夠看到視景體中的任何東西;
  • 視圖變換:允許我們將觀察點(diǎn)放在任何位置,并允許在任何方向上觀察場景;
  • 確認(rèn)視圖變換:就是確認(rèn)觀察者的位置,以及觀察者所觀察的方向
  • 從大局上考慮,在應(yīng)用任何其他模型變換之前,必須先應(yīng)用視圖變換。原因是,對于視覺坐標(biāo)系而言,視圖變換移動了當(dāng)前的工作坐標(biāo)系,所有后續(xù)變換都會基于新調(diào)整的坐標(biāo)系進(jìn)行,所以視圖變換應(yīng)該在其他所有的變換之前。

2.3 模型變換(Model)

常見的模型變換有三種

  • 平移:Translation
  • 旋轉(zhuǎn):Rotation
  • 縮放:Scaling
平移
  • 參數(shù)1:結(jié)果矩陣,將平移后的結(jié)果放到這個(gè)矩陣中;
  • 參數(shù)234:xyz軸上的移動距離;
void m3dTranslationMatrix44(M3DMatrix44f m, floata x, float y, float z);
旋轉(zhuǎn)
  • 參數(shù)1:弧度,m3dDegToRad():度數(shù)轉(zhuǎn)弧度;
  • 參數(shù)234:選擇圍繞哪個(gè)軸旋轉(zhuǎn),如果沿x軸旋轉(zhuǎn),則為(1,0,0),如果有其他組合軸,以具體需求來設(shè)定xyz之間的關(guān)系;
m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);
縮放
  • 參數(shù)1:結(jié)果矩陣,將縮放后的結(jié)果放到這個(gè)矩陣中;
  • 參數(shù)234:xyz軸上的縮放因子;
  • 翻轉(zhuǎn):如果沿某個(gè)軸縮放-1,則表示在該軸翻轉(zhuǎn),例如Xa x -1 = -Xa
void m3dScaleMatrix44(M3DMatrix44f m, floata xScale, float yScale, float zScale);
變換順序
  • 不同順序的變換結(jié)果是不一樣的
  • 矩陣a x 矩陣b矩陣b x 矩陣a 結(jié)果不一樣,矩陣部分講過了
  • 上圖中:先旋轉(zhuǎn)后平移 和 先平移后旋轉(zhuǎn)的結(jié)果不一樣

綜合變換公式(矩陣乘法)

void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);
  • 參數(shù)1:結(jié)果矩陣,(注意兩個(gè)矩陣的順序,這個(gè)說了很多回了)
  • 參數(shù)2:目標(biāo)矩陣1
  • 參數(shù)3:目標(biāo)矩陣2

2.4 模型視圖的二元性(ModelView)

模型視圖變換
  • 視圖和模型變換的結(jié)果其實(shí)是一樣的,區(qū)分開是為了程序員的方便;(“后視鏡里的世界,越來越遠(yuǎn)的道別”——周杰倫《一路向北》)
  • 模型視圖:值這兩種變換在變換管線中進(jìn)行組合,成為一個(gè)單獨(dú)的矩陣,即模型視圖矩陣;
  • 使用視圖變換的原因,在繪制對象之前應(yīng)用到一個(gè)虛擬對象(觀察者)之上的模型變換。在加入更多對象到場景之后,還會指定更多新的變換。初始變換(視圖變換)是所有其他變換參考的基礎(chǔ)。

2.5 投影變換(Projection)

投影投影

2.6 視口變換

  • 二維投影:上面所有變化完成之后的一個(gè)結(jié)果;
  • 視口變換:將二維投影映射到物理窗口坐標(biāo)的變換,顏色緩沖區(qū)和窗口像素存在一一對應(yīng)的關(guān)系;
  • 圖形硬件自動完成,不需要我們手動處理。

(三)模型視圖矩陣案例

3.1 案例一:正方形的旋轉(zhuǎn)移動

//類型
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
//在堆棧頂部載?一個(gè)單元矩陣
void GLMatrixStack::LoadIdentity(void);
//在堆棧頂部載入任何矩陣 
//參數(shù):4*4矩陣
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
//矩陣乘以矩陣堆棧頂部矩陣,相乘結(jié)果存儲到堆棧的頂部
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
//獲取矩陣堆棧頂部的值 GetMatrix 函數(shù) 
//為了適應(yīng)GLShaderMananger的使?用,或者獲取頂部矩陣的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);

3.2 案例二:使用三角形批次類創(chuàng)建球體、花托、圓柱(錐)、磁盤

這些圖形都有些什么特點(diǎn)?顯而易見,都是用三角形構(gòu)成的。怎么通過三角形來構(gòu)成這些圖形呢,這些就不是我們操心的東西了,很多OpenGL的先驅(qū)者已經(jīng)完成了這些特殊形狀的繪制,我們只需要調(diào)用接口即可。——“給我一個(gè)三角形批次類,我還你世界萬物”

關(guān)于移動、深度測試、邊框、混合、背面消除等技術(shù),這里就不做過多解釋,前面的文章已經(jīng)介紹的非常清楚了。

定義一組批次類

//球
GLTriangleBatch     sphereBatch;
//環(huán)
GLTriangleBatch     torusBatch;
//圓柱
GLTriangleBatch     cylinderBatch;
//錐
GLTriangleBatch     coneBatch;
//磁盤
GLTriangleBatch     diskBatch;

1. 繪制球體

/*
  gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
 參數(shù)1:sphereBatch,三角形批次類對象
 參數(shù)2:fRadius,球體半徑
 參數(shù)3:iSlices,從球體底部堆疊到頂部的三角形帶的數(shù)量;其實(shí)球體是一圈一圈三角形帶組成
 參數(shù)4:iStacks,圍繞球體一圈排列的三角形對數(shù)

 建議:一個(gè)對稱性較好的球體的片段數(shù)量是堆疊數(shù)量的2倍,就是iStacks = 2 * iSlices;
 繪制球體都是圍繞Z軸,這樣+z就是球體的頂點(diǎn),-z就是球體的底部。
 */
gltMakeSphere(sphereBatch, 3.0, 10, 20);
球體

2. 繪制花托

/*
 gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
 參數(shù)1:torusBatch,三角形批次類對象
 參數(shù)2:majorRadius,甜甜圈中心到外邊緣的半徑
 參數(shù)3:minorRadius,甜甜圈中心到內(nèi)邊緣的半徑
 參數(shù)4:numMajor,沿著主半徑的三角形數(shù)量
 參數(shù)5:numMinor,沿著內(nèi)部較小半徑的三角形數(shù)量
 */
gltMakeTorus(torusBatch, 3.0f, 0.75f, 15, 15);
花托

3. 繪制圓柱

/*
 void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
 參數(shù)1:cylinderBatch,三角形批次類對象
 參數(shù)2:baseRadius,底部半徑
 參數(shù)3:topRadius,頭部半徑
 參數(shù)4:fLength,圓形長度
 參數(shù)5:numSlices,圍繞Z軸的三角形對的數(shù)量
 參數(shù)6:numStacks,圓柱底部堆疊到頂部圓環(huán)的三角形數(shù)量
 */
圓柱

4. 繪制圓柱
圓錐其實(shí)就是沒有頂部的圓柱,所以還是使用同一個(gè)批次類,只是第2個(gè)參數(shù)不一樣而已

/*
 void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
 參數(shù)1:cylinderBatch,三角形批次類對象
 參數(shù)2:baseRadius,底部半徑
 參數(shù)3:topRadius,頭部半徑
 參數(shù)4:fLength,圓形長度
 參數(shù)5:numSlices,圍繞Z軸的三角形對的數(shù)量
 參數(shù)6:numStacks,圓柱底部堆疊到頂部圓環(huán)的三角形數(shù)量
 */
//圓柱體,從0開始向Z軸正方向延伸。
//圓錐體,是一端的半徑為0,另一端半徑可指定。
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
圓錐

5. 繪制磁盤

/*
void gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
 參數(shù)1:diskBatch,三角形批次類對象
 參數(shù)2:innerRadius,內(nèi)圓半徑
 參數(shù)3:outerRadius,外圓半徑
 參數(shù)4:nSlices,圓盤圍繞Z軸的三角形對的數(shù)量
 參數(shù)5:nStacks,圓盤外網(wǎng)到內(nèi)圍的三角形數(shù)量
 */
磁盤

(四)變換管線

4.1 矩陣堆棧

矩陣堆棧的作用

矩陣堆棧的作用是對矩陣的構(gòu)造和管理提供便捷,矩陣堆棧有以下一些特性:

  1. 默認(rèn)的堆棧深度是64
  2. 矩陣堆棧初始化時(shí),在堆棧中默認(rèn)保存了單元矩陣
  • 初始化單元矩陣:m3dLoadIdentity44方法的源碼并沒有找到。注釋說實(shí)現(xiàn)在 Math3d.cpp 文件中,我們并沒有獲取到這個(gè)文件,所以猜測它的實(shí)現(xiàn)原理應(yīng)該跟下面的 LoadMatrix 類似
inline void LoadIdentity(void) { 
    m3dLoadIdentity44(pStack[stackPointer]); 
    }
// LoadIdentity
// Implemented in Math3d.cpp
void m3dLoadIdentity33(M3DMatrix33f m);
void m3dLoadIdentity33(M3DMatrix33d m);
void m3dLoadIdentity44(M3DMatrix44f m);
void m3dLoadIdentity44(M3DMatrix44d m);
  • 加載一個(gè)矩陣:從源碼中可以看出,加載一個(gè)矩陣,實(shí)際上就是將一個(gè)矩陣放入當(dāng)前堆棧的棧頂位置。跟壓棧不一樣的地方是,壓棧是在棧頂上方新壓入一個(gè)矩陣,而load是修改當(dāng)前棧頂?shù)木仃?/li>
inline void LoadMatrix(const M3DMatrix44f mMatrix) { 
    m3dCopyMatrix44(pStack[stackPointer], mMatrix); 
    }
    
inline void m3dCopyMatrix44(M3DMatrix44d dst, const M3DMatrix44d src)
    { memcpy(dst, src, sizeof(M3DMatrix44d)); }    

矩陣堆棧的操作

壓棧和出棧一定是成對出現(xiàn)
壓棧和出棧一定是成對出現(xiàn)
壓棧和出棧一定是成對出現(xiàn)

  • 壓棧:復(fù)制當(dāng)前矩陣值,將結(jié)果放在堆棧頂部
void PushMatrix(const M3DMatrix44f mMatrix) {
        if(stackPointer < stackDepth) {
            stackPointer++;
            // 復(fù)制當(dāng)前矩陣,存入棧頂
            m3dCopyMatrix44(pStack[stackPointer], mMatrix);
            }
        else
            lastError = GLT_STACK_OVERFLOW;
        }
  • 出棧:移除頂部矩陣,恢復(fù)它下面的值
inline void PopMatrix(void) {
        if(stackPointer > 0)
            stackPointer--;
        else
            lastError = GLT_STACK_UNDERFLOW;
        }
  • 乘法:獲取棧頂矩陣,與目標(biāo)矩陣相乘,將結(jié)果矩陣放入棧頂
inline void MultMatrix(const M3DMatrix44f mMatrix) {
    M3DMatrix44f mTemp;
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mMatrix);
    }
  • 獲取矩陣:兩種方式,根據(jù)具體需求進(jìn)行選擇使用
    • 一種是帶返回值,直接返回棧頂矩陣:但是外部矩陣(變量)不能被修改。如果在外面修改的堆棧中的矩陣,可能會造成意想不到的結(jié)果;
    • 拷貝棧頂矩陣到傳進(jìn)來的參數(shù)地址:因?yàn)檫@里是拷貝了一份新的矩陣,所以不用擔(dān)心外面的修改會影響到堆棧里面的矩陣。
// Two different ways to get the matrix
const M3DMatrix44f& GetMatrix(void) { return pStack[stackPointer]; }
void GetMatrix(M3DMatrix44f mMatrix) { m3dCopyMatrix44(mMatrix, pStack[stackPointer]); }

矩陣堆棧的仿射變換

矩陣堆棧處理提供管理堆棧的功能,還提供了對創(chuàng)建旋轉(zhuǎn)、平移、縮放矩陣的支持

  1. 通過API獲取仿射變換需要的數(shù)據(jù);
  2. 創(chuàng)建仿射變換矩陣;
  3. 獲取棧頂矩陣;
  4. 將仿射變換矩陣和棧頂矩陣相乘的結(jié)果放入棧頂

這三個(gè)內(nèi)建API的過程也是我們自己在使用矩陣堆棧的時(shí)候需要用到的

// 縮放
void Scale(GLfloat x, GLfloat y, GLfloat z) {
    M3DMatrix44f mTemp, mScale;
    m3dScaleMatrix44(mScale, x, y, z);
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mScale);
    }

// 平移
void Translate(GLfloat x, GLfloat y, GLfloat z) {
    M3DMatrix44f mTemp, mScale;
    m3dTranslationMatrix44(mScale, x, y, z);
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mScale);           
    }

// 旋轉(zhuǎn)
void Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) {
    M3DMatrix44f mTemp, mRotate;
    m3dRotationMatrix44(mRotate, float(m3dDegToRad(angle)), x, y, z);
    m3dCopyMatrix44(mTemp, pStack[stackPointer]);
    m3dMatrixMultiply44(pStack[stackPointer], mTemp, mRotate);
    }

4.2 管理管線

管線的作用就是記錄模式視圖(MV)矩陣和投影(P)矩陣,在獲取MVP(模式視圖投影)矩陣時(shí),將MV矩陣和P矩陣相乘的結(jié)果返回。這么做有什么好處呢?

  1. 標(biāo)準(zhǔn)化:我們上面講過了,矩陣的乘法是有順序的,而MV矩陣和P矩陣哪個(gè)在前哪個(gè)在后,如果是我們手寫的矩陣乘法的時(shí)候,難免會出現(xiàn)錯(cuò)誤。而在管線里面,它將會以一個(gè)固定的順序來做乘法,外部是不需要知道實(shí)現(xiàn)細(xì)節(jié)的。具體的源碼實(shí)現(xiàn),后面會講。
  2. 封裝性:如果我們每次在使用MVP矩陣的時(shí)候,都手動去做乘法,這就太不程序員了。

管線源碼解讀

作為一個(gè)程序員,從源碼的角度來理解管線。

  1. 管線API:真的是異常的簡單
管線源碼
  • 三個(gè)setter方法
  • 四個(gè)getter方法
  1. setter方法:跟一般setter方法一樣,就就是保存一個(gè)地址
inline void SetModelViewMatrixStack(GLMatrixStack& mModelView) { _mModelView = &mModelView; }

inline void SetProjectionMatrixStack(GLMatrixStack& mProjection) { _mProjection = &mProjection; }

inline void SetMatrixStacks(GLMatrixStack& mModelView, GLMatrixStack& mProjection) {
    _mModelView = &mModelView;
    _mProjection = &mProjection;
    }

管線管理啥,只管理 模型視圖矩陣(MV)和 投影矩陣(P),因?yàn)榻涌诶锩嬷惶峁┝诉@兩個(gè)矩陣的設(shè)置入口??????

  1. getter方法
const M3DMatrix44f& GetModelViewProjectionMatrix(void)
    {
    m3dMatrixMultiply44(_mModelViewProjection, _mProjection->GetMatrix(), _mModelView->GetMatrix());
    return _mModelViewProjection;
    }

inline const M3DMatrix44f& GetModelViewMatrix(void) { return _mModelView->GetMatrix(); }
inline const M3DMatrix44f& GetProjectionMatrix(void) { return _mProjection->GetMatrix(); }

const M3DMatrix33f& GetNormalMatrix(bool bNormalize = false)
    {
    m3dExtractRotationMatrix33(_mNormalMatrix, GetModelViewMatrix());

    if(bNormalize) {
        m3dNormalizeVector3(&_mNormalMatrix[0]);
        m3dNormalizeVector3(&_mNormalMatrix[3]);
        m3dNormalizeVector3(&_mNormalMatrix[6]);
        }

    return _mNormalMatrix;
    }

其實(shí)管線的 getter 方法只有兩個(gè),中間兩個(gè)是 inline 方法,并不支持外部調(diào)用。

GetNormalMatrix 這個(gè)方法,暫時(shí)還沒用到,這里不做解釋,那就只剩下一個(gè)方法了。

GetModelViewProjectionMatrix
GetModelViewProjectionMatrix
GetModelViewProjectionMatrix

一般重要的東西,至少要默寫三遍,就是這個(gè)MVP矩陣。

如果你還有印象,我們前面在介紹著色器的時(shí)候,平面著色器和上色著色器中是不是有一個(gè)參數(shù)就是MVP矩陣。管線就是為了給我們提供這個(gè)邊界,不需要我們每次都手動去做乘法,它直接提供了MVP的結(jié)果給我們。

image

學(xué)習(xí)!記錄!知識總是環(huán)環(huán)相扣!前面的困惑在后面的學(xué)習(xí)中總會柳暗花明又一村!

4.3 照相機(jī)(觀察者)

角色幀:GLFrame

這個(gè)類型是GLTools庫中的一種數(shù)據(jù)結(jié)構(gòu),只在OpenGL階段使用,在后面的OpenGL ES階段中邊不在使用了,所以這里就不做過多的介紹。

protected:
        M3DVector3f vOrigin;    // Where am I?
        M3DVector3f vForward;   // Where am I going?
        M3DVector3f vUp;        // Which way is up?
  • vOrigin:對應(yīng)x軸
  • vForward:對應(yīng)z軸
  • vUp:對應(yīng)y軸

GLFrame的操作

源碼很長,也看不懂??,他是怎么轉(zhuǎn)換的,我們就不用去探索了,知道怎么用就行。

  • 獲取矩陣:GetMatrix,結(jié)果是一般矩陣,對應(yīng)世界坐標(biāo)系
//4.創(chuàng)建矩陣mObjectFrame
M3DMatrix44f mObjectFrame;
//從ObjectFrame 獲取矩陣到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//將modelViewMatrix 的堆棧中的矩陣 與 mOjbectFrame 矩陣相乘,存儲到modelViewMatrix矩陣堆棧中
modelViewMatrix.MultMatrix(mObjectFrame);
  • 獲取照相機(jī)矩陣:GetCameraMatrix,結(jié)果對應(yīng)的是觀察者坐標(biāo)系
//3.獲取攝像頭矩陣
M3DMatrix44f mCamera;
//從camereaFrame中獲取矩陣到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型視圖堆棧的 矩陣與mCamera矩陣 相乘之后,存儲到modelViewMatrix矩陣堆棧中
modelViewMatrix.MultMatrix(mCamera);

照相機(jī)(觀察者)

這只是一個(gè)邏輯存在的事物,在實(shí)際中并不存在,為什么這么說呢?

它本質(zhì)是啥,就是一個(gè)矩陣。那為什么這個(gè)矩陣要叫觀察者呢?

當(dāng)我們在使用這個(gè)矩陣的時(shí)候,它產(chǎn)生的變化效果,看起來是從觀察者角度去移動的。所以稱它為觀察者。

果然學(xué)習(xí)總是環(huán)環(huán)相扣,前面不理解不要緊,后面自然就會懂了。

4.4 變換流程

前面三小節(jié),分別從代碼和抽象的角度對某些現(xiàn)象進(jìn)行了解釋。如果是認(rèn)真的看到這里,那下面這個(gè)流程圖,不需要記,自然會刻在你的腦海里。

以下面這段代碼為例

// 1.模型視圖矩陣棧堆,壓棧
modelViewMatrix.PushMatrix();

// 2.獲取攝像頭矩陣
M3DMatrix44f mCamera;
//從camereaFrame中獲取矩陣到mCamera
cameraFrame.GetCameraMatrix(mCamera);
//模型視圖堆棧的 矩陣與mCamera矩陣 相乘之后,存儲到modelViewMatrix矩陣堆棧中
modelViewMatrix.MultMatrix(mCamera);

// 3.創(chuàng)建矩陣mObjectFrame
M3DMatrix44f mObjectFrame;
//從ObjectFrame 獲取矩陣到mOjectFrame中
objectFrame.GetMatrix(mObjectFrame);
//將modelViewMatrix 的堆棧中的矩陣 與 mOjbectFrame 矩陣相乘,存儲到modelViewMatrix矩陣堆棧中
modelViewMatrix.MultMatrix(mObjectFrame);

//4. 使用矩陣
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);

//5. pop
modelViewMatrix.PopMatrix();
矩陣堆棧流程圖

認(rèn)真讀完上面關(guān)于矩陣堆棧的操作,這個(gè)流程圖相對來說就是非常簡單了。核心是每一步操作會后矩陣堆棧中棧頂矩陣的變換。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容