第四章 OpenGL基礎變換 向量和矩陣
???????在第三章趾代,我們討論了如何繪制3D點绘搞、線和三角形晌纫。為了將一系列圖形轉(zhuǎn)換到連續(xù)的場景,我們必須將它們相對于其他圖形和觀察者進行排列淤翔。在本章,我們開始學習在坐標系中移動圖形佩谷。
本章內(nèi)容:
- 什么是向量旁壮,以及為什么要了解它
- 什么是矩陣,以及為什么要更認真地了解它
- 我們?nèi)绾问褂镁仃嚭拖蛄縼硪苿訋缀螆D形
- OpenGL對于模型視圖和投影矩陣的約定
- 什么是照相機谐檀,以及如何應用它轉(zhuǎn)換
- 如何將一個點光源位置轉(zhuǎn)換到視點坐標系
一抡谐、3D圖形數(shù)學
???????GLTools庫中有一個組建叫做Math3d,其中包含了大量好用的與OpenGL一致的3D數(shù)學例程和數(shù)據(jù)結(jié)構。
向量:
???????一個頂點是XYZ坐標空間上的一個位置桐猬,同時也是一個向量麦撵。
???????向量指出方向;同時也代表數(shù)量溃肪,一個向量的數(shù)量就是這個向量的長度厦坛。
Math3d庫有兩種數(shù)據(jù)類型:M3DVector3f表示一個三維向量(X,Y乍惊,Z)杜秸;
M3DVector4f表示一個四維向量(X, Y, Z, W)。
???????點乘:兩個單位向量之間的點乘運算將得到一個標量润绎。它表示兩個向量之間的夾角撬碟。要進行這種運算诞挨,兩個向量必須為單位長度。而返回的結(jié)果將在-1和+1之間呢蛤,實際是兩個向量之間夾角的余弦值惶傻。
//返回余弦值
float m3dDotProduct3(const M3DVector3f u, M3DVector3f v);
//返回弧度值
float m3dGetAngleBetweenVetors3(const M3DVector3f u, M3DVector3f v);
???????叉乘:兩個向量之間叉乘所得的結(jié)果是另外一個向量,這個新的向量與原來兩個向量定義的平面垂直其障。叉乘的兩個向量都不必為單位向量银室。兩個向量位置交換叉乘值不同,即叉乘不滿足交換律励翼。
//返回結(jié)果向量
float m3dCrossProduct3(M3DVector3f result, const M3DVector3f u, M3DVector3f v);
矩陣:
???????如果空間中有一點蜈敢,有X,Y汽抚,Z坐標定義抓狭,將它圍繞任意點沿任意方向旋轉(zhuǎn)一定角度后,我們需要知道這個點現(xiàn)在的位置造烁,就要用到矩陣否过。數(shù)學上,矩陣為一組排列在統(tǒng)一的行和列中的數(shù)字惭蟋,用程序設計語言來說就是一個二維數(shù)組苗桂。
3D程序設計中用到的幾乎全是兩種維度的矩陣,即3×3和4×4告组;在math3d庫中有兩種維度的矩陣類型:
???????typedef float M3DMatrix33f[9];
???????typedef float M3DMatrix44f[16];
二誉察、理解變換
視覺坐標:
???????視覺坐標是相對于觀察者的視角而言的,無論可能進行何種變換惹谐,我們都可以將它們視為“絕對的”屏幕坐標持偏。
視圖變換:
???????視圖變換是應用到場景中的第一種變換。對于視覺坐標系而言氨肌,視圖變換移動了當前的工作坐標系鸿秆。所有后續(xù)變換隨后都會基于新調(diào)整的坐標系進行。然后怎囚,在實際開始考慮如何進行這些變換時卿叽,就會更容易地看到這些變換是如何實現(xiàn)的了。
模型變換:
???????模型變換用于操縱模型和其中的特定對象恳守。這些變換將對象移動到需要的位置考婴,然后再對它們進行旋轉(zhuǎn)和縮放。
模型視圖的二元性:
???????實際上催烘,視圖和模型變換按照它們內(nèi)部效果和對場景的最終外觀來說是一樣的沥阱。將這兩者分開純粹是為了程序員的方便。將對象象后移動和將參考系坐標向前移動在視覺上沒有區(qū)別伊群,效果是相同的考杉。視圖變換和模型變換一樣策精,都應用在整個場景中,在場景中的對象常常在進行視圖變換后單獨進行模型變換崇棠。術語“模型視圖”是指這兩種變換在變換管線中進行組合咽袜,成為一個單獨的矩陣,即模型視圖矩陣枕稀。
投影變換:
???????投影變換將在模型視圖變換之后應用到頂點上询刹,這種投影實際上定義了視景體并創(chuàng)建了裁剪平面。投影變換指定一個完成的場景(所有模型變換都已完成)是如何投影到屏幕上的最終圖形萎坷。
???????在正投影中凹联,所有多邊形都是精確地按照指定的相對大小來在屏幕上繪制的。線和多邊形使用平行線來直接映射到2D屏幕上食铐,無論物體位置遠近,都按照相同大小來進行繪制僧鲁。典型情況下虐呻,這個投影用于渲染如屏幕菜單等二維圖像。
???????透視投影所顯示的場景與現(xiàn)實生活中更接近寞秃,透視投影的特點就是透視縮短斟叼,這種特性使得遠處的物體看起來比近處同樣大小的物體更小一些。
視口變換:
???????當上述變換完成時春寿,就得到了一個場景的二維投影朗涩,它將被映射到屏幕上某處的窗口上,這種到物理窗口坐標的映射是我們最后要做的變換绑改,成為視口變換谢床。
矩陣變換:
/*
mView: 平移
mModel: 旋轉(zhuǎn)
mModelView: 模型視圖
mModelViewProjection: 模型視圖投影MVP
*/
M3DMatrix44f mView, mModel, mModelView, mModelViewProjection;
//mModel旋轉(zhuǎn)矩陣,繞y軸旋轉(zhuǎn)yRot度
m3dRotationMatrix44(mModel, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);
//mView平移矩陣厘线,沿z軸移動-2.5
m3dTranslationMatrix44(mView, 0.0f, 0.0f, -2.5f);
//mModelview = mView * mModel
m3dMatrixMultiply44(mModelview, mView, mModel);
//mModelViewProjection = ProjectionMatrix * mView * mModel
m3dMatrixMultiply44(mModelViewProjection,
viewFrustum.GetProjectionMatrix(),
mModelview);
更多對象:
???????GLBatch類识腿,這個類的目的是為了解決容納一個頂點列表并將它們作為一個特定類型的圖元批次來進行渲染。而GLTriangleBatch造壮,這個類是專門作為三角形的容器的渡讼,每個頂點都可以有一個表面法線,已進行光照計算和紋理坐標耳璧。
變換管線:
使用矩陣堆棧:
???????我們在矩陣儲存時可能用到以下實例:
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrame cameraFrame;
GLFrame objectFrame;
//其中成箫,cameraFrame用于存儲觀察者矩陣,objectFrame用于存儲模型矩陣旨枯。projectionMatrix只用于存儲投影矩陣蹬昌,我們操作最多的是modelViewMatrix。
//模型矩陣繞世界坐標系y軸旋轉(zhuǎn)-5.0度
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
//觀察者矩陣向后退15.0,GLFrame中默認的朝向是z軸的負方向;即(0.0, 0.0, -1.0);向前走-15.0攀隔,即(0.0, 0.0, -1.0 * -15.0) = (0.0, 0.0, 15.0)
cameraFrame.MoveForward(-15.0f);渲染過程中矩陣棧操作凳厢,獲取MVP矩陣的計算結(jié)果
//壓棧
modelViewMatrix.PushMatrix();
//獲取觀察者矩陣
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
//棧頂矩陣乘以傳入矩陣账胧,相乘的結(jié)果簡存儲在棧頂
modelViewMatrix.MultMatrix(mCamera);
//獲取模型矩陣
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
//由于先乘的觀察者矩陣,現(xiàn)在再乘以模型矩陣
//棧頂 = M_view * M_model
modelViewMatrix.MultMatrix(mObjectFrame);
...
//由于初始化時傳入了先紫,modelViewMatrix和projectionMatrix的引用
//transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//下面的代碼會計算projectionMatrix * modelViewMatrix
//結(jié)合上面的代碼治泥,等價于M_projection * M_view * M_model
transformPipeline.GetModelViewProjectionMatrix()
...
//出棧
modelViewMatrix.PopMatrix();
???????其中,入棧遮精,是為了保存當前的矩陣棧狀態(tài)居夹;出棧,是為了恢復入棧前的矩陣棧狀態(tài)本冲。這個操作類似于iOS的Core Graphic中context的保存與恢復准脂。
???????//保存
???????void CGContextSaveGState(CGContextRef c);
???????//恢復
???????void CGContextRestoreGState(CGContextRef c);
優(yōu)化矩陣棧操作:
???????不同的實現(xiàn)方式,之前我們用cameraFrame和objectFrame來記錄相機和模型的變化檬洞,用到了兩個對象狸膏。但我們可以固定一個對象,變化另一個對象添怔。例如湾戳,之前的做法需要觀察者向后退,同時物體旋轉(zhuǎn)广料,把變化作用到了兩個物體上砾脑,所以用到了兩個矩陣分別記錄兩個物體的變化,最后再使用矩陣相乘把兩個變化合并起來艾杏。
如果固定一個物體韧衣,那么根據(jù)相對運動,就是把之前兩個物體的變化购桑,相對地作用到另一個物體上畅铭,就可以少用一個矩陣了。