特效筆記 -- 搞定坐標系變換_左乘_右乘_行主序_列主序的倒數(shù)第二篇文章

前言

筆者是一個游戲行業(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軸的正方向,則稱這個坐標系為左手直角坐標系机蔗。反之則是右手直角坐標系蒲祈。

left_hand_坐標系.jpg

定義左右手坐標系的作用:


在三維世界中甘萧,我們給定一個平面XOY,針對這個平面的旋轉角度θ就產(chǎn)生兩種情況--順時針和逆時針梆掸。所以對坐標系使用左手與右手的命名幔嗦,這種命名規(guī)則的作用就是用來方便判斷旋轉的正方向,這就是左手法則和右手法則沥潭。

針對上圖來說邀泉,在左手坐標系下,XOY面的旋轉正方向這樣獲得:大拇指朝向z軸正方向钝鸽,四指彎曲的方向就是左手坐標系下汇恤,旋轉的正方向。所以本文的所有旋轉在給定左右手坐標系的情況下拔恰,旋轉角度θ因谎,就是沿著當前坐標系的正方向進行旋轉θ角度。

測試可知:左手坐標下颜懊,順時針就是旋轉的正方向财岔,右手坐標系則正好相反

left_hand_坐標系_標記.jpg

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\}

這里你可能會提出兩個問題:

  1. 為什么要用三維向量去表示二維世界的P&V
  2. 為什么P的第三維度值是1,而V的第三維度值是0

1.1 二維坐標系的Rotate Translate Scale Formula

要回答這些問題我們需要考慮這樣的問題媳维,在二維世界中如何對點P完成平移(translate)旋轉(rotate)以及縮放(scale)操作酿雪。

考慮下圖中的點P移動到P',有公式如下:

translation.jpg

x' = x + t_x y' = y + t_y

考慮下圖中的點P旋轉到P'-- 在左手坐標系下旋轉角度θ的計算公式

rotation.png

x = R * cosΦ y = R * sinΦ

x' = R * cos(Φ-θ) = R * cosθ * cosΦ + R * sinθ * sinΦ=x*cosθ + y * sinθ y' = R * sin(Φ-θ) = R * cosθ * sinΦ - R * sinθ * cosΦ=-x*sinθ + y * cosθ

接下來我們考慮縮放的情況侄刽,對二維坐標系內(nèi)上X指黎,Y軸分別進行放縮Sx,Sy州丹,計算公式如下:
x' = x * S_x y' = y * S_y

1.2 二維坐標系的Rotate Translate Scale Matrix

我們接下來考慮一個問題醋安,如何方便的把RTS運算結合在一起呢?答案就是矩陣当叭。原因很簡單茬故,矩陣運算天生滿足結合律,我們把上述公式轉換為矩陣運算后就可以用一個矩陣來表示RTS行為了蚁鳖,這為以后的復雜運算提供了便利磺芭。

根據(jù)上述的公式,我們可以很簡單的得到Rotate Matrix&Scale Matrix

Rotate Matirx in left hand coordinate
\left[ \begin{matrix} x'\\ y' \end{matrix} \right ] = \left[ \begin{matrix} cosθ & sinθ \\ -sinθ & cosθ \end{matrix} \right] * \left[ \begin{matrix} x\\ y \end{matrix} \right]

Scale Matirx in left hand coordinate
\left[ \begin{matrix} x'\\ y' \end{matrix} \right ] = \left[ \begin{matrix} R_x & 0 \\ 0 & R_y \end{matrix} \right] * \left[ \begin{matrix} x\\ y \end{matrix} \right]

在n維坐標系中醉箕,平移矩陣需要n+1維的向量完成平移操作钾腺。所謂的齊次坐標就是就是將一個原本是n維的向量用一個n+1維向量來表示徙垫。

Translate Matirx in left hand coordinate
\left[ \begin{matrix} x' \\ y' \\ 1 \end{matrix} \right ] = \left[ \begin{matrix} 1 & 0 & T_x \\ 0 & 1 & T_y \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x\\ y \\ 1 \end{matrix} \right ]

注意:以上的計算公式計算結果都是在同一個坐標系內(nèi)部的,當我們使用XOY為basic coordinate的情況下放棒,以上公式的計算結果都是在basic coordinate下的

進而我們把旋轉矩陣和平移矩陣也引入齊次坐標來使得Rts Matrix可以結合姻报,公式為:
Rotate Matirx in left hand coordinate
\left[ \begin{matrix} x'\\ y' \\ 1 \end{matrix} \right] = \left[ \begin{matrix} cosθ & sinθ & 0 \\ -sinθ & cosθ & 0 \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x\\ y \\ 1 \end{matrix} \right]

Scale Matirx in left hand coordinate
\left[ \begin{matrix} x' \\ y' \\ 1 \end{matrix} \right] = \left[ \begin{matrix} R_x & 0 & 0 \\ 0 & R_y & 0 \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x\\ y \\ 1 \end{matrix} \right]

齊次坐標除了方便用于進行仿射(線性)幾何變換以后。它還能夠能夠用來明確區(qū)分向量和點间螟。

向量v是矢量吴旋,它沒有平移的概念,通過齊次坐標的N+1維 = 0厢破,使得它無法完成平移操作荣瑟,但是仍然可以受到RS Matrix影響。
\left[ \begin{matrix} x \\ y \\ 0 \end{matrix} \right] = \left[ \begin{matrix} 1 & 0 & T_x \\ 0 & 1 & T_y \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x \\ y \\ 0 \end{matrix} \right]

點P的齊次坐標的N+1維 = 1摩泪, 這就回答了問題2笆焰。

1.3 矩陣運算的左乘和右乘

由于矩陣運算滿足如下規(guī)律:
(A * B)^T=B^T*A^T

我們以平移矩陣為例,根據(jù)上述公式可以改寫成如下形式见坑,這就是矩陣左乘:
\left[ \begin{matrix} x' & y' & 1 \end{matrix} \right ] = \left[ \begin{matrix} x & y & 1 \end{matrix} \right] * \left[ \begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0\\ T_x & T_y & 1 \end{matrix} \right ]

我們把向量在左邊的矩陣乘法稱之為:矩陣左乘**
我們把向量在右邊的矩陣乘法稱之為:矩陣右乘**

ps:unity就是基于矩陣右乘的嚷掠,我們可以通過簡單的創(chuàng)建translate matrix來查看translate系數(shù)的所在位置來推測unity的矩陣是否右乘

     Matrix4x4 m  = Matrix4x4.Translate(new Vector3(5, 6, 7));

左乘和右乘在計算效率上有深入的考量,同時左乘右乘影響著rts矩陣的運算順序荞驴,詳情請看下文

1.4 矩陣的存儲方式不皆,行主序&列主序

針對一個特定的矩陣,它在內(nèi)存中的線性存儲的方式有兩種:行主序 & 列主序

\left[ \begin{matrix} a & b & c \\ d & e & f\\ g & h & i \end{matrix} \right]

對于一個數(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'中的值呢合瓢?

coordinate_world_matrix.png

復雜的問題簡單化坦胶,我們先計算X''_O'_Y''中的P''在X'_O'_Y'中的P'值:

x'' = R * cosθ_3 y'' = R * sinθ_3

x' = R * cos(θ_3-θ_2) = R * cosθ_3 * cosθ_2 + R * sinθ_3 * sinθ_2=x''*cosθ_2 + y'' * sinθ_2 y' = R * sin(θ_3-θ_2) = R * sinθ_3 * cosθ_2 - R * cosθ_3 * sinθ_2=-x''*sinθ_2 + y'' * cosθ_2

然后,我們在X'_O'_Y'中的點P'計算 X_O_Y的最后結果P

x = x' + a y = y' +b

把上述公式通過矩陣左乘的方式表達:

\left[ \begin{matrix} x & y & 1 \end{matrix} \right] = \left[ \begin{matrix} x'' & y'' & 1 \end{matrix} \right] * \left[ \begin{matrix} cosθ & -sinθ & 0 \\ sinθ & cosθ & 0\\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0\\ a & b & 1 \end{matrix} \right] = \left[ \begin{matrix} cosθ & -sinθ & 0 \\ sinθ & cosθ & 0\\ a & b & 1 \end{matrix} \right]

通過矩陣的轉置計算公式晴楔,我們可以得到矩陣右乘的版本:

\left[ \begin{matrix} x\\ y \\ 1 \end{matrix} \right] = \left[ \begin{matrix} 1 & 0 & a \\ 0 & 1 & b \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} cosθ & sinθ & 0 \\ -sinθ & cosθ & 0 \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x''\\ y'' \\ 1 \end{matrix} \right] = \left[ \begin{matrix} cosθ & sinθ & a \\ -sinθ & cosθ & b \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x''\\ y'' \\ 1 \end{matrix} \right]

通過上述推導結果顿苇,我們能夠觀察到一些坐標系變換必須要注意的性質(zhì):

  1. 矩陣的左乘&右乘直接影響了坐標系變化下的rotate translate順序。設X_O_Y是basic coordinate税弃,X''O''Y''是local coordinate纪岁,那么上述公式就成了local 2 world coordinate formula,通過矩陣右乘则果,局部坐標系P''變換到P需要先乘Mt幔翰,再乘Mr

P_w = M_t * M_r * P_l P_l = M^{-1}_r * M^{-1}_t * P_w

從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
  1. 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)坐標系變換

由于在左手坐標系下,世界坐標旋轉θ的公式已知

\left[ \begin{matrix} cosθ & sinθ & 0 \\ -sinθ & cosθ & 0 \\ 0 & 0 & 1 \end{matrix} \right] = transform.rotation

將上述公式帶入坐標系變換公式做修,就可以得到
\left[ \begin{matrix} x\\ y \\ 1 \end{matrix} \right] = transform.position * transform.rotation * \left[ \begin{matrix} x''\\ y'' \\ 1 \end{matrix} \right]

所以給定一個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中的值

  1. 矩陣右乘的local coordinate <--> world coordinate matrix & 中饰及,矩陣的相關位置對應著相應的功能蚓耽,旋轉&縮放相關的參數(shù)為R,平移相關的參數(shù)為T

\left[ \begin{matrix} R & R & T \\ R & R & T \\ 0 & 0 & 1 \end{matrix} \right] * \left[ \begin{matrix} x\\ y \\ 1 \end{matrix} \right ]

矩陣右乘 --- 在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);
  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矩陣杠茬,和上述兩種情況下的計算公式。

所有資源來自互聯(lián)網(wǎng)弛随,如有侵權瓢喉,煩請告知。紕漏之處舀透,請多多指教

東南形勝栓票,三吳都會,錢塘自古繁華盐杂,

煙柳畫橋逗载,風簾翠幕哆窿,參差十萬人家。

2020/3/21 北京 望京soho 赴杭前夕
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厉斟,一起剝皮案震驚了整個濱河市挚躯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌擦秽,老刑警劉巖码荔,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異感挥,居然都是意外死亡缩搅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門触幼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硼瓣,“玉大人,你說我怎么就攤上這事置谦√美穑” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵媒峡,是天一觀的道長瘟栖。 經(jīng)常有香客問我,道長谅阿,這世上最難降的妖魔是什么半哟? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮签餐,結果婚禮上寓涨,老公的妹妹穿的比我還像新娘。我一直安慰自己贱田,他們只是感情好缅茉,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著男摧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪译打。 梳的紋絲不亂的頭發(fā)上耗拓,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音奏司,去河邊找鬼乔询。 笑死,一個胖子當著我的面吹牛韵洋,可吹牛的內(nèi)容都是我干的竿刁。 我是一名探鬼主播黄锤,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼食拜!你這毒婦竟也來了鸵熟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤负甸,失蹤者是張志新(化名)和其女友劉穎流强,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呻待,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡打月,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚕捉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奏篙。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迫淹,靈堂內(nèi)的尸體忽然破棺而出报破,到底是詐尸還是另有隱情,我是刑警寧澤千绪,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布充易,位于F島的核電站,受9級特大地震影響荸型,放射性物質(zhì)發(fā)生泄漏盹靴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一瑞妇、第九天 我趴在偏房一處隱蔽的房頂上張望稿静。 院中可真熱鬧,春花似錦辕狰、人聲如沸改备。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悬钳。三九已至,卻和暖如春偶翅,著一層夾襖步出監(jiān)牢的瞬間默勾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工聚谁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留母剥,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像环疼,于是被迫代替她去往敵國和親习霹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359