向量、矩陣、三維變換及四元數

數學概念,特別是矩陣變換相關的數學知識一直是困擾圖形學初學者的一大難關。即使看了大量的相關資料,看起代碼或動手寫代碼的時候,還是感到力不從心。當遇到world/view/proj等矩陣的典型應用場景的時候,尚且還能簡單調用幾個API完成任務。但是一遇到稍微複雜一點的場景,就感覺對相關數學概念理解太過淺陋。本文嘗試深入剖析一番矩陣變換和投影,偏重理解思路,講求實用技巧。細細體會可加深對三維變換的理解。當然本文都是我自己學習相關數學知識過程中的一些心得,理解不對的,還請指出。

本文採用D3D左手坐標系,向量採用行向量,透視投影深度範圍為left[ 0, 1 right]

1. 向量與點

向量無所謂位置,可以表示為left[ px, py, pz, 0 right] ;點用行向量表示為left[ px, py, pz, 1 right]n,稱為齊次坐標

經常還用到非齊次坐標left[ px, py, pz, w right] left( wne 0 right) ,其表示的點是left[ frac{px}{w}, frac{py}{w}, frac{pz}{w}, 1  right]

向量和點的區別在於,點可以平移,而向量無所謂平移。向量可以理解為原點向空間一點的矢量,即 left[ px, py, pz, 0 right] = left[ px, py, pz, 1right] - left[0, 0, 0, 1 right]

2. 線性插值

二維直線上取兩點left( px1, py1 right) , left( px2, py2 right)n,該直線上任何一點left( px, py right)滿足:

frac{px - px1}{px2 - px1}  = frac{py - py1}{py2 - py1}

3. 基本變換

常用的基本變換包括平移、旋轉、伸縮等。理解基本變換應該從基變換出發。所謂基變換,就是變換矩陣對單位矩陣基向量的作用方式。

對於任意向量v=left[x,y,zright],都可以寫成v=[x,y,z]timesbegin{bmatrix}n1 & 0 & 0n0 & 1 & 0 n0 & 0 & 1nend{bmatrix},而向量與矩陣的乘積可表示為vM=begin{bmatrix}nx & y & zn end{bmatrix} timesnbegin{bmatrix}n1 & 0 & 0n0 & 1 & 0n0 & 0 & 1n end{bmatrix} timesnbegin{bmatrix}nm_{11} & m_{12} & m_{13} nm_{21} & m_{22} & m_{23} nm_{31} & m_{32} & m_{33}nend{bmatrix}n=nbegin{bmatrix}nx & y & znend{bmatrix} timesnbegin{bmatrix}nvec {m_{1}} nvec {m_{2}} nvec {m_{3}}nend{bmatrix}

即:轉換矩陣的每一行都能解釋成將原基向量經矩陣轉換後的新基向量。

例如:

平移可以表示如下:

begin{bmatrix} px & py & pz & 1 end{bmatrix} timesnn begin {bmatrix}n1 & 0 & 0 & 0 n0 & 1 & 0 & 0 n0 & 0 & 1 & 0 noff_{x} & off_{y} & off_{z} & 1n end{bmatrix}= begin{bmatrix} px + off_{x} & py + off_{y} & pz + off_{z} & 1end{bmatrix}

伸縮可以表示如下:

begin{bmatrix} px & py & pz & 1 end{bmatrix} timesnn begin {bmatrix}nscale_{x} & 0 & 0 & 0 n0 & scale_{y} & 0 & 0 n0 & 0 & scale_{z} & 0 n0 & 0 & 0 & 1n end{bmatrix}= begin{bmatrix} px * scale_{x} & py * scale_{y} & pz * scale_{z} & 1end{bmatrix}

注意觀察變換矩陣相應分量對平移和伸縮變換的作用。變換矩陣最後一行行向量row_{3}的作用是分別對原基向量x, y, z做平移;變換矩陣對角元素的作用是對原基向量做伸縮。

從模型坐標轉換為世界坐標的世界矩陣WorldMatrix表示如下:begin{bmatrix}nxaxis.x & xaxis.y & xaxis.z & 0nyaxis.x & yaxis.y & yaxis.z & 0 nzaxis.x & zaxis.y & zaxis.z & 0npos.x & pos.y & pos.z & 1nend{bmatrix}

觀察該矩陣發現,行向量row_{0}, row_{1}, row_{2}分別是各模型坐標基向量變換到世界坐標後的單位向量,而pos是模型原點對世界坐標系原點的偏移。

對於繞軸v的旋轉來說,可以把向量分解為(v_{bot}, v_{||})兩個分量,只有垂直分量對旋轉結果有貢獻。另一個理解方式是把旋轉分解為分別對x, y, z軸的旋轉yaw, pitch, roll,旋轉矩陣就是這三個矩陣的積。

下面再看視圖矩陣viewMatrix的構造。給定CameraEye, At, Up三個D3DXVECTOR3向量,調用D3DXMatrixLookAtLH()可得到ViewMatrix為[1]:

zaxis = normal(At - Eye) nxaxis = normal(cross(Up, zaxis))nyaxis = cross(zaxis, xaxis)nnbegin{bmatrix}nxaxis.x & yaxis.x & zaxis.x & 0 nxaxis.y & yaxis.y & zaxis.y & 0 nxaxis.z & yaxis.z & zaxis.z & 0 n-dot(xaxis, eye) & -dot(yaxis, eye) & -dot(zaxis, eye) & 1nend{bmatrix}

xaxis: 世界坐標系中相機坐標系的基x

yaxis: 世界坐標系中相機坐標系的基y

zaxisn: 世界坐標系中相機坐標系的基z

begin{bmatrix}ndot(xaxis, eye) & dot(yaxis, eye) & dot(zaxis, eye)nnend{bmatrix}: 世界坐標系中,原點指向eye的向量向基xaxis, yaxis, zaxis的投影,即舊原點向新原點的平移

對於鏡頭來說,局部坐標系(或模型坐標系)和視圖坐標系是同一個坐標系。世界矩陣將局部坐標轉換為世界坐標,同時也把視圖坐標轉換為世界坐標[9]。所以該視圖矩陣和世界矩陣互為逆矩陣。容易驗證ViewMatrix times WorldMatrix = I(其中I為單位矩陣)

另外,我們發現上述計算的ViewMatrix各基向量xaxis, yaxis, zaxis分別處於列向量的位置上,這裡不能把向量xaxis, yaxis, zaxis直接理解成對原基向量的變換。這是因為,從世界坐標向視圖坐標轉換時,轉換後的點相對於轉換前的點,在世界坐標中的位置並沒有改變,只是觀察基坐標發生了變化。此時應該將變換理解為世界坐標上一點向視圖坐標上的投影,也就是該點對新坐標軸向量xaxis, yaxis, zaxis的投影。投影后的坐標為:dot(v, axis)-dot(axis, eye)=begin{bmatrix}ndot(v, xaxis) ndot(v, yaxis)ndot(v, zaxis)nend{bmatrix} ^{T} - begin{bmatrix}ndot(xaxis, eye) ndot(yaxis, eye) ndot(zaxis, eye)nend{bmatrix}^{T}

4. 投影變換

投影變換是將視圖坐標變換到一個立方體空間的過程。有兩種投影方式,透視投影和平行投影。平行投影的對象原先也在一個立方體中,所以投影過程就是一個線性的伸縮和平移變換。透視投影的對象則在一個稜台內[7],所以投影過程帶有坐標軸的扭曲。

投影后的立方體可簡單表示為六個面(左右,上下,遠近):(l,r)(t,b)(zn,zf)

平行投影伸縮係數為scale = [frac{2}{r-l} , frac{2}{t-b}, frac{1}{zf-zn}],平移表示為bias = [frac{l+r}{l-r}, frac{t+b}{b-t}, frac{zn}{zn-zf}]。注意到,bias也經過了伸縮變換。若僅進行伸縮變換,實際是變換到原點為三個面frac{l+r}{2}, frac{t+b}{2}, {zn}的交點,立方體為(-1,1),(-1,1)(0,1)的範圍內。最終正交變換矩陣為begin{bmatrix}nfrac{2}{r-l} & 0 & 0 & 0n0 &frac{2}{t-b} & 0 & 0n0 & 0 & frac{1}{zf-zn} & 0nfrac{l+r}{l-r} & frac{t+b}{b-t} & frac{zn}{zn-zf} & 1nend{bmatrix}

透視投影伸縮係數為scale=[frac{2*zn}{r-l}, frac{2*zn}{t-b}, frac{zf}{zf-zn}]z坐標對x,y,z,w分量都有作用。轉換後的坐標對frac{1}{z}是線性關係。透視投影變換矩陣為begin{bmatrix}nfrac{2*zn}{r-l} & 0 & 0 & 0n0 & frac{2*zn}{t-b} & 0 & 0nfrac{l+r}{l-r} & frac{t+b}{b-t} & frac{zf}{zf-zn} & 1n0 & 0 & frac{zn*zf}{zn-zf} & 0nend{bmatrix}

注意上述都是offcenter情況下的一般情況,若變換到原點,可以令l+r=0, t+b = 0

下面介紹,給定透視投影矩陣M,如何得到viewfrustum各平面在view空間的表示方法[2]。

v=vMRightarrow (x, y, z, w)=begin{bmatrix}nvbullet col_{1} &nvbullet col_{2} &nvbullet col_{3} &nvbullet col_{4}nend{bmatrix}

而投影后的立方體各面可表示為-w < x < wn-w < y < wn0 < z < w,由此可得到viewfrustum各平面在view空間可表示為col_{4} pm col_{1}, col_{4} pm col_{2}, col_{4} pm col_{3}

對於TBDR[3]中使用的平面方程也可由此得到,只是需使用offcenter情況下的一般矩陣。有篇文章[4]從平面法線的角度也得到了同樣的結果。

另外需要提到的是,此節所講的投影是投影到立方體,並沒有丟失維度信息,所以和平移、旋轉和縮放一樣,都是可逆的。

5. 四元數與對偶四元數

單位四元數可用於旋轉。該單位四元數可以表示為q=(q_{1},q_{2},q_{3},q_{4})=(ncos(frac{theta }{2}), nsin(frac{theta}{2})*v_{x}, nsin(frac{theta}{2})*v_{y},nsin(frac{theta}{2})*v_{z}n)

他對應一個以單位向量v=(v_{x}, v_{y}, v_{z})為軸旋轉theta角度的旋轉操作。例如,對一個點P=(x,y,z),定義純四元數w=(0, x, y, z),經四元數q旋轉操作qwq^{-1}後也是一個純四元數(0, x, y, z),其中P=(x,y,z)就是點P繞軸v旋轉theta角度後得到的。之所以使用純四元數,是因為純四元數代表了三維空間的一個點,而純四元數經過上述旋轉操作後得到的還是一個純四元數,即還是一個三維空間中的點。

四元數和對偶四元數主要用於沿著旋轉路徑插值。

關於四元數的理解,可以看這篇文章[5]。對偶四元數,可參考[6][10]。

6. 參考文獻:

[1] D3DXMatrixLookAtLH function (Windows)

[2] www8.cs.umu.se/kurser/5

[3] twvideo01.ubm-us.net/o1

[4] Tile frustum calculation for the point light culling

[5] 如何形象地理解四元數? - 計算機圖形學

[6] 如何理解對偶四元數(dual quaternion)?為什麼用在蒙皮動畫中其效果比直接使用矩陣好? - 編程

[7] 深入探索透視投影變換

[8] 深入探討透視投影坐標變換

[9] Understanding the View Matrix

[10] cs.gmu.edu/~jmlien/teac

註:轉載請註明出處。


推薦閱讀:

【如何用十塊錢買房】Unity室內場景 + 光照練習 I
著色器語言之uniform限制符
小隨筆:利用Shader給斯坦福兔子長毛和實現雪地效果

TAG:计算机图形学 | 游戏开发 | Unity游戏引擎 |