OpenGL之3D数学的向量和矩阵

向量

基本概念

  • 有大小又有方向的量称之为向量,与之相对应的是标量,标量是只有大小没有方向的量;
  • 在3D笛卡尔坐标系:基本上,一个顶点就是XYZ 坐标空间上的一个位置,而在空间中给定的⼀个位置恰恰是由⼀个单独的 XYZ 定义的,而这样的 XYZ 就是向量;
  • 长度为0的向量称之为零向量,零向量与所有向量平行;
  • 模为1的向量称之为单位向量,单位向量并不是唯一的,每个向量单位化以后都是单位向量;
  • 空间向量长度(向量的模)计算公式:

在这里插入图片描述

  • math3d库,有2个数据类型,能够表示一个三维或者四维向量:
    M3DVector3f可以表示⼀个三维向量(x,y,z), M3DVector4f则可以表示⼀个四维向量(x,y,z,w);(在典型情况下,w坐标设为1.0。x,y,z值通过除以w,来进行缩放。而除以1.0则本质上不改变x,y,z值 )
   // 三维向量/四维向量的声明
   typedef float M3DVector3f[3]; 
   typedef float M3DVector4f[4];
   // 声明⼀个三维向量 M3DVector3f:类型 vVector:变量名 
   M3DVector3f vVector;
   // 声明一个四维向量并初始化⼀个四维向量 
   M3DVector4f vVertex = {0,0,1,1};
   // 声明一个三分量顶点数组,例如生成⼀个三角形 
   M3DVector3f vVerts[] = {
                        -0.5f,0.0f,0.0f, 
                        0.5f,0.0f,0.0f, 
                        0.0f,0.5f,0.0f};

运算
一、向量的数乘
  • 向量的数乘表示向量跟一个实数相乘的乘积,结果还是一个向量。 比如实数a跟一个向量V=(Vx, Vy)相乘,结果为aV=(aVx, aVy);
  • 在坐标轴中,如果一个物体的位置为A(3, 3, 3),如果将其位置乘以2再赋值给这个物体,那么A的位置就会变成A’(6,6,6)。也就是说,将这个物体的位置从A点移动到了A’点.
二、向量的加法
  • 两个向量相加以后的结果还是一个向量,新向量的各个分量的值等于两个向量的对应分量的加值。计算公式为A + B = (A1 + B1, A2 + B2 … , An + Bn)(例如两个二维向量相加,向量C等于向量A(3, 4)加向量B(5, 6),即:C = A + B = (3+5, 4+6));
  • 遵循“平行四边形法则”和“三角形法则”;
    在这里插入图片描述
三、向量的减法
  • 两个向量相减所得还是一个向量,这个向量的各个分量的值为旧向量的各个分量的差值。公式为:A - B = (A1 + B1, A2 + B2 … , An + Bn)。(例如两个二维向量相加,向量C等于向量A(3, 4)加向量B(5, 6),即:C = A - B = (3-5, 4-6))
  • 遵循“三角形法则”:减法的所得的最终向量,其方向是由减数向量的终点指向被减数向量的终点。
    在这里插入图片描述
四、向量的点乘(dot product)
  • 向量的点积表示为A · B = A1B1 + A1B1 + … + AnBn;
  • 几何意义:点乘表示了两个向量的接近程度,即夹角越小,两个向量越靠近,在等于0的时候平行,等于1的时候垂直。因此向量的点乘在游戏中经常用来判断两个向量的接近程度。比如两个移动的物体是否会撞上,需要不需要避开之类的AI计算。同时,已知两个向量,可以计算两个向量的夹角。从而进行旋转操作等
    A·B > 0,则夹角在[0, 90);
    A·B = 0,则夹角等于 90,两个向量垂直
    A·B < 0,则夹角在(90,180)
  • 点乘只能在2个向量之间进行;
  • 2个(三维向量)单元向量之间进行点乘运算将得到⼀个标量(不是三维向量,是⼀个标量),它表示两个向量之间的夹⻆;
    在这里插入图片描述
  • 性质:
    A·B = B·A 点乘满足交换律;
    A·(B + C) = A·B + A·C 点乘满足分配律;
    (aA)·B = a(A·B) 一个实数不影响两个向量的点乘
  • math3d 库中提供了关于点乘的API:
// m3dDotProduct3 函数获得2个向量之间的点乘结果,即余弦值cosα
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);

// m3dGetAngleBetweenVector3 即可获取2个向量之间夹角的弧度值 α = arccos余弦
float m3dGetAngleBetweenVector3(const M3DVector3f u,const M3DVector3f v);

五、向量的叉乘(cross product)
  • 两个叉乘所得出的新向量,垂直与这两个向量,并穿过这两个向量的交叉点。新向量的方向则取决于使用的坐标系,Unity使用的是左手坐标系,OpenGL使用的是右手坐标系。左手坐标系中,左手掌心向外,食指向上,中指向前,大拇指的方向就是x轴,食指指向的是y轴,中指指向的是z轴,将两个向量对准坐标轴,剩余的一个方向就是新向量的方向。
    在这里插入图片描述
  • 叉乘的模:|A X B| = |A||B|sinα,其中α是两个向量的夹角;
  • math3d 库中提供了关于叉乘的API
    // m3dCrossProduct3 函数获得2个向量之间的叉乘结果得到⼀个新的向量
    void m3dCrossProduct3(M3DVector3f result,const M3DVector3f  u ,const
    M3DVector3f v);

矩阵(Matrix)

概念
  • 在其他编程标准中, 许多矩阵库定义一个矩阵时,使用⼆维数组;OpenGL的约定里,更多倾向使用一维数组;
  • 矩阵声明
    // 三维矩阵/四维矩阵的声明
    typedef float M3DMatrix33f[9];
    typedef float M3DMatrix44f[16];
  • OpenGL 使⽤的是 Column-Major(以列为主)矩阵排序的约定;⾏优先矩阵(左)和列优先矩阵(右)如下图所示:
    ⾏优先矩阵排序与列优先矩阵排序

  • ⾏优先矩阵与列优先矩阵互为转置矩阵;

  • 矩阵的这16个值表示空间中⼀个特定的位置; 这4列中,每⼀列都是有4个元素组成的向量;(第一列:X轴方向,第二列:Y轴方向,第三列:Z轴方向,第四列:交换位置);

  • 如果将⼀个对象所有的顶点向量乘以矩阵,就能让整个对象变换到空间中给定的位置和⽅向;

  • 列向量进行了特别的标注:矩阵的最后⼀行都为0,只有最后⼀个元素为1。

单元矩阵
  • 主对角线上数据都是1,其余元素都是0,即为单元矩阵。其初始化方式有:
// 单元矩阵初始化⽅式1
GLFloat m[] = {
           1,0,0,0,  // X Column
           0,1,0,0,  // Y Column
           0,0,1,0,  // Z Column
           0,0,0,1}; // Translation
// 单元矩阵初始化方式2
M3DMatrix44f m = {
           1,0,0,0, // X Column
           0,1,0,0, // Y Column
           0,0,1,0, // Z Column
           0,0,0,1};// Translation
// 单元矩阵初始化⽅式3
void m3dLoadIdentity44f(M3DMatrix44f m);

  • 将⼀个向量✖ 单元矩阵,就相当于⼀个向量✖1, 不会发⽣任何改变;

在这里插入图片描述

  • 在线性代数中:
    变换后顶点向量 = V_local * M_model * M_view * M_pro
    变换后顶点向量 = 顶点 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 投影矩阵
    在这里插入图片描述

  • 在OpenGL 的维度,如下列公式:
    变换顶点向量 = M_pro * M_view * M_model * V_local
    变换顶点向量 = 投影矩阵 ✖ 视图变换矩阵 ✖ 模型矩阵 ✖ 顶点 在这里插入图片描述

  • 矩阵左乘的代码如下:
    从栈顶获取栈顶矩阵复制到 mTemp;将栈顶矩阵 mTemp 左乘 mMatrix;将结果放回栈顶空间⾥;

        inline void MultMatrix(const M3DMatrix44f mMatrix) {
            // 矩阵左乘 
			M3DMatrix44f mTemp;
			m3dCopyMatrix44(mTemp, pStack[stackPointer]);
			m3dMatrixMultiply44(pStack[stackPointer], mTemp, mMatrix);
			}
            
        inline void MultMatrix(GLFrame& frame) {
            M3DMatrix44f m;
            frame.GetMatrix(m);
            MultMatrix(m);
            }
            
  • ChangeSize函数中,得到投影矩阵,将投影矩阵压入投影矩阵堆栈栈顶,并与模型视图矩阵栈顶相乘,将结果覆盖栈顶,即 投影矩阵 * 单元矩阵 = 投影矩阵;
    RenderScene函数中,将栈顶矩阵copy一份,然后将观察者矩阵与模型视图矩阵堆栈栈顶相乘,其结果覆盖栈顶矩阵,即投影矩阵 * 视图矩阵 = 视图投影矩阵;
    得到模型矩阵,将模型矩阵与栈顶矩阵相乘,其结果覆盖栈顶矩阵,即栈顶 = 模型视图投影矩阵;
    // 投影矩阵 projectionMatrix
    modelViewMatrix.PushMatrix();
    M3DMatrix44f pm;
    projectionMatrix.GetMatrix(pm);
    modelViewMatrix.MultMatrix(pm);
    
    // 观察者矩阵 viewMatrix
    modelViewMatrix.PushMatrix();
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.MultMatrix(mCamera);
    
    // 模型变换矩阵 modelMatrix
    M3DMatrix44f mObjectFrame;
    viewFrame.GetCameraMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);
    

矩阵的运算

矩阵的点乘
  • A*B是以数学运算中矩阵相乘的方式实现的,即Mat矩阵A和B被当做纯粹的矩阵做乘法运算,这就要求A的列数等于B的行数时,才能定义两个矩阵相乘。如A是m×n矩阵,B是n×p矩阵,它们的乘积AB是一个m×p矩阵。
    在这里插入图片描述
  • C中第i行第j列所在元素C(i,j)等于A中第i行所有元素跟B中第j列所有元素一一对应的乘积之和。对于A、B都是2行2列矩阵的情况:
    在这里插入图片描述
矩阵的dot(A.dot(B))
  • A.dot(B)操作相当于数学向量运算中的点乘,也叫向量的内积、数量积;

  • 对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作,点乘的结果是一个标量;

  • 对于向量a和向量b:
    在这里插入图片描述
    a和b的点积公式为:
    在这里插入图片描述
    要求向量a和向量b的行列数相同。

  • Mat矩阵的dot方法扩展了一维向量的点乘操作,把整个Mat矩阵扩展成一个行(列)向量,之后执行向量的点乘运算,仍然要求参与dot运算的两个Mat矩阵的行列数完全一致;

  • dot方法声明中显示返回值是double,所以A.dot(B)结果是一个double类型数据,不是Mat矩阵,不能把A.dot(B)结果赋值给Mat矩阵。

  • dot操作不对参与运算的矩阵A、B的数据类型做要求,CV_8UC1、CV_32FC1等,可以是任何Opencv定义的类型;

  • 若参与dot运算的两个Mat矩阵是多通道的,则计算结果是所有通道单独计算各自.dot之后,再累计的和,结果仍是一个double类型数据。

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/107382005