關於opengl中的三維矩陣平移代碼,矩陣旋轉代碼,推導過程理解 OpenGL計算機圖形學的一些必要矩陣運算知識 glTranslatef(x,y,z)glRotatef(angle,x,y,z)函數

 

 

原文做者: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裏面的平移函數glTranslatefx,y,zc++

其做用就是將你繪點座標的原點在當前原點的基礎上平移一個(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)向量。

就是讓當前點與一個平移矩陣相乘來求得最終矩陣,來進行平移

 

任意線性變換均可以用矩陣表示爲易於計算的一致形式,而且多個變換也能夠很容易地經過矩陣的相乘鏈接在一塊兒
線性變換不是惟一能夠用矩陣表示的變換。R維的仿射變換與透視投影均可以用齊次座標表示爲RP維(即n+1 維的真實投影空間)的線性變換。所以,在三維計算機圖形學中大量使用着 4x4 的矩陣變換
  在opengl中就常常用到仿射變換的形式。

  仿射變換:爲了表示仿射變換,須要使用齊次座標,即用三向量 (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),使得 po = 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、縮放S3個最多見的仿射變換,平移變換隻對於點纔有意義,由於普通向量沒有位置概念,只有大小和方向.

 

而旋轉和縮放對於向量和點都有意義,你能夠用相似上面齊次表示來檢測。從中能夠看出,齊次座標用於仿射變換很是方便。

 

此外,對於一個普通座標的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的逆過程,對應操做

 

整個過程就是

 

 

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

 

  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軸旋轉,對應的矩陣以下

 

 

若是旋轉軸是過原點的,那麼第一步和最後一步的平移操做能夠省略,也就是把中間五個矩陣連乘起來,再轉置一下,獲得下面的繞任意軸旋轉的矩陣(這裏要注意本身的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)位置繞自身中心旋轉:

  那麼直接調用glTranslatefx,y,z先將物體移動,而後在調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)

 

 

  • 假如此時咱們要讓本身的opengl圖形在(x,y,z)位置繞世界座標系旋轉:

  那麼就是先旋轉在移動,先調用 glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z),而後在移動到位置 glTranslatefx,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就是你要繞軸旋轉的旋轉軸,而後你要在哪裏去繞這個軸旋轉就在旋轉完畢以後就調用glTranslatefx,y,z移動到那個位置上就完事了  此時不在原點的話就先glTranslatef(-x,-y,-z)移動回原點 在旋轉 而後在移動回去glTranslatefx,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,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識

相關文章
相關標籤/搜索