向量
首先我们了解一下标量和向量的区别:
标量:只有大小的量,如,1、3、5;
向量:有方向的标量;
在 3D 笛卡尔坐标系, 基本上. 一个顶点 就是XYZ 坐标空间上的⼀个位置. ⽽在空间中给定的
⼀个位置 恰恰是由一个单独的 XYZ 定义的. 而这这样的 XYZ 就是向量;
单位向量
向量⻓度为1. 我们称为长度为1的向量为单位向量.如、在X轴上的向量量 (1,0,0)。
向量长度(向量的模)计算公式:
如果⼀个向量不是单位向量, 而我们把它缩放到 1. 这个过程叫做标准化. 将⼀个向量进行标准化就是将它的缩为1; 也叫做单位化向量,即非零向量除以向量的模,如下图所示;
OPenGl定义向量[ math3d 库]
在OpenGL中提供了一个数学库类 #include<math3d.h>
我们会两个比较常见的类:
- M3DVector3f:表示三维向量(x,y,z)-(f)表示float类型的意思。
同时也可以用来设置三维数组也是没有问题的
typedef float M3DVector3f[3]; // Vector of three floats (x, y, z) 默认是一个一维数组
- M3DVector4f:表示四维向量(x,y,z,w)w表示缩放因子w默认为1.0-(f)同上
我们也可以用M3DVector4f来表示颜色值的设置
M3DVector4f color[] = {1.0f, 1.0f, 0.0f, 1.0f};
向量点乘
向量可以进⾏加法,减法计算. 但是向量里有⼀个在开发中使⽤价值非常高的操作,叫做
“点乘(dot product)” .点乘只能发生在2个向量之间进行;
2个(三维向量)单元向量之间进⾏点乘运算将得到⼀个标量(不是三维向量,是⼀个标量).
它表示两个向量之间的夹⻆;
前提条件: 2个向量必须为单位向量;
动作: 2个三维向量之间进⾏点乘;
结果: 返回⼀个[-1,1]范围的值. 这个值其实就是夹角的cos值(余弦值);
那么在代码中如何实现点乘呢?2个向量相乘
//1.m3dDotProduct3 函数获得2个向量量之间的点乘结果;
m3dDotProduct3(const M3DVector3f u, const M3DVector3f v)
根据两个向量获取角度值
float m3dGetAngleBetweenVectors3(const M3DVector3f u, const M3DVector3f v)
向量叉乘
向量之间的叉乘(cross product) 也是在业务开发里⾮常有⽤的⼀个计算方式; 2个向量之间叉乘就可以得到另外⼀个向量,新的向量会与原来2个向量定义的平面垂直. 同时进行叉乘,不必为单位向量;
前提: 2个普通向量
动作: 向量与向量叉乘
结果: 向量(垂直于原来2个向量定义的平面的向量)
math3d 库中提供了了关于叉乘的API
//1.m3dCrossProduct3 函数获得2个向量量之间的叉乘结果得到⼀个新的向量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f u ,const
M3DVector3f v);
注意:result是存放新返回的向量,与点乘不同的是,叉乘要按照顺序来设置,不然得出的值会是相反值,例如:v1乘v2是向上的向量,v2乘v1是向下的向量,所以需要注意顺序,不能乱乘。
矩阵
假设, 在空间有一个点.使用 xyz 描述它的位置. 此时让其围绕任意位置旋转一定⻆度
后. 我们需要知道这个点的新的位置. 此时需要通过矩阵进行计算;
为什么?
因为新的位置的x不单纯与原来的x还和旋转的参数有关. 甚⾄至于y和z坐标有关;
矩阵只有⼀行或者⼀列都是合理的. 只有⼀⾏或者⼀列数字可以称为向量. 也可以称为矩阵;
OPenGl中三维矩阵/四维矩阵的声明
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
在其他编程标准中, 许多矩阵库定义⼀个矩阵时,使用二维数组;
OpenGL的约定里,更多倾向使⽤⼀维数组; 这样做的原因是: OpenGL 使用的是 Column-Major(以列列为主)矩阵排序的约定,如下图:
- 行优先矩阵:一行一行读取
- 列优先矩阵:一列一列读取
两者的关系为:行优先矩阵经过转置 即可的到列优先矩阵
由上我们知道OPenGl中的矩阵是列优先矩阵,那么其中的值分别代表什么呢?如下图
这16个值表示空间中一个特定的位置; 这4列中,每一列都是有4个元素组成的向量;
如果将⼀个对象所有的顶点向量乘以这个矩阵,就能让整个对象变换到空间中给定的位置和⽅方向;
那么⼀个4*4矩阵是如何在3D空间中表示⼀个位置和⽅向的呢?
列向量进⾏了特别的标注:矩阵的最后一⾏都为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);
向量 X 单元矩阵 = 向量 X 1,不会发生任何变化;
向量与单元矩阵相乘的前提是:向量的列数 == 单元矩阵的行数;如下就不能相乘
矩阵的点乘
矩阵可以进行点乘的前提:两个矩阵的行列数相等
矩阵A · 矩阵B = 矩阵C
-规则: 矩阵A的第一个元素与矩阵B的第一个元素的乘积 = 矩阵C的第一个元素
矩阵的叉乘
矩阵可以进行叉乘的前提:第一个矩阵的列数 = 第二个矩阵的行数
矩阵A X 矩阵B = 矩阵C
规则:矩阵A第一行与矩阵B第一列对应元素乘积的综合 = 矩阵C的第一个元素
OpenGL中的矩阵相乘
- 线性代数⻆度
OpenGL中的矩阵规定是以行为主,所以顶点以列向量的方式表示
在线性代数学的维度,为了便于书写. 所以坐标计算. 都是从左往右顺序,进⾏计算. 如下
列公式:
变换后顶点向量 = V_local * M_model * M_view * M_pro
变换后顶点向量 = 顶点 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 投影矩阵
这种方式叫做左乘
,如下图以线性代数的方式进行MVP矩阵的计算
- OpenGL⻆度
OpenGL中的矩阵规定是以列为主,所以顶点以列向量的方式表示
在OpenGL 的维度. 如下列列公式:
变换顶点向量 = M_pro * M_view * M_model * V_local
变换顶点向量 = 投影矩阵 ✖ 视图变换矩阵 ✖ 模型矩阵 ✖ 顶点
这种方式叫做右乘
,如下图以OPenGL的方式进行MVP矩阵的计算
OPenGL中MVP矩阵相乘顺序
OpenGL矩阵堆栈中矩阵相乘源码分析
上图矩阵相乘主要有三步:
- 从栈顶获取栈顶矩阵 复制到 mTemp;
- 将栈顶矩阵 mTemp 左乘 mMatrix ;
- 将结果放回栈顶空间⾥;
在我们绘制金字塔、六边形的案例中,我们用到了MVP(模型视图投影矩阵),那么其中各矩阵的相乘规则是什么样的呢?
1、ChangeSize函数中,得到投影矩阵,将投影矩阵压入投影矩阵堆栈栈顶,并与模型视图矩阵栈顶相乘,将结果覆盖栈顶,即 投影矩阵 * 单元矩阵 = 投影矩阵
2、RenderScene函数中,将栈顶矩阵copy一份,然后将观察者矩阵与模型视图矩阵堆栈栈顶相乘,其结果覆盖栈顶矩阵,即投影矩阵 * 视图矩阵 = 视图投影矩阵
3、得到模型矩阵,将模型矩阵与栈顶矩阵相乘,其结果覆盖栈顶矩阵,即 模型矩阵 * 视图投影矩阵 = 模型视图投影矩阵
上述代码,矩阵堆栈的变化过程如下:
由此可知,在实际的代码中,mvp矩阵的计算顺序是pvm,最后再将顶点矩阵与mvp矩阵相乘,得到物体变换后的顶点和位置。