这一章很多内容都是高中数学和线性代数的知识,所以只写了它们当中没有讲过的内容。
主要是梳理了各个空间和各个空间之间的变换。
对于线性代数可以看看参考里的31B1线性代数的本质和games101L3。
一、背景:农场游戏
为了让读者更加理解数学计算的几何意义,我们先来假定一个场景。现在,假设我们正在开发一款卡通风格的农场游戏。在这个游戏里,玩家可以在农场里养很多可爱的奶牛。与普通农场游戏不同的是,我们的主角不是玩家,而是一头牛——妞妞,如图4.1所示。妞如不仅长得壮,它对很多事情都充满了好奇心。
读者:为什么游戏主角不是玩家呢?我们:因为我们的策划就是这么任性。
在故事的一开始,农场世界是没有数学概念的。通过下面的学习,我们会见证数学给这个世界带来了怎样翻天覆地的变化。
二、笛卡尔坐标系
在游戏制作中,我们使用数学绝大部分都是为了计算位置、距离和角度等变量。而这些计算大部分都是在笛卡儿坐标系(Cartesian Coordinate System)下进行的。
1、二维笛卡尔坐标系
图4.3显示了一个二维笛卡儿坐标系。它是不是很像一个棋盘呢?
一个二维的笛卡儿坐标系包含了两个部分的信息:
一个特殊的位置,即原点,它是整个坐标系的中心。
两条过原点的互相垂直的矢量,即x轴和y轴。这些坐标轴也被称为是该坐标系的基矢量。
虽然在图4.3中x轴和y轴分别是水平和垂直方向的,但这并不是必须的。想象把上面的坐标系整体向左旋转30°。而且,虽然图中的x轴指向右、y轴指向上,但这也并不是必须的。例如,在2.3.4节屏幕映射中,OpenGL和 DirectX使用了不同的二维笛卡儿坐标系,如图4.4所示。
把笛卡儿坐标系引入到奶牛农场后,所有的一切都变得清晰起来。我们把奶牛农场的中心定义成坐标原点,而把地理方向中的东、北定义成坐标轴方向。现在,如果奶牛再问:“妞妞,你现在在哪里啊?”妞如就可以回答说:“我在东1米、北3米的地方。”,如图4.5所示。
2、三维笛卡尔坐标系
在三维笛卡儿坐标系中,我们需要定义3个坐标轴和一个原点。图4.6显示了一个三维笛卡儿坐标系。
这3个坐标轴也被称为是该坐标系的基矢量( basis vector)。通常情况下,这3个坐标轴之间是互相垂直的,且长度为1,这样的基矢量被称为标准正交基(orthonormal basis),但这并不是必须的。例如,在一些坐标系中坐标轴之间互相垂直但长度不为1,这样的基矢量被称为正交基(orthogonal basis)。如非特殊说明,本书默认情况下使用的坐标轴指的都是标准正交基。
和二维笛卡儿坐标系类似,三维笛卡儿坐标系中的坐标轴方向也不是固定的,即不一定是像图4.6中那样的指向。但这种不同导致了两种不同种类的坐标系:左手坐标系(left-handedcoordinate space)和右手坐标系(right-handed coordinate space)。
3、左手坐标系和右手坐标系
对于二维笛卡尔坐标系,我们始终可以通过旋转得到所有等价的二维笛卡尔坐标系,但是对于三维笛卡尔坐标系,仅靠旋转无法使两个不同朝向的坐标系重合。于是就有了两种不同的三维笛卡尔坐标系:左手坐标系和右手坐标系。
如果两个坐标系具有相同的旋向性(handedness),那么我们就可以通过旋转的方法来让它们的坐标轴指向重合。但是,如果它们具有不同的旋向性(例如坐标系A属于左手坐标系,而坐标系B属于右手坐标系),那么就无法达到重合的目的。
那么,为什么叫左手坐标系和右手坐标系呢?和手有什么关系?这是因为,我们可以利用我们的双手来判断一个坐标系的旋向性。请读者举起你的左手,用食指和大拇指摆出一个“L”的手势,并且让你的食指指向上,大拇指指向右。现在,伸出你的中指,不出意外的话它应该指向你的前方(如果你一定要展示自己骨骼惊奇的话我也没有办法)。恭喜你,你已经得到了一个左手坐标系了!你的大拇指、食指和中指分别对应了+x、+y 和+z轴的方向,如图4.7所示。
同样,读者可以通过右手来得到一个右手坐标系。举起你的右手,这次食指仍然指向上,中指指向前方,不同的是,大拇指将指向左侧,如图4.8所示。
另外一个确定是左手还是右手坐标系的方法是,判断前向(forward)的方向。请读者坐直,向右伸直你的右手,此时右手方向就是x轴的正向,而你的头顶向上的方向就是y轴的正向。这时,如果你的正前方的方向是z轴的正向,那么你本身所在的坐标系就是一个左手坐标系;如果你的正前方的方向对应的是z轴的负向,那么这就是一个右手坐标系。
在不同旋向性的坐标系中,对应不同的旋转方式。具体来说,左手坐标系对应左手法则(left-hand rule),右手坐标系对应右手法则(right-hand rule)。拿出对应手,握拳,伸出大拇指指向旋转轴的正方向,那么旋转的正方向就是剩下4个手指的弯曲方向。反过来也可以通过拇指的弯曲方向判断旋转轴的方向。
用左手法则和右手法则来判断旋转正方向
从图中可以看出,左手坐标系中,旋转正方向是顺时针的,而在右手坐标系中,旋转正方向是逆时针的。
同时,做右手坐标系之间是可以进行相互转换的,最简单的方式是把其中一个轴翻转并保持其他两个周不变。
左右手坐标系之间是可以进行互相转换的。最简单的方法就是把其中一个轴反转,并保持其他两个轴不变。
可以看出,为了达到同样的视觉效果(这里指把妞妞移动到视觉上的同一个位置),左右手坐标系在z轴上的移动以及旋转方向是不同的。如果使用相同的数学运算(指均向z轴某方向移动或均朝旋转正方向旋转等),那么得到的视觉效果就是不一样的。因此,如果我们需要从左手坐标系迁移到右手坐标系,并且保持视觉上的不变,就需要进行一些转换。读者可以参见本章最后的扩展阅读部分。
4、Unity使用的坐标系
对于一个需要可视化虚拟的三维世界的应用(如 Unity)来说,它的设计者就要进行一个选择。对于模型空间和世界空间,Unity使用的是左手坐标系。这可以从 Scene视图的坐标轴显示看出来,如图4.12所示。这意味着,在模型空间中,一个物体的右侧(right)、上侧(up)和前侧(forward)分别对应了x轴、y轴和z轴的正方向。
但对于观察空间来说,Unity使用的是右手坐标系。观察空间,通俗来讲就是以摄像机为原点的坐标系。在这个坐标系中,摄像机的前向是z轴的负方向,这与在模型空间和世界空间中的定义相反。也就是说,z轴坐标的减少意味着场景深度的增加,如图4.13所示。
5、练习题
这是本书中第一次出现练习题的地方,希望你可以快速解决它们!
(1)在非常流行的建模软件3ds Max 中,默认的坐标轴方向是:x轴正方向指向右方,y轴正方向指向前方,z轴正方向指向上方。那么它是左手坐标系还是右手坐标系?
(2)在左手坐标系中,有一点的坐标是(0,0,1),如果把该点绕y轴正方向旋转+90°,旋转后的坐标是什么?如果是在右手坐标系中,同样有一点坐标为(0,0,1),把它绕y轴正方向旋转+90°,旋转后的坐标是什么?
(3)在Unity中,新建的场景中主摄像机的位置位于世界空间中的(0,1,-10)位置。在不改变摄像机的任何设置(如保持Rotation为(0,0,0),Scale为(1,1,1))的情况下,在世界空间中的(0,1,0)位置新建一个球体,如图4.14所示。
在摄像机的观察空间下,该球体的z值是多少?在摄像机的模型空间下,该球体的z值又是多少?
三、点和矢量
1、点和矢量的区别
2、矢量运算
2.1、矢量和标量的乘法/除法
2.2、矢量的加法和减法
2.3、矢量的模
2.4、单位矢量
2.5、矢量的点积
2.6、矢量的叉积
2.7、练习题
又到了做练习的时候了,大家是不是都很激动!那么,赶紧拿起笔、拿起纸开始吧!
1.是非题
①一个矢量的大小不重要,我们只需要在正确的位置把它画出来就可以了。
2、点可以认为是位置矢量,这是通过把矢量的尾固定在原点得到的。
③选择左手坐标系还是右手坐标系很重要,因为这会影响叉积的计算。
2.计算下面的矢量运算:
1、 |2,7,31|
2、2.5(5,4,10)
3、(3,4)/2
4、对(5,12)进行归一化
5、(1,1,1)进行归一化
6、(7,4)+(3,5)
7、(9,4,13)-(15,3,11)
3.假设,场景中有一个光源,位置在(10,13,11)处,还有一个点(2,1,1),那么光源距离该点的巨离是多少?
4.计算下面的矢量运算:
1、(4,7).(3,9)
2、(2,5,6).(3,1,2)-10
3、 0.5(-3,4).(-2,5)
4、(3,-1,2)×(-5,4,1)
5、(-5,4,1)×(3,-1,2)
5.已知矢量a和矢量b,a的模为4,b的模为6,它们之间的夹角为60°。计算:
1、a.b
2、|axb| 提示:sin 60°= 根号3/2≈0.866,cos60°=1/2=0.5。
6.假设,场景中有一个NPC,它位于点p处,它的前方(forward)可以使用矢量v来表示。
①如果现在玩家运动到了点x处,那么如何判断玩家是在NPC的前方还是后方?请使用数学公式来描述你的答案。提示:使用点积。
②使用你在a中提到的方法,代入p=(4,2),v=(-3,4),X=(10,6)来验证你的答案。
③现在,游戏有了新的需求:NPC 只能观察到有限的视角范围,这个视角的角度是Q,也就是说NPC最多只能看到它前方左侧或右侧Q/2角度内的物体。那么,我们如何通过点积来判断NPC是否可以看到点x呢?
7.在渲染中我们时常会需要判断一个三角面片是正面还是背面,这可以通过判断三角形的3个顶点在当前空间中是顺时针还是逆时针排列来得到。给定三角形的3个顶点p、p和 ps,如何利用叉积来判断这3个点的顺序是顺时针还是逆时针?假设我们使用的是左手坐标系,且p1、p2和p3都位于xy平面(即它们的z分量均为0),人眼位于z轴的负方向上,向z轴正方向观察,如图4.29所示。
四、矩阵
1、矩阵的定义
2、和矢量联系起来
3、矩阵运算
3.1、矩阵和标量的乘法
3.2、矩阵和矩阵的乘法
4、特殊的矩阵
4.1、方块矩阵
4.2、单位矩阵
4.3、转置矩阵
4.4、逆矩阵
4.5、正交矩阵
5、行矩阵还是列矩阵
6、练习题
1.判断下面矩阵的乘法是否存在。如果存在,计算它们的乘积。
2、判断下面的矩阵是否是正交矩阵
3.给定一个矢量(3,2,6),分别把它当成行矩阵和列矩阵与下面的矩阵相乘。考虑两种情况下得到的矢量结果是否一样。如果不一样,考虑如何得到相同的结果。
五、矩阵的几何意义:变换
1、什么是变换
2、齐次坐标
2.1、齐次坐标与仿射变换
通过线性代数的知识,我们知道线性变换可以由矩阵表示。
而线性变换包括缩放,旋转,错切,镜像和正交投影等,但不包括平移变换。
为了能用矩阵描述平移变换,我们引入齐次坐标。
2.2、齐次坐标
2.3、仿射变换(affine transform)
应用仿射变换即:
3、分解基础变换矩阵
4、平移矩阵
5、缩放矩阵
6、旋转矩阵
7、复合变换
六、坐标空间
1、为什么要使用这么多不同的坐标空间
我们需要在不同情况下使用不同的坐标空间,因为一些概念只有在特定的坐标空间才有意义,才更容易理解。这也是为什么在渲染中我们要使用这么多坐标空间。
2、坐标空间的变换
3、顶点的坐标空间变换过程
4、模型空间(model space)
模型空间又被称为对象空间(object space) 或局部空间(local space),它和某个模型或者是对象有关。每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。
在Unity中,模型空间使用左手坐标系,+z轴为前向。
模型空间的原点和坐标轴通常是由美术人员在建模软件里确定好的,当导入到Unity中,可以在顶点着色器中访问到模型的顶点信息,其中包含了每个顶点的坐标。这些坐标都是相对于模型空间中的原点(通常位于模型的重心)定义的。
在我们的农场游戏中,每个奶牛都有自己的模型坐标系。在模型坐标系中妞妞鼻子的位置是(0, 2, 4, 1)
5、世界空间(world space)
世界空间建立了我们所关心的最大的空间。例如,在农场游戏中世界空间指的就是农场。
通常,世界空间的原点在游戏空间的中心。
在Unity中,世界空间使用左手坐标系,但它的x轴、y轴和z轴是固定不变的。如果一个Transform没有任何父节点(parent),那么它的位置就是在世界坐标系中的位置。
农场游戏中的世界空间。世界空间的原点被放置在农场的中心。左下角显示了妞妞在世界空间中所做的变换。我们想要把妞妞的鼻子从模型空间变换到世界空间中
6、观察空间(view space)
观察空间也被称为摄像机空间(camera space)。观察空间可以认为是摄像机的模型空间。
摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点。
在Unity中,观察空间使用右手坐标系,-z为前向。
农场游戏中摄像机的观察空间。观察空间的原点位于摄像机处。注意在观察空间中,摄像机的前向是z轴的负方向(图中只画出了z轴正方向),这是因为Unity在观察空间中使用了右手坐标系。左下角显示了摄像机在世界空间中所做的变换。我们想要把妞妞的鼻子从世界空间变换到观察空间中
7、裁剪空间(clip space)
裁剪空间,也被称作齐次裁剪空间。用于变换的矩阵叫做裁剪矩阵(clip matrix) 也被称为投影矩阵(projection matrix)。
裁剪空间的目标是为了方便地对渲染图元进行裁剪,完全位于这块空间内部的图元将会被保留,完全位于这块空间外部的图元将会被剔除,而与这块空间边界相交的图元就会被裁剪。
其中,视锥体(view frustum) 决定了这块空间。视锥体指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。
视锥体有两种类型,这涉及两种投影类型:正交投影(orhographic projection) 和透视投影(perspective projection)。
透视投影(左图)和正交投影(右图)。左下角分别显示了当前摄像机的投影模式和相关属性
从图中可以发现,在透视投影中,地板上的平行线并不会保持平行,离摄像机越近网格越大,
离摄像机越远网格越小。而在正交投影中,所有的网格大小都一样, 而且平行线会一直保待平行。
可以注意到,透视投影模拟了人眼看世界的方式,而正交投影则完全保留了物体的距离和角度。
因此,在追求真实感的3D游戏中我们往往会使用透视投影,而在一些2D 游戏或渲染小地图等其
他HUD元素时,我们会使用正交投影。
在视锥体的6块裁剪平面中,有两块比较特殊,分别被称为近裁剪平面(near clip plane) 和远裁剪平面(far clip plane)。它们决定了摄像机可以看到的深度范围。
两种投影方式对应的视锥体也不同,具体如下:
视锥体和裁剪平面。左图显示了透视投影的视锥体,右图显示了正交投影的视锥体
投影矩阵有两个目的:
- 为投影做准备。投影矩阵并没有进行真正的投影工作,真正的投影工作发生在后面的齐次除法(homogenous division)。
- 对x、y、z分量进行缩放。经过投影矩阵的缩放后,我们可以直接使用w 分量作为一个范围值,
如果x 、y 、z 分量都位于这个范围内,就说明该顶点位千裁剪空间内。
8、屏幕空间(screen space)
屏幕空间是一个二维空间,也就是真正像素所在的位置,而不是虚拟的三维坐标。
在空间转换中,由裁剪空间转换得到。这个过程可以理解成两个步骤:
- 标准齐次除法(homongeous division),也被称为透视除法(perspective division)。就是用齐次坐标系的w分量去除x、y、z分量,得到归一化的设备坐标(NDC)。NDC空间是
的立方体。
经过齐次除法后,透视投影的裁剪空间会变换到一个立方体
经过齐次除法后,正交投影的裁剪空间会变换到一个立方体 - 根据变换后的x和y坐标来映射输出窗口的对应像素坐标。
9、总结
七、坐标变换与MVP矩阵
坐标变换指的一个顶点从模型空间变换到屏幕坐标的过程,经过的变化如下图:
渲染流水线中顶点的空间变换过程
顶点着色器的最基本的任务就是把顶点坐标从模型空间转换到裁剪空间中。这对应了上图中的前三个顶点变换过程。而在片元着色器中,我们通常也可以得到该片元在屏幕空间的像素位置。
其中,把模型变换、观察变换、投影变换称为MVP变换。
在Unity中,坐标系的旋向性也随着变化发生了改变,如下图所示:
Unity中各个坐标空间的旋向性
那么,一个顶点从模型空间变换到屏幕空间即:
1、模型变换(model transform)
模型变换是指是将顶点坐标从模型空间变换到世界空间中,也就是说把一个物体(game obejct)放到场景(scene)中。
所以,模型变换对应的矩阵为:
即先进行缩放,再进行旋转,最后进行平移。因为矩阵没有交换律,所以改变顺序对应总变换也会改变。
对一个顶点应用模型变换,得到:
2、观察变换(view transform)
观察变换也被称作视图变换,它是指将顶点坐标从世界坐标变换到观察空间中,即变换摄像机位置到原点,上方为y,观察方向为-z。
观察变换,摄像机移动到原点
模型变换和视图变换经常一起被叫做模型视图变换(modelview translation)。
3、投影变换(projection transform)
透视投影是指将顶点从观察空间转换到裁剪空间。
根据投影类型,可以分为透视投影和正交投影。
左边:透视投影 右边:正交投影
先定义可视空间如下:
3.1、正交投影
3.2、透视投影
透视投影就是最类似人眼所看东西的方式,遵循近大远小。这一点可以从它的视锥体中看出。
透视投影视锥体看起来像顶部切割后平行于底部的金字塔的实体形状。这是透视摄像机可以看到和渲染的区域的形状。
视锥体,左边是透视投影,右边是正交投影
下面的“视锥体”指“透视投影视锥体“。
我们定义一个视锥体,需要两个信息:长宽比(Aspect)和垂直的角度(FOV)
利用视锥体,我们可以得到物体的长宽高:
回到透视投影,透视投影的思路是用“远平面”和“近平面”框住物体,先把“远平面”向“近平面“挤压,然后做一次正交投影。
即透视投影分为两步:
- 将透视投影转换为正交投影
- 将正交投影转换到标准立方体
任何不满足上述条件的图元都要被剔除或者裁剪。
在透视投影中,投影矩阵对顶点进行了缩放。图3.38中标注了4个关键点经过投影矩阵变换后的结果。从这些结果可以看出x、y、z和w分量的范围发生的变化
从图中注意到,裁剪矩阵会改变空间的旋向性:空间从右手坐标系变换到左手坐标系。
4、视口变换(viewport transform)
视口变换是指将顶点从裁剪空间转换到屏幕空间中,即将处于标准平面映射到屏幕分辨率范围之内。
5、法线变换
进行非统一缩放时,如果使用和变换顶点相同的变换矩阵来变换法线,就会得到错误的结果,即变换后的法线方向与平面不再垂直
八、Unity Shader的内置变量(数学篇)
使用Unity 写Shader的一个好处在于,它提供了很多内置的参数,这使得我们不再需要自己手动计算一些值。本节将给出 Unity 内置的用于空间变换和摄像机以及屏幕参数的内置变量。这些内置变量可以在UnityShader Variables.cginc文件中找到定义和说明。
1、变换矩阵
2、摄像机和屏幕参数
九、答疑解惑
1、使用3X3还是4X4的变换矩阵
对于线性变换(例如旋转和缩放)来说,仅使用3×3的矩阵就足够表示所有的变换了。但如果存在平移变换,我们就需要使用4×4的矩阵。因此,在对顶点的变换中,我们通常使用4×4的变换矩阵。当然,在变换前我们需要把点坐标转换成齐次坐标的表示,即把顶点的w分量设为1。而在对方向矢量的变换中,我们通常使用3×3的矩阵就足够了,这是因为平移变换对方向矢量是没有影响的。
2、CG中的矢量和矩阵类型
3、Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS
十、扩展阅读
计算机图形学使用的数学还有很多,本书仅涵盖了其中非常小的一部分。如果读者想要深入学习这些知识的话,书籍12是非常好的图形学数学学习资料,读者可以在那里找到更多类型的变换及其数学表示。关于如何从左手坐标系转换到右手坐标系同时又保持视觉效果一样,可以参考资料13]。关于如何得到线性的深度值可以参考资料t。
[1]Fletcher Dunn,lan Parberry.3D Math Primer for Graphics and Game Development (2ndEdition). November 2, 2011 by AK Peters/CRC Press。
[2] Eric Lengyel. Mathematics for 3D game programming and computer graphics (3rd Edition).2011 by Charles River Media。
[3] David Eberly. Conversion of Left-Handed Coordinates to Right-Handed Coordinates。[4] http://www.humus.name/temp/Linearize%20depth.txt。
十一、练习题答案
1、2.5习题
2、3.7习题
3、4.6习题
十二、参考
3Blue1Brown线性代数的本质 笔记
games101 L3变换 笔记
games101 L4变换 笔记
王江荣:OpenGL, DirectX以及Unity对应投影矩阵