四、OpenGL - 图形变换

音视频开发:OpenGL + OpenGL ES + Metal 系列文章汇总

图形开发中,图形变换是比较重要的操作,OpenGL中是逐顶点变换的,因此本文探究顶点如何经过变换得到一个新的顶点。OpenGL中使用变换矩阵来实现图形变换过程,重点探究两个内容,一个是顶点向量与变换矩阵是如何计算得到新的顶点的,另一个是顶点都可以进行哪些变换(也就是变换矩阵有哪些)

主要内容:
1、向量的认识
2、矩阵的认识
3、图形变换
4、代码案例---球的自转

重点掌握:
1、向量与矩阵的叉乘,矩阵之间的叉乘,最后mpv的计算顺序
2、图形变换的种类及每种变换的使用
3、矩阵堆栈的使用,帮助我们进行矩阵计算

1、向量

在空间中一个顶点的位置可以用一个向量来表示,我们在顶点的变换时需要使用其向量来计算


向量.png

1.1 向量的认识

向量在空间3个值(x,y,z)组合起来表示2个重要的值,方向和大小,因此向量是有方向的标量,不仅有大小,还有方向
标量只有大小,比如1、2等

单位向量
简单说长度为1的向量就是单位向量,比如(1,0,0)
如果一个向量不是单位向量,我们可以将它的模缩放到1,这个过程叫做标准化,
我们在进行向量的点乘时,需要使用单位向量,所以需要进行向量的单位化(也叫标准化)

单位化
向量单位化只保留向量的方向,忽略长度

向量的单位化.png

1.2 向量的计算

1.2.1 点乘

通过两个向量点乘得到两个向量的夹角,是一个标量,必须是标准化向量相乘,如果不是,需要将向量进行标准化后再点乘


向量点乘.png

API

//⽅法1:返回的是-1,1之间的值。它代表这2个向量夹角的余弦值。即cosA
float m3dDotProduct3(const M3DVector3f u,const 
M3DVector3f v);

//⽅法2:返回2个向量之间的弧度值。(注意是弧度值)
float m3dGetAngleBetweenVector3(const M3DVector3f 
u,const M3DVector3f v);
1.2.2 叉乘(重点)

叉乘得到是一个垂直于两个向量所构成平面的向量,也就是法线,且不需要是单位向量。
想要将多个操作拼起来,比如平移的同时也进行旋转,就可以将平移矩阵和旋转矩阵进行叉乘,之后直接使用叉乘后的矩阵。

向量叉乘.png

叉乘API

//传入两个向量u和v
//result就是得到的法线向量,也就是两个向量组成的平面的垂直向量
void m3dCrossProduct3(M3DVector3f result,const M3DVector3f 
u ,const M3DVector3f v)
1.3 OpenGL中向量的使用

math3d库中提供了两个数据类型,相当于float类型的数组,是专门定义向量的,我们以后可以直接使用

三维向量:
M3DVector3f可以表示三维向量(x,y,z)
定义:typedef float M3DVector3f[3];
声明:
M3DVector3f vVerts[] = {
-0.5f,0.0f,0.0f,
0.5f,0.0f,0.0f,
0.0f,0.5f,0.0f
};

四维向量:
M3DVector4f表示四维向量(x,y,z,w),w表示缩放因子,将x/y/z通过处于w来进行缩放,w为1表示不缩放
除了名字为M3DVector4f,其他的都和三维向量一样

2、矩阵的认识

如果将一个对象所有的顶点向量,乘以这个矩阵,就能让整个对象 变换到空间中给定的位置和方向
OpenGL中有两种,包括三维矩阵和四维矩阵,在OpenGL中使用的是一维数组来实现的矩阵。

2.1 矩阵的分类

矩阵分为行优先矩阵和列优先矩阵,通过矩阵转置可以互相转换,OpenGL使用的是列优先矩阵。

行优先矩阵: 一行一行读取矩阵

行优先矩阵.png

列优先矩阵: 一列一列读取矩阵

列优先矩阵.png

2.2 单位矩阵

主对角线上数据都是1的矩阵就是单元矩阵,其特点是向量*单元矩阵,向量不发生变化,相当于数值中的1

2.3 矩阵的计算

2.3.1 点乘

前提:两个矩阵的行数和列数分别相等可以进行点乘
规则:相应位置的元素相乘

点乘.png
2.3.2 叉乘(重点)

前提:第一个矩阵的列数=第二个矩阵的行数
规则:第一个矩阵的n行的数据与第二个矩阵的n列对应数据对相乘的和

叉乘.png

API:

//三个参数,第一个参数是结果矩阵,后两个参数是传入计算的矩阵
m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);

2.4 OpenGL使用矩阵的特殊性

OpenGL中的矩阵都是4x4的,每一列都是4个元素组成的向量(注意这里每列是一个向量,一个向量有四个值)。

矩阵样式.png

1、第一列表示X轴方向
2、第二列表示Y轴方向
3、第三列表示Z轴方向
4、第四列表示交换、位置
5、列矩阵进行了特殊的标注,表示是以列为主的矩阵,主要体现为矩阵的最后行都是0,只有最后一个元素为1

2.5 矩阵创建

一维数组方式:

一维数组方式.png

OpenGL定义的变量:

OpenGL定义变量.png

方法调用:

初始化方法.png

3、视图的变换(矩阵的计算)

对顶点向量通过mvp矩阵相乘,可以得到新的顶点,而因为OpenGL中矩阵是列优先矩阵,所以需要采用左乘方式。

3.1 数学角度的矩阵计算

数学中为了计算方便,矩阵都是以行优先为标准,从左到右顺序计算。所以在数学中顶点以行向量的方式表示。
因此从数学角度来看MVP矩阵的计算,需要右乘,将MVP矩阵放在右边。

数学角度右乘.png

3.2 OpenGL角度(重点)

因为OpenGL中是列优先矩阵,根据叉乘的计算条件也就是第一个矩阵的列数=第二个矩阵的行数,所以需要左乘。

OpenGL角度左乘.png

在OpenGL中我们会使用MultMatrix()函数进行矩阵相乘,所以我们在使用时需要考虑顺序


OpenGL中矩阵计算.png

在OpenGL ES中我们也需要考虑矩阵计算的顺序,如下:


OpenGL ES中矩阵计算.png

3.3 总结

  1. 顶点的计算是逐个计算的,也就是向量计算的,而不是组成一个矩阵来计算的
  2. 向量作为一个点,矩阵作为一个变换过程,向量与矩阵叉乘得到的一个向量就是该点经过图形变换后得到的新的点。
  3. 因此可以说顶点的变换就是向量叉乘变换矩阵
  4. 根据矩阵的运算法则,同时OpenGL的矩阵都是列优先矩阵,所以需要需要从右向左叉乘

4、视图的变换种类(重点)

重点掌握模型视图投影矩阵各自变换的使用场景和API。

4.1 种类

变换类型.png

4.2 视图变换

视图变换用来确定场景中的观察者的有利位置。

4.2.1 变换过程

视图变换将观察者放在你希望的任何位置,并允许在任何方向上观察场景,确定视图变换就像在场景中防止观察者并让它指向某一个方向。
默认观察者位于原点,沿着z轴负方向进行观察(向屏幕内部“看过去”)。
我们可以通过API来对观察者进行左右上下前后移动以改变观察者位置。

4.2.2 实现API
    //向上移动
    cameraFrame.MoveUp(<#float fDelta#>);
    //向右移动
    cameraFrame.MoveRight(<#float fDelta#>);
    //向前移动
    cameraFrame.MoveForward(<#float fDelta#>);

注意:这里的向上、向右、向前移动,指的是观察者的移动,因此以向前为例,如果想要离屏幕更远,就要远离屏幕,所以传入的值是负值。

从大局上考虑,在应用任何其他模型变换之前,必须先应用模型变化,这样做是因为,对于视觉坐标系而言,视图变换移动了当前的工作的坐标系,后续的变化都会基于新调整的坐标系进行。

4.3 模型变换

模型变换其实就是物体通过平移、旋转、缩放的操作,将物体移动到你想要的位置的一个过程,总共有三种操作平移,旋转、缩放。
变换结果与变换顺序有关,因为矩阵相乘不满足交换律。


变换顺序.png
4.3.1 平移

物体沿着给定的方向进行移动


平移.png

方法
m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);
void m3dTranslationMatrix44(M3DMatrix44f m, floata x, float y, float z);
返回一个模型视图矩阵

4.3.2 旋转

物体围绕给定的坐标轴进行旋转

旋转.png

方法:
m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);
旋转需要通过角度转弧度,围绕哪个轴,就设置哪个轴的参数为1

4.3.3 缩放

根据物体大小进行了放大/缩小的操作


缩放.png

方法:
void m3dScaleMatrix44(M3DMatrix44f m, floata xScale, float yScale, float
zScale);
如果X轴翻转,参数是-1

4.4 投影变换

具体可以查看投影方式

4.5 仿射变换

OpenGL中提供的另一种设置图像变换的方式

仿射变换移动API.png
4.6 总结
  1. 图形变换包括视图变换、模型变换、投影变换
  2. 视图变换改变摄像机的位置
  3. 模型变换改变物体本身的位置
  4. 人眼所看到的物体大小样式由摄像机和物体决定,所以他们两者都可以决定我们所看到的视角
  5. 投影变换可以决定视景体大小,透视投影还可以对物体增加立体感,视景体大小简单讲就是物体所在的空间

5、OpenGL中的视图计算(重点)

5.1 矩阵堆栈

OpenGL提供矩阵堆栈来帮助我们进行矩阵计算,需要了解常用的API,以方便实际的计算。

API:
如果对栈有简单了解的话,这都很简单的,不做专门分析,使用时直接在这里查就够了。

//类型
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)

5.2 GLFrame的使用

GLFrame表示一个场景,有摄像机帧、角色帧两种。分别对应视图和模型
它可以获取矩阵,也可以直接压入到栈中

定义:
可以看到包含了三个值
vOrigin:当前所处的位置,默认是(0,0,0),处于原点
vForward:朝向,也就是摄像机所指向的方向,默认是(0,0,-1),朝向-z轴方向
vUp:表示哪个方向为朝上的方向,默认是(0,1,0),朝向+y轴方向

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

作用:
1、通过摄像机帧的移动可以实现视图变化

移动摄像机:

/*
     因为眼睛的方向是看向屏幕里面,所以看向屏幕里面的方向是正方向
     想要离得更远,就是负数
     */
    cameraFrame.MoveForward(-15.0f);

移动物体:

/*
     与摄像机相反的方向
     */
    objectFrame.MoveForward(15.0f);

2、获取矩阵

获取视图矩阵:

M3DMatrix44f mCamera;
    //从camereaFrame中获取矩阵,赋给mCamera
    cameraFrame.GetCameraMatrix(mCamera);

获取模型矩阵:

//4.创建矩阵mObjectFrame
    M3DMatrix44f mObjectFrame;
    //从ObjectFrame 获取矩阵到mOjectFrame中
    objectFrame.GetMatrix(mObjectFrame);

6、球的自转案例分析

你可能感兴趣的:(四、OpenGL - 图形变换)