原文做者: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,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识