向量
向量的定义
向量和标量的区别:
- 标量只有大小
- 向量不仅有大小、还有方向
单位向量
单位向量是长度为1的向量。
向量⻓度(向量的模)计算公式:
单位化向量
如果向量不是单位向量,而我们把它缩放到1,那么这个过程就叫做标准化,将一个向量进行标准化就是把它的模缩为1,也叫单位化向量。
OpenGL如何定义向量
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
};向量点乘(dot product)
m3dDotProduct3:获得2个向量量之间的点乘结果,即余弦值 = cosαm3dGetAngleBetweenVector3:获取2个向量之间夹⻆的角度,即α = arccos(余弦值)向量叉乘(cross product)
OpenGL中针对向量叉乘也提供了对应的API
m3dCrossProduct3对两个向量进行叉乘 并返回运算得到的结果向量。 矩阵
矩阵分类
- 行优先矩阵:一行一行读取
- 列优先矩阵:一列一列读取
- 两者的关系为:行优先矩阵经过转置 即可的到列优先矩阵
单元矩阵(Matrix)
要点总结:
- 对角线上元素都是1,其余元素都是0的矩阵就是单元矩阵
- 向量 X 单元矩阵 = 向量 X 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
}
- 单元矩阵初始化⽅方式②
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
}
- 单元矩阵初始化⽅方式③
void m3dLoadIdentity44f(M3DMatrix44f m);OpenGL中的矩阵
许多矩阵库都定义了一个二维矩阵作为C语言中的二维数组
typedef float M3DMatrix33f[9]; typedef float M3DMatrix44f[16];OpenGL的约定⾥,更多倾向使⽤⼀维数组; 这样做的原因是: OpenGL 使⽤的是 Column-Major(以列为主)矩阵排序的约定;
OpenGL中的矩阵都是4*4的矩阵,如图:
- 列向量进行了特殊的标注,表示这是以列为主的矩阵,主要体现为矩阵的最后一行都是0,只有最后一个元素为1
理解OpenGL中的矩阵相乘
线性代数⻆度
- 变换后顶点向量 = V_local * M_model * M_view * M_pro
- 变换后顶点向量 = 顶点 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 投影矩阵
上述mvp矩阵计算:顶点是行向量,要满足矩阵相乘的条件(叉乘),mvp矩阵必须放在右边,属于右乘。如图:
OpenGL角度
OpenGL中的矩阵规定是以列为主,所以顶点以列向量的方式表示- 变换后顶点向量 = M_pro * M_model * M_view * V_local
- 变换后顶点向量 = 投影矩阵 ✖ 模型矩阵 ✖ 观察矩阵 ✖ 顶点
OpenGL中实现矩阵左乘:
inline void MultMatrix(const M3DMatrix44f mMatrix) {
M3DMatrix44f mTemp;
m3dCopyMatrix44(mTemp, pStack[stackPointer]);
m3dMatrixMultiply44(pStack[stackPointer], mTemp, mMatrix);
}分析得知矩阵相乘主要有三步:- 从栈顶获取栈顶矩阵 复制到mTemp
- 将栈顶矩阵mTemp 左乘mMatrix
- 将结果放回栈顶空间⾥
回顾之前Demo里的矩阵变化代码:
可以得知在实际的代码中,mvp矩阵的计算顺序是pvm,最后再将mvp矩阵与顶点矩阵相乘,得到物体变换后的顶点和位置:
- ChangeSize函数中,得到投影矩阵,将投影矩阵压入投影矩阵堆栈栈顶,并与模型视图矩阵栈顶相乘,将结果覆盖栈顶,即 投影矩阵 * 单元矩阵 = 投影矩阵
- RenderScene函数中,将栈顶矩阵copy一份,然后将观察者矩阵与模型视图矩阵堆栈栈顶相乘,其结果覆盖栈顶矩阵,即投影矩阵 * 视图矩阵 = 视图投影矩阵
- 得到模型矩阵,将模型矩阵与栈顶矩阵相乘,其结果覆盖栈顶矩阵,即 栈顶 = 模型视图投影矩阵
OpenGL中的矩阵变换
基础变换
OpenGL中基础变化术语概述:
视觉坐标
视图变换
- 视图变换是应用到场景中的第一种变换。它用来确定场景中的有利位置。在默认情况下,透视投影中的观察点位于原点(0,0,0),并沿着z 轴的负方向进行观察(向显示器内部“看去”)。观察点相对于视觉坐标系进行移动,来提供特定的有利位置。当观察点位于原点(0,0,0)时,就像在透视投影中一样,绘制在z 坐标为正的位置的对象则位于观察者背后。
- 从大局上考虑,在应用任何其他模型变换之前,必须先应用视图变换。这样做是因为,对于视觉坐标系而言,视图变换移动了当前的工作坐标系。所有后续变换随后都会基于新调整的坐标系进行。然后,在实际开始考虑如何进行这些变换时,就会更容易地看到这些变换是如何实现的了。 (引用《OpenGL 超级宝典 第5版》96页)
模型变换
常见的模型变换:
- 平移:将矩阵m 在x方向平移x0个单位,y方向平移y0个单位,z方向平移z0个单位后结果存放在m矩阵中。
void m3dTranslationMatrix44(M3DMatrix44f m, floata x0, float y0, float z0);
复制代码- 旋转:围绕某一轴旋转一定弧度
m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);
复制代码- 缩放 :将矩阵m 在x方向缩放xScale倍,y方向缩放yScale倍,z方向缩放zScale倍后结果存放在m矩阵中。
void m3dScaleMatrix44(M3DMatrix44f m, floata xScale, float yScale, float zScale);
复制代码
- 综合变化: product = a ✖ b
void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f 下图中:a-平移 b-旋转 c-缩放
注意点:
- 当缩放的x/y/z参数传值-1时,可以实现物体围绕某一个轴的翻转
用一个正方形逐渐变换的过程举例说明
如下图(a)正方形先围绕z 轴进行旋转,然后再沿着变换后得到的新x 轴进行平移。
在图(b)中,同样的正方形首先沿着x 轴进行平移,然后再围绕z 轴进行旋转。
可以得知两者最终外观上有所不同是因为每次变换都与最后的变换结果相关,变换的顺序是不能交换的,交换后的矩阵相乘结果是不一致的。
模型视图的二元性
如上图将对象向后移动和将参考坐标系向前移动在视觉上没有区别。
投影变换
投影方式主要有两种
- 在正投影(或者说平行投影)中,所有多边形都是精确地按照指定的相对大小来在屏幕上绘制的,屏幕上物体与实物的比例是 = 1:1的。
- 透视投影的特点就是透视缩短(foreshortening),这种特性使得远处的物体看起来比近处同样大小的物体更小一些(远小近大)。
- 透视投影的优势在于,我们不必弄清楚线在哪里相交或远处的物体到底有多少。我们需要做的仅仅是指定适用模型视图变换的场景,然后应用透视投影矩阵。
一个简单的例子:
矩阵堆栈
矩阵堆栈的使用:
GLMatrixStack。这个类的构造函数允许指定堆栈的最大深度,默认的堆栈深度为64。这个矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。//类型
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
//在堆栈顶部载⼊⼀个单元矩阵
void GLMatrixStack::LoadIdentity(void);
//在堆栈顶部载⼊任何矩阵
//参数:4*4矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
//矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
//获取矩阵堆栈顶部的值 GetMatrix 函数
//为了适应GLShaderMananger的使⽤,或者获取顶部矩阵的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);//将当前矩阵压⼊堆栈(栈顶矩阵copy ⼀份到栈顶)
void GLMatrixStack::PushMatrix(void);
//将M3DMatrix44f 矩阵对象压⼊当前矩阵堆栈
void PushMatrix(const M3DMatrix44f mMatrix);
//将GLFame 对象压⼊矩阵对象
void PushMatrix(GLFame &frame);
//出栈(出栈指的是移除顶部的矩阵对象)
void GLMatrixStack::PopMatrix(void);压栈与出栈一个矩阵的真正价值在于通过压栈操作存储一个状态,然后通过出栈操作恢复这个状态。通过GLMatrixStack类,我们可以使用PushMatrix函数将矩阵压入堆栈来存储当前矩阵值。
矩阵堆栈中关于入栈、相乘、出栈的流程
- 原始矩阵堆栈中,拷贝一份栈顶矩阵,压入栈顶
- 当有变换操作时,变换操作的矩阵与矩阵堆栈栈顶矩阵相乘,将其结果覆盖栈顶矩阵
- 如果还有其他矩阵入栈,则继续相乘
- 当没有矩阵需要push,即图形绘制完成后,需要pop栈顶矩阵,还原原始状态
流程如图所示:
注意:push和pop操作是一一对应的,使用几次push,就需要使用几次pop还原原始状态
仿射变换
//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);GLFrame
GLFrame和矩阵对应 GLFrame定义:
GLFrame
{
protected:
M3DVector3f vOrigin;//x
M3DVector3f vForward; //z
M3DVector3f vUp; //y
}GLFrame操作也与矩阵相似:在矩阵堆栈中存储的是GLFrame对应的矩阵
//将堆栈的顶部压⼊任何矩阵
void GLMatrixStack::LoadMatrix(GLFrame &frame);
//矩阵乘以矩阵堆栈顶部的矩阵。相乘结果存储在堆栈的顶部 void GLMatrixStack::MultMatrix(GLFrame &frame);
//将当前的矩阵压栈
void GLMatrixStack::PushMatrix(GLFrame &frame);照相机管理
GetCameraMatrix这个函数⽤来检索条件适合的观察者矩阵,存入第一个参数中
void GetCameraMatrix(M3DMatrix44f m,bool bRotationOnly = flase);