计算机图形学之所以深奥难懂,很大原因是在于它是建立在虚拟世界上的数学模型。数学渗透到图形学的方方面面,当然也包括Shader。在学习Shader 的过程中,我们最常使用的就是矢量和矩阵(即数学的分支之一一一线性代数)。
很多读者认为图形学中的数学复杂难懂。的确,一些数学模型在初学者看来晦涩难懂。但很多情况下,我们需要打交道的只是一些基础的数学运算,而只要掌握了这些内容, 就会发现很多事情可以迎刃而解。我们在研究和学习他人编写的Shader 代码时,也不再会疑问: “他为什么要这么写”, 而是“哦,这里就是使用矩阵进行了一个变换而己。”
为了让读者能够参与到计算中来,而不是填压式地阅读,在一些小节的最后我们会给出一些练习题。练习题的答案会在本章最后给出(不要偷看答案! )。需要注意的是,这些练习题并不是可有可无的,我们并非想利用题海战术来让读者掌握这些数学运算,而是想利用这些练习题来阐述一些容易出错或实践中常见的问题。通过这些练习题,读者可以对本节内容有更加深刻的理解。

那么,拿起笔来,让我们一起走进数学的世界吧!

4.1 背景:农场游戏

为了让读者更加理解数学计算的几何意义,我们先来假定一个场景。现在,假设我们正在开发一款卡通风格的农场游戏。在这个游戏里,玩家可以在农场里养很多可爱的奶牛。与普通农场游戏不同的是,我们的主角不是玩家,而是一头牛一一妞妞, 如图4.1 所示。妞妞不仅长得壮,它对很多事情都充满了好奇心。

读者: 为什么游戏主角不是玩家呢? 我们: 因为我们的策划就是这么任性。
在故事的一开始,农场世界是没有数学概念的。通过下面的学习,我们会见证数学给这个世界带来了怎样翻天覆地的变化。

4.2 笛卡儿坐标系

在游戏制作中,我们使用数学绝大部分都是为了计算位置、距离和角度等变量。而这些计算大部分都是在笛卡儿坐标系( Cartesian Coordinate System ) 下进行的。这个名字来源于法国伟大的哲学家、物理学家、心理学家、数学家笛卡儿(Rene Descartes )。
那么,我们为什么需要笛卡儿坐标系呢?有这样一个传说,在笛卡儿的一生中,他每天的上午时光儿乎都是在床上度过的。笛卡儿并没有把这段时间用在睡懒觉上,而是思考了很多关于数学和哲学上的问题。有一天,笛卡儿发现一只苍蝇在天花板上爬来爬去,他观察了很长一段时间。笛卡儿想: 我要如何来描述这只苍蝇的运动轨迹呢?最后,笛卡儿意识到,他可以使用这只苍蝇距离房间内不同墙面的位置来描述,如图4.2 所示。他从床上起身,写下了他的发现。然后,他试图描述一些点的位置,正如他要描述苍蝇的位置一样。最后,笛告儿就发明了这个坐标平面。而这个坐标平面后来逐渐发展,就形成了坐标系系统。人们为了纪念笛卡儿的工作,就用他的名字来给这种坐标系进行命名。

4.2.1 二维笛卡儿坐标系

事实上,读者很可能一直在用二维笛卡儿坐标系,尽管你可能并没有听过笛卡儿这个名词。
你还记得在《哈利波特与魔法石》电影中,哈利和罗恩大战奇洛教授的魔法棋盘吗?这里的国际象棋棋盘也可以理解成是一个二维的笛卡儿坐标系。
图4.3 显示了一个二维笛卡儿坐标系。它是不是很像一个棋盘呢?

一个二维的笛卡儿坐标系包含了两个部分的信息:

  • 一个特殊的位置,即原点,它是整个坐标系的中心。
  • 两条过原点的互相垂直的矢量,即x 轴和y 轴。这些坐标轴也被称为是该坐标系的基矢量。

虽然在图4.3 中 x 轴和y 轴分别是水平和垂直方向的,但这并不是必须的。想象把上面的坐标系整体向左旋转30。。而且,虽然图中的x 轴指向右、y 轴指向上,但这也并不是必须的。例如,在2.3.4 节屏幕映射中, OpenGL 和DirectX 使用了不同的二维笛卡儿坐标系,如图4.4 所示。

而有了这个坐标系我们就可以精确地定位一个点的位置。例如,如果说:“在(1, 2)的位置上画一个点。”那么相信读者肯定知道这个位置在哪里。

我们来看一下笛卡儿坐标系给奶牛农场带来了什么变化。在没有笛卡儿坐标系的时候,奶牛们根本没有明确的位置概念。如果一头奶牛问:“妞妞,你现在在哪里啊?”姐姐只能回答说“我在这里”或者“我在那里”这些模糊的词语。但那头奶牛永远不会知道妞妞的确切位置。而把笛卡儿坐标系引入到奶牛农场后,所有的一切都变得清晰起来。我们把奶牛农场的中心定义成坐标原点,而把地理方向中的东、北定义成坐标轴方向。现在,如果奶牛再问:“姐姐,你现在在哪里啊?”妞妞就可以回答说: “我在东1米、北3米的地方。”,如图4.5 所示。

4.2.2 三维笛卡儿坐标系

在上面一节中,我们已经了解了二维笛卡儿坐标系。可以看出, 二维笛卡儿坐标系实际上是比较简单的。那么, 三维比二维只多了一个维度,是不是也就难了50%而己呢?
不幸的是,答案是否定的。三维笛卡儿坐标系相较于二维来说要复杂许多,但这并不意味着很难学会它。对人类来说,我们生活的世界就是三维的,因此对于理解更低维度的空间(一维和二维)是比较容易的。而对于同等维度的一些概念:理解起来难度就大一些:对于更高维度的空间(如四维空间),理解难度就更大了。

在三维笛卡儿坐标系中,我们需要定义3 个坐标轴和一个原点。图4.6 显示了一个三维笛卡儿坐标系。

这3个坐标轴也被称为是该坐标系的基矢量(basis vector)。通常情况下,这3 个坐标轴之间是互相垂直的,且长度为1,这样的基矢量被称为标准正交基( orthonormal basis ),但这并不是必须的。例如,在一些坐标系中坐标轴之间互相垂直但长度不为1,这样的基矢量被称为正交基(orthogonal basis ) 。如非特殊说明,本书默认情况下使用的坐标轴指的都是标准正交基。

读者:正交这个词是什么意思呢?

我们:正交可以理解成互相垂直的意思。在下面矩阵的内容中,我们还会看到正交矩阵的概念。
和二维笛卡儿坐标系类似, 三维笛卡儿坐标系中的坐标轴方向也不是固定的, 即不一定是像图4 .6 中那样的指向。但这种不同导致了两种不同种类的坐标系:左手坐标系( left-handed coordinate space )和右手坐标系( right-handed coordinate space)

4.2.3 左手坐标系和右手坐标系

为什么在三维笛卡儿坐标系中要区分左手坐标系和右手坐标系,而二维中就没有这些烦人的事情呢?这是因为,在二维笛卡儿坐标系中, x 轴和y 轴的指向虽然可能不同,就如我们在图4.4中看到的一样。但我们总可以通过一些旋转操作来使它们的坐标轴指向相同。以图4.4 中OpenGL和DirectX 使用的坐标系为例,为了把右侧的坐标轴指向转换到左侧那样的指向,我们可以首先对右侧的坐标系顺时针旋转180。此时它的y 轴指向上,而x 轴指向左。然后,我们再把整个纸面水平翻转一下,就可以把x 轴翻转到指向右了,此时左右两侧的坐标轴指向就完全相同了。从这种意义上来说,所有的二维笛卡儿坐标系都是等价的。
但对于三维笛卡儿坐标系,靠这种旋转有时并不能使两个不同朝向的坐标系重合。例如,在图4.6 中,+z 轴的方向指向纸面的内部,如果有另一个三维笛卡儿坐标系,它的+z 轴指向纸面外部, x 轴和y 轴保持不变,那么我们可以通过旋转把这两个坐标轴重合在一起吗?答案是否定的。
我们总可以让其中两个坐标轴的指向重合,但第三个坐标轴的指向总是相反的。
也就是说, 三维笛卡儿坐标系并不都是等价的。因此,就出现了两种不同的三维坐标系: 左手坐标系和右手坐标系。如果两个坐标系具有相同的旋向性( handedness ),那么我们就可以通过旋转的方法来让它们的坐标轴指向重合。但是,如果它们具有不同的旋向性〈例如坐标系A 属于左手坐标系,而坐标系B 属于右手坐标系〉,那么就无法达到重合的目的。
那么,为什么叫左手坐标系和右手坐标系呢?和手有什么关系?这是因为,我们可以利用我们的双手来判断一个坐标系的旋向性。请读者举起你的左手,用食指和大拇指摆出一个“L”的手势,并且让你的食指指向上,大拇指指向右。现在,伸出你的中指,不出意外的话它应该指向你的前方(如果你?定要展示自己骨锵惊奇的话我也没有办法〉。恭喜你, 你己经得到了一个左手坐标系了!你的大拇指、食指和中指分别对应了+x 、+y 和+z 轴的方向,如图4.7 所示。

同样,读者可以通过右手来得到一个右手坐标系。举起你的右手,这次食指仍然指向上,中指指向前方,不同的是,大拇指将指向左侧,如图4.8 所示。

正如我们之前所说,左手坐标系和右手坐标系之间无法通过旋转来同时使它们的3 个坐标轴指向重合,如果你不信,你现在可以拿自己的双手来试验一下。
另外一个确定是左手还是右手坐标系的方法是,判断前向( forward ) 的方向。请读者坐直,向右伸直你的右手,此时右手方向就是x 轴的正向,而你的头顶向上的方向就是y 轴的正向。这时,如果你的正前方的方向是z 轴的正向,那么你本身所在的坐标系就是一个左手坐标系: 如果你的正前方的方向对应的是z 轴的负向,那么这就是一个右手坐标系。

除了坐标轴朝向不同之外,左手坐标系和右手坐标系对于正向旋转的定义也不同,即在初高中物理中学到的左手法则(left-hand rule ) 和右手法贝lj (right-hand rule )。假设现在空间中有一条直线,还有一个点,找们希望把这个点以该直线为旋转轴旋转某个角度,比如旋转30度。读者可以拿一支笔当成这个旋转轴,再拿自己的手当成这个需要旋转的点,可以发现,我们有两个旋转方向可以选择。那么,我们应该往哪个方向旋转呢?这意味着,我们需要在坐标系中定义一个旋转的正方向。在左手坐标系中,这个旋转正方向是由左手法则定义的,而在右手坐标系中则是由右手法则定义的。
在左手坐标系中,我们可以这样来应用左手法则:还是举起你的左手,握拳,伸出大拇指让它指向旋转轴的正方向,那么旋转的正方向就是剩下4 个手指的弯曲方向。在右手坐标系中,使用右手法则对旋转正方向的判断类似,如图4.9 所示。

从图4.9 中可以看出,在左手坐标系中,旋转正方向是顺时针的,而在右手坐标系中,旋转正方向是逆时针的。
左右手坐标系之间是可以进行互相转换的。最简单的方法就是把其中一个轴反转,并保持其他两个轴不变。

对于开发者来说,使用左手坐标系还是右手坐标系都是可以的,它们之间并没有优劣之分。无论使用哪种坐标系,绝大多数情况下并不会影响底层的数学运算,而只是在映射到视觉上时会有差别(见练习题2 )。这是因为,一个点或者旋转在空间内来说是绝对的。一些较真儿的读者可能会看不惯“绝对”这个词: “你怎么能忽略相对论呢?这世上一切都是相对的!”这些读者请容我解释。这里所说的绝对是说, 在我们所关心的最广阔的空间中,这些值是绝对的。例如我说,把你的书从桌子的左边移到右边,你不会对这个过程产生什么疑问, 此时我们关心的整个空间就是桌子这个空间,而在这个空间中,书的运动是绝对的。但是,在数学的世界中,我们需要使用一种数学模型来精确地描述它们,这个模型就是坐标系。一旦有了坐标系,每个点的位置就不再是绝对的,而是相对于这个坐标系来说的。这种相对关系导致,即便从数学表示上来说两种表示方式完全一样,但从视觉上来说是不一样的。
我们可以在奶牛农场的例子中体会左手坐标系和右手坐标系的分别。我们假设,妞妞想要到一个新的地方,因为那里的草很美味。妞妞知道到达这个目标点的“绝对路径”是怎样的,如图4.10 所示。

我们可以分别在一个左手坐标系和右手坐标系中描述这样一次运动, 即使用数学表达式来描述它。我们会发现,在不同的坐标系中描述这样同一次运动是不一样的,如图4.11 所示。

在左手坐标系中, 3 个坐标轴的朝向如图4.11 左图所示。妞妞首先向x 轴正方向平移1个单位,然后再向z 轴负方向移动4 个单位,最后朝旋转的正方向旋转60。。而在右手坐标系中,+z轴的方向和左手坐标系中刚好相反,因此妞妞首先向x 轴正方向平移1个单位(与左手坐标系中的移动一致),然后再向z 轴正方向移动4 个单位( 与左手坐标系中的移动相反) ,最后朝旋转的负方向旋转60。(与左手坐标系中的旋转相反)。
可以看出,为了达到同样的视觉效果(这里指把妞妞移动到视觉上的同一个位置),左右手坐标系在z 轴上的移动以及旋转方向是不同的。如果使用相同的数学运算(指均向z 轴某方向移动或均朝旋转正方向旋转等〉,那么得到的视觉效果就是不一样的。因此,如果我们需要从左手坐标系迁移到右手坐标系,并且保持视觉上的不变,就需要进行一些转换。读者可以参见本章最后的扩展阅读部分。

4.2.4 Unity 使用的坐标系

对于一个需要可视化虚拟的三维世界的应用(如Unity )来说,它的设计者就要进行一个选择。对于模型空间和世界空间(在4.6 节中会具体讲解这两个空间是什么) , Unity 使用的是左手坐标系。这可以从Scene 视图的坐标轴显示看出来,如图4.12 所示。这意味着,在模型空间中,一个物体的右侧( right)、上侧( up )和前侧( forward )分别对应了x 轴、y 轴和z 轴的正方向。

但对于观察空间来说, Unity 使用的是右手坐标系。观察空间,通俗来讲就是以摄像机为原点的坐标系。在这个坐标系中,摄像机的前向是z 轴的负方向,这与在模型空间和世界空间中的定义相反。也就是说, z 轴坐标的减少意味着场景深度的增加,如图4.13 所示。

关于Unity 中使用的坐标系的旋向性,我们会在4.5.9 节中详细地讲解。

4.2.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 值又是多少?

4.3 点和矢量

点( point ) 是n 维空间〈游戏中主要使用二维和三维空间〉中的一个位置, 它没有大小、宽度这类概念。在笛卡儿坐标系中,我们可以使用2 个或3 个实数来表示一个点的坐标,如P=(Px, Py)表示二维空间的点, P=(Px, Py, Pz)表示三维空间中的点。
矢量( vector,也被称为向量〉的定义则复杂一些。在数学家看来,矢量就是一串数字。你可能要问了, 点的表达式不也是一串数字吗?没错,但矢量存在的意义更多是为了和标量(scalar)区分开来。通常来讲,矢量是指n 维空间中一种包含了模( magnitude ) 和方向( direction ) 的有向线段,我们通常讲到的速度(velocity 〉就是一种典型的矢量。例如,这辆车的速度是向南80km/h
(向南指明了矢量的方向, 80km/h 指明了矢量的模〉。而标量只有模没有方向, 生活中常常说到的距离( distance )就是一种标量。例如, 我家离学校只有200m (200m 就是一个标量)。
具体来讲。

  • 矢量的模指的是这个矢量的长度。一个矢量的长度可以是任意的非负数。
  • 矢量的方向则描述了这个矢量在空间中的指向。

矢量的表示方法和点类似。我们可以使用v=(x,y)来表示二维矢量,用v=(x,y,z)来表示三维矢量,用v=(x, y, z, w)来表示四维矢量。为了方便阐述, 我们对不同类型的变量在书写和印刷上使用不同的样式。

  • 对于标量,我们使用小写字母来表示,如a,b, x, y, z, θ, α等。
  • 对于矢量, 我们使用小写的粗体字母来表示,如a , b , u, v 等。
  • 对于后面要学习的矩阵,我们使用大写的粗体字母来表示,如A, B, S, M, R 等。

在图4.15 中, 一个矢量通常由一个箭头来表示。我们有时会讲到一个矢量的头(head ) 和尾(tail)。矢量的头指的是它的箭头所在的端点处, 而尾指的是另一个端点处,如图4.15 所示。

那么一个矢量要放在哪里呢?从矢量的定义来看,它只有模和方向两个属性,并没有位置信息。这听起来很难理解, 但实际上在生活中我们总是会和这样的矢量打交道。例如,当我们讲到一个物体的速度时,可能会这样说“那个小偷正在以100km/h的速度向南逃窜”(快抓住他!〉,这里的“以100km/h的速度向南”就可以使用一个矢量来表示。通常,矢量被用于表示相对于某个点的偏移

( displacement ), 也就是说它是一个相对量。只要矢量的模和方向保持不变, 无论放在哪里,都是同一个矢量。

4.3.1 点和矢量的区别

回顾一下,点是一个没有大小之分的空间中的位置, 而矢量是一个有模和方向但没有位置的量。从这里看,点和矢量具有不同的意义。但是,从表示方式上两者非常相似。
在上一节中我们提到, 矢量通常用于描述偏移量, 因此, 它们可以用于描述相对位置,即相对于另一个点的位置,此时矢量的尾是一个位置,那么矢量的头就可以表示另一个位置了。而一个点可以用于指定空间中的一个位置〈即相对于原点的位置〉。如果我们把矢量的尾固定在坐标系原点, 那么这个矢量的表示就和点的表示重合了。图4.16 表示了两者之间的关系。

尽管上面的内容看起来显而易见,但区分点和矢量之间的不同是非常重要的,尽管它们在数学表达式上是一样的,都是一串数字而已。如果一定要给它们之间建立一个联系的话,我们可以认为,任何一个点都可以表示成一个从原点出发的矢量。为了明确点和矢量的区别,在本书后面的内容中,我们将用于表示方向的矢量称为方向矢量。

4.3.2 矢量运算

在下面的内容里,我们将给出一些最常见的矢量运算。幸运的是,这些运算大多很好理解。
对于每种运算,我们会先给出数学上的描述,然后再给出几何意义上的解释。同样,为了让读者加深印象,我们会在最后给出一些练习题。相信读完本节后,你一定可以快速地解泱它们!
1 矢量和标量的乘法/除法
还记得吗?标量是只有模没有方向的量,虽然我们不能把矢量和标量进行相加/相减的运算(想象一下,你会把速度和距离相加吗〉,但可以对它们进行乘法运算,结果会得到一个不同长度且可能方向相反的新的矢量。
公式非常简单,我们只需要把矢量的每个分量和标量相乘即可:
注意,对于乘法来说,矢量和标量的位置可以互换。但对于除法,只能是矢量被标量除, 而不能是标量被矢量除,这是没有意义的。从几何意义上看,把一个矢量v 和一个标量k 相乘,意味着对矢量v 进行一个大小为 lkl 的缩放。例如,如果想要把一个矢量放大两倍,就可以乘以2 。当k<0 时,矢量的方向也会取反。图4.17 显示了这样的一些例子。
2. 矢量的加法和减法
我们可以对两个矢量进行相加或相减,其结果是一个相同维度的新矢量。
我们只需要把两个矢量的对应分量进行相加或相减即可。公式如下:
需要注意的是, 一个矢量不可以和一个标量相加或相减,或者是和不同维度的矢量进行运算。
从几何意义上来看,对于加法,我们可以把矢量a 的头连接到矢量b 的尾,然后画一条从a的尾到b 的头的矢量,来得到a 和b 相加后的矢量。也就是说,如果我们从一个起点开始进行了一个位置偏移a, 然后又进行一个位置偏移b,那么就等同于进行了一个a+b 的位置偏移。这被称为矢量加法的三角形定则(triangle rule )。矢量的减法是类似的,如图4.18 所示。
3. 矢量的模
正如我们之前讲到的一样,矢量是有模和方向的。矢量的模是一个标量, 可以理解为是矢量在空间中的长度。它的表示符号通常是在矢量两旁分别加上一条垂直线(有的文献中会使用两条垂直线)。三维矢量的模的计算公式如下:
我们可以从几何意义来理解上述公式。对于二维矢量来说,我们可以对任意矢量构建一个三角形,如图4.20 所示。
从图4.20 可以看出,对于二维矢量,其实就是使用了勾股定理,矢量的两个分量的绝对值对应了三角形两个直角边的长度,而斜边的长度就是矢量的模。

4. 单位矢量
在很多情况下,我们只关心矢量的方向而不是模。例如,在计算光照模型时,我们往往需要得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长。在这些情况下,我们就需要计算单位矢量( unit vector )
单位矢盘指的是那些模为1 的矢量。单位矢量也被称为被归一化的矢量( normalized vector)。对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化(normalization)
给定任意非零矢量v,我们可以计算和v 方向相同的单位矢量。在本书中,我们通过在一个矢量的头上添加一个戴帽符号来表示单位矢量,例如v。为了对矢量进行归一化,我们可以用矢量除以该矢量的模来得到。公式如下:

零矢量(即矢量的每个分量值都为0 ,如v=(0,0,0))是不可以被归一化的。这是因为做除法运算时分母不能为0 。
从几何意义上看,对二维空间来说,我们可以画一个单位圆,那么单位矢量就可以是从圆心出发、到圆边界的矢量。在三维空间中,单位矢量就是从一个单位球的球心出发、到达球面的矢量。
图4.21 给出了三维空间内的一些单位矢量。

需要注意的是,在后面的章节中我们将会不断遇到法线方向(也被称为法矢量〉、光源方向等,这些矢量不一定是归一化后的矢量。由于我们的计算往往要求矢量是单位矢量,因此在使用前应先对这些矢量进行归一化运算。

5. 矢量的点积

矢量之间也可以进行乘法,但是和标量之间的乘法有很大不同。矢量的乘法有两种最常用的种类:点积(dot product,也被称为内积, inner product ) 和叉积(cross product,也被称为外积, outer product )。在本节中,我们将讨论第一种类型:点积。
读者可能认为上面几节的内容部很简单,“这些都显而易见嘛”。那么从这一节开始,我们就会遇到一些真正需要花费力气(真的只要一点点〉去记忆的公式。幸运的是,绝大多数公式是有几何意义的,也就是说,我们可以通过画图的方式来理解和帮助记忆。
比仅仅记住这些公式更加重要的是,我们要真正理解它们是做什么的。只有这样,我们才能在需要时想起来,“噢,这个需求我可以用这个公式来实现!”在我们编写Shader 的过程中,通常程序接口都会提供这些公式的实现,因此我们往往不需要手工输入这些公式。例如,在Unity Shader中,我们可以直接使用形如dot(a,b)的代码来对两个矢量值进行点积的运算。
点积的名称来源于这个运算的符号: a·b。中间的这个圆点符号是不可以省略的。点积的公式有两种形式,我们先来看第一种。两个三维矢量的点积是把两个矢量对应分量相乘后再取和,最后的结果是一个标量。

下面是一些例子:

(1, 2, 3) ·(0.5, 4, 2.5) = 0.5+8+7.5= 16
(-3, 4, 0)·(5, -1, 7) = -15+(-4)+0= -19

矢量的点积满足交换律,即 a·b=b·a
点积的几何意义很重要,因为点积几乎应用到了图形学的各个方面。其中一个几何意义就是 投影(projection)
假设,有一个单位矢量a和另一个长度不限的矢量b。现在,我们希望得到b 在平行于a的一条直线上的投影。那么,我们就可以使用点积a·b 来得到b 在a 方向上的有符号的投影。
那么,投影到底是什么意思呢?这里给出一个通俗的解释。我们可以认为,现在有一个光源,它发出的光线是垂直于a 方向的,那么b 在a 方向上的投影就是b 在a 方向上的影子,如图4.22所示。

需要注意的是,投影的值可能是负数。投影结果的正负号与a 和b 的方向有关:当它们的方向相反(夹角大于90度〉时,结果小于0:当它们的方向互相垂直(夹角为90度)时,结果等于0;当它们的方向相同(夹角小于90度〉时,结果大于0 。

图4.23 给出了这3 种情况的图示。

也就是说,点积的符号可以让我们知道两个矢量的方向关系。
那么,如果a 不是一个单位矢量会如何呢?这很容易想到任何两个矢量的点积a·b 等同于b在a 方向上的投影值,再乘以a 的长度。
点积具有一些很重要的性质,在Shader 的计算中,我们会经常利用这些性质来帮助计算。

性质一: 点积可结合标量乘法。
上面的“结合”是说,点积的操作数之一可以是另一个运算的结果,即矢量和标量相乘的结果。公式如下:

也就是说,对点积中其中一个矢量进行缩放的结果,相当于对最后的点积结果进行缩放。
性质二:点积可结合矢量加法和减法,和性质一类似。
这里的“结合”指的是,点积的操作数可以是矢量相加或相减后的结果。用公式表达就是:

a·(b+ c)= a·b+ a·c

把上面的c 换成 -c 就可以得到减法的版本。
性质三: 一个矢量和本身进行点积的结果,是该矢量的模的平方。
这点可以很容易从公式验证得到:

这意味着,我们可以直接利用点积来求矢量的模,而不需要使用模的计算公式。当然,我们需要对点积结果进行开平方的操作来得到真正的模。但很多情况下,我们只是想要比较两个矢量的长度大小,因此可以直接使用点积的结果。毕竟,开平方的运算需要消耗一定性能。
现在是时候来看点积的另一种表示方法了。这种方法是从三角代数的角度出发的,这种表示方法更加具有几何意义,因为它可以明确地强调出两个矢量之间的角度。我们先直接给出第二个公式。

初看之下,似乎和公式一没有什么联系,怎么会相等呢?我们先来看最简单的情况。假设,我们对两个单位矢量进行点积,

a·b,如图4.24 所示。

6. 矢量的叉积

另一个重要的矢量运算就是叉积(cross product ),也被称为外积( outer product )。与点积不同的是,矢量叉积的结果仍是一个矢量,而非标量。
和点积类似,叉积的名称来源于它的符号: a × b。同样, 这个叉号也是不可省略的。两个矢量的叉积可以用如下公式计算:

上面的公式看起来很复杂,但其实是有一定规律的。图4.25 给出了这样的规律图示。

例如:
(1,2,3)×(-2,-1,4)=((2)(4)-(3)(-1), (3)(-2)- (1)( 4), (1)(-1) - (2)(-2)) = (8 -(-3),(-6)-4, (-1)-(-4))=( 11 , 10, 3)
需要注意的是,叉积不满足交换律,即a x b ≠ bxa 。实际上,叉积是满足反交换律的, 即a x b= - (b x a)。而且叉积也不满足结合律,即(a × b) ×c ≠ a x (b x c)
从叉积的几何意义出发,我们可以更加深入地理解它的用处。对两个矢量进行叉积的结果会得到一个同时垂直于这两个矢量的新矢量。我们已经知道, 矢量是由一个模和方向来定义的, 那么这个新的矢量的模和方向是什么呢?
我们先来看它的模。ax b 的长度等于a 和b 的模的乘积再乘以它们之间夹角的正弦值。公式如下:

读者可能已经发现,上述公式和点积的计算公式很类似,不同的是,这里使用的是正弦值。
如果读者对中学数学还有记忆的话,可能还会发现,这和平行四边形的面积计算公式是一样的。
如果你忘记了,没关系,我们在这里回忆一下。
如图4.26 所示,我们使用a 和b 构建一个平行四边形。

我们知道,平行四边形的面积可以使用|b|h 来得到,即底乘以高。而h 又可以使用lal和夹角θ来得到,即

你可能会问,如果a 和b 平行(可以是方向完全相同,也可以是完全相反〉怎么办, 不就不能构建平行四边形了吗?我们可以认为构建出来的平行四边形面积为0,那么a x b=0。注意,这里得到的是零向量,而不是标量0 。
下面,我们来看结果矢量的方向。你可能会说:“方向?不是已经说了方向了嘛,就是和两个矢量都垂直就可以了啊。”但是,如果你仔细想一下就会发现, 实际上我们有两个方向可以选择,这两个方向都和这两个矢量垂直。那么,我们要选择哪个方向呢?
这里就要和之前提到的左手坐标系和右手坐标系联系起来了,如图4.27 所示。

这个结果是怎么得到的呢?来,先举起你的右手。在右手坐标系中, a×b 的方向将使用右手法则来判断。我们先想象把手心放在了a 和b 的尾部交点处,然后张开你的手掌让手掌方向和a 的方向重合,再弯曲你的四指让它们向b 的方向靠拢,最后伸出你的大拇指! 大拇指指向的方向就是右手坐标系中a x b 的方向了。如果你实在不明白怎么摆放和扭动你的手,那么就看图4.28 好了.

同理,我们可以使用左手法则来判断左手坐标系中ax b 的方向。赶紧举起你的左手试试吧(你可能会发现这个姿势比较扭曲。〉!
需要注意的是,虽然看起来左右手坐标系的选择会影响叉积的结果,但这仅仅是“看起来”而己。从叉积的数学表达式可以发现,使用左手坐标系还是右手坐标系不会对计算结果产生任何影响,它影响的只是数字在三维空间中的视觉化表现而己。当从右手坐标系转换为左手坐标系时,所有点和矢量的表达和计算方式都会保持不变,只是当呈现到屏幕上时, 我们可能会发现, “咦,怎么图像反过来了! ”。当我们想要两个坐标系达到同样的视觉效果时,可能就需要改变一些数学运算公式, 这不在本书的范畴内。有兴趣的读者可以参考本章的扩展阅读部分。

那么,叉积到底有什么用呢?最常见的一个应用就是计算垂直于一个平面、三角形的矢量。另外,还可以用于判断三角面片的朝向。读者可以在本节的练习题中找到这些应用。

4.3.3 练习题

第4章 学习Shader所需的数学基础(上)(坐标系、点和矢量)相关推荐

  1. Unity Shader入门精要--第4 章 学习Shader 所需的数学基础

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 4.1 背景:农场游戏 4.2 笛卡儿坐标系 4.2.2 三维笛卡儿坐标系 4.2.3 左手坐标系和右手坐标系 4.2.4 Unity ...

  2. Unity Shader入门精要--第4 章 学习Shader 所需的数学基础:点和矢量

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 点和矢量 4.3.1 点和矢量的区别 参考 前言 点(point)是n 维空间(游戏中主要使用二维和三维空间)中的一个位置,它没有大小. ...

  3. Unity Shader入门精要第四章:学习Shader 所需的数学基础--坐标空间

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.4.6.1 为什么要使用这么多不同的坐标空间 二.4.6.3 顶点的坐标空间变换过程 4.6.4 模型空间 4.6.6 观察空间 4 ...

  4. Shader学习笔记(三)学习Shader所需的数学基础

    感受高数 一.笛卡尔坐标系 1.二维笛卡尔坐标系 2.三维笛卡尔坐标系 二.点和矢量 1.矢量和标量的乘法/除法 2.矢量的加法和减法 3.矢量的模 4.单位矢量 5.矢量的点积 6.矢量的叉积(cr ...

  5. 《软件质量保证与测试》学习笔记【第三章 软件测试过程所需技能】

    目录 第三章 软件测试过程所需技能(软件测试计划书) 前言 3.1软件测试计划 1.软件测试计划书的定义 2.软件测试计划的作用 3.如何制定软件测试计划 4.IEEE测试计划模板 第三章 软件测试过 ...

  6. Linux_《Linux命令行与shell脚本编程大全》第二章学习总结

    时间:2017年04月05日 说明:本文部分内容均摘取自书籍<Linux命令行与shell脚本编程大全>,版权归原作者所有.<Linux命令行与shell脚本编程大全>(第三版 ...

  7. 多维随机变量及其分布——《概率论及其数理统计》第三章学习笔记

    多维随机变量及其分布--<概率论及其数理统计>第三章学习笔记 文章目录 多维随机变量及其分布--<概率论及其数理统计>第三章学习笔记 前言 MindMap 二维随机变量 定义与 ...

  8. C++程序设计教程(钱能)第四章 学习笔记

    C++程序设计教程(钱能)第四章 学习笔记 4.1 名词解释与操作符 4.1.1 名词解释 4.1.2 操作符汇总 4.1.3 操作符的说明 4.2 算数运算问题 4.2.1 周而复始的整数 4.2. ...

  9. 数据载入、存储及文件格式——《利用python数据分析》第六章学习

    数据载入.存储及文件格式--<利用python数据分析>第六章学习 前言 这次带来第六章的学习笔记,希望这次效率高点,快点写完. 代码环境这次小升级,Pycharm 2021.3 + An ...

最新文章

  1. 文本分类step by step(二)
  2. 中国队蝉联国际奥数冠军,6名选手获5金1银,3人保送北大、3人保送清华
  3. The number of Oracle redo threads (2) is not the same as the number of checkpoint threads (1)
  4. 移动端重构系列1——前期准备工作
  5. 深度解读 OpenYurt :边缘自治能力设计解析
  6. heroku能用mysql吗_heroku连接到mysql数据库
  7. asp.net 服务器控件的 ID,ClientID,UniqueID 的区别
  8. 让你的PHP也能执行JS并获得JS函数的返回值
  9. Ellex激光器参数与激光消融手术风险的关系
  10. Spring Boot 1.5.x新特性:动态修改日志级别
  11. 一款非常好用的音乐标签及管理工具:Yate for Mac
  12. azure blob_如何在Azure Blob存储中恢复意外删除
  13. js 定时器_Node.js实战6:定时器,使用timer延迟执行
  14. 如何测试并调试基于 NDK 的 Android 应用
  15. php 基础系列之 php快速入门
  16. 公众号笔记: 2018年12月
  17. tvs二极管5.0SMDJ24CA-H ISO7637 测试报告
  18. 游戏中常见英语词汇,做游戏的,玩游戏总是要碰到的
  19. 18-FreeSwitch-配置G729转码
  20. 肖特基二极管工作原理

热门文章

  1. ETL:WJLYZQWDD
  2. Visual Studio 2019的下载及安装
  3. C/C++编程学习 - 第4周 ⑦ 判断是否为两位数
  4. HTML5 网页添加背景音乐
  5. 基于Linux+ARM的远程视频监控--系列开题
  6. MySQL基础(非常全)
  7. OpenGLES 3.0
  8. Lua(二)数据类型+变量
  9. 相差为2的两个素数称为孪生素数
  10. windows配置java环境