前言
筆者是一個游戲行業(yè)的程序員,讀書的時候做的基本都是native graphic library的項目,工作了反而對渲染管線細節(jié)接觸少很多由桌,說到底還是現(xiàn)在的商業(yè)引擎都太好用了啊喂。目前絕大多數(shù)的渲染算法在github,shader toy上都能找到非常完整的實現(xiàn)允睹,大量的內(nèi)置函數(shù)和宏隱藏了復雜的渲染細節(jié)。假如你需要修改unity自帶的復雜pbr光照幌氮,或者實現(xiàn)一個簡單的billboard shader缭受,做為程序員為了能繼續(xù)方便的打磨別人的輪子,搞明白這些內(nèi)置函數(shù)&變量的數(shù)據(jù)計算過程就成了必要條件该互。
這篇文章包含了左&右手坐標系 行主序(row major) 列主序(column major)左乘 右乘 坐標系變換的相關推導和使用注意事項米者。
相信我,我高考數(shù)學選擇錯了一半都能搞懂宇智,你肯定也可以蔓搞。
左手坐標系&右手坐標系
左手坐標系是指在空間直角坐標系中,讓左手拇指指向x軸的正方向随橘,食指指向y軸的正方向喂分,如果中指能指向z軸的正方向,則稱這個坐標系為左手直角坐標系机蔗。反之則是右手直角坐標系蒲祈。
定義左右手坐標系的作用:
在三維世界中甘萧,我們給定一個平面XOY,針對這個平面的旋轉角度θ就產(chǎn)生兩種情況--順時針和逆時針梆掸。所以對坐標系使用左手與右手的命名幔嗦,這種命名規(guī)則的作用就是用來方便判斷旋轉的正方向,這就是左手法則和右手法則沥潭。
針對上圖來說邀泉,在左手坐標系下,XOY面的旋轉正方向這樣獲得:大拇指朝向z軸正方向钝鸽,四指彎曲的方向就是左手坐標系下汇恤,旋轉的正方向。所以本文的所有旋轉在給定左右手坐標系的情況下拔恰,旋轉角度θ因谎,就是沿著當前坐標系的正方向進行旋轉θ角度。
測試可知:左手坐標下颜懊,順時針就是旋轉的正方向财岔,右手坐標系則正好相反
ps:unity就是基于左手坐標系的,我們可以通過簡單的代碼進行觀察物體是否沿著XOZ面順時針旋轉
transform.rotation = Quaternion.Euler(0, 45, 0);
1. 點河爹,向量匠璧,齊次坐標
為了理解簡單點,本文大部分都會先考慮二維的情況咸这,三維世界的情況其實就是二維世界的擴展
在二維世界中夷恍,點P&向量V的定義:
p=\left\{x, y, 1\right\} v=\left\{x, y, 0\right\}
這里你可能會提出兩個問題:
- 為什么要用三維向量去表示二維世界的P&V
- 為什么P的第三維度值是1,而V的第三維度值是0
1.1 二維坐標系的Rotate Translate Scale Formula
要回答這些問題我們需要考慮這樣的問題媳维,在二維世界中如何對點P完成平移(translate)旋轉(rotate)以及縮放(scale)操作酿雪。
考慮下圖中的點P移動到P',有公式如下:
考慮下圖中的點P旋轉到P'-- 在左手坐標系下旋轉角度θ的計算公式
接下來我們考慮縮放的情況侄刽,對二維坐標系內(nèi)上X指黎,Y軸分別進行放縮Sx,Sy州丹,計算公式如下:
1.2 二維坐標系的Rotate Translate Scale Matrix
我們接下來考慮一個問題醋安,如何方便的把RTS運算結合在一起呢?答案就是矩陣当叭。原因很簡單茬故,矩陣運算天生滿足結合律,我們把上述公式轉換為矩陣運算后就可以用一個矩陣來表示RTS行為了蚁鳖,這為以后的復雜運算提供了便利磺芭。
根據(jù)上述的公式,我們可以很簡單的得到Rotate Matrix&Scale Matrix
Rotate Matirx in left hand coordinate
Scale Matirx in left hand coordinate
在n維坐標系中醉箕,平移矩陣需要n+1維的向量完成平移操作钾腺。所謂的齊次坐標就是就是將一個原本是n維的向量用一個n+1維向量來表示徙垫。
Translate Matirx in left hand coordinate
注意:以上的計算公式計算結果都是在同一個坐標系內(nèi)部的,當我們使用XOY為basic coordinate的情況下放棒,以上公式的計算結果都是在basic coordinate下的
進而我們把旋轉矩陣和平移矩陣也引入齊次坐標來使得Rts Matrix可以結合姻报,公式為:
Rotate Matirx in left hand coordinate
Scale Matirx in left hand coordinate
齊次坐標除了方便用于進行仿射(線性)幾何變換以后。它還能夠能夠用來明確區(qū)分向量和點间螟。
向量v是矢量吴旋,它沒有平移的概念,通過齊次坐標的N+1維 = 0厢破,使得它無法完成平移操作荣瑟,但是仍然可以受到RS Matrix影響。
點P的齊次坐標的N+1維 = 1摩泪, 這就回答了問題2笆焰。
1.3 矩陣運算的左乘和右乘
由于矩陣運算滿足如下規(guī)律:
我們以平移矩陣為例,根據(jù)上述公式可以改寫成如下形式见坑,這就是矩陣左乘:
我們把向量在左邊的矩陣乘法稱之為:矩陣左乘**
我們把向量在右邊的矩陣乘法稱之為:矩陣右乘**
ps:unity就是基于矩陣右乘的嚷掠,我們可以通過簡單的創(chuàng)建translate matrix來查看translate系數(shù)的所在位置來推測unity的矩陣是否右乘
Matrix4x4 m = Matrix4x4.Translate(new Vector3(5, 6, 7));
左乘和右乘在計算效率上有深入的考量,同時左乘右乘影響著rts矩陣的運算順序荞驴,詳情請看下文
1.4 矩陣的存儲方式不皆,行主序&列主序
針對一個特定的矩陣,它在內(nèi)存中的線性存儲的方式有兩種:行主序 & 列主序
對于一個數(shù)組float[9] array
行主序的存儲方式是:a - b - c - d - e - f - g - h - i
列主序的存儲方式是:a - d - g - b - e - h - c - f - i
ps:unity的matrix4x4就是列主序的戴尸,請注意m.xy中x是行位置粟焊,y是列位置冤狡,他們和行主序列主序無關
2. 坐標系轉換
上文講述了在basic coordinate下針對一個點P的Rts Formula孙蒙,計算結果一直都在同一個坐標系下。
現(xiàn)在我們考慮一個新的問題:
在一個給定的basic coordinate(左手坐標系or右手坐標系)下有一個點P悲雳,計算新的坐標系A下的點P'的值
舉個例子挎峦,我們提供兩個坐標系X''_O'_Y''和X_O_Y,如何計算在X''_O'_Y''坐標系下的P''點在X'_O'_Y'中的值呢合瓢?
復雜的問題簡單化坦胶,我們先計算X''_O'_Y''中的P''在X'_O'_Y'中的P'值:
然后,我們在X'_O'_Y'中的點P'計算 X_O_Y的最后結果P
把上述公式通過矩陣左乘的方式表達:
通過矩陣的轉置計算公式晴楔,我們可以得到矩陣右乘的版本:
通過上述推導結果顿苇,我們能夠觀察到一些坐標系變換必須要注意的性質(zhì):
- 矩陣的左乘&右乘直接影響了坐標系變化下的rotate translate順序。設X_O_Y是basic coordinate税弃,X''O''Y''是local coordinate纪岁,那么上述公式就成了local 2 world coordinate formula,通過矩陣右乘则果,局部坐標系P''變換到P需要先乘Mt幔翰,再乘Mr
從P_local變換到P_world坐標系下漩氨,可以分成三個步驟:
- X''_O'_Y'' 旋轉到與X'O''Y'重合 -- 計算P''在X'O''Y'中的值P'
- X'_O'_Y' 縮放到與X_O_Y一致
- X'_O'_Y' 平移到與X_O_Y重合 -- 計算P'在X_O_Y的值P
- unity中game object的transform.rotation是basic coordinate下旋轉θ到transform local coordinate的旋轉四元數(shù)。
vector3 p' = Matrix4x4.rotate(transform.rotation).multiPoint(p)
注意:如果p是basic coordinate下遗增, p'仍然是basic coordinate下的叫惊,上述這樣使用旋轉四元數(shù)并不會讓點實現(xiàn)坐標系變換
由于在左手坐標系下,世界坐標旋轉θ的公式已知
將上述公式帶入坐標系變換公式做修,就可以得到
所以給定一個transform和基于這個transform的local coordinate點P霍狰,計算這個P在世界坐標系的代碼為:
protected Vector3 Coordinate2World(Transform a, Vector3 a_local_p)
{
// transform.rotation equals FromToRotation(Vector3.forward, a.forward)
//Quaternion q = Quaternion.FromToRotation(Vector3.forward, a.forward);
//Matrix4x4 m_q = Matrix4x4.Rotate(q);
Matrix4x4 m_q = Matrix4x4.Rotate(a.rotation);
Matrix4x4 m_t = Matrix4x4.Translate(a.position);
return (m_t * m_q).MultiplyPoint(a_local_p);
}
這個旋轉應用于一些特殊的向量就會有特殊的幾何意義,比如vector.forward使用下面的代碼
vector3 v' = Matrix4x4.rotate(transform.rotation).multiVector(v)
得到的V'就是local coordinate下vector.forward在basic coordinate中的值
- 矩陣右乘的local coordinate <--> world coordinate matrix & 中饰及,矩陣的相關位置對應著相應的功能蚓耽,旋轉&縮放相關的參數(shù)為R,平移相關的參數(shù)為T
矩陣右乘 --- 在local to world matrix中旋炒,T = obj.position - vector3.zero = obj.position = vec2(m02, m12)
矩陣右乘 --- 在world to local matrix中步悠,T = -obj.position + vector3.zero = -obj.position = vec2(m02, m12)
小提示:由于unity是使用矩陣右乘的,我們在shader中可以很方便的在unity_ObjectToWorld獲取物體的world position瘫镇。
float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1);
- 設X_O_Y和X''_O'_Y''都是local coordinate鼎兽,這樣我們就得到了一個廣義的旋轉矩陣推導,同時回答了第二章一開始提出的問題
在一個給定的坐標系A(左手坐標系or右手坐標系)下有一個點P铣除,計算新的坐標系B下的點P'的值
從上文可以得到坐標系變換的頭一步需要將 X''_O'_Y'' 的P''點變換到一個虛擬的坐標系X'O''Y'下谚咬,這一步需要X'O''Y'下旋轉角度θ到X''_O'_Y'' 的矩陣和translate(O''_inworld - O'_inworld),計算代碼如下:
protected Vector3 Coordinate2Coordinate(Transform a, Vector3 a_local_p, Transform b)
{
Quaternion q = Quaternion.FromToRotation(b.forward, a.forward);
Matrix4x4 m_q = Matrix4x4.Rotate(q);
Matrix4x4 m_t = Matrix4x4.Translate(a.position - b.position);
return (m_t * m_q).MultiplyPoint(a_local_p);
}
3. 總結
上文中的公式推導都是基于二維坐標系的尚粘,但是RTS的順序择卦,坐標系變換原理都是一樣的,所有的rts變換在計算目標不同的時候公式是不同的郎嫁。
在baisc coordinate下對P點進行旋轉秉继,平移,縮放得到的結果P'仍然是在basic coordinate中泽铛,而basic coordinate下有點P尚辑,獲取local coordinate的坐標P'是另外一回事,不要搞混了盔腔。
下一篇文章會仔細推導一下三維坐標系的rts矩陣杠茬,和上述兩種情況下的計算公式。