渲染管線中的頂點變換

概述

在圖形學渲染管線中,一個頂點坐標,大概要經歷局部坐標系、世界坐標系、相機坐標系、裁剪坐標系,最後到窗口坐標系,顯示在螢幕上。

坐标空间变换示意图

在這些過程中,從一個坐標繫到另一個坐標系,都需要進行一定的變換。下面,將介紹每次變換的方式。

注意,本文是針對OpenGL的。

局部空間->世界空間

這一變換過程,主要是將模型放置在世界空間中,進行一定的縮放、旋轉或平移。這一步比較簡單,只要將相應的矩陣作用到模型的局部空間坐標即可。

比如,對模型縮放(left(S_{x},S_{y},S_{z} right)),然後繞Z軸旋轉(theta)度,再進行(left(T_{x},T_{y},T_{z} right))的平移。注意,這裡的變換順序是不能變的,即要先進行縮放,再進行旋轉,最後進行平移。據此,我們可以構建模型變換矩陣。

[M_{model}= begin{bmatrix} 1 & 0 & 0 & T_{x} \ 0 & 1 & 0 & T_{y} \ 0 & 0 & 1 & T_{z} \ 0 & 0 & 0 & 1 \ end{bmatrix} begin{bmatrix} cos{theta} & -sin{theta} & 0 & 0 \ sin{theta} & cos{theta} & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 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} ]

世界空間->相機空間

首先定義一下相機:

  • 坐標為(vec{e})

  • 觀察方向(vec{g})

  • 向上方向(vec{t})

示意圖如下所示:

相机坐标系示意图

有一個性質注意一下:當相機和相機「看「到的物體一起變換時,相機」看「到的內容是不變的。這樣,可以將相機的坐標移動到世界坐標的原點,向上方向對齊世界坐標的Y軸,觀察方向對齊世界坐標的-Z軸。然後,對物體進行相同的變換即可。

在數學上,這個過程大概這樣:

  • 將相機移動到坐標原點
  • 旋轉觀察方向(vec{g})到-Z軸
  • 旋轉向上方向(vec{t})到Y軸
  • 旋轉((vec{g} times vec{t}))到X軸

大體分為兩步:先位移,後旋轉。即(M_{view} = R_{view}T_{view})。

平移部分:

[T_{view} = begin{bmatrix} 1 & 0 & 0 & -x_{e} \ 0 & 1 & 0 & -y_{e} \ 0 & 0 & 1 & -z_{e} \ 0 & 0 & 0 & 1 \ end{bmatrix} ]

對於旋轉部分,先補充一些知識點。對於二維空間來說:

[R_{theta} = begin{pmatrix} cos{theta} & -sin{theta} \ sin{theta} & cos{theta} \ end{pmatrix} ]

[R_{-theta} = begin{pmatrix} cos{theta} & sin{theta} \ -sin{theta} & cos{theta} \ end{pmatrix} = R_{theta}^mathrm{T} ]

根據定義,旋轉(theta)角度和旋轉(-theta)角度是互逆的,即:(R_{-theta} = R_{theta}^{-1})。

所以,對於旋轉變換,可以得出旋轉矩陣的逆等於它的轉置,即:

[R_{theta}^mathrm{T} = R_{theta}^{-1} ]

回到上面的旋轉部分,直接求相機的坐標軸旋轉到世界坐標軸的矩陣不是很方便,但是反過來,求世界坐標軸旋轉到相機的坐標軸很容易:

[R_{view}^{-1} = begin{bmatrix} x_{vec{g} times vec{t}} & x_{vec{t}} & x_{-vec{g}} & 0 \ y_{vec{g} times vec{t}} & y_{vec{t}} & y_{-vec{g}} & 0 \ z_{vec{g} times vec{t}} & z_{vec{t}} & z_{-vec{g}} & 0 \ 0 & 0 & 0 & 1 \ end{bmatrix} ]

根據旋轉矩陣的逆等於它的轉置,得出:

[R_{view} = (R_{view}^{-1})^mathrm{T} = begin{bmatrix} x_{vec{g} times vec{t}} & y_{vec{g} times vec{t}} & z_{vec{g} times vec{t}} & 0 \ x_{vec{t}} & y_{vec{t}} & z_{vec{t}} & 0 \ x_{-vec{g}} & y_{-vec{g}} & z_{-vec{g}} & 0 \ 0 & 0 & 0 & 1 \ end{bmatrix} ]

根據(M_{view} = R_{view}T_{view}),可以得出:

[M_{view} = R_{view}T_{view} = begin{bmatrix} x_{vec{g} times vec{t}} & y_{vec{g} times vec{t}} & z_{vec{g} times vec{t}} & 0 \ x_{vec{t}} & y_{vec{t}} & z_{vec{t}} & 0 \ x_{-vec{g}} & y_{-vec{g}} & z_{-vec{g}} & 0 \ 0 & 0 & 0 & 1 \ end{bmatrix} begin{bmatrix} 1 & 0 & 0 & -x_{e} \ 0 & 1 & 0 & -y_{e} \ 0 & 0 & 1 & -z_{e} \ 0 & 0 & 0 & 1 \ end{bmatrix} ]

相機空間->裁剪空間

在一個頂點著色器運行的最後,期望所有的坐標都能落在一個特定的範圍內,且任何在這個範圍之外的點都應該被裁剪掉(Clipped)。被裁剪掉的坐標就會被忽略,所以剩下的坐標就將變為螢幕上可見的片段。這也就是裁剪空間(Clip Space)名字的由來。

因為將所有可見的坐標都指定在-1.0到1.0的範圍內不是很直觀,所以我們會指定自己的坐標集(Coordinate Set)並將它變換回標準化設備坐標系。

由投影矩陣創建的觀察箱(Viewing Box)被稱為平截頭體(Frustum),每個出現在平截頭體範圍內的坐標都會最終出現在用戶的螢幕上。將特定範圍內的坐標轉化到標準化設備坐標系的過程(而且它很容易被映射到2D觀察空間坐標)被稱之為投影(Projection),因為使用投影矩陣能將3D坐標投影(Project)到很容易映射到2D的標準化設備坐標系中。

這裡要注意一下,OpenGL是右手坐標系的,但是在NDC中,是左手坐標系的,這裡要特別注意!!!

相機空間轉換到裁剪空間,有需要用到投影變換。有兩種投影變換:正交投影和透視投影。下面分別介紹一下。

正交投影

我們先定義一個正交投影的視錐體([l,r] times [b,t] times [f,n])(注意,n和f都是負數,f是遠平面,所以f<n),它是一個長方體。我們需要做的,就是將正交投影的視錐體轉換到標準立方體(即標準化設備坐標,([-1,1]^{3}))。注意,這裡([f,n])映射到NDC中的[1,-1]。

這裡,分成兩個步驟:平移和縮放。正交投影的矩陣如下:

[M_{ortho} = begin{bmatrix} frac{2}{r-l} & 0 & 0 & 0 \ 0 & frac{2}{t-b} & 0 & 0 \ 0 & 0 & frac{2}{f-n} & 0 \ 0 & 0 & 0 & 1 \ end{bmatrix} begin{bmatrix} 1 & 0 & 0 & -frac{r+l}{2} \ 0 & 1 & 0 & -frac{t+b}{2} \ 0 & 0 & 1 & -frac{n+f}{2} \ 0 & 0 & 0 & 1 \ end{bmatrix}= begin{bmatrix} frac{2}{r-l} & 0 & 0 & -frac{r+l}{r-l} \ 0 & frac{2}{t-b} & 0 & -frac{t+b}{t-b} \ 0 & 0 & frac{2}{f-n} & -frac{f+n}{f-n} \ 0 & 0 & 0 & 1 \ end{bmatrix} ]

透視投影

對於透視投影,分成兩步操作:

  • 首先,「壓扁」視錐體成一個長方體(n->n,f->f)((M_{persp->ortho}));
  • 然後,做正交投影操作((M_{ortho}),即上面的正交投影)。

透视投影和正交投影的视锥体示意图

觀察下圖:

从X轴观察

根據相似三角形的關係,可以得出:

[y^{'} = frac{n}{z}y ]

類似的,可以得出:

[x^{'} = frac{n}{z}x ]

由此,可以得出下面的關係:

[M_{persp->ortho}^{(4 times 4)} begin{pmatrix} x\ y\ z\ 1\ end{pmatrix}= begin{pmatrix} frac{n}{z}x \ frac{n}{z}y \ unknown \ 1 \ end{pmatrix} ]

下面,說一個齊次坐標的性質:在3D坐標系統中,(left ( x,y,z,1 right )),(left ( kx,ky,kz,k neq 0 right )),(left ( xz,yz,z^{2},z neq 0 right ))都表示相同的坐標---(left ( x,y,z right ))。例如:(left ( 1,0,0,1 right ))和(left ( 2,0,0,2 right ))都表示坐標(left ( 1,0,0 right ))。

所以,有如下關係:

[M_{persp->ortho}^{(4 times 4)} begin{pmatrix} x\ y\ z\ 1\ end{pmatrix}= begin{pmatrix} frac{n}{z}x \ frac{n}{z}y \ unknown \ 1 \ end{pmatrix} = begin{pmatrix} nx \ ny \ unknown \ z \ end{pmatrix} ]

更進一步的,可以得到:

[M_{persp->ortho} = begin{pmatrix} n & 0 & 0 & 0 \ 0 & n & 0 & 0 \ ? & ? & ? & ? \ 0 & 0 & 1 & 0 \ end{pmatrix} ]

現在,還剩下第三列是未知的。

經過觀察上面的透視投影視錐體,可以得出以下推論:

  1. 近平面上的點的坐標都不會改變;

  2. 遠平面上的點,Z坐標不改變。

根據推論1,近平面上的點(left (x,y,n,1 right ))經過變換後,不會改變。即:

[M_{persp->ortho} begin{pmatrix} x \ y \ n \ 1 \ end{pmatrix} = begin{pmatrix} x \ y \ n \ 1 \ end{pmatrix} = begin{pmatrix} nx \ ny \ n^{2} \ n \ end{pmatrix} ]

根據:

[M_{persp->ortho} begin{pmatrix} x\ y\ z\ 1\ end{pmatrix}= begin{pmatrix} nx \ ny \ unknown \ z \ end{pmatrix} ]

因為(n^{2})與x和y都沒有關係,所以可以得出(M_{persp->ortho})的第三列的形式是(left (0,0,A,B right ))。

根據:

[left(0,0,A,B right) begin{pmatrix} x \ y \ n \ 1 \ end{pmatrix} = n^{2} ]

可以得出:

[An+B = n^{2} ]

根據推論2,遠平面的中心點(left (0,0,f,1 right)),經過變換後,還是本身。如下:

[M_{persp->ortho} begin{pmatrix} 0 \ 0 \ f \ 1 \ end{pmatrix} = begin{pmatrix} 0 \ 0 \ f \ 1 \ end{pmatrix} = begin{pmatrix} 0 \ 0 \ f^{2} \ f \ end{pmatrix} ]

所以,可以得出:

[left(0,0,A,B right) begin{pmatrix} 0 \ 0 \ f \ 1 \ end{pmatrix} = f^{2} ]

即:

[Af + B = f^{2} ]

到這裡,可以得出方程組:

[begin{cases} An + B = n^{2} \ Af + B = f^{2} \ end{cases} Rightarrow begin{matrix} A = n + f \ B = -nf \ end{matrix} ]

到這裡,可以得出(M_{persp->ortho}):

[M_{persp->ortho} = begin{bmatrix} n & 0 & 0 & 0 \ 0 & n & 0 & 0 \ 0 & 0 & n+f & -nf \ 0 & 0 & 1 & 0 \ end{bmatrix} ]

最終,透視投影矩陣:

[M_{persp} = M_{ortho}M_{persp->ortho} = begin{bmatrix} frac{2n}{r-l} & 0 & frac{l+r}{l-r} & 0 \ 0 & frac{2n}{t-b} & frac{b+t}{b-t} & 0 \ 0 & 0 & frac{f+n}{f-n} & frac{2nf}{n-f} \ 0 & 0 & 1 & 0 \ end{bmatrix} ]

裁剪空間->窗口空間

在裁剪空間的最後,所以的可見的點都在標準設備坐標系(NDC)中,即坐標坐落在範圍([-1,1]^{3})內。

先不考慮Z軸的變換。

從NDC到窗口空間,需要經過視口變換。定義一個螢幕空間:(left (0,0,w,h right))。平面左下角的坐標位(left (0,0 right)),右上角的坐標為(left (w,h right))。對於X和Y坐標的變換,即從(left(-1,1right) times left(-1,1right))到(left(0,wright) times left(0,hright))。

這裡,經過兩步變換:

  1. 將NDC的中心平移到窗口的中心;

    [T_{viewport} = begin{pmatrix} 1 & 0 & 0 & frac{w}{2} \ 0 & 1 & 0 & frac{h}{2} \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 1 \ end{pmatrix} ]

  2. 將NDC的大小縮放到螢幕的大小。

[R_{viewport} = begin{pmatrix} frac{w}{2} & 0 & 0 & 0 \ 0 & frac{h}{2} & 0 & 0 \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 1 \ end{pmatrix} ]

合併到一起:

[M_{viewport} = R_{viewport}T_{viewport} = begin{pmatrix} frac{w}{2} & 0 & 0 & frac{w}{2} \ 0 & frac{h}{2} & 0 & frac{h}{2} \ 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 1 \ end{pmatrix} ]

對於Z坐標,從(left(-1,1right))映射到了(left(0,1right))。這裡只是簡單的線性映射。假設(z^{'} = Az+B),當(z)等於-1時,(z^{'})等於0;當(z)等於1時,(z^{'})等於1。可得如下方程組:

[begin{cases} A(-1) + B = 0 \ A(1) + B = 1 \ end{cases} Rightarrow begin{cases} A = frac{1}{2} \ B = frac{1}{2} \ end{cases} ]

所以,(z^{'} = frac{1}{2}z + frac{1}{2})。代入上述(M_{viewport})矩陣,可得:

[M_{viewport} = begin{pmatrix} frac{w}{2} & 0 & 0 & frac{w}{2} \ 0 & frac{h}{2} & 0 & frac{h}{2} \ 0 & 0 & frac{1}{2} & frac{1}{2} \ 0 & 0 & 0 & 1 \ end{pmatrix} ]

參考

Tags: