您當前的位置:首頁 > 動漫

實時渲染中的座標系變換(2):3D變換基礎

作者:由 IgorKakarote 發表于 動漫時間:2020-03-14

上一篇 寫了些實時渲染中常見2D座標系變換相關的東西。到這一篇,可以推一推3D座標系的變換公式了。回到上一篇也提過的圖形學常考面試題:

請問在常見的3D渲染管線裡,從製作好mesh,到這個mesh渲染到顯示屏上,中間經歷了哪幾個座標系變換?

Well,答案是:object space -> world space -> camera space -> clip space -> screen space。

這一篇不會把這些東西都寫完。本篇主要還是梳理3D座標系變換的基礎,分析左手系右手系的變換,梳理object space 到 camera space 的變換流程,並對應地在Unity/UE4中做一些驗證。

常用3D座標系:Unity/Unreal

上一篇 提到,常用2D座標系存在“x右y上” 和 “x右y下” 兩個。同理地,常用的3D座標系也存在兩個:左手系和右手系。——為什麼明明在數學上可以統一的東西,落實到應用上卻會存在兩個標準,平添不少麻煩?我猜是當年那些人為了爭奪“氣宗”/“劍宗”誰是“正統”而遺留下來的歷史問題吧。

3D左手系如下圖所示,比如Direct3D。(一個約定俗成的慣例是:X軸為紅色、Y軸為綠色、Z軸為藍色)

實時渲染中的座標系變換(2):3D變換基礎

左手系(如Direct3D。X: Red, Y: Green, Z: Blue)

3D右手系如下圖所示,比如OpenGL:

實時渲染中的座標系變換(2):3D變換基礎

右手系(如OpenGL。X: Red, Y: Green, Z: Blue)

Unity和Unreal都是左手系,但是也有一些區別。Unity的3D座標系設定,可以參考Unity 的viewport右上角:

實時渲染中的座標系變換(2):3D變換基礎

Unity 3D Axis,左手系

UE4的3D座標系設定,參考UE4的viewport左下角:

實時渲染中的座標系變換(2):3D變換基礎

Unreal 3D Axis,左手系

Unreal式左手系的常見3D座標系變換

下面推導一下常見3D座標系變換,以UE4的座標系來進行。之後再分析Unity和右手系裡。

3D座標系的常見變換是 平移/旋轉/縮放。跟 2D座標系變換 的一個區別是,沒有“錯切變換”這個型別。當然實際上,類似錯切變換的效果是可以在3D裡構建出來的,比如相機的投影變換,就可以認為是錯切變換的一個變體。另外,根據3D變換是否改變物體形狀,可以將3D座標系變換分為兩類:剛性變換(包括平移/旋轉),和非剛性變換(包括縮放,投影變換,和一些骨骼蒙皮變換,等)。

平移

列向量表示法

\begin{bmatrix} 1 & 0 & 0 & T_x \\ 0 & 1 & 0 & T_y \\ 0 & 0 & 1 & T_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot  \begin{bmatrix} x \\ y \\ z \\ 1 \\ \end{bmatrix} =  \begin{bmatrix} x + T_x \\ y + T_y \\ z + T_z \\ 1 \end{bmatrix}

行向量表示法

\begin{bmatrix} x & y & z & 1 \end{bmatrix} \cdot  \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ T_x & T_y & T_z & 1 \end{bmatrix} =  \begin{bmatrix} x + T_x & y + T_y & z + T_z & 1 \end{bmatrix}

縮放

列向量表示法

\begin{bmatrix} S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot  \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} =  \begin{bmatrix} S_x \cdot x \\ S_y \cdot y \\ S_z \cdot z \\ 1 \end{bmatrix}

行向量表示法

\begin{bmatrix} x & y & z & 1\\ \end{bmatrix} \cdot \begin{bmatrix} S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}  =  \begin{bmatrix} S_x \cdot x & S_y \cdot y & S_z \cdot z & 1 \end{bmatrix}

跟2D座標系變換同理地,這裡的縮放,除了用來表示放大縮小外,還可以用來表示映象(當

S_x , S_y , S_z < 0

時)

旋轉

在UE4的3D座標系中,從z軸正方向往下看,並且確保x軸在視線中向右(也就是up vector為y軸負方向)時,得到的 x-y 2D座標系其實就是 上一篇 提到過的“x右y下”座標系。在這個座標系內,繞z軸

順時針

旋轉

\beta

角的旋轉矩陣是

\begin{bmatrix} \cos(\beta) & -\sin(\beta) \\ \sin(\beta) & \cos(\beta) \end{bmatrix}

。擴充套件到3維空間(即增加一個z軸),齊次座標的旋轉矩陣為:(

從z軸往下看,由x軸正方向朝向y軸正方向的旋轉

列向量表示法

\begin{bmatrix} \cos(\beta) & -\sin(\beta) & 0 & 0 \\ \sin(\beta) & \cos(\beta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \cdot  \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}

行向量表示法

\begin{bmatrix} x & y & z & 1\\ \end{bmatrix} \cdot  \begin{bmatrix} \cos(\beta) & \sin(\beta) & 0 & 0 \\ -\sin(\beta) & \cos(\beta) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

同理可以得出另外兩個軸的旋轉矩陣,如下:

從x軸正方向看,由y軸正方向、朝向z軸正方向的旋轉(旋轉角記作

\alpha

;把y當作橫軸,z當作縱軸,此時y-z構成的是一個“y右z下” 的2D座標系,可套用上一篇 推導的旋轉矩陣)

列向量表示法

\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\alpha) & -\sin(\alpha) & 0 \\ 0 & \sin(\alpha) & \cos(\alpha) & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \cdot  \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}

行向量表示法

\begin{bmatrix} x & y & z & 1 \end{bmatrix} \cdot  \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(\alpha) & \sin(\alpha) & 0 \\ 0 & -\sin(\alpha) & \cos(\alpha) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

從y軸正方向看,由z軸正方向、朝向x軸正方向的旋轉(旋轉角記作

\gamma

;把z當作橫軸,x當作縱軸,此時z-x構成的是一個“z右x下”的2D座標系,可套用上一篇 推導的旋轉矩陣)

列向量表示法

\begin{bmatrix} \cos(\gamma) & 0 & \sin(\gamma) & 0 \\ 0 & 1 & 0 & 0 \\ -\sin(\gamma) & 0 & \cos(\gamma) & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} \cdot  \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}

行向量表示法

\begin{bmatrix} x & y & z & 1 \end{bmatrix} \cdot  \begin{bmatrix} \cos(\gamma) & 0 & -\sin(\gamma) & 0 \\ 0 & 1 & 0 & 0 \\ \sin(\gamma) & 0 & \cos(\gamma) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

順帶補充一個 齊次座標系 相關的潛規則:

在表示齊次座標系時,並不是一股腦地”末尾增加一個1“就行了,而是要區分一下是”點/point“ 還是”向量/vector“

一個3維空間點

X = \begin{bmatrix} x \\ y \\ z \end{bmatrix}

的齊次座標表達方式,是”末尾加1“,即

\begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix}

一個三維空間向量

\vec{X} = \begin{bmatrix} x \\ y \\ z \end{bmatrix}

的齊次座標表達方式,是”末尾加0“,即

\begin{bmatrix} x \\ y \\ z \\ 0 \end{bmatrix}

之所以會有這個區分,是因為 向量代表的只是一個方向資訊,跟具體在3維空間中什麼位置是無關的。因此對一個向量施加平移變換,是沒有意義的。透過對向量的齊次座標末尾加0,消除了平移變換的影響。如果對一個向量施加平移變換,會是如下這個符合預期的”平移無效“的結果:

\begin{bmatrix} 1 & 0 & 0 & T_x \\ 0 & 1  & 0 & T_y \\ 0 & 0 & 1 & T_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot  \begin{bmatrix} x \\ y \\ z \\ 0 \end{bmatrix} =  \begin{bmatrix} x \\ y \\ z \\ 0 \end{bmatrix}

Unity式左手系的常見3D座標變換

平移、縮放變換公式,跟上述UE4的變換矩陣相同

旋轉:因為都是左手系,所以在Unity座標系中

從x軸正方向看,看到的是“y右z下” 2D座標系

從y軸正方向看,看到的是“z右x下” 2D座標系

從z軸正方向看,看到的是“x右y下” 2D座標系

因此繞x/y/z軸的旋轉矩陣,跟UE4相同

右手系(如OpenGL)的常見3D座標變換

平移、縮放變換公式,跟上述UE4的變換矩陣相同

旋轉:因為是右手系,所以前面的所有“#右#下” 2D座標系,到這裡都變成了“#右#上”。因此前面的這些旋轉公式,可以直接拿過來使用,只不過

旋轉方向,從“順時針” 變成了“逆時針”

下面梳理一下本篇開頭的問題裡,從Object Space到 Camera Space的座標系變換(Camera Space之後的變換,涉及到投影矩陣,下一次再寫)

Object Space -> World Space

Object Space,是使用Blender/Max/Maya等DCC工具建模時,使用的座標系。

World Space,是將製作完成的模型擺放到 Unity/Unreal 等引擎的場景中時,使用的座標系。

所以將模型製作完成,並把模型擺放到場景裡面去以後,其實已經進行了一次座標系變換,那就是 Object Space 到 World Space。

如果使用的是 Unity或Unreal,那 World Space 都是 左手系,見前面的兩張viewport截圖。

在這種前提下,

如果 DCC工具的座標系(即Object Space)也是左手系

,那事情是比較清晰的:

ObjectSpace到WorldSpace,是兩個左手系之間的變換,其變換是可以可以用一系列平移/旋轉 來完成的。

比如下圖的兩個左手系 3D 座標系之間的變換:

實時渲染中的座標系變換(2):3D變換基礎

兩個左手系之間的座標系變換

至於這個座標系變換該怎麼計算:記第一個左手系的原點為

O_1 = (0,0,0)

,3個單位向量為

\vec{x_1} = (1,0,0),\vec{y_1}=(0,1,0),\vec{z_1}=(0,0,1)

。記第二個左手系的原點和單位向量,在第一個座標系中的表達方式為

O_2,\vec{x_2},\vec{y_2},\vec{z_2}

。那麼使用如下3個步驟,可以將座標系2變換到座標系1:

第一步,對座標系2進行平移操作,使得

O_1

O_2

重合,變換矩陣記作

M_a

(使用列向量表示法對應的變換矩陣);

第二步,對座標系2進行旋轉操作,使得

\vec{x_2}

\vec{x_1}

重合(這個旋轉不再是繞x/y/z軸進行,需要使用到3D空間中、繞任意軸旋轉的變換公式,這個下一篇講),變換矩陣記作

M_b

第三步,對座標系2進行旋轉操作,因為

\vec{x_2}

\vec{x_1}

已經重合了,所以此處只要繞

\vec{x}

軸進行旋轉,使得

\vec{y_1}與\vec{y_2},\vec{z_1}與\vec{z_2}重合

即可。變換矩陣記作

M_c

在列向量表示法下,使用上述3個步驟的級聯變換矩陣(

記作 M = M_c \cdot M_b \cdot M_a

),可以將座標系2變換到座標系1。具體來說(注意 point 和 vector 的齊次座標的區別):

M \cdot O_2 = O_1

,用齊次座標展開是

M \cdot \begin{bmatrix} O_{2x} \\ O_{2y} \\ O_{2z} \\ 1 \end{bmatrix} =  \begin{bmatrix} O_{1x} \\ O_{1y} \\ O_{1z} \\ 1 \end{bmatrix}

M \cdot \vec{x_2} = \vec{x_1}

,用齊次座標展開是

M \cdot \begin{bmatrix} \vec{x_2}_x \\ \vec{x_2}_y \\ \vec{x_2}_z \\ 0 \end{bmatrix} = \begin{bmatrix} \vec{x_1}_x \\ \vec{x_1}_y \\ \vec{x_1}_z \\ 0 \end{bmatrix}

同理,

M \cdot \vec{y_2} = \vec{y_1}

M \cdot \vec{z_2} = \vec{z_1}

對於3D座標系1(

O_1,\vec{x_1},\vec{y_1},\vec{z_1}

)中的某個點

p_1

,記其座標為

p_1 = (a,b,c)

,則其幾何含義其實是:

p_1 = O_1 + a \cdot \vec{x_1} + b \cdot \vec{y_1} + c \cdot \vec{z_1}

,用矩陣可以表示為(注意都是列向量):

p_1 = \begin{bmatrix} \vec{x_1} & \vec{y_1}  & \vec{z_1} & O_1  \end{bmatrix} \cdot \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} = \begin{bmatrix} \vec{x_1}_x & \vec{y_1}_x & \vec{z_1}_x & O_{1x} \\ \vec{x_1}_y & \vec{y_1}_y & \vec{z_1}_y & O_{1y} \\ \vec{x_1}_z & \vec{y_1}_z & \vec{z_1}_z & O_{1z} \\ 0 & 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} =  T_1 \cdot c_1

把變換矩陣

M

加進來,配合前面的推導,可得:

p1 = \begin{bmatrix} M \cdot \vec{x_2} & M \cdot \vec{y_2} & M \cdot \vec{z_2} & M \cdot O_2 \end{bmatrix} \cdot \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} = M \cdot \begin{bmatrix} \vec{x_2} & \vec{y_2} & \vec{z_2} & O_2 \end{bmatrix} \cdot \begin{bmatrix} a \\ b \\ c \\ 1 \end{bmatrix} = M \cdot T_2 \cdot c_1

結合上述,可得

T_1 \cdot c_1 = M \cdot T_2 \cdot c_1

,即:

T_1 = M \cdot T_2

因此,對3D座標系1中的任意點

p_1

,需要求其在座標系2中的座標

p_2

時,實際上的求解過程就變成了:已知

T_1 \cdot p_1  = M \cdot T_2 \cdot p_1 = T_2 \cdot p_2,求 p_2

對上式做一個變換,即可得

p_2 = {T_2}^{-1} T_1 p_1 = {T_2}^{-1} M T_2 p_1

從座標系1到座標系2的變換矩陣就是 : #FormatImgID_65#

然後來梳理一下左右手系之間的變換。

這種變換,在Object Space 到 World Space的轉換過程中,是真的會出現的。舉例來說,Blender裡的座標系就是右手系,如下圖:

實時渲染中的座標系變換(2):3D變換基礎

Blender建模時使用的是右手系,模型匯入Unity/UE4等左手系引擎時,會有一個左右手系的座標變換

其實,這方面UE4和Blender的預設處理是:不處理…… 舉個例子,在blender中製作這樣一個cube模型,延x軸拉長,並且6個面給不同的材質,賦予6種顏色(x+: red, x-: magenta, y+: yellow, y-: green, z+: blue, z-: cyan):

實時渲染中的座標系變換(2):3D變換基礎

Blender box mesh:x/y/z 正方向的3種顏色

實時渲染中的座標系變換(2):3D變換基礎

Blender box mesh:x/y/z 負方向

這個模型匯入到UE4以後,形狀沒變化,但是y軸方向的顏色反了:y軸的正負方向,從 黃色/綠色,變成了 綠色/黃色。

實時渲染中的座標系變換(2):3D變換基礎

UE4 blender mesh:x/y/z 正方向

實時渲染中的座標系變換(2):3D變換基礎

UE4 blender mesh:x/y/z負方向

究其原因,就是因為中間這個右手繫到左手系的轉換被忽略了,到了UE4裡,直接用blender中的右手系座標數值,在左手系中進行了渲染和顯示,其座標系的差異見下圖:

實時渲染中的座標系變換(2):3D變換基礎

左:UE4左手系;右:Blender右手系;不做處理而直接使用模型時,y軸會被反向

那麼應該怎麼處理呢?參考上圖,答案基本上已經呼之欲出了,那就是在從Blender匯入UE4時,先把模型沿y軸做一個映象,把”右手系“變成”左手系“(目前在常用DCC工具的匯出設定裡基本都可以找到類似的設定)。做完這一步之後,Object Space 到 World Space 的操作,就是標準的左手系之間的座標變換,前面已經論述過了。

World Space -> Camera Space

這一步比較直觀,因為同一個引擎(比如Unity/Unreal)裡,World Space 和 Camera Space都同為 左手系或者右手系,因此使用上面推導“Object Space->World Space”時講的辦法,即可以完成 World Space 到 Camera Space 的座標系變換。

3D基礎變換效果示例

模型擺放到 Unity/Unreal 的場景中時,已經被賦予了一個 transform 屬性,包含這個模型在 world space 的平移/旋轉/縮放。有的時候,可以透過程式碼來修改這個 transform 屬性,製作一些物體移動/旋轉/縮放的效果。

除此之外,在材質中,還可以對模型的每個頂點進行操作、修改其位置。這步操作,在Unity中是直接寫 vertex shader,在UE4中則是調節材質中的”WorldPositionOffset“屬性。

比如在UE4中使用如下的簡單材質,即可以讓一個有一些網格的模型動起來。

實時渲染中的座標系變換(2):3D變換基礎

材質

實時渲染中的座標系變換(2):3D變換基礎

模型

實時渲染中的座標系變換(2):3D變換基礎

實時渲染中的座標系變換(2):3D變換基礎

標簽: 座標系  變換  3D  space  UE4