OpenGL--3D数学相关(向量和矩阵)

3,190 阅读12分钟

相关目录:

  1. OpenGL--图形API简介
  2. OpenGL--相关名词解释
  3. OpenGL--环境配置
  4. OpenGL--案例1-绘制一个三角形
  5. OpenGL--案例2-绘制正方形
  6. OpenGL--图像撕裂
  7. OpenGL--3D数学相关(向量和矩阵)
  8. OpenGL--矩阵变换和矩阵堆栈

向量

什么是向量 ?

先来了解一下什么是向量, 其实向量就是一串数字, 它存在的意义是为了和 标量 (物理学中的称呼, 也就是我们说的 "数量" ) 区分开来。一般来讲, 向量在 坐标系中同时具备 大小方向 (具备 "知道坐标", "可知长度", "可知方向"), 就像我们平时所说的 速度 就是向量, 然后 距离 就是标量了。

2D 向量

3D 向量

上图分别展示了在 2D 向量 和 3D 向量, 向量是没有位置的, 只有方向和长度大小。

向量的表示

上面了解了关于向量的概念, 那么接下来来看一下向量的相关操作, 先来看下向量的表示

向量的表示

如上图, 现在知道 A, B 两个点的坐标 (二维坐标系中), 那么如何去表示 向量a 和 向量b 呢。

a 向量的表示:

B - A = (x1-x2, y1-y2);

b 向量的表示:

A - B = (x2-x1, y2-y1);

可以看出, 想要要表示一个向量, 就必须知道向量上的两个点的坐标, 然后再根据向量的方向去决定做减法的顺序。(上面的例子是在 二维坐标系下的, 如果是 3D 向量的话就是增加了一维 分量的加减。)

向量的运算和特殊向量

1. 零向量

对于任意向量x,都有x+y=x,则x被称为零向量。例如,3D零向量为[0 0 0]。零向量非常特殊,因为它是唯一大小为零的向量,并且唯一一个没有方向的向量。

2. 负向量

对于向量x,如果x+(-x)=0,则-x就是负向量。向量为负则表示将得到一个和原向量大小相等, 方向相反的向量。

3. 向量的模

向量的模 指向量的大小或者说长度。

在线性代数中,向量的模通常用在向量两边各加两条竖线的方式表示,如||v||,表示向量v的模。向量的模的计算公式如下:

  

img

对于2D,3D向量的如下

  img

4. 标准化向量

先说 单位向量 , 单位向量就是长度为 1 的向量 (有时候也叫做法线)。所谓的标准化向量就是将一个不是 单位向量 的向量缩放到 1, 这个过程叫做 标准化 (也叫 单位化向量)。

对于任意非零向量v,都能计算出一个和v方向相同的单位向量n,这个过程被称作为向量的“标准化”,要标准化向量,将向量除以它的大小(模)即可。

  img

5. 标量与向量的运算

标量与向量之间虽然 不能够相加减, 但是可以乘除, 向量乘以标量或者除以标量, 相当于以因子来缩放向量的长度

  img

对于2D,3D向量的如下

  img

6. 向量的加法和减法

如果两个向量的维数相同, 那么他们能够相加减, 运算结果的向量的维数和原向量相同。加法等于两个向量分量的相加, 减法相当于加上一个负向量。

img

向量的加法和减法得出了三角形法则, 即 向量的首位相连就会得到加法的加过。

向量的点乘 和 叉乘

点乘

img

应用到2D,3D中为

a·b = axbx + ayby

a·b = axbx + ayby+ azbz 

注: x,y,z 为角标, 是 "·", 不是 " * " 哦

标量可以和向量相乘, 向量也可以和向量相乘, 这种叫做 点乘 (也叫内积) 。另外, 标量与向量相乘不可以写 "·", 向量与向量相乘必须写 "·", 向量的点乘优先级高于加减。向量点乘后的结果是标量

接下来通过一张图来一起看看向量的点积计算:

点积计算 向量的点积计算就是 **将两个向量的分量进行相乘得到的结果再相加** , 得到的结果是一个标量值。**这个标量值就等于两个向量的模长乘积再乘以两个向量的夹角的余弦值** , 由此可以知道, 通过向量的点积运算是可以得到两个向量的夹角的。

叉乘

两个向量的叉乘得到的是一个 新的向量 , 并且这个向量垂直于原来的两个向量。向量的叉乘只可以运用在 3D向量中

img

cross(a, b) = |a| * |b| * sin(α)

叉乘运算的顺序

进行叉乘运算的两个向量是存在顺序关系的, 顺序不同, 得到的结果也是不同的。至于结果的判断可以用 右手定责 去判断, 所谓的右手定则就是 让右手除去大拇指的四根手指伸向向量叉乘的方向 (比如 b x a, 就从 b 向量握向 a 向量) 握拳, 此时大拇指指向的就是叉乘得到的垂直向量的方向。可以看下面一张图:

OpenGL 中的向量

如何定义向量

OpenGL 中在 math3d 中给我们提供了一套关于向量的 API。

M3DVector3f 可以表示一个三维向量 (x, y, z)

M3Dvector4f 可以表示一个思维向量 (x, y, z, w)

在典型情况下, w 坐标设为 1.0。x, y, z 值通过除以 w 来进行缩放。

代码参考:

// 三维向量/思维向量声明
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
};

点乘

math3d 库中提供了关于点乘的 API:

// 1.m3dDotProduct3 函数获得2个向量之间的点乘结果;
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);

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

叉乘

math3d 库中提供了关于叉乘的API:

// 1.m3dCrossProduct3 函数获得2个向量之间的叉乘结果得到一个新的向量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f  u ,const M3DVector3f v);

矩阵

定义

矩阵的记法

在线性代数中, 矩阵就是以行和列形式组成的矩形数字块(向量是标量的数组,矩阵是向量的数组)。

img

矩阵我们通常使用大中括号来表示, 也可以使用竖线的方式来表示, 通常用大写字母来表示矩阵。当使用矩阵的分量时, 我们用下标法来表示矩阵的分量, 注意的是矩阵的分量是从1开始, 而不是0。

方阵

行数和列数相同的矩阵称为方阵。方阵的概念非常重要, 通常在3D中使用的矩阵就是2x2, 3x3, 4x4方阵。

方阵的 对角线元素 就是方阵中分量行号和列号相同的元素, 例如3x3矩阵中的m11, m22, m33这三个分量就是对角线元素, 其他的元素则是 非对角线元素

img

  

对角矩阵

在方阵中, 如果非对角元素都为0, 则该方阵称为对角矩阵。例如

img

单元矩阵

在对角矩阵中, 如果对角线元素都为1, 则该对角矩阵为单位矩阵。单位矩阵是一种特殊的对角矩阵, 单位矩阵乘以任意矩阵得到都是原来的矩阵。例如, 3D单位矩阵如下

  img

转置矩阵

所谓的 转置矩阵 就是将矩阵的行元素变为矩阵的列元素, 将矩阵的列元素变为矩阵的行元素, 矩阵M的转置记做MT。

  img

  • 行向量的矩阵转置为列向量, 列向量的矩阵转置为行向量。

  • 对于任意对角矩阵D, 都有转置矩阵还等于它本身, 单元矩阵同样。

  • 对于任意矩阵D, 转置两次等于其本身。

矩阵的运算

1.标量和矩阵的乘法

标量k和矩阵M相乘会得到一个和原矩阵维数相同的矩阵N, 矩阵N的每个元素等于矩阵M的每个元素与标量k相乘, 公式如下。

  img

2.矩阵与矩阵的乘法

矩阵的乘法定义为一个 r x n 矩阵A 与一个 n x c 矩阵B 相乘, 则得到一个 r x c 矩阵AB。 例如一个 4 x 2 矩阵A 与一个 2 x 5 矩阵B 相乘, 得到的是一个 4x5 矩阵AB。

img

对于新得到的 矩阵AB 记做 C, 则 矩阵C 的每个元素 Cij 等于 矩阵A 第i行向量与 矩阵B 第j列向量的点乘结果。示例如下

  

img

矩阵C 的元素 c24 就等于 矩阵A 的第二行向量与 矩阵B 的第4行向量的点乘的结果。

3. 向量与矩阵的乘法 (重要)

因为向量也属于矩阵, 所以也必须要满足矩阵与矩阵的乘法规则, 所以对于行向量而言, 行向量要左乘矩阵, 对于列向量而言, 列向量要右乘矩阵。行向量和列向量与一个相同的矩阵相乘的时候会得到不同的结果, 因此我们要注意这是行向量和列向量的区别之一, 在进行矩阵运算时特别小心。

示例如下, 对于行向量和列向量而言, 得到的结果是不一样的。

  

img

v : 行向量

h: 列向量

在DX3中使用的是行向量,但是在OpenGL中使用的是列向量, 因此在运算前要注意行向量和列向量的转置才能保证不会出现计算的问题。

OpenGL 中的矩阵 (Matrix)

矩阵的定义及初始化

1. 定义

// 三维矩阵/思维矩阵的声明
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];

这里在其他的变成标准中, 许多矩阵的定义是使用的 二维数组。但是在 OpenGL 的约定里, 更多倾向的是使用 一位数组。原因是 OpenGL 使用的是 Column-Major (以列为主) 排序的约定 (也就是上面提到的 转置矩阵)。

另外, 中的矩阵都是4x4的,每一列都是由4个元素组成的向量, 具体如下图所示:

矩阵的最后一行都为 0, 只有最后一个元素为 1。

2. 初始化

OpenGL中单元矩阵有3种初始化方法:

  • 通过 GLFloat 类创建一个一位数组
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 
}
  • 通过 M3DMatrix44f 创建一个单元矩阵
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
}
  • 通过方法 m3dLoadIdentity44f 创建单元矩阵
void m3dLoadIdentity44f(M3DMatrix44f m);

OpenGL 中的矩阵变换

1. 变换顺序

OpenGL采用的是相机模型,就是把视图变换操作类比为使用照相机拍摄照片的过程,具体步骤如下(这里和红宝书有一些改变):

  1. 将准备拍摄的对象移动到场景中指定位置。(模型变换,Model Transform)
  2. 将相机移动到准备拍摄的位置,将它对准某个方向。(视图变换,View Transform)
  3. 设置相机的焦距,或者调整缩放比例。(投影变换,Projection Transform)
  4. 拍摄照片并变换到需要的图片大小。(这个是视口变换,我们这里不做讨论)

这 4 个步骤中的前 3 个如果用矩阵去实现在 线性代数OpenGL 下的情况是不一样的, 下面来一一了解一下:

2. 线性代数角度

在线性代数数学的维度, 为了便于书写. 所以坐标计算. 都是从左往右顺序,进行计算. 如下列公式 :

从数学角度理解mvp矩阵的计算, 由于顶点是行向量, 要满足矩阵相乘的规定条件必须将mvp矩阵放在右边, 属于右乘。 (ps: 上面的向量与矩阵的乘法中也有提到)

  • 变换后顶点向量 = V_local * M_model * M_view * M_pro
  • 变换后顶点向量 = 顶点 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 投影矩阵

3. OpenGL角度

OpenGL中的矩阵规定是以列为主,所以顶点以列向量的方式表示。

从OpenGL角度理解mvp矩阵的计算,由于顶点是列向量,如果项进行矩阵规则,就需要满足矩阵相乘的条件,需要将mvp矩阵的顺序颠倒为pvm,且放在列向量的左边,属于左乘

  • 变换顶点向量 = M_pro * M_view * M_model * V_local

  • 变换顶点向量 = 投影矩阵 ✖ 视图变换矩阵 ✖ 模型矩阵 ✖ 顶点

左乘:设A为baimp的矩阵du,B为pn的矩阵,那么称m*n的矩阵C为zhi矩阵A与B的乘积,记作C=AB,称为A左乘以B。

右乘:设daoA为mp的矩阵,B为pn的矩阵,那么称m*n的矩阵C为矩阵A与B的乘积,记作C=AB,称为B右乘以A。

img

注:矩阵乘法一般不满足交换律

最后

以上就是本次的全部内容, 主要涉及了 向量矩阵 的相关内容, 另外关于矩阵的变换可能不是很全面, 后续再补充。如果有不明白或者不正确的地方欢迎小伙伴们指出, 感谢大家~

和谐学习, 不急不躁~