
在正式進(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è)向量之間的夾角。

- 條件: 2個(gè)向量必須為單位向;
- 動作: 2個(gè)三維向量之間進(jìn)?點(diǎn)乘
- 結(jié)果:返回一個(gè)[-1,1]范圍的值,這個(gè)值其實(shí)就是 夾?的cos值(余弦值),
點(diǎn)乘結(jié)果是一個(gè)標(biāo)量 - 用途:游戲中計(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)?叉乘,不必為單位向量。

- 前提:兩個(gè)普通向量
- 動作:向量與向量叉乘
- 結(jié)果:向量(垂直于原來2個(gè)向量定義的平面),
叉乘結(jié)果是一個(gè)向量 - 注意:
這兩個(gè)向量的順序不一樣,得到的結(jié)果向量方向相反 - 用途:游戲求法線(垂直于平面的線)
math3d 庫中提供了關(guān)于叉乘的API
//1.m3dCrossProduct3 函數(shù)獲得2個(gè)向量之間的叉乘結(jié)果得到一個(gè)新的向量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f u ,const
M3DVector3f v);
1.2 矩陣
矩陣的定義
- 矩陣的定義
假設(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)

- 矩陣只有一行或者一列都是合理的。
- 只有一行或一列的數(shù)字可以成為向量,也可以稱為矩陣。
聲明向量,用的是
- M3DVector3f[];
- M3DVector4f[];
聲明矩陣,用的是
- M3DMatrix33f[];
- M3DMatrix44f[];
//三維矩陣/四維矩陣的聲明
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
在OpenGL中,推薦使用一維數(shù)組來定義矩陣,也可以使用二維數(shù)組
- 行矩陣和列矩陣

- 行矩陣:行優(yōu)先矩陣排序,圖中按行讀取,
A0->A1->A2->A3->... - 列矩陣:列優(yōu)先矩陣排序,圖中按列讀取,
A0->A1->A2->A3->... - OpenGL 中以列優(yōu)先,在數(shù)學(xué)中,稱列矩陣為
轉(zhuǎn)置矩陣
如何識別列矩陣,如下圖中的例子
- 矩陣的最后一行都是0
- 最后一個(gè)元素為1

- 這16個(gè)值,每一個(gè)都對應(yīng)一個(gè)特定的值
- 每一列分別對應(yīng)一個(gè)“軸”
- 矩陣的最后一行都是0
- 最后一個(gè)元素為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 x 矩陣2,矩陣1的列 = 矩陣2的行 - 矩陣A x 單元矩陣 = 矩陣A

- 左邊:4(行)
4(列) - 右邊:
4(行)1(列)
滿足條件,所以可以做乘法

- 左邊: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,表示投影矩陣

- 從上圖的過程中可以看出,實(shí)際計(jì)算中,不管多少個(gè)頂點(diǎn),都是兩個(gè)兩個(gè)逐個(gè)計(jì)算的,不會一次性的全部計(jì)算
- 上圖中的矩陣是行矩陣,不是OpenGL的習(xí)慣
OpenGL的習(xí)慣——矩陣左乘
我們知道矩陣的乘法是有順序的,上面的是按照線性代數(shù)的習(xí)慣,進(jìn)行的乘法,而且是使用的行矩陣,但是在OpenGL中,我們習(xí)慣使用列矩陣,通過相反的順序 PVM 來進(jìn)行乘法,過程如下圖所示:

- 變換后的頂點(diǎn)向量 = M_
pro* M_view* M_model* V_local - 變換后的頂點(diǎn)向量 = 投影矩陣 x 觀察矩陣 x 模型矩陣 x 頂點(diǎn)
OpenGL ES 中的代碼片段

(二)理解變換以及模型視圖矩陣
投影(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);

- 參數(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)造和管理提供便捷,矩陣堆棧有以下一些特性:
- 默認(rèn)的堆棧深度是64
- 矩陣堆棧初始化時(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)、平移、縮放矩陣的支持
- 通過API獲取仿射變換需要的數(shù)據(jù);
- 創(chuàng)建仿射變換矩陣;
- 獲取棧頂矩陣;
- 將仿射變換矩陣和棧頂矩陣相乘的結(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é)果返回。這么做有什么好處呢?
-
標(biāo)準(zhǔn)化:我們上面講過了,矩陣的乘法是有順序的,而MV矩陣和P矩陣哪個(gè)在前哪個(gè)在后,如果是我們手寫的矩陣乘法的時(shí)候,難免會出現(xiàn)錯(cuò)誤。而在管線里面,它將會以一個(gè)固定的順序來做乘法,外部是不需要知道實(shí)現(xiàn)細(xì)節(jié)的。具體的源碼實(shí)現(xiàn),后面會講。 -
封裝性:如果我們每次在使用MVP矩陣的時(shí)候,都手動去做乘法,這就太不程序員了。
管線源碼解讀
作為一個(gè)程序員,從源碼的角度來理解管線。
- 管線API:真的是異常的簡單

- 三個(gè)setter方法
- 四個(gè)getter方法
-
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è)置入口??????
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é)果給我們。

學(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è)流程圖相對來說就是非常簡單了。核心是每一步操作會后矩陣堆棧中棧頂矩陣的變換。
