繞某一點旋轉矩陣,分析,

. 簡介

計算機圖形學中的應用非常廣泛的變換是一種稱爲仿射變換的特殊變換,在仿射變換中的基本變換包括平移、旋轉、縮放、剪切這幾種。本文以及接下來的幾篇文章重點介紹一下關於旋轉的變換,包括二維旋轉變換、三維旋轉變換以及它的一些表達方式(旋轉矩陣、四元數、歐拉角等)。

2. 繞原點二維旋轉

首先要明確旋轉在二維中是繞着某一個點進行旋轉,三維中是繞着某一個軸進行旋轉。二維旋轉中最簡單的場景是繞着座標原點進行的旋轉,如下圖所示:

 

 

如圖所示點v 繞 原點旋轉θ 角,得到點v’,假設 v點的座標是(x, y) ,那麼可以推導得到 v’點的座標(x’, y’)(設原點到v的距離是r,原點到v點的向量與x軸的夾角是ϕ )
x=rcosϕy=rsinϕ
x′=rcos(θ+ϕ)y′=rsin(θ+ϕ)
通過三角函數展開得到
x′=rcosθcosϕrsinθsinϕ
y′=rsinθcosϕ+rcosθsinϕ
帶入x和y表達式得到
x′=xcosθysinθ
y′=xsinθ+ycosθ
寫成矩陣的形式是:

 

儘管圖示中僅僅表示的是旋轉一個銳角θ的情形,但是我們推導中使用的是三角函數的基本定義來計算座標的,因此當旋轉的角度是任意角度(例如大於180度,導致v’點進入到第四象限)結論仍然是成立的。

3. 繞任意點的二維旋轉

繞原點的旋轉是二維旋轉最基本的情況,當我們需要進行繞任意點旋轉時,我們可以把這種情況轉換到繞原點的旋轉,思路如下:
1. 首先將旋轉點移動到原點處
2. 執行如2所描述的繞原點的旋轉
3. 再將旋轉點移回到原來的位置

 

也就是說在處理繞任意點旋轉的情況下需要執行兩次平移的操作。假設平移的矩陣是T(x,y),也就是說我們需要得到的座標 v’=T(x,y)*R*T(-x,-y)(我們使用的是列座標描述點的座標,因此是左乘,首先執行T(-x,-y))

在計算機圖形學中,爲了統一將平移、旋轉、縮放等用矩陣表示,需要引入齊次座標。(假設使用2x2的矩陣,是沒有辦法描述平移操作的,只有引入3x3矩陣形式,才能統一描述二維中的平移、旋轉、縮放操作。同理必須使用4x4的矩陣才能統一描述三維的變換)。

對於二維平移,如下圖所示,P點經過x和y方向的平移到P’點,可以得到:

 

 

x′=x+tx

y′=y+ty
由於引入了齊次座標,在描述二維座標的時候,使用(x,y,w)的方式(一般w=1),於是可以寫成下面矩陣的形式


按矩陣乘法展開,正好得到上面的表達式。也就是說平移矩陣是

 

如果平移值是(-tx,-ty)那麼很明顯平移矩陣式

 

 

我們可以把2中描述的旋轉矩陣也擴展到3x3的方式,變爲:

 


從平移和旋轉的矩陣可以看出,3x3矩陣的前2x2部分是和旋轉相關的,第三列與平移相關。有了上面的基礎之後,我們很容易得出二維中繞任意點旋轉的旋轉矩陣了,只需要把三個矩陣乘起來即可:
 

 

4. 三維基本旋轉

我們可以把一個旋轉轉換爲繞基本座標軸的旋轉,因此有必要討論一下繞三個座標值x、y、z的旋轉。
本文在討論過程中使用的是類似於OpenGL中定義的右手座標系,同時旋轉角度的正負也遵循右手座標系的約定。如下圖所示

 

 

4.1 繞X軸的旋轉

在三維場景中,當一個點P(x,y,z)繞x軸旋轉θ角得到點P’(x’,y’,z’)。由於是繞x軸進行的旋轉,因此x座標保持不變,y和z組成的yoz(o是座標原點)平面上進行的是一個二維的旋轉,可以參考上圖(y軸類似於二維旋轉中的x軸,z軸類似於二維旋轉中的y軸),於是有:
x′=x
y′=ycosθzsinθ
z′=ysinθ+zcosθ
寫成(4x4)矩陣的形式

 

4.2 繞Y軸旋轉

繞Y軸的旋轉和繞X軸的旋轉類似,Y座標保持不變,除Y軸之外,ZOX組成的平面進行一次二維的旋轉(Z軸類似於二維旋轉的X軸,X軸類似於二維旋轉中的Y軸,注意這裏是ZOX,而不是XOZ,觀察上圖中右手系的圖片可以很容易瞭解到這一點),同樣有:
x′=zsinθ+xcosθ
y′=y
z′=zcosθxsinθ
寫成(4x4)矩陣的形式

 

 

4.3 繞Z軸旋轉

與上面類似,繞Z軸旋轉,Z座標保持不變,xoy組成的平面內正好進行一次二維旋轉(和上面討論二維旋轉的情況完全一樣)

 

 

4.4 小結

上面描述了三維變換中繞單一軸旋轉的矩陣表達形式,繞三個軸旋轉的矩陣很類似,其中繞y軸旋轉的矩陣與繞x和z軸旋轉的矩陣略有點不同(主要是三個軸向順序和書寫矩陣的方式不一致導致的,繞三個不同座標旋轉軸以及其他二個座標軸組成平面的順序是: XYZ(繞x軸) YZX(繞y軸) ZXY(繞z軸),其中繞y軸旋轉,其他兩個軸是ZX,這和我們書寫矩陣按

 

 

 


的方式不一致,而導致看起來繞Y軸旋轉的矩陣似乎是和其他兩個矩陣不一致。如果我們顛倒寫法,將公式寫成

 


的方式,那麼這三個旋轉矩陣看起來在形式上就統一了,都是

 

 


這種表現形式了(左上角都是−sinθ

5. 繞任意軸的三維旋轉

繞任意軸的三維旋轉可以使用類似於繞任意點的二維旋轉一樣,將旋轉分解爲一些列基本的旋轉。繞任意軸旋轉如下圖所示:

 

 

P點繞向量u旋轉θ角,得到點Q,已知P點的座標和向量u,如何求Q點的座標。
我們可以把向量u進行一些旋轉,讓它與z軸重合,之後旋轉P到Q就作了一次繞Z軸的三維基本旋轉,之後我們再執行反向的旋轉,將向量u變回到它原來的方向,也就是說需要進行的操作如下:
1. 將旋轉軸u繞x軸旋轉至xoz平面
2. 將旋轉軸u繞y軸旋轉至於z軸重合
3. 繞z軸旋轉θ
4. 執行步驟2的逆過程
5. 執行步驟1的逆過程
原始的旋轉軸u如下圖所示:

 


第1、2、3步驟如下圖所示:

 

 

 

步驟1將向量u旋轉至xoz平面的操作是一個繞x軸的旋轉操作,步驟2將向量u旋轉到與z軸重合,第1、2步驟的示意圖如下:

 


作點P在yoz平面的投影點q,q的座標是(0, b, c),原點o與q點的連線oq和z軸的夾角就是u繞x軸旋轉的角度。通過這次旋轉使得u向量旋轉到xoz平面(圖中的or向量)【步驟1】
過r點作z軸的垂線,or與z軸的夾角爲β, 這個角度就是繞Y軸旋轉的角度,通過這次旋轉使得u向量旋轉到與z軸重合【步驟2】

步驟1中繞x軸旋轉的是一次基本的繞x軸的三維旋轉,按照之前的討論,旋轉矩陣是:

 

 


這裏的θ就是圖中所示的α角 (注意α角度是繞x旋轉的正的角度)
從圖中我們還可以得到:


於是旋轉矩陣(記作 Rx(α))爲:

 


在完成步驟1之後,向量u被變換到了r的位置,我們繼續步驟2的操作,繞y軸旋轉負的β角(注意:這裏的β是負的),經過這次變換之後向量u與z軸完全重合,由於這一步也是執行的一次繞Y軸的基本旋轉,旋轉矩陣(記作 Ry(−β))爲:

 

 


使用−β替換表達式中的θ,此外根據圖中描述,我們可以計算得到:

 

 


帶入上面的表達式,於是旋轉矩陣(記作 Ry(−β))爲:

 

在完成前面兩個步驟之後,u方向和z軸完全重合,因此執行旋轉θ角,執行的是一次繞z軸的基本三維旋轉(記作 R(θ),根據之前的討論,我們可以得到:


最後兩步驟是前面1和2的逆操作,也就是繞Y軸旋轉β 和繞X軸旋轉−α,這兩個矩陣分別記作 Ry(β) 和 Rx(−α),得到它們的方式很簡單,只需要將上面步驟1和步驟2中的角度修改成相反數即可,也就是:

 

 

最終得到 繞任意軸u旋轉的旋轉矩陣是【因爲使用的列向量,因此執行的是左乘(從右往左)】:

MR=Rx(−α)Ry(β)Rz(θ)Ry(−β)Rx(α)=

 

 



(注:式中的(u,v,w)對應上文中向量(a,b,c),公式我自己筆算過,爲了減少編輯公式的時間(使用LaTex編輯太繁瑣,因此找了一張公式的圖片貼在此處)

如果向量是經過單位化的(單位向量),那麼有a2+b2+c2=1,可以簡化上述的公式,得到:

 

6繞任意軸旋轉

繞任意軸旋轉的情況比較複雜,主要分爲兩種情況,一種是平行於座標軸的,一種是不平行於座標軸的,對於平行於座標軸的,我們首先將旋轉軸平移至與座標軸重合,然後進行旋轉,最後再平移回去。

  • 將旋轉軸平移至與座標軸重合,對應平移操作
  • 旋轉,對應操作
  • 步驟1的逆過程,對應操作

整個過程就是

對於不平行於座標軸的,可按如下方法處理。(該方法實際上涵蓋了上面的情況)

  1. 將旋轉軸平移至原點
  2. 將旋轉軸旋轉至YOZ平面
  3. 將旋轉軸旋轉至於Z軸重合
  4. 繞Z軸旋轉θ度
  5. 執行步驟3的逆過程
  6. 執行步驟2的逆過程
  7. 執行步驟1的逆過程

假設用v1(a1, b2, c2)和v2(a2, b2, c2)來表示旋轉軸,θ表示旋轉角度。爲了方便推導,暫時使用右手系並使用列向量,待得出矩陣後轉置一下即可,上面步驟對應的流程圖如下。

步驟1是一個平移操作,將v1v2平移至原點,對應的矩陣爲

步驟2是一個旋轉操作,將p(p = v2 -v1)旋轉至XOZ平面,步驟3也是一個旋轉操作,將p旋轉至與Z軸重合,這兩個操作對應的圖如下。

做點p在平面YOZ上的投影點q。再過q做Z軸垂線,則r是p繞X軸旋轉所得,且旋轉角度爲α,且

,   

於是旋轉矩陣爲

現在將r繞Y軸旋轉至與Z軸重合,旋轉的角度爲-beta(方向爲順時針),且

,    

於是得到旋轉矩陣爲

最後是繞Z軸旋轉,對應的矩陣如下

如果旋轉軸是過原點的,那麼第一步和最後一步的平移操作可以省略,也就是把中間五個矩陣連乘起來,再轉置一下,得到下面的繞任意軸旋轉的矩陣

對應的函數代碼如下

複製代碼

 1 void RotateArbitraryAxis(D3DXMATRIX* pOut, D3DXVECTOR3* axis, float theta)
 2 {
 3     D3DXVec3Normalize(axis, axis);
 4     float u = axis->x;
 5     float v = axis->y;
 6     float w = axis->z;
 7 
 8     pOut->m[0][0] = cosf(theta) + (u * u) * (1 - cosf(theta));
 9     pOut->m[0][1] = u * v * (1 - cosf(theta)) + w * sinf(theta);
10     pOut->m[0][2] = u * w * (1 - cosf(theta)) - v * sinf(theta);
11     pOut->m[0][3] = 0;
12 
13     pOut->m[1][0] = u * v * (1 - cosf(theta)) - w * sinf(theta);
14     pOut->m[1][1] = cosf(theta) + v * v * (1 - cosf(theta));
15     pOut->m[1][2] = w * v * (1 - cosf(theta)) + u * sinf(theta);
16     pOut->m[1][3] = 0;
17 
18     pOut->m[2][0] = u * w * (1 - cosf(theta)) + v * sinf(theta);
19     pOut->m[2][1] = v * w * (1 - cosf(theta)) - u * sinf(theta);
20     pOut->m[2][2] = cosf(theta) + w * w * (1 - cosf(theta));
21     pOut->m[2][3] = 0;
22 
23     pOut->m[3][0] = 0;
24     pOut->m[3][1] = 0;
25     pOut->m[3][2] = 0;
26     pOut->m[3][3] = 1;

複製代碼

如果旋轉軸是不過原點的,那麼第一步和最後一步就不能省略,將所有七個矩陣連乘起來,得到如下變換矩陣

對應如下這個超長的矩陣,在這裏(u, v, w) = (a2, b2, c2) - (a1, b1, c1),且是單位向量,a, b, c分別表示(a1, b1, c1)

將上面的過程寫成函數,該函數接受四個參數,第一個參數是一個輸出參數,用來保存得到的旋轉矩陣,第二個和第三個參數是旋轉軸的兩個端點,最後一個參數是旋轉角度θ,注意,在函數中我們已經將上面的矩陣轉置了,因爲上面是按照列向量計算的。

複製代碼

 1 void RotateArbitraryLine(D3DXMATRIX* pOut, D3DXVECTOR3* v1, D3DXVECTOR3* v2, float theta)
 2 {
 3     float a = v1->x;
 4     float b = v1->y;
 5     float c = v1->z;
 6 
 7     D3DXVECTOR3 p = *v2 - *v1;
 8     D3DXVec3Normalize(&p, &p);
 9     float u = p.x;
10     float v = p.y;
11     float w = p.z;
12 
13     float uu = u * u;
14     float uv = u * v;
15     float uw = u * w;
16     float vv = v * v;
17     float vw = v * w;
18     float ww = w * w;
19     float au = a * u;
20     float av = a * v;
21     float aw = a * w;
22     float bu = b * u;
23     float bv = b * v;
24     float bw = b * w;
25     float cu = c * u;
26     float cv = c * v;
27     float cw = c * w;
28 
29     float costheta = cosf(theta);
30     float sintheta = sinf(theta);
31 
32     pOut->m[0][0] = uu + (vv + ww) * costheta;
33     pOut->m[0][1] = uv * (1 - costheta) + w * sintheta;
34     pOut->m[0][2] = uw * (1 - costheta) - v * sintheta;
35     pOut->m[0][3] = 0;
36 
37     pOut->m[1][0] = uv * (1 - costheta) - w * sintheta;
38     pOut->m[1][1] = vv + (uu + ww) * costheta;
39     pOut->m[1][2] = vw * (1 - costheta) + u * sintheta;
40     pOut->m[1][3] = 0;
41 
42     pOut->m[2][0] = uw * (1 - costheta) + v * sintheta;
43     pOut->m[2][1] = vw * (1 - costheta) - u * sintheta;
44     pOut->m[2][2] = ww + (uu + vv) * costheta;
45     pOut->m[2][3] = 0;
46 
47     pOut->m[3][0] = (a * (vv + ww) - u * (bv + cw)) * (1 - costheta) + (bw - cv) * sintheta;
48     pOut->m[3][1] = (b * (uu + ww) - v * (au + cw)) * (1 - costheta) + (cu - aw) * sintheta;
49     pOut->m[3][2] = (c * (uu + vv) - w * (au + bv)) * (1 - costheta) + (av - bu) * sintheta;
50     pOut->m[3][3] = 1;
51 }

複製代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章