天天看点

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

Unity系列文章目录

文章目录

  • ​​Unity系列文章目录​​
  • ​​前言​​
  • ​​点和矢量​​
  • ​​4.3.1 点和矢量的区别​​
  • ​​参考​​

前言

点(point)是n 维空间(游戏中主要使用二维和三维空间)中的一个位置,它没有大小、宽度这类概念。在笛卡儿坐标系中,我们可以使用2 个或3 个实数来表示一个点的坐标,如:P=(Px,

Py),表示二维空间的点,P=(Px, Py,Pz)表示三维空间中的点。

矢量(vector,也被称为向量)的定义则复杂一些。在数学家看来,矢量就是一串数字。你

可能要问了,点的表达式不也是一串数字吗?没错,但矢量存在的意义更多是为了和标量(scalar)

区分开来。通常来讲,矢量是指n 维空间中一种包含了模(magnitude)和方向(direction)的有

向线段,我们通常讲到的速度(velocity)就是一种典型的矢量。例如,这辆车的速度是向南80km/h

(向南指明了矢量的方向,80km/h 指明了矢量的模)。而标量只有模没有方向,生活中常常说到

的距离(distance)就是一种标量。例如,我家离学校只有200 米(200 米就是一个标量)。

具体来讲。

 矢量的模指的是这个矢量的长度。一个矢量的长度可以是任意的非负数。

 矢量的方向则描述了这个矢量在空间中的指向。

矢量的表示方法和点类似。我们可以使用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 表示了两者之间的关系。

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

尽管上面的内容看起来显而易见,但区分点和矢量之间的不同是非常重要的,尽管它们在数

学表达式上是一样的,都是一串数字而已。如果一定要给它们之间建立一个联系的话,我们可以

认为,任何一个点都可以表示成一个从原点出发的矢量。为了明确点和矢量的区别,在本书后面

的内容中,我们将用于表示方向的矢量称为方向矢量。

4.3.2 矢量运算

在下面的内容里,我们将给出一些最常见的矢量运算。幸运的是,这些运算大都很好理解。

对于每种运算,我们会先给出数学上的描述,然后再给出几何意义上的解释。同样,为了让读者

加深印象,我们会在最后给出一些练习题。相信读完本节后,你一定可以快速地解决它们!

1.矢量和标量乘法/除法

还记得吗?标量是只有模没有方向的量,虽然我们不能把矢量和标量进行相加/相减的运算

(想象一下,你会把速度和距离相加吗),但可以对它们进行乘法运算,结果会得到一个不同长度

且可能方向相反的新的矢量。

公式非常简单,我们只需要把矢量的每个分量和标量相乘即可:

kv=(kvx,kvy, kvz)

类似的,一个矢量也可以被一个非零的标量除。这等同于和这个标量的倒数相乘:

v

k

 (x, y, z)

k

 1

k

(x, y, z)  x

k

, y

k

, z

k

 

 

,k  0

下面给出一些例子:

2(1,2,3)=(2,4,6)

−3.5(2, 0)=(−7, 0)

(1,2,3)

2

 (0.5,1,1.5)

注意,对于乘法来说,矢量和标量的位置可以互换。但对于除法,只能是矢量被标量除,而

不能是标量被矢量除,这是没有意义的。

从几何意义上看,把一个矢量v 和一个标量k 相乘,意味着对矢量v 进行一个大小为|k|的缩

放。例如,如果想要把一个矢量放大两倍,就可以乘以2。当k<0 时,矢量的方向也会取反。图

4.17 显示了这样的一些例子。

2.矢量的加法和减法

我们可以对两个矢量进行相加或相减,其结果是一个相同维度的新矢量。

我们只需要把两个矢量的对应分量进行相加或相减即可。公式如下:

a+b=(ax+bx,ay+by, az+bz)

a–b=(ax−bx,ay-by, az−bz)

下面是一些例子:

(1,2,3)+(4,5,6)=(5,7,9)

(5,2,7) − (3,8,4)=(2, −6,3)

需要注意的是,一个矢量不可以和一个标量相加或相减,或者是和不同维度的矢量进行运算。

从几何意义上来看,对于加法,我们可以把矢量a 的头连接到矢量b 的尾,然后画一条从a

的尾到b 的头的矢量,来得到a 和b 相加后的矢量。也就是说,如果我们从一个起点开始进行了

一个位置偏移a,然后又进行一个位置偏移b,那么就等同于进行了一个a+b 的位置偏移。这被称为矢量加法的三角形定则(triangle rule)。矢量的减法是类似的。如图4.18 所示。

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

读者需要时刻谨记,在图形学中矢量通常用于描述位置偏移(简称位移)。因此,我们可以利

用矢量的加法和减法来计算一点相对于另一点的位移。

假设,空间内有两点a 和b。还记得吗,我们可以用矢量a 和b 来表示它们相对于原点的位

移。如果我们想要计算点b 相对于点a 的位移,就可以通过把b 和a 相减得到,如图4.19 所示。

3.矢量的模

正如我们之前讲到的一样,矢量是有模和方向的。矢量的模是一个标量,可以理解为是矢量

在空间中的长度。它的表示符号通常是在矢量两旁分别加上一条垂直线(有的文献中会使用两条

垂直线)。三维矢量的模的计算公式如下:

|v|= 2 2 2

vx  vy  vz

其他维度的矢量的模计算类似,都是对每个分量的平方相加后再开根号得到。

下面给出一些例子:

| (1, 2,3) | 12  22  32  1 4  9  14  3.742

| (3, 4) | 32  42  9 16  25  5

我们可以从几何意义来理解上述公式。对于二维矢量来说,我们可以对任意矢量构建一个三

角形,如图4.20 所示。

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

从图4.20 可以看出,对于二维矢量,其实就是使用了勾股定理,矢量的两个分量的绝对值对

应了三角形两个直角边的长度,而斜边的长度就是矢量的模。

4.单位矢量

在很多情况下,我们只关心矢量的方向而不是模。例如,在计算光照模型时,我们往往需要

得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长。在这些情况下,我们就需要

计算单位矢量(unit vector)。

单位矢量指的是那些模为1 的矢量。单位矢量也被称为被归一化的矢量(normalized vector)。

对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化(normalization)。

给定任意非零矢量v,我们可以计算和v 方向相同的单位矢量。在本书中,我们通过在一个

矢量的头上添加一个戴帽符号来表示单位矢量,例如^ v 。为了对矢量进行归一化,我们可以用矢

量除以该矢量的模来得到。公式如下:

^ =

| |

v v

v

,v 是任意非零矢量

下面给出一些例子:

(3,4)

| (3,4) |

 (3,4)

32  (4)2

 (3,4)

25

 (3,4)

5

 3

5

, 4

5

 

 

 (0.6,0.8)

零矢量(即矢量的每个分量值都为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。中间的这个圆点符号是不可以省略的。点积的公式

有两种形式,我们先来看第一种。两个三维矢量的点积是把两个矢量对应分量相乘后再取和,最

后的结果是一个标量。

公式一:

a·b=(ax, ay, az) ·(bx, by, bz)= axbx+ ayby+azbz

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

下面是一些例子:

(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 种情况的图示。

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

也就是说,点积的符号可以让我们知道两个矢量的方向关系。

那么,如果^a 不是一个单位矢量会如何呢?这很容易想到,任何两个矢量的点积a·b 等同于b

在a 方向上的投影值,再乘以a 的长度。

点积具有一些很重要的性质,在Shader 的计算中,我们会经常利用这些性质来帮助计算。

性质一:点积可结合标量乘法。

上面的“结合”是说,点积的操作数之一可以是另一个运算的结果,即矢量和标量相乘的结

果。公式如下:

(ka)·b= a·(kb)=k(a·b)

也就是说,对点积中其中一个矢量进行缩放的结果,相当于对最后的点积结果进行缩放。

性质二:点积可结合矢量加法和减法,和性质一类似。

这里的“结合”指的是,点积的操作数可以是矢量相加或相减后的结果。用公式表达就是:

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

把上面的c 换成−c 就可以得到减法的版本。

性质三:一个矢量和本身进行点积的结果,是该矢量的模的平方。

这点可以很容易从公式验证得到:

v·v=vxvx+ vyvy+ vzvz=|v|2

这意味着,我们可以直接利用点积来求矢量的模,而不需要使用模的计算公式。当然,我们

需要对点积结果进行开平方的操作来得到真正的模。但很多情况下,我们只是想要比较两个矢量

的长度大小,因此可以直接使用点积的结果。毕竟,开平方的运算需要消耗一定性能。

现在是时候来看点积的另一种表示方法了。这种方法是从三角代数的角度出发的,这种表示

方法更加具有几何意义,因为它可以明确地强调出两个矢量之间的角度。

我们先直接给出第二个公式。

公式二:

a·b=|a||b|cos

初看之下,似乎和公式一没有什么联系,怎么会相等呢?我们先来看最简单的情况。假设,

我们对两个单位矢量进行点积,即^a · ^b ,如图4.24 所示。

到了产生魔法的时间了!我们知道^ b 的模为1,且读者应

该记得cos =

邻边

斜边

。我们可以发现,图中^a · ^ b 的结果刚好就

是cos 对应的直角边。因此,由图4.24 可以得到:

^a · ^ b =

邻边

斜边

=cos

这也就是说,两个单位矢量的点积等于它们之间夹角的

余弦值。再应用性质一就可以得到公式二了:

a·b=(|a| ^a )·(|b| ^ b )=|a||b|( ^a · ^ b )=|a||b|cos

也就是说,两个矢量的点积可以表示为两个矢量的模相乘,再乘以它们之间夹角的余弦值。

从这个公式也可以看出,为什么计算投影时两个矢量的方向不同会得到不同符号的投影值:当夹

角小于90°时,cos>0;当夹角等于90°时,cos=0;当夹角大于90°时,cos<0。

利用这个公式我们还可以求得两个向量之间的夹角(在0°~180°):

=arcos( ^a · ^ b ),假设^a 和^ b 是单位矢量。

其中,arcos 是反余弦操作。

6.矢量的叉积

另一个重要的矢量运算就是叉积(cross product),也被称为外积(outer product)。与点积

不同的是,矢量叉积的结果仍是一个矢量,而非标量。

和点积类似,叉积的名称来源于它的符号:a×b。同样,这个叉号也是不可省略的。两个矢

量的叉积可以用如下公式计算:

a×b=(ax, ay, az)×(bx, by, bz)=(aybz−azby, azbx−axbz, axby−aybx)

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

▲图4.25 三维矢量叉积的计算规律。不同颜色的线表示了计算结果矢量中对应颜色的分量的计算路径。

以红色为例,即结果矢量的第一个分量,它是从第一个矢量的y 分量出发乘以第二个矢量的z 分量,

再减去第一个矢量的z 分量和第二矢量的y 分量的乘积

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

例如:

(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×b≠b×a。实际上,叉积是满足反交换律的,即

a×b=−(b×a)。而且叉积也不满足结合律,即 (a×b) ×c≠a×(b×c)。

从叉积的几何意义出发,我们可以更加深入地理解它的用处。对两个矢量进行叉积的结果会

得到一个同时垂直于这两个矢量的新矢量。我们已经知道,矢量是由一个模和方向来定义的,那

么这个新的矢量的模和方向是什么呢?

我们先来看它的模。a×b 的长度等于a 和b 的模的乘积再乘以它们之间夹角的正弦值。公式

如下:

|a×b|=|a||b|sin

读者可能已经发现,上述公式和点积的计算公式很类似,不同的是,这里使用的是正弦值。

如果读者对中学数学还有记忆的话,可能还会发现,这和平行四边形的面积计算公式是一样的。

如果你忘记了,没关系,我们在这里回忆一下。

如图4.26 所示,我们使用a 和b 构建一个平行四边形。

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

来得到,即

A=|b|h=|b|(|a|sin)=|a||b|sin=|a×b|

你可能会问,如果a 和b 平行(可以是方向完全相同,也可以是完全相反)怎么办,不就不

能构建平行四边形了吗?我们可以认为构建出来的平行四边形面积为0,那么a×b=0。注意,这

里得到的是零向量,而不是标量0。

下面,我们来看结果矢量的方向。你可能会说:“方向?不是已经说了方向了嘛,就是和两个

矢量都垂直就可以了啊。”但是,如果你仔细想一下就会发现,实际上我们有两个方向可以选择,

这两个方向都和这两个矢量垂直。那么,我们要选择哪个方向呢?

这里就要和之前提到的左手坐标系和右手坐标系联系起来了,如图4.27 所示。

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

这个结果是怎么得到的呢?来,举起你的双手!哦,不……先举起你的右手。在右手坐标系

中,a×b 的方向将使用右手法则来判断。我们先想象把手心放在了a 和b 的尾部交点处,然后张

开你的手掌让手掌方向和a 的方向重合,再弯曲你的四指让它们向b 的方向靠拢,最后伸出你的

大拇指!大拇指指向的方向就是右手坐标系中a×b 的方向了。如果你实在不明白怎么摆放和扭动

你的手,那么就看图4.28 好了。

同理,我们可以使用左手法则来判断左手坐标系中

a×b 的方向。赶紧举起你的左手试试吧(你可能会发现

这个姿势比较扭曲)!

需要注意的是,虽然看起来左右手坐标系的选择会

影响叉积的结果,但这仅仅是“看起来”而已。从叉积

的数学表达式可以发现,使用左手坐标系还是右手坐标

系不会对计算结果产生任何影响,它影响的只是数字在

三维空间中的视觉化表现而已。当从右手坐标系转换为

左手坐标系时,所有点和矢量的表达和计算方式都会保

持不变,只是当呈现到屏幕上时,我们可能会发现,“咦,

怎么图像反过来了!”。当我们想要两个坐标系达到同样

的视觉效果时,可能就需要改变一些数学运算公式,这

不在本书的范畴内。有兴趣的读者可以参考本章的扩展

阅读部分。

那么,叉积到底有什么用呢?最常见的一个应用就是计算垂直于一个平面、三角形的矢量。

另外,还可以用于判断三角面片的朝向。读者可以在本节的练习题中找到这些应用。

参考