向量及矩阵在OpenGL中的应用

908 阅读9分钟

虽然我们在使用OpenGL的过程中运用过向量和矩阵,但是并不是说我们必须要懂得向量和矩阵的数学原理才能使用够使用OpenGL。就好比我们不需要知道汽车是怎么造出来的,也不需要发动机是怎么工作的,我们一样可以开汽车。但是如果我们对此有一定的了解,也可以帮助我们知道什么时候需要更换机油、什么时候该去保养汽车了。所以简单的了解一下向量和矩阵的知识还是有必要的。

向量

定义

简单一句话来概括就是:即有大小,又有方向的量,就叫向量。 比如下图3D坐标系中:

如上图,点O是坐标系的原点,点p的坐标是(x,y,z);由O指向P的线段长度是(x的平方+y的平方+z的平方)再开根号。OP即有长度,也有方向O指向P,称其为向量OP。

单位向量

向量长度我们称之为模,模为1的向量,我们称之为单位向量

单位化向量

将非单位向量转化为单位向量的过程标准化。

OpenGL如何定义向量【math3D库】

math3D库中有两个数据类型来定义三维向量和四维向量

  • M3DVector3f 可以表示一个三维向量(x,y,z);
  • M#Dvector4f 可以表示一个四维向量(x,y,z,w);一般情况下w设置为1,x,y,z通过除以w来进行缩放。

三维向量量/四维向量量的声明

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)” .点乘只能发⽣生在2个向量之间进行;

2个(三维向量)单元向量 之间进行点乘运算将得到⼀个标量(不不是三维向量,是⼀一个标量). 它表示两个向量之间的夹角;

前提条件: 2个向量必须为单位向量; 动作: 2个三维向量之间进⾏点乘 结构: 返回⼀一个[-1,1]范围的值. 这个值其实就是 夹角的cos值(余弦值)

如何单位化向量量? (x/|xyz|, y/|xyz|, z/|xyz|); 使⽤⼀个⾮零向量除以它的模(向量的长度), 就可以得到⽅向相同的单位向量;

OpenGL提供了m3dDotProduct3 函数获得2个向量之间的点乘结果

float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);

m3dGetAngleBetweenVector3 即可获取2个向量量之间夹⻆的弧度值

float m3dGetAngleBetweenVector3(const M3DVector3f u,const
M3DVector3f v);

向量的叉乘

向量之间的叉乘(cross product) 也是在业务开发里非常有⽤的⼀个计算方式; 2个向量 之间叉乘就可以得到另外⼀个向量,新的向量会与原来2个向量定义的平面垂直. 同时进行叉乘,不必为单位向量;

前提: 2个普通向量 动作: 向量与向量叉乘 结果: 向量(垂直于原来2个向量定义的平面的向量)

如上图,向量V1和向量V2的叉乘得到的结果向量V3,也称其为向量V1和向量V2的法向量.

openGL中使用m3dCrossProduct3函数获取两个向量叉乘的结果

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

矩阵

定义

由 m × n 个数aij排成的m行n列的数表称为m行n列的矩阵,简称m × n矩阵。记作:

这m×n 个数称为矩阵A的元素,简称为元,数aij位于矩阵A的第i行第j列,称为矩阵A的(i,j)元,以数 aij为(i,j)元的矩阵可记为(aij)或(aij)m × n,m×n矩阵A也记作Amn。

OpenGL中常用的矩阵

三维矩阵

typedef float M3DMatrix33f[9];

四维矩阵

typedef float M3DMatrix44f[16];

在其他编程标准中, 许多矩阵库定义一个矩阵时,使⽤⼆维数组; OpenGL的约定里,更多倾向使用 一维数组; 这样做的原因是: OpenGL 使⽤的是 Column-Major(以列为主)矩阵排序的约定;

列优化的矩阵

行优化的矩阵

列优先的矩阵的奥秘之处在于16个值表示空间中一个特定的位置,这4列中,每一列都是4个元素组成的向量

一个4*4矩阵是如何在3D空间中表示一个位置和⽅向的 列向量进行了特别的标注:矩阵的最后⼀行都为0,只有最后⼀个元素为1

如果将⼀个对象所有的顶点向量 乘以这个矩阵,就能让整个 对象变换到空间中给定的位置和⽅向上,如平移、旋转和缩放。

单元矩阵

对角线上的元素全部为1,其他元素全部为0的矩阵称之为单元矩阵

OpenGL中单元矩阵的定义

方式一

GLFloat m[] = {
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
};

方式二

M3DMatrix44f m[] = {
    1,0,0,0,
    0,1,0,0,
    0,0,1,0,
    0,0,0,1
};

方式三

void m3dLoadIdentify44f(M3DMatrix44f m);

矩阵的乘法

两个矩阵的乘法仅当第一个矩阵A的列数和另一个矩阵B的行数相等时才能定义。如A是m×n矩阵和B是n×p矩阵,它们的乘积C是一个m×p矩阵.

  • 结合律: (AB)C = A(BC);
  • 左分配律: (A+B)C = AC+BC;
  • 右分配律: A(B+C) = AB+AC;
  • 矩阵乘法不满足交换律。AB不等于BA

变换顶点向量 = 投影矩阵 ✖ 视图变换矩阵 ✖ 模型矩阵 ✖ 顶点

当一个图形发生平移、旋转和缩放时,其实是在平移、旋转和缩放图形上面的所有顶点。而我们可以通过设置投影矩阵、视图变换矩阵和模型矩阵与顶点向量进行相乘,从而将平移、旋转和缩放后的结果映射到顶点坐标上得到新的顶点坐标。

  • 1.从栈顶获取栈顶矩阵,将栈顶矩阵与投影矩阵相乘,并将结果压入栈顶;
  • 2.从栈顶获取栈顶矩阵,将栈顶矩阵与视图变换矩阵相乘,并将结果压入栈顶;
  • 3.从栈顶获取栈顶矩阵,将栈顶矩阵与模型变换矩阵相乘,并将结果压入栈顶。

#理解在OpenGL里的变化

OpenGL常用的变换如下图:

视图变换

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

模型变换

模型变换: 用于操纵模型与其中某特定变换. 这些变换将对象移动到需要的位置. 通过旋 转,缩放,平移

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);

先旋转再平移和先平移再旋转最后的效果是不一样的,下面的函数在调用时要注意参数的顺序.

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);

使⽤用照相机(摄像机) 和 ⻆角⾊色帧 进⾏行行移动

class GLFrame
       {
protected:
M3DVector3f vOrigin; // Where am I? M3DVector3f vForward; // Where am I going? M3DVector3f vUp; // Which way is up?
}

GLFrame

//将堆栈的顶部压⼊入任何矩阵
void GLMatrixStack::LoadMatrix(GLFrame &frame);
//矩阵乘以矩阵堆栈顶部的矩阵。相乘结果存储在堆栈的顶部 void GLMatrixStack::MultMatrix(GLFrame &frame);
//将当前的矩阵压栈
void GLMatrixStack::PushMatrix(GLFrame &frame);


照相机管理理

//GLFrame函数,这个函数⽤用来检索条件适合的观察者矩阵
void GetCameraMatrix(M3DMatrix44f m,bool bRotationOnly = flase);