OpenGL 的矩阵变换
OpenGL 渲染 3D 物体到屏幕上的过程其实类似于我们平时使用的相机拍照的过程, 这个步骤大致如下:
- 把相机固定在三脚架并让它对准场景 (视图变换)
- 把场景中的物体调整摆放好 (模型变换)
- 选择相机的镜头, 并调整放大倍数 (投影变换)
- 确定最终照片的大小 (视口变换)
注: 视图变换必须要在模型变换之前, 其它可以在任何时候。
接下来我们来分开了解一下上面的每个步骤:
视图变换
-
视图变换, 设置摄像机的位置。是应用到场景中的第一种变换, 它用来确定场景中的有利位置。默认情况下, 透视投影位于原点 (0, 0, 0), 并且沿着 z轴 负方向进行观察。
-
视图变换可以将观察者放在你希望的任何位置, 并允许在任何方向上观察场景。确定视图变换就像在场景中放置观察者并让它指向某一个方向。
-
从大局上考虑, 在应用任何其他模型变换之前, 必须先应用视图变换。这样做是因为, 对于视觉坐标系而言, 视图变换移动了当前的工作的坐标系; 后续的变化都会基于新调整的坐标系进行。
模型变换
-
设置物体的位置和方向, 包括: 旋转, 移动, 缩放, 而且要注意组合使用他们的顺序, 像先平移再旋转与先旋转再平移它们的表现是不一样的。
-
有时可以不必通过移动摄像机(视图变换)来观察物体, 而是移动这个物体(模型变换), 这两个方式都能实现同样地效果, 有时使用其中一种变换比使用另一种变换要方便得多。这也是把视图变换和模型变换都统一为模型视图矩阵的原因。

- 从上面的图也可以看到, 同一个物体, 先经过旋转或者先经过平移, 最后得到的结果是不一样的
投影变换
- 设置视景体, 将3维坐标投影为2维屏幕坐标, 位于视景体之外的东西将被裁剪掉。
- 它分为两种投影: 正投影和透视投影, 其中正投影就是平行投影, 不管远近物体看上去都一样。而透视投影则是近大远小的真实效果。

视口变换
其实就是指定最终图像显示在屏幕哪个区域, 一般我们是设置和屏幕一样大。
模型视图矩阵
视图二元性
实际上, 上面的视图变换和模型变换按照它们的内部效果和对场景的最终外观来说是一样的。将两者区分开纯粹是为了程序员的方便, 将对象向后移动和将参考系向前移动在视觉上没有区别。
视觉变换和模型变换一样, 都应用在整个场景中, 在场景中的对象常常在进行视图变换后单独进行模型变换。术语 “模型视图” 是指这两种变换在变换管线中进行组合, 成为一个单独的矩阵, 即 模型视图矩阵 。
模型视图矩阵
模型视图矩阵是一个 4x4 矩阵
, 它表示变换后的坐标系, 我们可以用来设置对象和确定对象的方向。为图元提供的顶点将作为一个单列矩阵的形式来使用, 并乘以一个模型视图矩阵来获得一个相对于视觉坐标的经过变换的新坐标。
我们可以调用 math3d
库中的以下函数来进行矩阵变换操作:
// 1、单位矩阵
void m3dLoadIdentity44(M3DMatrix44f m);
// 2、创建平移矩阵
// 一个平移矩阵仅仅是将我们的顶点沿着3个坐标轴的一个或多个进行平移。
void m3dTranslationMatrix44(M3DMatrix44f m,float x,float y,float z);
// 3、创建旋转矩阵
// 围绕一个x、y和z变量指定的向量来进行旋转。旋转的角度沿逆时针方向按照弧度计算,由变量angle指定。可以围绕一个轴进行旋转,也可以围绕任意一个由x、y、和z变量指定的向量来进行旋转。
void m3dRotationMatrix44(M3DMatrix44f m,float angle,float x,float y,float z);
// 4、创建缩放矩阵
// 缩放矩阵可以沿着3个坐标轴方向按照指定因子放大或缩小所有顶顶啊,以改变对象的大小。
void m3dScaleMatrix44(M3DMatrix44f m,float xScale,float yScale,float zScale)
// 5、综合变换
// 将两种变换加在加在一起就是综合变换,只需将两个矩阵相乘
void m3dMatrixMultiply44(M3DMatrix44f product,const M3DMatrix44f a,const M3DMatrix b);
矩阵堆栈
矩阵相关
矩阵
矩阵是一种功能很强大的数学工具, 它大大简化了求解变量之间有复杂关系的方程式或者方程组的过程。矩阵在坐标变换中使用的非常广泛。
举例: 假如在空间中有一个点 A 由 x,y,z 坐标轴定义, 现在将它围绕任意点任意方向旋转一定的角度, 如果我们想要知道点 A 现在的位置, 这时候就需要用到矩阵。因为现在的 A 点的 x 坐标不仅与原来的 x坐标 位置有关, 还和 旋转参数 以及 y 和 z 坐标的值有关, 解决这种问题就是矩阵最擅长的。
投影矩阵
投影矩阵分为 正投影 和 透视投影:
- 正投影会产生一个平行投影, 这种投影在绘制从远处观察不产生任何透视缩短的特定物体是非常有用。
// 用这个函数设置正投影
GLFrustum::SetOrthographic(GLfloat xMin, GLfloat xMax, GLfloat yMin, GLfloat yMax, GLfloat zMin, GLfloat zMax);
- 透视投影会有一个 3D 效果, 看起来更加逼真, 渲染 3D 图形时常常选择透视投影
// 用这个函数设置透视投影
GLFrustum::SetPerspective(float fFov, float fAspect, float fNear, float fFar);
矩阵堆栈
什么是矩阵堆栈?
OpenGL 的矩阵堆栈指的是 内存中专门用来存放矩阵数据的某块特殊区域 。一般来说, 矩阵堆栈常用于构造 具有继承性的模型(由一些简单目标构成的复杂模型)。矩阵堆栈对于复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。
为什么要使用矩阵堆栈?
在绘制图形后, 可能会发生各种各样的变化和复杂场景切换, 这些变换实现的原理一般都是通过矩阵来进行操作的。OpenGL 中的变换一般包括 视图变换、模型变换、投影变换等, 每次变换后, OpenGL 都会呈现一种新的状态 (状态机) 。
但是有些时候在经过变换之后我们想回到之前的状态, 这个时候就是为什么要使用矩阵堆栈的重点了。他给我们提供了两个函数 PushMatrix()
和 PopMatrix()
, 通过这两个函数帮助我们可以回到之前的状态, 并且当前的所有操作都不会影响到之前的状态。下面来看一下矩阵堆栈是如何使用的, 以及 前面的 2 个函数是如何工作的。
矩阵堆栈的使用
1. 使用
//类型
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);
2. 压栈、出栈
//将当前矩阵压入堆栈(栈顶矩阵copy 一份到栈顶)
void GLMatrixStack::PushMatrix(void);
//将M3DMatrix44f 矩阵对象压入当前矩阵堆栈
void PushMatrix(const M3DMatrix44f mMatrix);
//将GLFame 对象压入矩阵对象
void PushMatrix(GLFame &frame);
//出栈(出栈指的是移除顶部的矩阵对象)
void GLMatrixStack::PopMatrix(void);
下面通过一张图片来看一下 压栈和出栈的过程 (ps: 图片来自网络):
