阅读 243

OpenGL/OpenGL ES 入门:基础变换 - 初识向量/矩阵

系列推荐文章:
OpenGL/OpenGL ES入门:图形API以及专业名词解析
OpenGL/OpenGL ES入门:渲染流程以及固定存储着色器
OpenGL/OpenGL ES入门:图像渲染实现以及渲染问题
OpenGL/OpenGL ES入门:基础变换 - 初识向量/矩阵
OpenGL/OpenGL ES入门:纹理初探 - 常用API解析
OpenGL/OpenGL ES入门: 纹理应用 - 纹理坐标及案例解析(金字塔)
OpenGL/OpenGL ES入门: 顶点着色器与片元着色器(OpenGL过渡OpenGL ES)
OpenGL/OpenGL ES入门: GLKit以及API简介
OpenGL/OpenGL ES入门: GLKit使用以及案例

确定对象位置和方向的能力对于任何3D图形编程人员来说都是非常重要的,正如我们将要看到的,围绕着原点来描述对象的维度,再将对象变换到需要的位置实际上是非常方便的。

向量

为空间中(任意选择)的一个点,以及空间中从坐标系原点到这个点坐标的一条带箭头的线段,这个带箭头的线段可以视为一个向量

向量

向量能够代表的第一个量就是方向,第二个量就是数量。 方向:比如X轴就是向量(1,0,0)。在X方向为+1,而在Y方向和Z方向则为0; 数量:一个向量的数量就是这个向量的长度。对于上面X轴的向量(1,0,0)来说,向量的长度为1。我们把长度为1的向量称为单位向量

math3d库有两个数据类型,能够表示一个三维或四维向量:M3DVertor3f可以表示一个三维向量(X,Y,Z),而M3DVertor4f则可以表示一个四维向量(X,Y,Z,W),其中W为缩放因子,一般情况下为1.

// 简单的声明
M3DVector3f vVector;
M3DVector4f vVertex = { 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之间,实际上就是这两个向量之间夹角的余弦值

向量的点乘

math3d库中也包含一些有用的函数使用点乘操作。

m3dDotProduct3函数来实际获得两个向量之间点乘结果

// 两个单位向量夹角的余弦值 float m3dDotProduct3(const M3DVector3f u, const M3DVector3f v);

m3dGetAngleBetweenVectors3函数返回夹角的弧度值 // 返回这个夹角的弧度值 float m3dGetAngleBetweenVectors3(const M3DVector3f u, const M3DVector3f v);

叉乘

两个向量之间叉乘所得的结果是另外一个向量,这个新向量与原来两个向量定义的平面垂直。要进行叉乘,这两个向量都不必为单位向量。 与点乘还有一个不同之处是叉乘不符合交换定律即 V1 X V2 != V2 X V1.

向量的叉乘

math3d库中也有一个函数m3dCrossProduct3对两个向量进行叉乘并返回运算得到的结果向量

void m3dCrossProduct3(M3DVector3f result, const M3DVector3f u, const M3DVector3f v);

矩阵

它是一种功能非常强大的数学工具,大大简化了求解变量之间有复杂关系的方程或方程组的过程。其中一个具有普遍性的例子和图形程序设计人员密切相关,就是坐标变换。 例如,如果在空间中有一个点,由x,y和z坐标定义,将它围绕任意点沿任意方向选择一定角度后,我门需要知道这个点现在的位置,就要用到矩阵。为什么呢?因为新的x坐标不仅与原来的x坐标和其他旋转参数有关,还与原y和z坐标值有关。这种变量与解之间的相关性就是矩阵最擅长解决的问题。

在我们进行3D程序设计工作是,我们将使用的几乎全部是两种维度的矩阵,即3 x 3 和 4 x 4矩阵。在math3d库中,也有这两种维度的矩阵数据类型。

typedef float M3DMatrix33f[9]; typedef float M3DMatrix44f[16];

理解变换

在我们指定顶点和这些顶点出现在屏幕上之间的这段时间里,可能会发生3中类型的几何变换: 视图变换、模型变换和投影变换

变换的术语概念

视觉坐标

视觉坐标是相对于观察者的视角而言的,无论可能进行何种变换,我们都可以将它们视为“绝对的”屏幕坐标。

视觉坐标系
在左边的坐标系1中,视觉坐标系是以场景的观察者的角度(也就是垂直于显示器的方向)。 在右边的坐标系2中,视觉坐标系稍稍进行了选择,这样就可以更好的观察Z轴的位置关系。

从观察者的角度来看,x轴和y轴的正方形分别指向右方和上方。z轴的正方形从原点指向使用者,而z轴的负方向则从观察者只想屏幕内部。

视图变换

视图变换是应用到场景中的第一种变换。他用来确定场景中的游离位置。在默认情况下,透视投影中的观察点位于原点(0,0,0),并沿着z轴的负方向进行观察(向显示器内部看)。观察点相对于视觉坐标系进行移动,来提供特定的有利位置。当观察点位于原点(0,0,0)时,就像在透视投影中一样,绘制在z坐标为正的位置的对象则位于观察者背后。 在正投影中,观察者被认为是在z轴正方向无穷远的位置,能够看到视景体中的任何东西。 视图变换允许我们把观察点放在所希望的任何位置,并允许在任何方向上观察场景,确定视图变换就想在场景中放置照相机并让它指向某个方向。

模型变换

下图展示了3种最普遍的模型变换:
平移: 对象沿着给定的轴进行移动
旋转: 对象围绕着一条坐标轴进行旋转
缩放: 对象的大小进行了指定数量的放大或缩小。缩放可以是不均匀的,即不同维度可以进行不同程度的缩放。

平移 旋转 缩放

场景或对象的最终外观可能在很大程度上取决于应用的模型变换顺序。 如下图,模型变换先旋转后平移与先平移后旋转,结果是不同的。

模型变换

模型视图的二元性

实际上,视图和模型变换按照它们内部效果和对场景的最终外观来说是一样的。将这两者分开是为了我们(程序员)方便。比如将对象向后移动和将参考坐标系向前移动在视觉上没有区别,如下图:

模型视图是指这两种变换在变换管线中进行组合,成为一个单独的矩阵。即模型视图矩阵。

投影变换

投影变换将在模型视图变换之后应用到顶点上。这种投影实际上定义了视景体并创建了裁剪平面。 更具体的说,投影变换指定一个完成的场景(所有模型变换都已完成)是如何投影到屏幕上的最终图像。

  • 在正投影中,所有多边形都是精确的按照指定的相对大小来在屏幕上绘制的
  • 透视投影所显示的场景与现实生活更加接近,透视投影的特点就是透视缩短,这种特性似的远处的物体看起来比近处同样大小的物体更小一些。

视口变换

当上述都完成后,就得到一个场景的二维投影,它将被映射到屏幕上某处的窗口上。这种到物理窗口的映射是我们最后要做的变换,即视口变换

这个过程图形硬件会为我们做好,所以不必太操心这个过程。

模型视图矩阵

单位矩阵

将一个向量乘以一个单位矩阵,就相当于用这个向量乘以1,不会发生任何变化。 单位矩阵中除了对角线上的一组元素为1之外,其他元素均为0.

单元矩阵

可以在OpenGL中这样生成一个单位矩阵:

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 };
                
或者使用`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 };
复制代码

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类 这个类的构造函数允许指定堆栈的最大深度,默认的堆栈深度为64.
// 同时这个矩阵堆栈在初始化时,已经在堆栈中包含了单位矩阵。

GLMatrixStack::GLMatrixStack(int iStackDepth = 64);

// 在堆栈顶部载入一个单元矩阵
void GLMatrixStack::LoadIdentity(void);

// 在堆栈顶部载入任何矩阵
// 参数:4x4矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);

// 矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部
void GLMatrixStack::MultMatrix(const M3DMatrix44f);

// 获取矩阵堆栈顶部的值 GetMatrix 函数
// 为了适应GLShaderManager的使用,或者获取顶部矩阵的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3Datrix44f mMatrix);

复制代码

压栈、出栈

一个矩阵的真正价值在于通过压栈操作存储一个状态,然后通过出栈操作恢复这个状态。 通过GLMatrixStack类,我们可以使用PushMatrix函数将矩阵压入堆栈来存储当前矩阵的值。而PopMatrix将移除顶部矩阵,并恢复它下面的值。

// 将当前矩阵压入堆栈(栈顶矩阵copy一份到栈顶)
void GLMatrixStack::PushMatrix(void);

// 将M3DMatrix44f矩阵对象压入当前矩阵堆栈
void PushMatrix(const M3DMatrix44f mMatrix);

// 将GLFrame对象压入矩阵对象
void PushMatrix(GLFrame &frame);

// 出栈(出栈指的是移除顶部的矩阵对象)
void GLMatrixStack::PopMatrix(void);
复制代码

具体过程参照下面流程图

压栈 出栈

仿射变换

GLMatrixStack类也内建了对创建旋转、平移和缩放矩阵的支持。如下函数:

// 平移
void MatrixStack::Translate(GLfloat x, GLfloat y, GLfloat z);

// 旋转 参数angle是传递的度数, 而不是弧度
void MatrixStack::Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);

// 缩放
void MatrixStack::Scale(GLfloat x, GLfloat y, GLfloat z);
复制代码
关注下面的标签,发现更多相似文章
评论