自學(xué)OpenGL(九)-數(shù)學(xué)基礎(chǔ)
前言
計(jì)算機(jī)圖形學(xué)中大量使用了數(shù)學(xué)原理沼本,尤其是矩陣和矩陣代數(shù)卖氨。雖然我們傾向于認(rèn)為3D圖像學(xué)編程是現(xiàn)代的技術(shù)領(lǐng)域之一坞淮,但是它用到的很多技術(shù)實(shí)際上可以追溯到上百年前当纱。其中一些甚至是文藝復(fù)興時(shí)期的偉大哲學(xué)家們就已經(jīng)理解并記錄的鳍置。
3D 圖形學(xué)中幾乎每個(gè)方面揍愁、每種效果--移動(dòng)、縮放晨横、透視紋理咒程、光照鸠天、陰影等等,都是在很大程度是以數(shù)學(xué)方式實(shí)現(xiàn)帐姻。
這里稠集,我們假定大家都具備基礎(chǔ)的矩陣運(yùn)算知識(shí)奶段。如果在閱讀過(guò)程中發(fā)現(xiàn)不理解的地方,自行去學(xué)習(xí)相關(guān)知識(shí)巍杈,個(gè)人推薦去嗶哩嗶哩搜索宋浩老師,筆者在大學(xué)沒(méi)有開(kāi)數(shù)學(xué)課扛伍,所有數(shù)學(xué)理論知識(shí)筷畦,包括線(xiàn)性代數(shù)、微積分都是在宋浩老師那里學(xué)的刺洒”畋觯看視頻的同時(shí)再配合使用微信讀書(shū)去搜索相關(guān)教材,把習(xí)題做一遍逆航,效果翻倍鼎文。
3D坐標(biāo)
3D 中間通常用3個(gè)坐標(biāo)軸X、Y和Z來(lái)表示因俐。這3個(gè)軸可以以?xún)煞N方式來(lái)布置:左手或者右手
知道圖形編程化境使用的坐標(biāo)系是很重要的拇惋。例如,OpenGL中的坐標(biāo)系大體是右手坐標(biāo)系抹剩,而其他3D系統(tǒng)的坐標(biāo)系則有可能是左手的撑帖。在本博客中,如果沒(méi)有特別說(shuō)明澳眷,我們默認(rèn)是右手坐標(biāo)系胡嘿。
點(diǎn)
3D中間中的點(diǎn)可以通過(guò)使用形如(2,8钳踊,-3)的符號(hào)衷敌,列出X、Y拓瞪、Z的值來(lái)表示缴罗。不過(guò)如果用齊次坐標(biāo)來(lái)表示點(diǎn)會(huì)更有用。n維空間中點(diǎn)的坐標(biāo)用n+1個(gè)分量來(lái)表示祭埂。三維空間中的齊次坐標(biāo)有4個(gè)值瞒爬,前三個(gè)值表示X、Y沟堡、Z侧但、,第四個(gè)W總是非零值 航罗,通常情況為1禀横,因此,我們會(huì)將之前的點(diǎn)表示為(2粥血,8柏锄,-3酿箭,1)。正如我們稍后將要看到的趾娃,齊次坐標(biāo)會(huì)是我們的圖形學(xué)計(jì)算更加的高效缭嫡。
用來(lái)存儲(chǔ)齊次3D坐標(biāo)的GLSL數(shù)據(jù)類(lèi)型是vec4。GLM庫(kù)包含適用在C++/OpenGL應(yīng)用中創(chuàng)建3元和4元的點(diǎn)抬闷,分別叫做vec3和vec4.
關(guān)于齊次坐標(biāo)不太熟悉的同學(xué)可以看這里:https://zhuanlan.zhihu.com/p/258437902
矩陣
矩陣是矩形值陣列妇蛀,它的元素通常使用下表訪(fǎng)問(wèn)。第一個(gè)下標(biāo)行號(hào)笤成,第二個(gè)下表為列號(hào)评架,下標(biāo)從0開(kāi)始。我們?cè)?D徒刑計(jì)算中要用到的矩陣大多數(shù)大小為4X4
A00 A01 A02 A03
A10 A11 A12 A13
A20 A21 A22 A23
A30 A31 A32 A33
GLSL 語(yǔ)言中的mat4數(shù)據(jù)類(lèi)型用來(lái)儲(chǔ)存4X4的矩陣炕泳。同樣纵诞,GLM中有mat4類(lèi)泳衣是梨花并存儲(chǔ)4X4矩陣。
單位矩陣用一條對(duì)角線(xiàn)的值為1培遵,其余值全為0:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
轉(zhuǎn)置
GLM 庫(kù)和GLSL庫(kù)都有轉(zhuǎn)置函數(shù)浙芙,分別是glm::transpose(mat4)和 transpose(mat4)
加法
加法非常簡(jiǎn)單,就是逐分量相加:
點(diǎn)與矩陣相乘
點(diǎn)左乘一個(gè)矩陣得到一個(gè)新的點(diǎn)
矩陣相乘
這里我們拿一個(gè)4X4的矩陣相乘舉個(gè)例子:
矩陣相乘也稱(chēng)為矩陣合并籽腕,這塊知識(shí)完全遵循線(xiàn)性代數(shù)里面的矩陣運(yùn)算規(guī)則茁裙,如果線(xiàn)性代數(shù)這方面知識(shí)的同學(xué),建議先去學(xué)習(xí)一下線(xiàn)性代數(shù)节仿。
變換矩陣
在圖形學(xué)中晤锥,矩陣通常用來(lái)進(jìn)行物體的變換,所以我們一般稱(chēng)之為變換矩陣廊宪。變換矩陣的重要特征之一就是他們都是4X4矩陣矾瘾。這是因?yàn)槲覀儧Q定使用齊次坐標(biāo)系,否則箭启,變換舉證可能會(huì)有不同的維度并且無(wú)法相乘壕翩。正如我們所見(jiàn),確保變換矩陣大小想相同并不只是為了方便傅寡,同時(shí)讓他們可以任意組合放妈,進(jìn)行預(yù)先計(jì)算變換矩陣以提高性能。
平移矩陣
平移矩陣用于將物體從一位置到另一個(gè)位置荐操,矩陣表示為:
GLM 中有一些函數(shù)適用于構(gòu)建與點(diǎn)相乘的平移矩陣:
- glm::translate(x, y, z)構(gòu)建的平移矩陣芜抒;
- mat4 X vec4
縮放矩陣
縮放矩陣用于改變物體的大小或者將點(diǎn)想遠(yuǎn)點(diǎn)反方向移動(dòng)。因此托启,縮放物體涉及縮放它的點(diǎn)的集合宅倒,縮放矩陣變換表示為:
GLM 中有一些函數(shù)適用于構(gòu)建與點(diǎn)相乘的縮放矩陣:
- glm::scale(x, y, z) 構(gòu)建縮放矩陣;
- mat4 X vec4
旋轉(zhuǎn)矩陣
旋轉(zhuǎn)掃尾復(fù)雜一些屯耸,因?yàn)樵?D空間中旋轉(zhuǎn)需要指定旋轉(zhuǎn)周和旋轉(zhuǎn)的角度或弧度拐迁,任何旋轉(zhuǎn)都可以表示為繞著X蹭劈、Y、Z軸旋轉(zhuǎn)的組合线召。圍繞這三個(gè)軸的旋轉(zhuǎn)角度被稱(chēng)為歐拉角铺韧。旋轉(zhuǎn)變換有3種,見(jiàn)下圖:
GLM 中也有一些用于構(gòu)建旋轉(zhuǎn)矩陣的函數(shù)缓淹。
- glm::rotate(mat4, θ, x哈打,y, z)構(gòu)建繞X、Y割卖、Z旋轉(zhuǎn)θ度的矩陣前酿;
- mat4 X vec4患雏;
實(shí)踐中鹏溯,當(dāng)3D空間中旋轉(zhuǎn)軸部穿過(guò)原點(diǎn)時(shí),物體使用歐拉角旋轉(zhuǎn)需要額外的步驟淹仑。一般有:平移旋轉(zhuǎn)軸以使它經(jīng)過(guò)原點(diǎn)丙挽;繞X、Y匀借、Z軸旋轉(zhuǎn)適當(dāng)?shù)臍W拉角颜阐;復(fù)原步驟(1)中的平移。旋轉(zhuǎn)變換中的三個(gè)旋轉(zhuǎn)變換矩陣都有自己的特性吓肋,即反響旋轉(zhuǎn)的矩陣等于其轉(zhuǎn)置矩陣凳怨。
旋轉(zhuǎn)矩陣時(shí)蒸餃矩陣,蒸餃矩陣的逆等于矩陣的轉(zhuǎn)置是鬼,反響旋轉(zhuǎn)其實(shí)就是再乘以旋轉(zhuǎn)矩陣的逆
向量
向量表示大小和方向肤舞,他們沒(méi)有特定位置。移動(dòng)向量并不能改變它代表的含義均蜜。
在GLM和GLSL中有許多3D圖形學(xué)中經(jīng)常用到的向量操作李剖。如加減法、歸一化囤耳、點(diǎn)積篙顺、插積
加減法
A ± B=(u ± x, v ± y, w ± z)
1.glm: vec3 ± vec3
2.GLSL: vec3 ± vec3
歸一化(長(zhǎng)度為1)
A=A/|A|=A/sqrt(u2+v2+w2),其中|A| ≡向量A的長(zhǎng)度
glm: normalize(vec3) 或normalize(vec4)
GLSL: normalize(vec3) 或normalize(vec4)
點(diǎn)積
A·B=ux+vy+wz
- glm: dot(vec3,vec3) 或dot(vec4,vec4)
- GLSL: dot(vec3,vec3) 或dot(vec4,vec4)
插積
A × B=(vz-wy, wx-uz, uy-vx)
- glm: cross(vec3,vec3)
- GLSL: cross(vec3,vec3)
點(diǎn)積的應(yīng)用
點(diǎn)擊最重要也最基本的應(yīng)用時(shí)求解兩向量夾角充择。設(shè)向量V和W德玫,計(jì)算其夾角為θ。因此椎麦,如果V和W是單位向量(歸一化向量)化焕,則有:
有趣的是,我們后面會(huì)看到通常用到的是cos(θ)铃剔,而非θ撒桨,因此連個(gè)推到出的共識(shí)都很有用查刻。
- 求向量的大小根號(hào)下V.V ;
- 求解兩向量是否正交,若正交凤类,則V.W=0;
- 求解兩向量是否平行穗泵,若平行,則 V.W = |V||W|谜疤;
- 求解兩向量是否平行佃延,但方向相反,若滿(mǎn)足夷磕,則 V.W = -|V||W|履肃;
- 求解兩向量夾角是否在-90~90度之間 V.W > 0 ;
- 求解點(diǎn)P到平面S的最小距離;
叉積的應(yīng)用
兩個(gè)向量叉積的一個(gè)重要特性是坐桩,他會(huì)生成一個(gè)新的向量尺棋,新的向量正交于前兩個(gè)向量所定義的平面。任意兩個(gè)不貢獻(xiàn)的向量都定義了一個(gè)平面绵跷,例如考慮兩個(gè)人一向量V和W膘螟。由于向量可以在不改變?nèi)我夂x的情況下移動(dòng),因此碾局,可以將他們移動(dòng)到起點(diǎn)相交的位置荆残,下圖展示了V和W定義的平面,以及其插積所得到法向量净当。其所得法向量的方向遵循右手定則内斯。即右手手指從V向W卷會(huì)是的大拇指指向法向量R。
通過(guò)叉積來(lái)獲得法向量的能力對(duì)我們后面要學(xué)習(xí)的光照部分非常重要像啼。為了確定光照效果俘闯,我們需要知道所渲染模型的外向法向量。圖3.8中展示了一個(gè)例子埋合,其中有一個(gè)6個(gè)點(diǎn)(頂點(diǎn))構(gòu)成的簡(jiǎn)單模型备徐,使用叉積計(jì)算來(lái)獲得其中一面的外向法向量。
局部空間和世界空間
局部空間
當(dāng)建立物體的3D模型是甚颂,我們通常以最方便的定位方式描述模型蜜猾。如果模型是個(gè)球形,那么我們很可能將球心定位于原點(diǎn)振诬,并賦予它一個(gè)方便的半徑蹭睡,比如1。模型定義的空間叫做局部空間赶么。OpenGL文檔使用的術(shù)語(yǔ)是物體空間(object space)肩豁。
之后這個(gè)球形可能用于一個(gè)大模型的部分,如成為機(jī)器人的頭部。這個(gè)機(jī)器人清钥,當(dāng)然琼锋,定義在他自己的局部空間。我們可以用下圖的舉證變換通過(guò)縮放祟昭、旋轉(zhuǎn)缕坎、平移,將球形模型房子啊機(jī)器人模型的空間篡悟。通過(guò)這種方式谜叹,可以分成次地構(gòu)建復(fù)雜模型,使用這種方式搬葬,通過(guò)設(shè)定物體在模型世界中的朝向和大小荷腊,將物體放在模擬這個(gè)世界的空間中,這個(gè)空間叫做世界空間急凰。將對(duì)象定位及定向在世界空間的矩陣成為模型矩陣或M女仰。
視覺(jué)空間和合成相機(jī)
到此為止,我們所接觸的變換矩陣全都在3D空間中操作香府,但是我們最終需要將3D空間中的物體展示在2D顯示器上董栽。為達(dá)到這一個(gè)目標(biāo)码倦,我們需要找到一個(gè)有利點(diǎn)企孩。正如我們?cè)诂F(xiàn)實(shí)世界通過(guò)眼睛觀(guān)察一樣,我們也必須找到一點(diǎn)并確定觀(guān)察方向作為我們觀(guān)察虛擬世界的窗口袁稽。這個(gè)點(diǎn)叫做“視圖“或”視覺(jué)“空間勿璃,或”合成相機(jī)“。
觀(guān)察3D世界需要如下步驟:
- 將相機(jī)放入世界的某個(gè)位置推汽;
- 調(diào)整相機(jī)的角度补疑,通常需要一套自己的直角坐標(biāo)系;
- 定義一個(gè)視體(view volume)歹撒;
- 將視體內(nèi)的對(duì)象投影到投影平面(projection plane)上莲组。
OpenGL 有一個(gè)固定在原點(diǎn)并看向Z軸副方向的相機(jī)
為了應(yīng)用OpenGL相機(jī),我們需要做的是將它抹你移動(dòng)到何時(shí)的位置和方向暖夭。我們需要先找出在世界中的物體與我們期望的相機(jī)位置的相對(duì)位置锹杈,如下圖
需要做的變換如下:
- 將Pw平移,其向量為負(fù)的期望相機(jī)位置迈着;
- 將Pw旋轉(zhuǎn)竭望,其角度為負(fù)的期望相機(jī)旋轉(zhuǎn)的歐拉角。我們可以構(gòu)建一個(gè)單一變換舉證已完成旋轉(zhuǎn)和平移裕菠,這個(gè)矩陣叫做視圖變換矩陣V咬清。矩陣V通過(guò)合并矩陣T和R。
在本例中,從右向左旧烧,我們先平移世界空間中的點(diǎn)Pw影钉,之后旋轉(zhuǎn)
Pc=R(T*Pw)=(R*T)Pw;
將R*T用V來(lái)表示,即:
Pc=V*Pw;
通常掘剪,V舉證與模型矩陣M合并成為一個(gè)模型-視圖矩陣斧拍,即:
MV=V*M;
之后,點(diǎn)Pm在自己的模型空間通過(guò)如下一個(gè)步驟就可以直接轉(zhuǎn)換至合成相機(jī)空間:
Pc=MV*Pm;
在復(fù)雜場(chǎng)景中杖小,當(dāng)我們需要對(duì)每個(gè)頂點(diǎn)肆汹,而非知識(shí)一個(gè)點(diǎn)做這個(gè)變換的時(shí)候,這種方法的好處就很明顯了予权。通過(guò)預(yù)先計(jì)算MV昂勉,對(duì)于空間中每一個(gè)點(diǎn)的變換只需要我們進(jìn)行一次矩陣乘法計(jì)算。之后我們將看到扫腺,我們可以將這個(gè)過(guò)程延伸到與計(jì)算機(jī)更多的合并矩陣岗照,以大量減少每個(gè)頂點(diǎn)的計(jì)算量。
投影矩陣
當(dāng)我們?cè)O(shè)置好相機(jī)之后笆环,就可以學(xué)習(xí)投射矩陣了攒至,我們需要學(xué)習(xí)兩個(gè)重要的投射矩陣是:
- 透視投射;
- 正射投射躁劣;
透視投影矩陣
透視投影舉證通過(guò)使用透視概念模仿我們看真實(shí)世界的方式迫吐,嘗試讓2D的圖像看起來(lái)像是3D的。物體近大遠(yuǎn)小账忘,3D空間中有的平行線(xiàn)用透視法畫(huà)出來(lái)就不再平行志膀。
我們通過(guò)使用變換矩陣將平行線(xiàn)變?yōu)榍‘?dāng)?shù)牟黄叫芯€(xiàn)來(lái)實(shí)現(xiàn)這個(gè)效果。這個(gè)矩陣叫做透視矩陣鳖擒,通過(guò)定義四個(gè)參數(shù)來(lái)進(jìn)行視體的構(gòu)造溉浙。其中4個(gè)參數(shù)是眾橫比,視場(chǎng)蒋荚、投射平面和近裁剪平面戳稽、遠(yuǎn)裁剪平面。只有在遠(yuǎn)近裁剪平面間的物體才會(huì)被渲染期升。近裁剪平面同時(shí)也是物體所投射到的平面惊奇,通常放在離眼睛或相機(jī)較近的位置。視場(chǎng)是可視空間的縱向角度吓妆∩奘保縱橫比是遠(yuǎn)近裁剪平面的寬度比高度。通過(guò)這些元素所形成的形狀叫做視錐行拢,如下圖:
透視矩陣用于將3D空間中的點(diǎn)變換至近裁剪平面上合適的位置祖秒,它的構(gòu)建需要先計(jì)算q、A、B竭缝、C的值房维,之后用這些值來(lái)構(gòu)建透視矩陣,如下圖所示:
生成透視變換矩陣很容易抬纸,只需要將所描述的公式插入一個(gè)4X4矩陣咙俩。GLM庫(kù)也包含了一個(gè)用于構(gòu)建透視矩陣的函數(shù)glm::perspective()。
正射投影矩陣
在正射投影中湿故,平行線(xiàn)仍然是平行的阿趁,即不使用透視。正射與透視相反坛猪,在視體中的物體不因距相機(jī)距離做任何調(diào)整脖阵,而直接進(jìn)行投影。
正射投影是一種平行投影墅茉,其中所有的投射都與投影平面垂直命黔。正射矩陣通過(guò)如下參數(shù)構(gòu)建:
- 從相機(jī)到投影平面的距離Znear;
- 從相機(jī)到遠(yuǎn)平面的距離Zfar就斤;
-
L悍募、R、T洋机、B的值坠宴,其中L、R分別是投射平面左右邊界的X坐標(biāo)槐秧,T和B分別是投射平面上下邊界的Y坐標(biāo)啄踊,如下圖:
圖片 19.png
并非所有平線(xiàn)投影都是正射投影忧设,其他投影暫時(shí)不講刁标。
平行投影與我們眼睛所見(jiàn)到的真實(shí)世界不同。但是他們?cè)诤芏嗲闆r下都有用處址晕,比如投射陰影膀懈、進(jìn)行3D裁剪以及CAD中用在CAD中因?yàn)闊o(wú)論物體如何擺放,其尺寸都不變谨垃。
LookAt 矩陣
當(dāng)想要吧相機(jī)放在某處并看像一個(gè)特定的位置時(shí)启搂,就需要用到它,但是這個(gè)操作非常頻繁刘陶,因此為它專(zhuān)門(mén)構(gòu)建一個(gè)矩陣通常比較有用胳赌。
LookAt 變換由相機(jī)旋轉(zhuǎn)決定。我們通過(guò)指定大致旋轉(zhuǎn)朝向的向量匙隔。通常疑苫,可以通過(guò)一系列叉積獲得相機(jī)旋轉(zhuǎn)的正面、側(cè)面以及上面。
我們可以將這個(gè)過(guò)程構(gòu)建一個(gè)C++/OpenGL使用函數(shù)捍掺,通過(guò)指定相機(jī)位置撼短、目標(biāo)位置以及向上向量Y,構(gòu)建一個(gè)LookAt矩陣的矩陣挺勿。由于GLM中已經(jīng)有一個(gè)用來(lái)構(gòu)建LookAt矩陣的函數(shù)glm::lookAt(),我們用它就可以了曲横。