1.向量
1.1 向量与标量
标量是对我们平时所用数字的技术称谓,像是1,4,100等。标量只有大小。
向量除了有大小之外,还有方向。在3D笛卡尔坐标系中,XYZ可以表示一个点的位置,还可以用XYZ表示一个向量。可以这样理解,一个点在原点位置,沿着XYZ方向平移了|XYZ|大小的位置,移动到了XYZ点。

1.2 向量的模
向量的模也就是向量的大小,计算公式如下:
1.3 单位向量
如果一个向量的长度为1,我们就称这个向量为单位向量。
如果一个向量的长度不是1,而我们把它的长度缩放为1,成为单位向量,这个过程叫做单位化向量。将XYZ坐标,分别除以向量的模,即可得到向量所在方向上的单位向量。
1.4 math3d 库
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
};
1.5 向量点乘
向量可以进行加法、减法运算,还可以进行一种有意思的运算——点乘。
两个(三维向量)单位向量之间的点乘运算将得到一个标量,它表示两个向量之间的夹角。
要进行这种运算,这两个向量不必须是单位向量,返回的结果将在-1.0到+1.0之间。实际上返回结果就是两个向量之间的余弦值。

可以看出向量的点乘满足交换律。
math3d 库中提供了了关于点乘的API
//1.m3dDotProduct3 函数获得2个向量之间的点乘结果;
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);
//2.m3dGetAngleBetweenVector3 即可获取2个向量量之间夹⻆角的弧度值;
float m3dGetAngleBetweenVector3(const M3DVector3f u,const M3DVector3f v);
1.6 向量点乘
向量之间的叉乘(cross product)也是在业务开发中非常有用的一个计算方式。2个向量之间叉乘可以得到另外一个向量,新的向量会与原来的2个向量定义的平面垂直。要进行叉乘,2两向量都不必为单位向量。

//1.m3dCrossProduct3 函数获得2个向量量之间的叉乘结果得到⼀一个新的向量量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f u ,const M3DVector3f v);
2. 矩阵
2.1 矩阵构造
如果在空间总有一个点,由x, y, z坐标定义,将它围绕任意点沿任意方向旋转一定角度后,我们需要知道现在这个点的位置,就要用到矩阵。因为新的x坐标不仅与原来的x坐标和其他旋转参数有关,还和y,z坐标值有关。

在我们是用进行3D程序设计时,我们将使用的几乎全部是两种维度的矩阵,即3x3和4x4矩阵。在math3d库中也有这两种维度的矩阵数据类型。
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
在许多编程语言中,使用二维数组定义一个矩阵。OpenGL约定中拒绝了这个传统并使用一个一维数组。这样做的原因是:OpenGL使用一种叫做Column-Major(以列为主)矩阵排序的约定。

下图展示了一个4x4矩阵是如何在3D空间中表示一个位置和方向的。

奇妙的是,如果有一个包含不同坐标系的位置和方向的4x4矩阵,然后用一个表示原来坐标系的向量(表示为一个列矩阵)乘以这个矩阵,得到的结果是一个转换到新坐标系下的新向量。这就意味着,如果将一个物体的所有顶点向量乘以这个矩阵,就能让整个对象变换到空间中给定的位置和方向。
2.2 单位矩阵
单位矩阵中除了对角线上的一组元素之外,其他元素都为0。将一个向量乘以一个单位矩阵,就相当于用这个向量乘以1,不会发生任何改变。
在OpenGL中我们可以这样生成一个单位矩阵:
//单元矩阵初始化⽅方式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
}
或者使用math3d的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
}
在math3d库中还有一个快捷函数m3dLoadIdentity44f,这个函数初始化一个空的单位矩阵。
void m3dLoadIdentity44f(M3DMatrix44f m);
2.3 矩阵乘法
在某些情况下,两个矩阵能够相乘。一个rXn矩阵A能够乘以一个nXc矩阵B,结果是一个rXc矩阵,记作AB。
例如,设A为4x2的矩阵,B为2x5矩阵,那么结果AB为4x5矩阵。

矩阵乘法计算规则如下: 记rXn矩阵A与nXc矩阵B的积rXc矩阵AB为C.
下面以2x2矩阵乘法为例:
3. 变换
| 视图变换 | 指定观察者或照相机的位置 |
|---|---|
| 模型变换 | 在场景中移动物体 |
| 模型视图 | 描述视图/模型变换的二元性 |
| 投影 | 改变视景体大小和设置它的投影方式 |
| 视口 | 伪变化,只是对窗口上的最终输出进行缩放 |
3.1 视觉坐标
视觉坐标是相对于观察者而言的。下图从两个不同视点显示了视觉坐标系。在a坐标系中,视觉坐标系是以场景的观察者的角度(也就是垂直于显示器的方向)。在b坐标系中,视觉坐标系稍稍进行了旋转,这样就可以更好的观察z轴位置关系了。

3.2 视图变换
视图变换是应⽤到场景中的第一种变换, 它⽤来确定场景中的有利位置,在默认情况下, 透视投影中位于原点(0,0,0),并沿着 z 轴负⽅向进行观察 (向显示器内部”看过去”).
当观察者点位于原点(0,0,0) 时,就像在透视投影中一样.
视图变换将观察者放在你希望的任何位置.并允许在任何⽅向上观察场景, 确定视图变换就像在场景中放置观察者并让它指向某一个⽅向;
从⼤局上考虑, 在应用任何其他模型变换之前, 必须先应⽤视图变换. 这样做是因为, 对于视觉坐标系⽽言, 视图变换移动了当前的工作的坐标系; 后续的变化都会基于新调整的坐标系进行.
3.3 模型变换
模型变换用于操纵模型和其中的特定对象。这些变换将模型移动到需要的位置,然后再对它进行旋转和缩放。
平移:

m3dTranslationMatrix44函数,来使用变换矩阵
void m3dTranslationMatrix44(M3DMatrix44f m, floata x, float y, float z);
旋转:

m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);
缩放:

void m3dScaleMatrix44(M3DMatrix44f m, floata xScale, float yScale, float zScale);
有时候,继续要将对象平移到指定位置,又需要将它旋转一定角度,我们可以将这两种变换加在一起也就是利用两个矩阵相乘的结果。
math3d库函数m3dMatrixMultiply44用来将两个矩阵相乘并返回运算结果。
void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);
3.4 模型视图
实际上,视图变换和模型变换按照它们内部效果和对场景的最终外观来说是一样的。将对象向后移动和将参考坐标系向前移动在视觉上没有区别,其效果是相同的。模型视图是指,这两种变换在交换管线中进行组合,成为一个单独矩阵,即模型视图矩阵。
3.5 投影变换
投影变换将在模型视图变换之后应用到顶点之上。更具体来说,投影变换指定一个完成的场景(所有模型变换都已完成)是如何投影到屏幕上的最终图像。
3.6 视口变换
当前面的变换都完成后,就得到了一个场景的二维投影,它将被映射到屏幕上某处的窗口上。这种到物理窗口标的映射是我们最后要做的变换,成为视口变换。图形硬件会为我们处理好,我们无需操心。
4.矩阵堆栈
在使用OpenGL进行绘制时,我们需要进行大量的矩阵相乘,相乘结果的存储等工作。math3d会为我们提供的一个实用类GLMatrixStack矩阵堆栈来完成帮助完成这些工作。
这个类的构造函数允许指定堆栈的最大深度,默认为64。这个矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。
//初始化
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
我们可以通过调用,在顶部载入这个单位矩阵:
void GLMatrixStack::LoadIdentity(void);
或者可以在堆栈的顶部载入任何矩阵:
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
此外,我们可以用一个矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后将保存在堆栈的顶部。
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
最后只要用GetMatrix函数就可以获得矩阵堆栈顶部的值。
//直接获取顶部的值
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
//获取顶部堆栈的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
4.1 压栈出栈
压栈:
//将当前矩阵压⼊堆栈(栈顶矩阵copy 一份到栈顶)
void GLMatrixStack::PushMatrix(void);
//将M3DMatrix44f 矩阵对象压入当前矩阵堆栈
void PushMatrix(const M3DMatrix44f mMatrix);
//将GLFame 对象压入矩阵对象
void PushMatrix(GLFame &frame);
出栈:
//出栈(出栈指的是移除顶部的矩阵对象)
void GLMatrixStack::PopMatrix(void);
借用下图来说明一下矩阵堆栈的使用流程:

4.2 仿射变换
GLMatrixStack类也内建了对旋转、平移、缩放的支持。相应函数如下:
//Rotate 函数angle参数是传递的度数,⽽而不不是弧度
void MatrixStack::Rotate(GLfloat angle,GLfloat x,GLfloat y,GLfloat z);
void MatrixStack::Translate(GLfloat x,GLfloat y,GLfloat z);
void MatrixStack::Scale(GLfloat x,GLfloat y,GLfloat z);
这些函数将变换施加到矩阵堆栈的栈顶元素。