原文作者:aircraft
原文鏈接:https://www.cnblogs.com/DOMLX/p/12166896.html
為什么引入齊次坐標的變換矩陣可以表示平移呢? - Yu Mao的回答 - 知乎 https://www.zhihu.com/question/26655998/answer/43847213為什么引入齊次坐標的變換矩陣可以表示平移呢? - Yu Mao的回答 - 知乎 https://www.zhihu.com/question/26655998/answer/43847213
在opengl里面涉及到了許多的計算機圖形學的知識,當然也涉及到了許多矩陣運算類的知識,基本都是在線性代數里面學過的。
就比如opengl里面的平移函數glTranslatef(x,y,z),
其作用就是將你繪點坐標的原點在當前原點的基礎上平移一個(x,y,z)向量。
就是讓當前點與一個平移矩陣相乘來求得最終矩陣,來進行平移
那么就要先從矩陣乘法開始
一.矩陣乘法:
若要把矩陣與矩陣相乘,我們要計算行與列的"點積"……這是什么意思?我們來看個例子:
求 第一行 和 第一列 的答案:
"點積" 是把 對稱的元素相乘,然后把結果加起來:
(1, 2, 3) • (7, 9, 11) = 1×7 + 2×9 + 3×11 = 58
我們把第一個元素相配(1 和 7),然后相乘。第二個元素(2 和 9) 和第三個元素(3 和 11)也一樣,然后把結果加起來。
想多看一個例子?這是第一行與第二列:
(1, 2, 3) • (8, 10, 12) = 1×8 + 2×10 + 3×12 = 64
第二行 和 第一列也同樣做:
(4, 5, 6) • (7, 9, 11) = 4×7 + 5×9 + 6×11 = 139
第二行 和 第二列:
(4, 5, 6) • (8, 10, 12) = 4×8 + 5×10 + 6×12 = 154
我們得到:
二.矩陣的線性變換以及glTranslatef(x,y,z)函數詳解
對於opengl的glTranslatef(x,y,z)函數就是依靠線性變換來進行平移操作的,
其作用就是將你繪點坐標的原點在當前原點的基礎上平移一個(x,y,z)向量。
就是讓當前點與一個平移矩陣相乘來求得最終矩陣,來進行平移
仿射變換:為了表示仿射變換,需要使用齊次坐標,即用三向量 (x,y, 1) 表示二向量,對於高維來說也是如此。按照這種方法,就可以用矩陣乘法表示變換。規定:x' =x+tx;y' =y+ty。在矩陣中增加一列與一行,除右下角的元素為 1 外其它部分填充為 0,通過這種方法,所有的線性變換都可以轉換為仿射變換。通過這種方法,使用與前面一樣的矩陣乘積可以將各種變換無縫地集成到一起。當使用仿射變換時,其次坐標向量w從來不變,這樣可以把它當作為 1。
在矩陣的初等變換中,矩陣的左乘代表着行變換,TA=B。
矩陣的右乘相當於列變換, AT=C。
當三維坐標發生旋轉、平移時,就需要考慮到矩陣是左乘還是右乘。
設有旋轉矩陣R,平移矩陣T, 坐標矩陣A。
-若是繞着靜態的世界坐標系旋轉,有RA,即左乘旋轉矩陣
- 若是繞着動態的自身坐標系旋轉,有A’R’, 即右乘旋轉矩陣
這個意思就是 我們先glTranslatef(x,y,z)移動后旋轉的話,那么就是物體先移動 然后繞着自身旋轉也是繞自身坐標系旋轉。先旋轉 在移動 那么就是繞世界坐標系旋轉了
好接下來介紹一下矩陣平移
舉個二維點移動的例子:
設某點向x方向移動 dx, y方向移動 dy ,[x,y]為變換前坐標, [X,Y]為變換后坐標。
則 X = x+dx; Y = y+dy;
然后其中的矩陣運算過程是:我們先將(x,y)點坐標轉化為其次坐標(x,y,1)這是在計算機圖形學中經常用到的(不知道為什么要轉換為齊次坐標后面會講)
那么就可以得到:
[ 1 0 0 ]
[X, Y, 1] = [x, y, 1][ 0 1 0 ] ;
[ dx dy 1 ]
這個時候X = x+dx; Y = y + dy; 是不是就實現了坐標的移動???
hhhh 沒看懂的話把上面的矩陣乘法在看一次 動動手,寫兩筆就出來的東西不要一直想
那么在舉個三維點移動的例子:
同樣的 先(x,y,z)點坐標轉化為其次坐標(x,y,z,1) 然后變換后的(X,Y,Z)不就是等於(x,y,z,1)乘以下圖的變換矩陣嗎???
動動手用矩陣乘法得出:X = x+dx; Y = y + dy;Z = z + dz; 不就移動好了嗎????
需要特別注意的是在opengl中的矩陣采用列優先存儲,矩陣表示如下
那么剛才為什么要轉化齊次坐標??
我們可以看這篇博客:其次坐標的理解
我摘抄主要的部分在下面了:
“齊次坐標表示是計算機圖形學的重要手段之一,它既能夠用來明確區分向量和點,同時也更易用於進行仿射(線性)幾何變換
對於一個向量v以及基oabc,可以找到一組坐標(v1,v2,v3),使得v = v1 a + v2 b + v3 c (1)
而對於一個點p,則可以找到一組坐標(p1,p2,p3),使得 p – o = p1 a + p2 b + p3 c (2),
從上面對向量和點的表達,我們可以看出為了在坐標系中表示一個點(如p),我們把點的位置看作是對這個基的原點o所進行的一個位移,即一個向量——p – o(有的書中把這樣的向量叫做位置向量——起始於坐標原點的特殊向量),我們在表達這個向量的同時用等價的方式表達出了點p:p = o + p1 a + p2 b + p3 c (3)
(1)(3)是坐標系下表達一個向量和點的不同表達方式。這里可以看出,雖然都是用代數分量的形式表達向量和點,但表達一個點比一個向量需要額外的信息。如果我寫出一個代數分量表達(1, 4, 7),誰知道它是個向量還是個點!
我們現在把(1)(3)寫成矩陣的形式:v = (v1 v2 v3 0) X (a b c o)
p = (p1 p2 p3 1) X (a b c o),這里(a,b,c,o)是坐標基矩陣,右邊的列向量分別是向量v和點p在基下的坐標。這樣,向量和點在同一個基下就有了不同的表達:3D向量的第4個代數分量是0,而3D點的第4個代數分量是1。像這種這種用4個代數分量表示3D幾何概念的方式是一種齊次坐標表示。
這樣,上面的(1, 4, 7)如果寫成(1,4,7,0),它就是個向量;如果是(1,4,7,1),它就是個點。下面是如何在普通坐標(Ordinary Coordinate)和齊次坐標(Homogeneous Coordinate)之間進行轉換:
(1)從普通坐標轉換成齊次坐標時
如果(x,y,z)是個點,則變為(x,y,z,1);
如果(x,y,z)是個向量,則變為(x,y,z,0)
(2)從齊次坐標轉換成普通坐標時
如果是(x,y,z,1),則知道它是個點,變成(x,y,z);
如果是(x,y,z,0),則知道它是個向量,仍然變成(x,y,z)
以上是通過齊次坐標來區分向量和點的方式。從中可以思考得知,對於平移T、旋轉R、縮放S這3個最常見的仿射變換,平移變換只對於點才有意義,因為普通向量沒有位置概念,只有大小和方向.
而旋轉和縮放對於向量和點都有意義,你可以用類似上面齊次表示來檢測。從中可以看出,齊次坐標用於仿射變換非常方便。
此外,對於一個普通坐標的點P=(Px, Py, Pz),有對應的一族齊次坐標(wPx, wPy, wPz, w),其中w不等於零。比如,P(1, 4, 7)的齊次坐標有(1, 4, 7, 1)、(2, 8, 14, 2)、(-0.1, -0.4, -0.7, -0.1)等等。因此,如果把一個點從普通坐標變成齊次坐標,給x,y,z乘上同一個非零數w,然后增加第4個分量w;如果把一個齊次坐標轉換成普通坐標,把前三個坐標同時除以第4個坐標,然后去掉第4個分量。
為什么引入齊次坐標可以表示平移?
首先我們用一個矢量來表示空間中一個點:
如果我們要將其平移,平移的矢量為:
那么正常的做法就是:
如果不引入齊次坐標,單純采用3X3矩陣乘法來實現平移
你想做的就是找到一個矩陣,使得
然后你就會發現你永遠也找不到這樣的矩陣
所以我們需要新引入一個維度,原來
那么我們可以找到一個4X4的矩陣來實現平移
現在,就有:
三.矩陣實現旋轉以及glRotatef(angle,x,y,z)函數詳解
函數原型:glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)
該函數用來設置opengl中繪制實體的自轉方式,即物體如何旋轉
參數說明:
angle:旋轉的角度,單位為度;
x,y,z表示繞着那個軸旋轉,如果取值都為0,則表示默認的繞x軸逆時針旋轉。
x,y為0,z不為0時,表示繞z軸旋轉;x,z為0,y不為0時,表示繞y軸旋轉;y,z為0,x不為0,表示繞x軸旋轉。
旋轉的逆順時針是通過x,y,z值得正負來確定的:取值為正時,表示逆時針旋轉;取值為負時,表示順時針旋轉。
例:glRotatef(30,0,-1,0);
表示繞y軸順時針方向旋轉30度。
關於逆時針與順時針,可用右手定則:
即手握住某個坐標軸,大拇指指向某軸的正方向,其余四個手指的彎曲方向即為繞某軸旋轉的逆時針方向;反之為順時針方向。
好,看完了函數 我們接下來理解一下矩陣的旋轉 當然也包括圖形的旋轉。。。。
二維極坐標系 情況如下:
二維情況的旋轉c++代碼 :
//將空間點繞X軸旋轉 //輸入參數 y z為空間點原始y z坐標 //thetax為空間點繞X軸旋轉多少度,角度制范圍在-180到180 //outy outz為旋轉后的結果坐標 void codeRotateByX(double y, double z, double thetax, double& outy, double& outz) { double y1 = y;//將變量拷貝一次,保證&y == &y這種情況下也能計算正確 double z1 = z; double rx = thetax * CV_PI / 180; outy = cos(rx) * y1 - sin(rx) * z1; outz = cos(rx) * z1 + sin(rx) * y1; }
上面是二維的情況,那么我直接想象這是三維的 z軸指向屏幕外,可以上面不就是在三維空間 繞Z軸旋轉的情況嗎 也就是在XOY面移動 就只改變了x,y坐標
於是可得下面的轉換方程
(式一)
寫成矩陣的形式就是(不理解看 第一步的矩陣乘法知識 在不會敲爆你的狗頭!!!)
求得旋轉矩陣為
由於這里使用齊次坐標,所以還需加上一維,最終變成如下形式
這樣就得到了三維空間中繞Z軸旋轉的 旋轉矩陣式
對於繞X軸旋轉的情況,我們只需將式一中的x用y替換,y用z替換,z用x替換即可。替換后得到
(式二)
對應的旋轉矩陣為
繞X軸旋轉矩陣
對於繞Y軸旋轉的情況,只需對式二做一次同樣的替換即可,的到的變換方程為
對應的變換矩陣為
繞Y軸旋轉矩陣
四.如何繞任意軸旋轉以及怎么用glRotatef(angle,x,y,z)繞任意軸旋轉
繞通過原點的任意旋轉軸的旋轉是:
glRotatef(theta, vx, vy, vx)
其中vx,vy,vz用與定義通過坐標原點的旋轉軸的方向,theta用於指定旋轉角度。
如果旋轉軸不在原點,可以先用glTranslatef(tx, ty, tz)將旋轉軸平移到原點,調用上述函數旋轉完成后再平移回原來的地方:glTranslatef(-tx, -ty, -tz)
至於為什么呢 已經有大佬的博客寫過整個推導過程 直接推薦給你們:
繞任意軸旋轉
博客地址是:https://www.cnblogs.com/graphics/archive/2012/08/10/2627458.html
當然為了大家方便看 我直接摘抄主要過來如下:
繞任意軸旋轉的情況比較復雜,主要分為兩種情況,一種是平行於坐標軸的,一種是不平行於坐標軸的,對於平行於坐標軸的,我們首先將旋轉軸平移至與坐標軸重合,然后進行旋轉,最后再平移回去。
- 將旋轉軸平移至與坐標軸重合,對應平移操作
- 旋轉,對應操作
- 步驟1的逆過程,對應操作
整個過程就是
對於不平行於坐標軸的,可按如下方法處理。(該方法實際上涵蓋了上面的情況)
- 將旋轉軸平移至原點
- 將旋轉軸旋轉至YOZ平面
- 將旋轉軸旋轉至於Z軸重合
- 繞Z軸旋轉θ度
- 執行步驟3的逆過程
- 執行步驟2的逆過程
- 執行步驟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軸旋轉,對應的矩陣如下
如果旋轉軸是過原點的,那么第一步和最后一步的平移操作可以省略,也就是把中間五個矩陣連乘起來,再轉置一下,得到下面的繞任意軸旋轉的矩陣(這里要注意自己的opengl中是列向量還是行向量 需不要轉置這個問題 列向量就不需要轉置這一步了)
即
void RotateArbitraryAxis(D3DXMATRIX* pOut, D3DXVECTOR3* axis, float theta) { D3DXVec3Normalize(axis, axis); float u = axis->x; float v = axis->y; float w = axis->z; pOut->m[0][0] = cosf(theta) + (u * u) * (1 - cosf(theta)); pOut->m[0][1] = u * v * (1 - cosf(theta)) + w * sinf(theta); pOut->m[0][2] = u * w * (1 - cosf(theta)) - v * sinf(theta); pOut->m[0][3] = 0; pOut->m[1][0] = u * v * (1 - cosf(theta)) - w * sinf(theta); pOut->m[1][1] = cosf(theta) + v * v * (1 - cosf(theta)); pOut->m[1][2] = w * v * (1 - cosf(theta)) + u * sinf(theta); pOut->m[1][3] = 0; pOut->m[2][0] = u * w * (1 - cosf(theta)) + v * sinf(theta); pOut->m[2][1] = v * w * (1 - cosf(theta)) - u * sinf(theta); pOut->m[2][2] = cosf(theta) + w * w * (1 - cosf(theta)); pOut->m[2][3] = 0; pOut->m[3][0] = 0; pOut->m[3][1] = 0; pOut->m[3][2] = 0; pOut->m[3][3] = 1;
如果旋轉軸是不過原點的,那么第一步和最后一步就不能省略,將所有七個矩陣連乘起來,得到如下變換矩陣
對應如下這個超長的矩陣,在這里(u, v, w) = (a2, b2, c2) - (a1, b1, c1),且是單位向量,a, b, c分別表示(a1, b1, c1)
void RotateArbitraryLine(D3DXMATRIX* pOut, D3DXVECTOR3* v1, D3DXVECTOR3* v2, float theta) { float a = v1->x; float b = v1->y; float c = v1->z; D3DXVECTOR3 p = *v2 - *v1; D3DXVec3Normalize(&p, &p); float u = p.x; float v = p.y; float w = p.z; float uu = u * u; float uv = u * v; float uw = u * w; float vv = v * v; float vw = v * w; float ww = w * w; float au = a * u; float av = a * v; float aw = a * w; float bu = b * u; float bv = b * v; float bw = b * w; float cu = c * u; float cv = c * v; float cw = c * w; float costheta = cosf(theta); float sintheta = sinf(theta); pOut->m[0][0] = uu + (vv + ww) * costheta; pOut->m[0][1] = uv * (1 - costheta) + w * sintheta; pOut->m[0][2] = uw * (1 - costheta) - v * sintheta; pOut->m[0][3] = 0; pOut->m[1][0] = uv * (1 - costheta) - w * sintheta; pOut->m[1][1] = vv + (uu + ww) * costheta; pOut->m[1][2] = vw * (1 - costheta) + u * sintheta; pOut->m[1][3] = 0; pOut->m[2][0] = uw * (1 - costheta) + v * sintheta; pOut->m[2][1] = vw * (1 - costheta) - u * sintheta; pOut->m[2][2] = ww + (uu + vv) * costheta; pOut->m[2][3] = 0; pOut->m[3][0] = (a * (vv + ww) - u * (bv + cw)) * (1 - costheta) + (bw - cv) * sintheta; pOut->m[3][1] = (b * (uu + ww) - v * (au + cw)) * (1 - costheta) + (cu - aw) * sintheta; pOut->m[3][2] = (c * (uu + vv) - w * (au + bv)) * (1 - costheta) + (av - bu) * sintheta; pOut->m[3][3] = 1; }
上面代碼如果不太會用的話可以用這篇博客的 我覺得寫得還不錯:
https://www.cnblogs.com/singlex/p/3DPointRotate.html
代碼為:
#include <iostream> #include <math.h> using namespace std; #define CV_PI 3.1415926 //定義返回結構體 struct Point3f { Point3f(double _x, double _y, double _z) { x = _x; y = _y; z = _z; } double x; double y; double z; }; //點繞任意向量旋轉,右手系 //輸入參數old_x,old_y,old_z為旋轉前空間點的坐標 //vx,vy,vz為旋轉軸向量 //theta為旋轉角度角度制,范圍在-180到180 //返回值為旋轉后坐標點 Point3f RotateByVector(double old_x, double old_y, double old_z, double vx, double vy, double vz, double theta) { double r = theta * CV_PI / 180; double c = cos(r); double s = sin(r); double new_x = (vx*vx*(1 - c) + c) * old_x + (vx*vy*(1 - c) - vz * s) * old_y + (vx*vz*(1 - c) + vy * s) * old_z; double new_y = (vy*vx*(1 - c) + vz * s) * old_x + (vy*vy*(1 - c) + c) * old_y + (vy*vz*(1 - c) - vx * s) * old_z; double new_z = (vx*vz*(1 - c) - vy * s) * old_x + (vy*vz*(1 - c) + vx * s) * old_y + (vz*vz*(1 - c) + c) * old_z; return Point3f(new_x, new_y, new_z); } int main() { Point3f A = RotateByVector(0, 2, 0, 1, 0, 0, 270); cout << A.y << endl; system("pause"); return 0; }
五.結合OpenGL來進行
- 假如此時我們要讓自己的opengl圖形原點中心旋轉:
那么直接調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z) 即可 不斷旋轉的話 就直接把angle在其他循環函數中不斷加一對360取余數就行了
- 假如此時我們要讓自己的opengl圖形在(x,y,z)位置繞自身中心旋轉:
那么直接調用glTranslatef(x,y,z)先將物體移動,然后在調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)
- 假如此時我們要讓自己的opengl圖形在(x,y,z)位置繞世界坐標系旋轉:
那么就是先旋轉在移動,先調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z),然后在移動到位置 glTranslatef(x,y,z)這也是先移動在旋轉 和先旋轉在移動的區別,一個繞自身中心旋轉,一個繞世界坐標系旋轉
此時的 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)函數中的 x,y,z 就是一個軸向量了 繞哪個軸旋轉的意思 x = 1,y = 0; z = 0;那么就是繞X軸旋轉 其他同理 即手握住某個坐標軸,大拇指指向某軸的正方向,其余四個手指的彎曲方向即為繞某軸旋轉的逆時針方向;反之為順時針方向。
- 假如此時我們要在opengl中繞任意軸旋轉,那么跟上面一樣,也是先旋轉 在移動 此時 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)函數 中的x,y,z就是你要繞軸旋轉的旋轉軸,然后你要在哪里去繞這個軸旋轉就在旋轉完畢之后就調用glTranslatef(x,y,z)移動到那個位置上就完事了 此時不在原點的話就先glTranslatef(-x,-y,-z)移動回原點 在旋轉 然后在移動回去glTranslatef(x,y,z)
參考博客:https://blog.csdn.net/zbq_tt5/article/details/90046527
https://www.cnblogs.com/csyisong/archive/2008/12/09/1351372.html
https://www.zhihu.com/question/26655998/answer/43847213
https://blog.csdn.net/yangmeng900816/article/details/46816007
https://www.cnblogs.com/graphics/archive/2012/08/08/2609005.html
若有興趣交流分享技術,可關注本人公眾號,里面會不定期的分享各種編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,后端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識