在本章当中,作者着重介绍了几个和3D图形学重要的数学知识,线性代数基础好的同学可以直接绕过本章,说实话这篇博客写到这里,我是非常犹豫的,本章节的内容可以说是很基础,但是相当的枯燥,犹豫再三,还是决定一点点写出来吧,好记性不如烂笔头,妈耶 |
★提高阅读体验★ 👉 ♠ 一级标题 👈👉 ♥ 二级标题 👈👉 ♣ 三级标题 👈👉 ♦ 四级标题 👈 |
目录
♠ 向量
指具有大小和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小,在OpenGL当中我们用到的多是三维向量
如上图所示(x,y,z)就是一个标准向量,箭头方向就是向量方向,从原点到xyz点的距离就是向量大小
♥ 单位向量
大小为1的向量,我们统称为单位向量,如:
- (1,0,0)
- (0,1,0)
- (0,0,1)
♥ 归一化
如果一个向量不是单位向量,我们把它变成单位向量,该过程称为归一化,例如
- (15,0,0) ——> (1,0,0)
归一化的向量只有方向和原来是相同的,大小变成了1
♥ 点乘
传送门:点乘百度百科
♣ 代数定义
两个向量 a ⃗ \vec{a} a, 和 b ⃗ \vec{b} b的数量积称为点乘(又叫内积、点积),计算方式如下图所示
♣ 几何意义
两个向量 a ⃗ \vec{a} a, 和 b ⃗ \vec{b} b,夹角为θ,点乘计算方式如下
由公式可以看出,如果 a ⃗ \vec{a} a和 b ⃗ \vec{b} b都是单位向量(长度1),那么点乘结果就是夹角的余弦值
注:该定义只对二维和三维空间有效
这个运算可以简单地理解为:在点积运算中,第一个向量投影到第二个向量上(这里,向量的顺序是不重要的,点积运算是可交换的)
♥ 叉乘
向量积,数学中又称外积、叉积,物理中称矢积、叉乘,是一种在向量空间中向量的二元运算。与点积不同,它的运算结果是一个向量而不是一个标量。并且两个向量的叉积与这两个向量垂直
传送门:点乘百度百科
两个向量 a ⃗ \vec{a} a, 和 b ⃗ \vec{b} b,夹角为θ,叉乘的计算方式如下图所示
a ⃗ \vec{a} a与 b ⃗ \vec{b} b向量的向量积的方向与这两个向量所在平面垂直,且遵守右手定则
♥ 向量的长度
就是向量的模,根据勾股定理计算向量在多维空间的长度
♥ 反射和折射
在给定入射向量和表面法向量(垂直于反射面的向量)的情况下,我们可以获得反射向量,并且在给定折射率的情况下,我们可以得出新的折射方向
♠ 矩阵
传送门:矩阵百度百科
在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合, 如下图所示
我们为什么要用到矩阵呢,在这里就先简单的理解一下即可,即:
矩阵可以很便捷的表示向量在空间中的变化,例如: a ⃗ \vec{a} a和矩阵B的乘积表示 a ⃗ \vec{a} a按照矩阵B的规则去变化,矩阵B可能代表旋转,缩放或者移动等复合的多元变化
注:OpenGL编程中我们可使用的矩阵大小普遍是2X2、3X3、4X4
♥ 矩阵的构造和运算符
OpenGL中并非将一个4X4的矩阵作为一个二维数组来运算,而是作为一个包含16个浮点值的单个数组来计算,这16个值可以代表空间中的一个具体位置,以及3条坐标轴相对于观察者的方向
左上角的三列元素表示为x、y、z轴的方向,相互垂直(正交),第四列表示为xyz的位置或平移距离
注:
OpenGL的矩阵为列优先,一列一列的读取,例如上图的读取顺序:A00、A01、A02、A03、A10、A11…
在文章底部有一些参考文章,参考文章内写了向量和一些变换矩阵的推导,看一遍就很容易理解了,作者写的很详细,很棒!
♠ 坐标系
OpenGL对顶点的处理过程包含了一系列的转换过程,一系列转换可以表示成一个矩阵,而矩阵的名字往往是根据当前的坐标空间来命名,例如:将一个对象的顶点从模型空间转换到视图空间的矩阵通常称为模型——视图矩阵
♥ 对象坐标
每个模型在建模的时候都有自己的坐标系,它围绕自身坐标系去进行变化,旋转、平移、缩放等
如上图所示的模型,有以自己为中心的坐标系
♥ 世界坐标
这个就比较好理解吧,形容所有事物的位置都需要一个参考系,参考系的原点就是世界坐标的原点,例如unity或者cocos中的scene就是世界坐标系,scene的原点就是世界坐标原点
♥ 视图坐标
通常也称为摄像机或视点坐标,是以观察者的角度建立的坐标系,以unity场景摄像机为例,参照下图
♥ 裁剪和标准化设备空间
在上一章管线相关的内容是我们曾经提及到裁剪和标准化设备空间
,这里再聊一下,因为设备分标率不同,在显示前OpenGL会把接收到的顶点坐标根据四分量的w做一次分割,最终所有的顶点坐标会变成-1到1大小的值,不满足显示条件的顶点会被裁剪剔除
♠ 坐标转换
我们通过向量和矩阵相乘将坐标从一个空间转移到另一个空间,这一模块我们来看看一些常见的变换矩阵,以及和矩阵相乘的计算方式
♥ 单位矩阵
上图就是一个4X4的单位矩阵,除对角线是1其他全是0
♣ 向量和矩阵的乘积
上图是向量(x,y,z,w)和单位矩阵相乘的结果,任意向量和单位矩阵相乘都是他自身
♥ 平移矩阵
我们已经知道了向量和矩阵的乘法公式,我们来看一个简单的平移矩阵
在默认w是1.0的情况下,上述矩阵代表沿x轴移动1,y轴移动2,z轴移动3
♥ 旋转矩阵
若是要围绕3条坐标轴任意一条去旋转,需要用到旋转矩阵,旋转矩阵稍微麻烦一点,我们可以先从二维坐标系来简单推导一下
♣ 旋转矩阵的推导
注:
以下推导逻辑摘自为什么OpenGL里的变换矩阵是4x4的
我们来简单分析一下坐标系和内容
- 二维坐标系包含xy轴
- 2维向量 O A ⃗ \vec{OA} OA的值是(3,2)
- 2维向量 O j ⃗ \vec{Oj} Oj和 O i ⃗ \vec{Oi} Oi都是长度为1的标准向量
根据上图我们可以看出, O A ⃗ \vec{OA} OA向量是和单位矩阵相乘沿x移动3,沿y移动2的结果,而单位矩阵是由 O j ⃗ \vec{Oj} Oj和 O i ⃗ \vec{Oi} Oi组成的
现在我们将坐标系逆时针旋转45°,得到一个新的坐标系,我们继续按图分析
- O A ⃗ \vec{OA} OA变成了 O A ′ ⃗ \vec{OA′} OA′
- O j ⃗ \vec{Oj} Oj变成了 O j ′ ⃗ \vec{Oj′} Oj′
- O i ⃗ \vec{Oi} Oi变成了 O i ′ ⃗ \vec{Oi′} Oi′
- i′的坐标是(cos45°, sin45°)
- j′的坐标是(-sin45°, cos45°)
根据之前的旋转公式新向量 O A ′ ⃗ \vec{OA′} OA′的结果就是上图的结果,因为 O A ′ ⃗ \vec{OA′} OA′相对于 O i ′ ⃗ 和 \vec{Oi′}和 Oi′和\vec{Oj′}的相对位置是没有变化的,由此我们得到旋转的二维矩阵
♣ 三维旋转矩阵
我们已经在二维推算出了旋转矩阵,那么三维就是多了一个轴的问题
- 绕z轴旋转
看起来是不是很熟悉,我们在xy二维坐标系上的旋转,切换到三维坐标系,其实就是在绕z轴旋转,我们保持z轴不变,就很轻易的得到了绕z轴旋转的矩阵
- 绕x轴旋转
- 绕y轴旋转
同理我们可以用类似的方式推导出绕x轴和绕y轴旋转的矩阵
♥ 欧拉角
空间中三个角的集合,每个角代表围绕三个正交向量xyz其中一个进行旋转,自身欧拉角旋转会出现万向节死锁,不建议用
♥ 缩放矩阵
sx、sy、sz分别对应xyz轴的缩放,图示是x轴长度变为原来的两倍
♠ 转换
我们已经了解到了向量从一个空间转换到另一个空间,只需要和相应的矩阵相乘就可以了,我们往往不会用向量一个一个去乘需要的矩阵,而是将需要的矩阵先相乘,最后再和向量乘
♥ 链接转换
vmath::mat4 translation_matrix = vmath::translate(4.0f, 10.0f, -20.0f);
vmath::mat4 rotation_matrix = vmath::rotate(45.0f, vmath::vec3(0.0f, 1.0f, 0.0f));
vmath::mat4 composite_matrix = translation_matrix * rotation_matrix;
vmath::vec4 input_vertex = vmath::vec4(...);
vmath::vec4 transformed_vertex = composite_matrix *
input_vertex;
看上述代码为书中案例,一个移动矩阵translation_matrix
和一个旋转矩阵rotation_matrix
,我们先将两个矩阵相乘得到复合矩阵composite_matrix
,再将复合矩阵和向量相乘
注意: 变换顺序和编码顺序是反的,上述代码先旋转再移动
♥ 四元数
传送门:四元数百度百科
一系列的旋转可以表示为一系列的四元数相乘,生成一个最终的四元数表示所有的旋转。虽然可以制定一串矩阵表示绕着各个笛卡尔轴进行旋转,然后将它们乘在一起,但这种方法容易产生万向节死锁。如果我们用一系列四元组来做同样的事情,将不会发生万向节死锁。为了便于我们编码,vmath库包含vmath::quaternion类实现了这里描述的大部分功能
♥ 模型——视图转换
在一个简单的OpenGL应用中最常用的一个变换是将模型从模型空间转换到视图空间,表达这一变换的矩阵称为模型——视图转换
♣ 观察矩阵
简单理解就是为观察点或者说是摄像机设置一个观察矩阵,目的就是计算模型从世界坐标系转换到观察坐标系后的坐标
以上图untiy的scene空间和game空间为例,模型cube在scene内是处于世界坐标系内,通过摄像机或者说通过观察矩阵,最终转为在Game视窗内的观察坐标系
♥ 投影转换
投影转换在模型——视图转换
后用于顶点,明确了一个完成场景如何投影成屏幕上的最终图像,我们常用的两种投影即:正交投影和透视投影
♣ 透视矩阵
透视投影往往更加真实,和真实的屋里空间相同,近大远小,有透视效果
透视矩阵包含几个参数,同样以unity的透视相机为例,其参数包含远近平面的距离和左右上下裁剪平面的世界坐标,整体的形状呈现一个平头椎体
♣ 正交矩阵
正交投影和透视投影不同,在正交投影下的物体无论远近都会按照指定的尺寸绘制在屏幕上,常用于2D游戏
构建正交矩阵所用参数为视图空间内左右上下场景边界坐标,以及远近平面的位置,以unity正交相机为例,其形状为一个长方体
♠ 插值、直线、曲线、样条
寻找一系列已知点之间的值的过程叫做插值
♥ 插值
vec4 mix(vec4 A, vec4 B, float t)
这一快没啥好讲的,就是找两个向量中间的某个值,看书中例子点P就是向量A和向量t的和,平滑的改变t可以看到从A平滑的变换到B,GLSL有专门的一个接口处理线性插值
♥ 曲线
在真实世界中,对象以平滑的曲线移动并且平滑地加速、减速。一个曲线可用三个或更多控制点所表示。对大多数曲线来说,有超过三个控制点,其中两个是端点,其他的点定义了曲线的形状
上图是一个简单的贝塞尔曲线
A和C是曲线的端点,B点的位置可以定义曲线的形状
连接AB、BC,然后我们在两条直线上用简单的线性插值寻找一对新的点:D和E
连接DE,然后沿着它做插值计算寻找一个新的点:P
数学公式:
D = A + t(B - A)
E = B + t(C - B)
P = D + t(E - D)
随着插值t的不断变大,P点会沿着曲线从A移动到C
拥有两个控制点B和C的贝塞尔曲线
♥ 样条
一个样条实际上就是一个长的曲线,它由几个小的曲线(比如贝塞尔)组成,每个小的曲线都在局部范围内定义它的形状。其中表示各个小曲线端点的控制点至少是被各个线段共享的(就是这些端点将小曲线组成一个样条。这样的控制点被称为焊接点 welds,它们之间的控制点被称为绳结 knots),并且通常内部控制点的一个或多个也在邻近线段间以某种方式共享或联系起来。任意数目的曲线都可以这种方式连接起来,可以形成任意长的路径
上图便是由10个控制点组成的一个样条,它包含了三个贝塞尔曲线,为了使动作平滑且流畅,对于控制点位置的选择必须非常小心。插值点P的值的变化速率(即P的速度)随着关于t的曲线方程而不同。如果这个函数是非连续的,那P就会突然变向,然后我们的对象就会到处乱跳
♠ 参考文章
OpenGL超级宝典(第7版)笔记16 向量 矩阵 它们都有什么用
♠ 推送
- Github
https://github.com/KingSun5
♠ 结语
本章并没有特别深奥的数学问题,很多用到的东西像矩阵相乘、点乘、叉乘、四元数在各种数学库中均有封装,知道这个东西并且会用就可以了,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。
本文属于原创文章,转载请著名作者出处并置顶!!