欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-20 14:50:40
...

向量

基本概念

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

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

  • 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));
  • 遵循“平行四边形法则”和“三角形法则”;
    OpenGL之3D数学的向量和矩阵
三、向量的减法
  • 两个向量相减所得还是一个向量,这个向量的各个分量的值为旧向量的各个分量的差值。公式为:A - B = (A1 + B1, A2 + B2 … , An + Bn)。(例如两个二维向量相加,向量C等于向量A(3, 4)加向量B(5, 6),即:C = A - B = (3-5, 4-6))
  • 遵循“三角形法则”:减法的所得的最终向量,其方向是由减数向量的终点指向被减数向量的终点。
    OpenGL之3D数学的向量和矩阵
四、向量的点乘(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个(三维向量)单元向量之间进行点乘运算将得到⼀个标量(不是三维向量,是⼀个标量),它表示两个向量之间的夹⻆;
    OpenGL之3D数学的向量和矩阵
  • 性质:
    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轴,将两个向量对准坐标轴,剩余的一个方向就是新向量的方向。
    OpenGL之3D数学的向量和矩阵
  • 叉乘的模:|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(以列为主)矩阵排序的约定;⾏优先矩阵(左)和列优先矩阵(右)如下图所示:
    OpenGL之3D数学的向量和矩阵

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

  • 矩阵的这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, 不会发⽣任何改变;

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

  • 在线性代数中:
    变换后顶点向量 = V_local * M_model * M_view * M_pro
    变换后顶点向量 = 顶点 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 投影矩阵
    OpenGL之3D数学的向量和矩阵

  • 在OpenGL 的维度,如下列公式:
    变换顶点向量 = M_pro * M_view * M_model * V_local
    变换顶点向量 = 投影矩阵 ✖ 视图变换矩阵 ✖ 模型矩阵 ✖ 顶点 OpenGL之3D数学的向量和矩阵

  • 矩阵左乘的代码如下:
    从栈顶获取栈顶矩阵复制到 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矩阵。
    OpenGL之3D数学的向量和矩阵
  • C中第i行第j列所在元素C(i,j)等于A中第i行所有元素跟B中第j列所有元素一一对应的乘积之和。对于A、B都是2行2列矩阵的情况:
    OpenGL之3D数学的向量和矩阵
矩阵的dot(A.dot(B))
  • A.dot(B)操作相当于数学向量运算中的点乘,也叫向量的内积、数量积;

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

  • 对于向量a和向量b:
    OpenGL之3D数学的向量和矩阵
    a和b的点积公式为:
    OpenGL之3D数学的向量和矩阵
    要求向量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类型数据。