向量、矩阵变换
©shuan9999
1. 矩阵
-
OpenGL在渲染的时候是通过模型视图矩阵和投影矩阵运算得到最终显示的坐标。
模型矩阵(Model): 将顶点从局部坐标系转换到世界坐标系中;
视图矩阵(View): 将顶点从世界坐标转化到视图坐标系下;
投影矩阵(Projection): 将顶点从视图坐标系转换到规范立方体中(即屏幕中);
模型视图投影矩阵(MVP) = 投影矩阵 * 模型视图矩阵 (不能写成 模型视图矩阵 * 投影矩阵,矩阵乘法不满足交换律) -
为了保证每一次渲染的独立性,需要在每一次渲染前保存当前状态(PushMatrix),并在渲染结束后恢复这个状态(PopMatrix)。
-
单位矩阵
-
将一个向量乘以一个单位矩阵,就相当于用这个向量乘以1,不会发生任何变化。 单位矩阵中除了对角线上的一组元素为1之外,其他元素均为0。
-
可以通过如下方式生成一个单位矩阵:
1、
GLFloat m[] = { 1.0f,0.0f,0.0f,0.0f,
0.0f,1.0f,0.0f,0.0f,
0.0f.0.0f,1.0f,0.0f,
0.0f,0.0f,0.0f,1.0f };
2、或者使用math3d
的M3DMatrix44f
类型:
M3DMatrix44f m = { 1.0f,0.0f,0.0f,0.0f,
0.0f,1.0f,0.0f,0.0f,
0.0f.0.0f,1.0f,0.0f,
0.0f,0.0f,0.0f,1.0f };
3、在math3d
库中,还有一个快捷函数m3dLoadIdentity44
,这个函数初始化一个单位矩阵。
void m3dLoadIdentity44(M3DMatrix44f m); -
平移
-
一个平移矩阵仅仅是将我们的顶点沿着3个坐标轴重的一个或多个进行平移。
可以调用math3d库中的m3dTranslationMatrix44函数来使用变换矩阵void m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z);
-
旋转
-
将一个对象沿着3个坐标轴中的一个或任意向量进行旋转,需要一个旋转矩阵。
void m3dRotationMatrix44(M3DMatrix44f m, float x, float y, float z);
-
缩放
-
缩放矩阵可以沿着3个坐标轴方向按照指定因子放大或缩小所有顶点,以改变对象的大小。缩放不一定是一致的,我们可以在不同的方向同时使用它来进行伸展和压缩。
M3DMatrix44f m;
void m3dScaleMatrix44(M3DMatrix44f m, float xScale, float yScale, float zScale); -
综合变换
-
为了将对象移动道想要的位置,我们可能需要先将它平移到指定位置,然后在旋转得到想要的结果。又因为4 x 4变换矩阵包含一个位置和一个方向,那么一个矩阵就可以完成这两种转换。只需将两个矩阵相乘,结果得到的矩阵包含结合道一起的转换,都在一个矩阵中。
在math3d
库中,m3dMatrixMultiply44
用来将两个矩阵相乘并返回运算结果。void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);
-
在代码中应用矩阵:
//初始化
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); -
矩阵变换
//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); -
GLMatrixStack::LoadIdentity(void)
、void GLMatrixStack::PushMatrix(void);
二者很像,都是往栈顶append一个节点,之所以要这样去做就是为了不破坏栈中的原始数据,当你操作完成后将push进来的直接pop掉就能恢复到初始状态,也就是说push之后要记得pop,否则就是埋坑。 -
矩阵变换在3D环境中的典型渲染循环流畅
-
OpenGL 变换术语概况
-
视图变换
-
视图变换是应用到场景中的第一种变换。他用来确定场景中的游离位置。在默认情况下,透视投影中的观察点位于原点(0,0,0),并沿着z轴的负方向进行观察(向显示器内部看)。观察点相对于视觉坐标系进行移动,来提供特定的有利位置。当观察点位于原点(0,0,0)时,就像在透视投影中一样,绘制在z坐标为正的位置的对象则位于观察者背后。
-
在正投影中,观察者被认为是在z轴正方向无穷远的位置,能够看到视景体中的任何东西。
视图变换允许我们把观察点放在所希望的任何位置,并允许在任何方向上观察场景,确定视图变换就想在场景中放置照相机并让它指向某个方向 -
总之就是变换相机的位置
-
模型变换
-
下图展示了3种最普遍的模型变换:
平移: 对象沿着给定的轴进行移动
旋转: 对象围绕着一条坐标轴进行旋转
缩放: 对象的大小进行了指定数量的放大或缩小。缩放可以是不均匀的,即不同维度可以进行不同程度的缩放。
-
物体最终外观在很大程度上取决于应用的模型变换顺序。 如下图,模型变换先旋转后平移与先平移后旋转,结果是不同的。
-
投影变换
-
投影变换将在模型视图变换之后应用到顶点上。这种投影实际上定义了视景体并创建了裁剪平面。 更具体的说,投影变换指定一个完成的场景(所有模型变换都已完成)是如何投影到屏幕上的最终图像。
- 在正投影中,所有多边形都是精确的按照指定的相对大小来在屏幕上绘制的
- 透视投影所显示的场景与现实生活更加接近,透视投影的特点就是透视缩短,这种特性似的远处的物体看起来比近处同样大小的物体更小一些。
2. 向量
-
3个值(x、y、z)组合起来表示2个重要的值,方向和数量
-
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;
//类似,声明一个四分量的操作:
M3DVector4f vVectro= {0.0f,0.0f,1.0f,1.0f};
//声明一个三分量顶点数组,例如生成一个三⻆形
M3DVector3f vVerts[] = {
-0.5f,0.0f,0.0f,
0.5f,0.0f,0.0f,
0.0f,0.5f,0.0f
}; -
点乘
-
两个单位向量之间的点乘运算将得到一个标量(只有一个值),它表示两个向量之间的夹角。要进行这种运算,这两个向量必须为单位向量,返回的结果将在-1~1之间,实际上就是这两个向量之间夹角的余弦值
//实现点乘方法:
//方法1:返回的是-1,1之间的值。它代表这个2个向量的余弦值。
float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);
//方法2:返回2个向量之间的弧度值。
float m3dGetAngleBetweenVector3(const M3DVector3f u,const M3DVector3f v); -
叉乘
-
两个向量之间叉乘所得的结果是另外一个向量,这个新向量与原来两个向量定义的平面垂直。要进行叉乘,这两个向量都不必为单位向量。 与点乘还有一个不同之处是叉乘不符合交换定律即 V1 x V2 != V2 x V1。
void m3dCrossProduct3(M3DVector3f result, const M3DVector3f u, const M3DVector3f v);