本文同時發(fā)布在我的個人博客上:https://dragon_boy.gitee.io
坐標系
有左手坐標系和右手坐標系兩種,Unity使用的是右手坐標系。
點和矢量
我們使用兩個或三個以上的實數(shù)來表示一個點的坐標,如。
矢量是指n維空間中一種包含了模和方向的有向線段。矢量的表示方法和點類似,如。
矢量通常由一個箭頭表示,由起點指向終點。矢量常被用于表示相對于某個點的偏移,只要矢量的模和方向保持不變,無論在哪里,都是同一個矢量。
矢量運算
矢量和標量乘除
對乘法,將矢量的每個分量和標量相乘即可:
對除法,同理:,標量需非零。
矢量加減法
兩個矢量加減法,把對應的分量進行相加或相減即可:
幾何意義上,矢量相加即前者起點連接至后者終點,矢量相減即后者終點指向前者終點。
矢量的模
矢量的模即矢量的長度:
單位矢量
單位矢量即模為1的矢量。將某個非零矢量轉(zhuǎn)化為單位矢量的過程稱為歸一化。
矢量乘法
矢量之間的乘法有兩種,點積和叉積。
點積公式有兩個:
1:
2(為兩個矢量的夾角):
點積的結(jié)果在幾何意義上是獲得矢量在
上的投影與
的模的乘積,如果二者都是單位矢量,那么點積結(jié)果就是前者在后者上的投影。
叉積公式:
下面是另一種表示:
上述表明矢量叉積的結(jié)果的模是兩矢量構(gòu)成平行四邊形的面積。
在幾何意義的上,叉積的結(jié)果是一個新的矢量,分別垂直于和
,且沿
、
和叉積的方向可構(gòu)成一個右手坐標系。
矩陣
矩陣有行列之分,以矩陣為例:
矢量的矩陣表示法
矢量可以看作時的列矩陣或
的行矩陣。
矩陣運算
矩陣和標量的乘法
將矩陣的每個元素和標量相乘即可。
矩陣和矩陣的乘法
兩個矩陣相乘,要求前一個矩陣的列數(shù)等于后一個矩陣的行數(shù),相乘得到的矩陣的行數(shù)是第一個矩陣的行數(shù),列數(shù)是第二個矩陣的列數(shù)。如的維度是
,
的維度是
,那么
的維度是
。
矩陣的乘法即前一個矩陣的每一行(行)與后一個矩陣的每一列(
列)相乘(可看作行構(gòu)成的矢量和列構(gòu)成的矢量點積),每次相乘的結(jié)果填寫在對應的
行
列上。
注意,矩陣乘法不滿足交換律,但滿足結(jié)合律。
特殊矩陣
方陣
方陣即行列數(shù)相等的矩陣。
同時,如果除對角線的元素外全是0的方陣稱為對角矩陣。
單位矩陣
針對上述的對角矩陣,如果對角線的元素全是0的話,那么就稱為單位矩陣。
轉(zhuǎn)置矩陣
對于一個的矩陣
,它的轉(zhuǎn)置
為
的矩陣。轉(zhuǎn)置運算即將原矩陣的行列翻轉(zhuǎn)。原矩陣的
行變?yōu)?img class="math-inline" src="https://math.jianshu.com/math?formula=i" alt="i" mathimg="1">列,
列變?yōu)?img class="math-inline" src="https://math.jianshu.com/math?formula=j" alt="j" mathimg="1">行。
注意,矩陣的轉(zhuǎn)置的轉(zhuǎn)置等于原矩陣。
矩陣的串接的轉(zhuǎn)置等于反向串接各個矩陣的轉(zhuǎn)置:
逆矩陣
只有方陣才有逆矩陣。
一個矩陣和它的逆矩陣的乘積為單位矩陣:
零矩陣沒有逆矩陣。如果一個矩陣有逆矩陣,那么這個矩陣是可逆的,或非奇異的,相反則是不可逆的或奇異的。
如果一個矩陣的行列式為不為0,那么該矩陣就是可逆的(具體不解釋)。
注意,逆矩陣的逆矩陣為原矩陣。
單位矩陣的逆矩陣是它本身。
轉(zhuǎn)置矩陣的逆矩陣是逆矩陣的轉(zhuǎn)置:
矩陣串接相乘后的逆矩陣等于反向串接各個矩陣的逆矩陣:
注意,如果某個矩陣或矢量進行了一個矩陣變化,那么使用逆矩陣可以還原這個變換:
正交矩陣
如果一個方陣和它的轉(zhuǎn)置矩陣的乘積是單位矩陣,那么這個方陣就是正交矩陣:
將正交矩陣與逆矩陣的概念結(jié)合起來,就可以得到,如果一個矩陣正交,那么它的轉(zhuǎn)置矩陣和逆矩陣相等:
上述式子在實際計算中非常有用,因為逆矩陣的計算量很大(涉及伴隨矩陣),所以如果能判斷某個變換矩陣是正交的話,可以很簡單地用轉(zhuǎn)置矩陣代替它的逆矩陣進行計算。
那么如何判斷一個矩陣是否正交,我們把一個矩陣的列看作是一個矢量,稱為基矢量,那么根據(jù)正交矩陣定義:
根據(jù)上述等式,得:
,
,
都是單位矢量,且兩兩垂直。那么滿足這樣條件的矩陣的就是正交矩陣。
矢量的矩陣表示
在Unity中,常將矢量當做是列矩陣來使用,即放到矩陣的右邊來進行乘法。這種情況下,矩陣乘法通常是右乘,即:
變換
概念
變換是將一些數(shù)據(jù)通過某種方式進行轉(zhuǎn)換的過程。
一個非常常見的變換是線性變換,指的是可以保留矢量加和標量乘的變換。數(shù)學公式為:
縮放是一種線性變換,旋轉(zhuǎn)也是一種線性變換,我們可以使用一個的矩陣就可以對一個三維向量進行線性變換。除此之外還有錯切,鏡像,正交投影。
但平移變換不是線性變換,我們不能使用一個的矩陣對一個三維向量進行變換。
由此出現(xiàn)仿射變換,即合并線性變換和平移變換的變換類型。仿射變換使用一個的矩陣來表示,這樣我們需要將矢量擴展到四維空間下,即齊次坐標空間。
齊次坐標
對于一個三維向量,我們擴展一個維度,分量稱為。對于坐標,
常設為1,而對于方向,常設為0。因為我們只想對位置進行平移變換,方向不行。
基礎變換矩陣
我們使用一個的矩陣來表示平移、旋轉(zhuǎn)和縮放。一個基礎的變換矩陣可以分解為4個部分:
表示旋轉(zhuǎn)縮放的矩陣,
表示平移。
平移矩陣
下面是一個平移矩陣(針對點)的例子:
如果是方向矢量的話,由于分量為0,平移變換不會有影響:
平移變換的逆矩陣就是反向平移得到的矩陣:
平移矩陣并不是正交矩陣。
縮放矩陣
下面是一個縮放矩陣(針對點)的例子:
對方向矢量縮放:
如果,那么這樣的縮放為統(tǒng)一縮放,否則為非統(tǒng)一縮放。由于非統(tǒng)一縮放會改變角度和比例信息,所以針對方向向量的縮放不能使用上述的方式。
縮放矩陣的逆矩陣如下:
縮放矩陣不是正交矩陣。
上面的矩陣只適合沿坐標軸方向縮放,如果要在任意方向縮放,就要使用一個復合變換,先縮放軸,再沿坐標軸縮放。
旋轉(zhuǎn)矩陣
繞x軸旋轉(zhuǎn):
繞y軸旋轉(zhuǎn):
繞z軸旋轉(zhuǎn):
旋轉(zhuǎn)矩陣是正交矩陣。
復合變換
大多數(shù)情況下,將平移、旋轉(zhuǎn)、縮放結(jié)合起來的順序往往是:先縮放,后旋轉(zhuǎn),再平移,也就是:
注意,針對旋轉(zhuǎn)的順序,Unity的順序是zxy,即旋轉(zhuǎn)變換矩陣是:
旋轉(zhuǎn)時的坐標系有兩種可以選擇:
- 繞坐標系E的z軸旋轉(zhuǎn),繞y軸旋轉(zhuǎn),再繞x軸旋轉(zhuǎn)。即進行一次旋轉(zhuǎn)時不一起旋轉(zhuǎn)當前坐標系。
- 繞E的z軸旋轉(zhuǎn),E繞z軸旋轉(zhuǎn)相同角度的E',繞E‘的y軸旋轉(zhuǎn),E’繞y軸旋轉(zhuǎn)相同角度的E'',最后繞E''的x軸旋轉(zhuǎn)。即再旋轉(zhuǎn)時,把坐標系一起轉(zhuǎn)動。
在上述兩種方式中,旋轉(zhuǎn)的結(jié)果不一樣,Unity是第一種。
坐標空間
模型空間
模型空間即模型的局部坐標系,在Unity中是左手坐標系。
世界空間
世界空間被用來描述物體在場景中的位置,在Unity中,世界空間是左手坐標系。
將頂點從模型空間變換到世界空間的變換稱為模型變換(Model)。這一變換通常由組成。
觀察空間
也被稱為攝像機空間。
攝像機決定了我們渲染游戲所使用的視角。在觀察空間中,攝像機位于原點,在Unity中,+x指向攝像機的右方,+y指向攝像機的上方,+z指向攝像機的后方,即攝像機指向-z軸。
將頂點從世界空間轉(zhuǎn)換到觀察空間的變換為觀察變換(View)。
為得到頂點在觀察空間中的位置,有兩種方法:一是計算觀察空間的三個坐標軸在世界空間下的表示,然后構(gòu)建出從觀察空間變換到世界空間的變換矩陣,再對矩陣求逆來得到從世界空間變換到世界空間的矩陣。二是想象平移整個觀察空間,讓攝像機的原點位于世界坐標的原點,坐標軸與世界空間的坐標軸重合即可。
這里使用第二種方法,即想辦法讓攝像機變換到世界空間的原點即可(注意,由于觀察空間使用右手坐標系,因此需要對z分量取反)。
裁剪空間
將頂點從觀察空間轉(zhuǎn)換到裁剪空間的變換矩陣稱為裁剪矩陣,也被稱為投影矩陣(Projection)。裁剪空間由視錐體決定。
視錐體是空間中的一塊區(qū)域,視錐體內(nèi)的區(qū)域是攝像機可以看到的空間。視錐體由六個平面構(gòu)成,這些平面稱為裁剪平面。視錐體有兩種類型,透視投影和正交投影。
在視錐體的裁剪平面中有兩塊很特殊,分別稱為近裁剪平面和遠裁剪平面,它們決定了攝像機可以看到的深度范圍。

投影矩陣有兩個目的:
- 為投影做準備
- 對x、y、z分量進行縮放
透視投影

我們利用上圖的參數(shù)可以計算出近裁剪平面和遠裁剪平面的高度:
裁剪平面的寬度我們可以通過橫縱比得到。假設攝像機的橫縱比為:
根據(jù)上述信息,投影矩陣為:
使用上述投影矩陣后:
可以看到,投影矩陣本質(zhì)上是對x、y、z坐標進行一些縮放,同時z分量也進行了平移,縮放的目的是為了方便裁剪。此時w分量不再是1,而是-z,通過這個w,我們可以判斷一個頂點是否在視錐體內(nèi),在的話必須滿足下面條件:
透視投影后,空間從右手坐標系變?yōu)樽笫肿鴺讼怠?/p>
正交投影

假設橫縱比為,那么:
正交投影矩陣:
使用正交投影矩陣后:
使用正交投影后,w分量仍是1。
屏幕空間
這一步的變換,我們會得到真正的像素位置。
將頂點從裁剪空間投影到屏幕空間有兩個步驟:首先進行透視除法,用x、y、z分量除以w分量。這一步得到的坐標被稱為NDC,經(jīng)過透視除法的裁剪空間變換到一個立方體內(nèi):

對于正交投影沒什么區(qū)別。
在Unity中,屏幕空間左下角的像素坐標是(0,0)。接下來就是將NDC縮放值屏幕坐標系。
透視除法和屏幕映射的過程總結(jié)如下:
法線變換
法線是一個方向矢量,垂直于表面,與其垂直的一個矢量被稱為切線。
切線由兩個頂點之間的差值計算得來,因此可以使用變換頂點的變換矩陣來變換切線。假設不考慮平移變換,變換后的切線為:
不過,如何直接這么變換的話,法線就有可能不垂直于表面了:

下面我們可以來推導一下正確的法線變換矩陣。
首先,切線與法線垂直,即,假設變換法線的矩陣為
,變換后法線仍與切線垂直,那么:
經(jīng)推導得:
由于,如果
,則上式成立,即
。
如果變換矩陣是正交矩陣,那么G = M_{A->B},當然,這限于只包含旋轉(zhuǎn)變換。如果只包含旋轉(zhuǎn)和統(tǒng)一縮放,那么
。
Unity Shader的內(nèi)置相關(guān)數(shù)學變量
內(nèi)置變量:Built-in shader variables
內(nèi)置方法:Built-in shader helper functions