原文做者:aircrafthtml
原文連接:http://www.javashuo.com/article/p-dfpmmmfu-g.html前端
爲何引入齊次座標的變換矩陣能夠表示平移呢? - Yu Mao的回答 - 知乎 https://www.zhihu.com/question/26655998/answer/43847213爲何引入齊次座標的變換矩陣能夠表示平移呢? - Yu Mao的回答 - 知乎 https://www.zhihu.com/question/26655998/answer/43847213python
在opengl裏面涉及到了許多的計算機圖形學的知識,固然也涉及到了許多矩陣運算類的知識,基本都是在線性代數裏面學過的。ios
就好比opengl裏面的平移函數glTranslatef(x,y,z),c++
其做用就是將你繪點座標的原點在當前原點的基礎上平移一個(x,y,z)向量。web
就是讓當前點與一個平移矩陣相乘來求得最終矩陣,來進行平移編程
那麼就要先從矩陣乘法開始後端
一.矩陣乘法:ruby
若要把矩陣與矩陣相乘,咱們要計算行與列的"點積"……這是什麼意思?咱們來看個例子:app
求 第一行 和 第一列 的答案:
"點積" 是把 對稱的元素相乘,而後把結果加起來:
(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替換便可。替換後獲得
(式二)
對應的旋轉矩陣爲
對於繞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
固然爲了你們方便看 我直接摘抄主要過來以下:
繞任意軸旋轉的狀況比較複雜,主要分爲兩種狀況,一種是平行於座標軸的,一種是不平行於座標軸的,對於平行於座標軸的,咱們首先將旋轉軸平移至與座標軸重合,而後進行旋轉,最後再平移回去。
整個過程就是
對於不平行於座標軸的,可按以下方法處理。(該方法實際上涵蓋了上面的狀況)
假設用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來進行
那麼直接調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z) 便可 不斷旋轉的話 就直接把angle在其餘循環函數中不斷加一對360取餘數就好了
那麼直接調用glTranslatef(x,y,z)先將物體移動,而後在調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat 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軸旋轉 其餘同理 即手握住某個座標軸,大拇指指向某軸的正方向,其他四個手指的彎曲方向即爲繞某軸旋轉的逆時針方向;反之爲順時針方向。
參考博客: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,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識