Skip to content

Latest commit

 

History

History
1358 lines (750 loc) · 90.4 KB

File metadata and controls

1358 lines (750 loc) · 90.4 KB

2 - 数学基础

图形学很大程度上就是将数学直接转换成代码。数学越简洁,最终的代码就越清晰;因此本书很大一部分内容专注于为每个任务选择最合适的数学工具。本章回顾了高中和大学数学中的各种工具,设计初衷更多是作为参考资料而非教程。本章看起来可能像是各种主题的大杂烩,事实上也确实如此;选择每个主题都有其原因:要么是因为它在"标准"数学课程中有些不常见,要么是因为它在图形学中具有核心重要性,或者是因为它通常不会从几何角度来讲解。除了建立本书所使用符号记法的回顾之外,本章还强调了一些在标准本科课程中有时会被跳过的要点,例如三角形上的重心坐标。本章并不旨在对这些材料进行严格的理论处理;相反,重点强调直觉理解和几何解释。线性代数的讨论推迟到第6章,就在讨论变换矩阵之前。建议读者浏览本章以熟悉所涵盖的主题,并在需要时回过头来参考。本章末尾的练习题可能有助于确定哪些主题需要复习。

2.1 集合和映射

映射(也称为函数)是数学和编程的基础。就像程序中的函数一样,数学中的映射接受某种类型的参数,并将其映射到(返回)特定类型的对象。在程序中,我们称之为"类型";在数学中,我们会明确指出集合。当我们有一个对象是某个集合的成员时,我们使用符号 $\in$。例如,

$$a \in S$$

可以读作"$a$ 是集合 $S$ 的成员"。给定任意两个集合 $A$$B$,我们可以通过取两个集合的笛卡尔积来创建第三个集合,记作 $A \times B$。这个集合 $A \times B$ 由所有可能的有序对 $(a,b)$ 组成,其中 $a \in A$$b \in B$。作为简写,我们使用记号 $A^2$ 来表示 $A \times A$。我们可以扩展笛卡尔积的概念,从三个集合创建所有可能的有序三元组的集合,依此类推,可以从任意多个集合得到任意长度的有序元组。

常用的集合包括:

$\mathbb{R}$ —— 实数集; • $\mathbb{R}^+$ —— 非负实数集(包括零); • $\mathbb{R}^2$ —— 实数平面上的有序对; • $\mathbb{R}^n$ —— $n$ 维笛卡尔空间中的点; • $\mathbb{Z}$ —— 整数集; • $S^2$ —— 单位球面上的三维点集($\mathbb{R}^3$ 中的点)。

注意,虽然 $S^2$ 由嵌入在三维空间中的点组成,但它位于可以用两个变量参数化的曲面上,因此可以将其视为二维集合。

映射的记号使用箭头和冒号,例如:

$$f : \mathbb{R} \to \mathbb{Z}$$

可以读作"存在一个名为 $f$ 的函数,它以实数作为输入并将其映射到整数"。这里,箭头前面的集合称为函数的定义域,右侧的集合称为目标集。计算机程序员可能更习惯以下等价的表述:"存在一个名为 $f$ 的函数,它有一个实数参数并返回一个整数"。换句话说,上述集合记号等价于常见的编程记号:

integer f(real) ← 等价于 → f : ℝ → ℤ

因此,冒号-箭头记号可以视为一种编程语法。就是这么简单。

$f(a)$ 被称为 $a$,而集合 $A$(定义域的一个子集)的像是目标集的子集,它包含 $A$ 中所有点的像。整个定义域的像被称为函数的值域

2.1.1 逆映射

Figure2.1 如果我们有一个函数 $f : A \to B$,可能存在一个逆函数 $f^{-1}: B \to A$,其定义规则为 $f^{-1}(b) = a$,其中 $b = f(a)$。这个定义只有在以下条件下才有效:$B$ 中的每个 $b$ 都是某个点在 $f$ 下的像(即值域等于目标集),并且只有唯一的一个这样的点(即只有唯一的 $a$ 使得 $f(a) = b$)。这样的映射或函数被称为双射。双射将 $A$ 中的每个 $a$ 映射到 $B$ 中唯一的 $b$,对于 $B$ 中的每个 $b$,都恰好存在一个 $A$ 中的 $a$ 使得 $f(a) = b$(图2.1)。骑手和马匹之间的双射表示每个人都骑一匹马,每匹马都被人骑。这两个函数分别是 rider(horse) 和 horse(rider)。它们互为逆函数。不是双射的函数没有逆函数(图2.2)。 Figure2.2 双射的一个例子是 $f : \mathbb{R} \to \mathbb{R}$,其中 $f(x) = x^3$。逆函数是 $f^{-1}(x) = \sqrt[3]{x}$。这个例子表明标准记号可能有些别扭,因为 $x$$f$$f^{-1}$ 中都被用作虚拟变量。有时使用不同的虚拟变量会更直观,即 $y = f(x)$$x = f^{-1}(y)$。这样得到更直观的表示:$y = x^3$ 和 $x = \sqrt[3]{y}$

没有逆函数的函数例子是 $\text{sqr} : \mathbb{R} \to \mathbb{R}$,其中 $\text{sqr}(x) = x^2$。这有两个原因:首先 $x^2 = (-x)^2$,其次定义域中没有成员映射到目标集的负数部分。注意,如果我们将定义域和值域都限制为 $\mathbb{R}^+$,那么我们可以定义一个逆函数。这时,$\sqrt{x}$ 是一个有效的逆函数。

2.1.2 区间

我们经常需要指定函数处理值受限的实数。一种约束方式是指定区间。区间的一个例子是零和一之间的实数,不包括零和一。我们记作 $(0,1)$。因为它不包含端点,这被称为开区间。相应的闭区间包含其端点,用方括号表示:$[0, 1]$。这种记号可以混合使用;即 $[0, 1)$ 包括零但不包括一。当写区间 $(a, b)$ 时,我们假设 $a \leq b$。表示区间的三种常见方式如图2.3所示。 Figure2.3 区间的笛卡尔积经常被使用。例如,为了表示点 $x$ 在三维单位立方体中,我们说 $x \in [0, 1]^3$

区间与集合运算结合使用特别有用:交集、并集和差集。例如,两个区间的交集是它们共有的点的集合。符号 $\cap$ 用于表示交集。例如,$(3, 5) \cap (4, 6] = [4, 5)$。对于并集,符号 $\cup$ 用于表示任一区间中的点。例如,$[3, 5) \cup (4, 6] = [3, 6]$。与前两个运算符不同,差运算符根据参数顺序产生不同的结果。减号用于差运算符,它返回左区间中不在右区间中的点。例如,$[3, 5) - [4, 6] = [3, 4)$ 和 $[4, 6) - [3, 5) = [5, 6)$。使用区间图来可视化这些运算特别容易(图2.4)。 Figure 2.4

2.1.3 对数

尽管对数在今天不如计算器出现之前那样普遍,但在处理含有指数项的方程时,对数仍然很有用。根据定义,每个对数都有一个底数 $a$。"以 $a$ 为底的 $x$ 的对数"写作 $\log_a x$,定义为"$a$ 的多少次幂等于 $x$",即

$$y = \log_a x \Leftrightarrow a^y = x$$

注意,对数底 $a$ 和将 $a$ 升幂的函数互为逆函数。这个基本定义有几个推论:

$$a^{\log_a x} = x$$

$$\log_a (a^x) = x$$

$$\log_a (xy) = \log_a x + \log_a y$$

$$\log_a (x/y) = \log_a x - \log_a y$$

$$\log_a x = \frac{\log_b x}{\log_b a}$$

当我们将微积分应用于对数时,特殊数字 $e = 2.718\ldots$ 经常出现。以 $e$ 为底的对数称为自然对数。我们采用常见的简记 $\ln$ 来表示它:

$$\ln x = \log_e x$$

注意,"$\equiv$" 符号可以读作"根据定义等价于"。与虚数单位 $i$ 一样,特殊数字 $e$ 在大量的上下文中出现。许多领域除了 $e$ 之外还使用特定的底数进行运算,并在记号中省略底数,即 $\log x$。例如,天文学家经常使用以10为底的对数,理论计算机科学家经常使用以2为底的对数。由于计算机图形学借鉴了许多领域的技术,我们将避免使用这种简记。

对数和指数的导数说明了为什么自然对数是"自然的":

$$\frac{d}{dx} \log_a x = \frac{1}{x \ln a}$$

$$\frac{d}{dx} a^x = a^x \ln a$$

上述常数乘数只有当 $a = e$ 时才等于1。

2.2 解二次方程

一元二次方程的形式为

$$Ax^2 + Bx + C = 0,$$

其中 $x$ 是实数未知数,$A$、$B$ 和 $C$ 是已知常数。如果你考虑二维 $xy$ 图,其中 $y = Ax^2 + Bx + C$,解就是 $y$ 的"零点"对应的 $x$ 值。因为 $y = Ax^2 + Bx + C$ 是一条抛物线,根据抛物线与 $x$ 轴相离、相切或相交的情况,将有零个、一个或两个实数解(图2.5)。 Figure 2.5 为了解析求解二次方程,我们首先除以 $A$

$$x^2 + \frac{B}{A}x + \frac{C}{A} = 0$$

然后,我们"配方"来组合项:

$$\left(x + \frac{B}{2A}\right)^2 = \frac{B^2}{4A^2} - \frac{C}{A}$$

将常数项移到右边并开方得到:

$$x + \frac{B}{2A} = \pm\sqrt{\frac{B^2}{4A^2} - \frac{C}{A}}$$

两边减去 $\frac{B}{2A}$ 并将分母为 $2A$ 的项合并,得到熟悉的形式:

$$x = \frac{-B \pm \sqrt{B^2 - 4AC}}{2A} \quad (2.1)$$

这里,"$\pm$" 符号表示有两个解,一个取正号,一个取负号。因此,$3 \pm 1$ 等于"2或4"。注意,决定实数解个数的项是

$$D = B^2 - 4AC,$$

它被称为二次方程的判别式。如果 $D > 0$,有两个实数解(也称为根)。如果 $D = 0$,有一个实数解("重根")。如果 $D < 0$,没有实数解。

例如,$2x^2 + 6x + 4 = 0$ 的根是 $x = -1$$x = -2$,而方程 $x^2 + x + 1 = 0$ 没有实数解。这些方程的判别式分别是 $D = 4$$D = -3$,因此我们预期得到相应数量的解。

在程序中,通常最好先计算 $D$,如果 $D$ 为负,则在不开方的情况下返回"无根"。

2.3 三角学

2.3.1 角

尽管我们对角度有些习以为常,但我们应该回到角度的定义,以便将角度的概念扩展到球面上。角度是由两条半直线(从原点发出的无限射线)或方向形成的,必须使用某种约定来决定它们之间形成的两个可能角度,如图2.6所示。角度由它在单位圆上截取的弧段长度来定义。一种常见的约定是使用较小的弧长,角度的符号由指定两条半直线的顺序决定。使用这种约定,所有角度都在范围 $[-\pi, \pi]$ 内。

Figure2.6

这些角度中的每一个都是两个方向"截取"的单位圆弧的长度。因为单位圆的周长是 $2\pi$,两个可能的角度之和为 $2\pi$。这些弧长的单位是弧度。另一个常见的单位是,其中圆的周长是 $360°$。因此,$\pi$ 弧度的角度是 $180°$,通常记作 $180°$。度和弧度之间的转换是:

$$\text{度} = \frac{180}{\pi} \cdot \text{弧度}$$

$$\text{弧度} = \frac{\pi}{180} \cdot \text{度}$$

2.3.2 三角函数

给定一个直角三角形,其边长分别为 $a$、$o$ 和 $h$,其中 $h$ 是最长边的长度(总是与直角相对),即斜边,一个重要的关系由勾股定理描述:

$$a^2 + o^2 = h^2$$

从图2.7可以看出这是正确的,其中大正方形的面积为 $(a+o)^2$,四个三角形的总面积为 $2ao$,中心正方形的面积为 $h^2$。因为三角形和内部正方形均匀地细分了大正方形,我们有 $2ao + h^2 = (a + o)^2$,这很容易操作为上述形式。 Figure2-7 我们定义角 $\phi$ 的正弦和余弦,以及其他基于比值的三角函数表达式:

$$\sin \phi = \frac{o}{h}; \quad \csc \phi = \frac{h}{o};$$

$$\cos \phi = \frac{a}{h}; \quad \sec \phi = \frac{h}{a};$$

$$\tan \phi = \frac{o}{a}; \quad \cot \phi = \frac{a}{o}.$$

这些定义允许我们建立极坐标系,其中一个点由距离原点的距离和相对于正 $x$ 轴的有符号角度表示(图2.8)。注意约定角度在范围 $\theta \in (-\pi, \pi]$ 内,正角度是从正 $x$ 轴逆时针方向。这种逆时针对应正数的约定是任意的,但在图形学的许多场合都会使用,所以值得记住。 Figure2-8 三角函数是周期性的,可以接受任何角度作为参数。例如,$\sin(A) = \sin(A + 2\pi)$。这意味着当考虑定义域为 $\mathbb{R}$ 时,这些函数不可逆。通过限制标准反函数的值域来避免这个问题,这在几乎所有现代数学库中都以标准方式完成(例如,Plauger (1991))。定义域和值域为:

$$\arcsin : [-1, 1] \rightarrow [-\pi/2, \pi/2];$$

$$\arccos : [-1, 1] \rightarrow [0, \pi]; \quad (2.2)$$

$$\arctan : \mathbb{R} \rightarrow (-\pi/2, \pi/2);$$

$$\text{atan2} : \mathbb{R}^2 \rightarrow (-\pi, \pi].$$

最后一个函数 $\text{atan2}(s, c)$ 通常非常有用。它接受一个与 $\sin A$ 成比例的 $s$ 值和一个按相同因子缩放 $\cos A$$c$ 值,并返回 $A$。假设该因子为正。理解这个函数的一种方式是,它返回二维笛卡尔坐标点 $(s, c)$ 在极坐标中的角度(图2.9)。 Figure2-9

2.3.3 常用的等式

位移恒等式(Shifting identities)

$$\sin(-A) = -\sin A$$

$$\cos(-A) = \cos A$$

$$\tan(-A) = -\tan A$$

$$\sin(\pi/2 - A) = \cos A$$

$$\cos(\pi/2 - A) = \sin A$$

$$\tan(\pi/2 - A) = \cot A$$

勾股恒等式(Pythagorean identities)

$$\sin^2 A + \cos^2 A = 1$$

$$\sec^2 A - \tan^2 A = 1$$

$$\csc^2 A - \cot^2 A = 1$$

加法和减法恒等式(Addition and subtraction identities)

$$\sin(A + B) = \sin A \cos B + \sin B \cos A$$

$$\sin(A - B) = \sin A \cos B - \sin B \cos A$$

$$\sin(2A) = 2\sin A \cos A$$

$$\cos(A + B) = \cos A \cos B - \sin A \sin B$$

$$\cos(A - B) = \cos A \cos B + \sin A \sin B$$

$$\cos(2A) = \cos^2 A - \sin^2 A$$

$$\tan(A + B) = \frac{\tan A + \tan B}{1 - \tan A \tan B}$$

$$\tan(A - B) = \frac{\tan A - \tan B}{1 + \tan A \tan B}$$

$$\tan(2A) = \frac{2\tan A}{1 - \tan^2 A}$$

半角恒等式(Half-angle identities)

$$\sin^2(A/2) = \frac{1 - \cos A}{2}$$

$$\cos^2(A/2) = \frac{1 + \cos A}{2}$$

任意三角形定律

以下恒等式适用于边长为 $a$、$b$、$c$,对应角度分别为 $A$、$B$、$C$ 的任意三角形(图2.10):

$$\frac{\sin A}{a} = \frac{\sin B}{b} = \frac{\sin C}{c} \quad \text{(正弦定律)}$$

$$c^2 = a^2 + b^2 - 2ab \cos C \quad \text{(余弦定律)}$$

$$\frac{a + b}{a - b} = \frac{\tan\left(\frac{A+B}{2}\right)}{\tan\left(\frac{A-B}{2}\right)} \quad \text{(正切定律)}$$

三角形面积

三角形的面积也可以用这些边长来计算:

$$\text{三角形面积} = \frac{1}{4}\sqrt{(a + b + c)(-a + b + c)(a - b + c)(a + b - c)}$$

2.3.4 立体角和球面三角学

本节中的传统三角学处理的是平面上的三角形。三角形也可以定义在非平面表面上,其中一个在许多领域(例如天文学)中出现的是单位半径球面上的三角形。这些球面三角形的边是球面上大圆(单位半径圆)的弧段。对这些三角形的研究被称为球面三角学,在图形学中并不常用,但当它出现时,有时是至关重要的。我们不会在这里讨论其细节,但希望读者知道当这些问题确实出现时,存在相应的理论领域,并且有许多有用的规则,如球面余弦定律和球面正弦定律。关于球面三角学机制的应用示例,可参见关于采样三角形光源(投影到球面三角形)的论文 (Arvo, 1995b)。

对计算机图形学更为核心的重要概念是立体角。虽然角度允许我们量化诸如"在我的视野中那两根杆子的分离度是多少"这样的问题,但立体角让我们能够量化诸如"那架飞机覆盖了我视野的多少部分"这样的问题。对于传统角度,我们将杆子投影到单位圆上,并测量它们在单位圆上之间的弧长。我们经常处理角度,以至于我们中的许多人可能会忘记这个定义,因为现在对我们来说这一切都是如此直观。立体角同样简单,但它们可能看起来更令人困惑,因为我们大多数人是在成年后才学习它们的。

对于立体角,我们投影"看到"飞机的可见方向,并将其投影到单位球面上,然后测量面积。这个面积就是立体角,就像弧长是角度一样。虽然角度以弧度为单位测量,总和为 $2\pi$(单位圆的总长度),立体角以**球面度(steradians)**为单位测量,总和为 $4\pi$(单位球面的总面积)。

2.4 向量

向量描述长度和方向。它可以有效地用箭头来表示。如果两个向量具有相同的长度和方向,那么它们就是相等的,即使我们认为它们位于不同的位置(图2.11)。你应该尽可能将向量视为箭头,而不是坐标或数字。在某个时刻,我们必须在程序中将向量表示为数字,但即使在代码中,它们也应该作为对象来操作,只有底层的向量运算才需要了解它们的数值表示 (DeRose, 1989)。向量将用粗体字符表示,例如 $\mathbf{a}$。向量的长度记为 $|\mathbf{a}|$单位向量是任何长度为1的向量。零向量是长度为零的向量。零向量的方向是未定义的。

向量可以用来表示许多不同的事物。例如,它们可以用来存储偏移量,也称为位移。如果我们知道"宝藏埋在秘密集合地点向东两步、向北三步的地方",那么我们知道偏移量,但不知道从哪里开始。向量也可以用来存储位置,这是位置的另一个词。位置可以表示为相对于另一个位置的位移。通常,有一个约定的原点位置,所有其他位置都作为相对于该原点的偏移量来存储。

需要注意的是,位置不是向量。正如我们将要讨论的,你可以将两个向量相加。然而,将两个位置相加通常没有意义,除非它是计算位置加权平均时的中间运算 (Goldman, 1985)。将两个偏移量相加确实有意义,这也是偏移量是向量的一个原因。但这强调了位置不是偏移量;它是相对于特定原点位置的偏移量。偏移量本身并不是位置。

2.4.1 向量操作

向量具有我们与实数相关联的大多数常用算术运算。当且仅当两个向量具有相同的长度和方向时,它们才相等。两个向量根据平行四边形法则相加。该法则规定,两个向量的和是通过将其中一个向量的尾部放在另一个向量的头部来求得的(图2.12)。和向量是"完成三角形"的向量,该三角形由这两个向量开始形成。平行四边形是通过以任一顺序求和形成的。这强调了向量加法具有交换律

$$\mathbf{a} + \mathbf{b} = \mathbf{b} + \mathbf{a}$$

注意,平行四边形法则只是形式化了我们对位移的直觉。想象沿着一个向量从尾到头走,然后沿着另一个向量走。净位移就是平行四边形的对角线。你也可以为向量创建一个一元负号:$-\mathbf{a}$(图2.13)是一个与 $\mathbf{a}$ 长度相同但方向相反的向量。这允许我们也定义减法:

$$\mathbf{b} - \mathbf{a} = -\mathbf{a} + \mathbf{b}$$

你可以用平行四边形来可视化向量减法(图2.14)。我们可以写出:

$$\mathbf{a} + (\mathbf{b} - \mathbf{a}) = \mathbf{b}$$

向量也可以相乘。实际上,有几种涉及向量的乘积。首先,我们可以通过将向量乘以实数 $k$缩放向量。这只是改变向量的长度而不改变其方向。例如,$3.5\mathbf{a}$ 是一个与 $\mathbf{a}$ 方向相同的向量,但它的长度是 $\mathbf{a}$ 的3.5倍。我们将在本节后面讨论两种涉及两个向量的乘积:点积叉积,以及在第6章中讨论涉及三个向量的乘积:行列式

Figue2-12

2.4.2 笛卡尔坐标系下的向量

一个2D向量可以写成任意两个非零且不平行向量的组合。这两个向量的这种性质称为线性无关。两个线性无关的向量构成一个2D基底,因此这些向量被称为基向量。例如,向量 $\mathbf{c}$ 可以表示为两个基向量 $\mathbf{a}$$\mathbf{b}$ 的组合(图2.15):

$$\mathbf{c} = a_c\mathbf{a} + b_c\mathbf{b}\tag{2.3}$$

Figure2.16

注意权重 $a_c$$b_c$ 是唯一的。如果两个向量是正交的(即它们彼此成直角),基底就特别有用。如果它们同时也是单位向量,这种情况下它们是标准正交的,则更加有用。如果我们假设已知两个这样的"特殊"向量 $\mathbf{x}$$\mathbf{y}$,那么我们可以用它们在笛卡尔坐标系中表示所有其他向量,其中每个向量都用两个实数来表示。例如,向量 $\mathbf{a}$ 可以表示为:

$$\mathbf{a} = x_a\mathbf{x} + y_a\mathbf{y}$$

其中 $x_a$$y_a$ 是2D向量 $\mathbf{a}$ 的实数笛卡尔坐标(图2.16)。注意这在概念上与方程(2.3)并没有真正的不同,其中基向量不是标准正交的。但笛卡尔坐标系有几个优势。例如,根据勾股定理,$\mathbf{a}$ 的长度为:

$$|\mathbf{a}| = \sqrt{x_a^2 + y_a^2}$$

在笛卡尔系统中计算点积、叉积和向量坐标也很简单,我们将在后续章节中看到。

按照惯例,我们将 $\mathbf{a}$ 的坐标写成有序对 $(x_a, y_a)$ 或列矩阵的形式:

$$\mathbf{a} = \begin{pmatrix} x_a \ y_a \end{pmatrix}$$

我们使用哪种形式取决于排版的便利性。我们偶尔也会将向量写成行矩阵,表示为 $\mathbf{a}^T$

$$\mathbf{a}^T = \begin{pmatrix} x_a & y_a \end{pmatrix}$$

我们也可以用笛卡尔坐标表示3D、4D等向量。对于3D情况,我们使用一个既与 $\mathbf{x}$ 正交又与 $\mathbf{y}$ 正交的基向量 $\mathbf{z}$

2.4.3 点积

两个向量相乘最简单的方式是点积。$\mathbf{a}$ 和 $\mathbf{b}$ 的点积记作 $\mathbf{a} \cdot \mathbf{b}$,通常也称为标量积,因为它返回一个标量。点积返回一个与其参数的长度和它们之间夹角 $\phi$ 相关的值(图2.17):

$$\mathbf{a} \cdot \mathbf{b} = |\mathbf{a}| |\mathbf{b}| \cos \phi \tag{2.4}$$

在图形程序中,点积最常见的用途是计算两个向量之间夹角的余弦值。

点积也可以用来求一个向量在另一个向量上的投影。这是向量 $\mathbf{a}$ 垂直投影到向量 $\mathbf{b}$ 上的长度 $a_{\rightarrow b}$(图2.18):

$$a_{\rightarrow b} = |\mathbf{a}| \cos \phi = \frac{\mathbf{a} \cdot \mathbf{b}}{|\mathbf{b}|}$$

点积遵循我们在实数算术中熟悉的结合律和分配律:

$$\begin{align} \mathbf{a} \cdot \mathbf{b} &= \mathbf{b} \cdot \mathbf{a} \\ \mathbf{a} \cdot (\mathbf{b} + \mathbf{c}) &= \mathbf{a} \cdot \mathbf{b} + \mathbf{a} \cdot \mathbf{c} \\ (k\mathbf{a}) \cdot \mathbf{b} &= \mathbf{a} \cdot (k\mathbf{b}) = k\mathbf{a} \cdot \mathbf{b} \end{align} \tag{2.6}$$

如果2D向量 $\mathbf{a}$$\mathbf{b}$ 用笛卡尔坐标表示,我们可以利用 $\mathbf{x} \cdot \mathbf{x} = \mathbf{y} \cdot \mathbf{y} = 1$$\mathbf{x} \cdot \mathbf{y} = 0$ 来推导出它们的点积为:

$$\begin{align} \mathbf{a} \cdot \mathbf{b} &= (x_a\mathbf{x} + y_a\mathbf{y}) \cdot (x_b\mathbf{x} + y_b\mathbf{y}) \\ &= x_ax_b(\mathbf{x} \cdot \mathbf{x}) + x_ay_b(\mathbf{x} \cdot \mathbf{y}) + x_by_a(\mathbf{y} \cdot \mathbf{x}) + y_ay_b(\mathbf{y} \cdot \mathbf{y}) \\ &= x_ax_b + y_ay_b \end{align}$$

类似地,在3D中我们可以得到:

$$\mathbf{a} \cdot \mathbf{b} = x_ax_b + y_ay_b + z_az_b$$

Figure2.17-2.18

2.4.4 叉积

叉积 $\mathbf{a} \times \mathbf{b}$ 通常仅用于三维向量;广义叉积在本章注释中给出的参考文献中讨论。叉积返回一个垂直于叉积两个参数的3D向量。所得向量的长度与 $\sin \phi$ 相关:

$$|\mathbf{a} \times \mathbf{b}| = |\mathbf{a}| |\mathbf{b}| \sin \phi$$

量值 $|\mathbf{a} \times \mathbf{b}|$ 等于由向量 $\mathbf{a}$$\mathbf{b}$ 形成的平行四边形的面积。此外,$\mathbf{a} \times \mathbf{b}$ 既垂直于 $\mathbf{a}$ 也垂直于 $\mathbf{b}$(图2.19)。注意这样的向量只有两个可能的方向。

Figure2-19

根据定义,$x$、$y$ 和 $z$ 轴方向上的向量为:

$$\begin{align} \mathbf{x} &= (1, 0, 0) \\ \mathbf{y} &= (0, 1, 0) \\ \mathbf{z} &= (0, 0, 1) \end{align}$$

我们约定 $\mathbf{x} \times \mathbf{y}$ 必须在正 $z$ 或负 $z$ 方向。这种选择在某种程度上是任意的,但标准做法是假设:

$$\mathbf{z} = \mathbf{x} \times \mathbf{y}$$

三个笛卡尔单位向量的所有可能排列为:

$$\begin{align} \mathbf{x} \times \mathbf{y} &= +\mathbf{z} \\ \mathbf{y} \times \mathbf{x} &= -\mathbf{z} \\ \mathbf{y} \times \mathbf{z} &= +\mathbf{x} \\ \mathbf{z} \times \mathbf{y} &= -\mathbf{x} \\ \mathbf{z} \times \mathbf{x} &= +\mathbf{y} \\ \mathbf{x} \times \mathbf{z} &= -\mathbf{y} \end{align}$$

由于 $\sin \phi$ 的性质,我们也知道一个向量与自身的叉积是零向量,所以 $\mathbf{x} \times \mathbf{x} = \mathbf{0}$,等等。注意叉积不满足交换律,即 $\mathbf{x} \times \mathbf{y} \neq \mathbf{y} \times \mathbf{x}$

细心的观察者会注意到,上述讨论并不能让我们明确地画出笛卡尔坐标轴是如何相互关联的。更具体地说,如果我们把 $\mathbf{x}$$\mathbf{y}$ 放在人行道上,$\mathbf{x}$ 指向东方,$\mathbf{y}$ 指向北方,那么 $\mathbf{z}$ 是指向天空还是指向地面?通常的约定是让 $\mathbf{z}$ 指向天空。这被称为右手坐标系。这个名称来自于记忆方法:用你的右手手掌和手指"抓住" $\mathbf{x}$,然后将其旋转向 $\mathbf{y}$。向量 $\mathbf{z}$ 应该与你的拇指对齐。这在图2.20中有所说明。

Figure2-20

叉积满足分配律:

$$\mathbf{a} \times (\mathbf{b} + \mathbf{c}) = \mathbf{a} \times \mathbf{b} + \mathbf{a} \times \mathbf{c}$$

以及乘数分配律:

$$\mathbf{a} \times (k\mathbf{b}) = k(\mathbf{a} \times \mathbf{b})$$

然而,叉积不满足交换律,右手定则的一个结果是:

$$\mathbf{a} \times \mathbf{b} = -(\mathbf{b} \times \mathbf{a})$$

在笛卡尔坐标系中,我们可以使用显式展开来计算叉积:

$$\begin{align} \mathbf{a} \times \mathbf{b} &= (x_a\mathbf{x} + y_a\mathbf{y} + z_a\mathbf{z}) \times (x_b\mathbf{x} + y_b\mathbf{y} + z_b\mathbf{z}) \\ &= x_ax_b\mathbf{x} \times \mathbf{x} + x_ay_b\mathbf{x} \times \mathbf{y} + x_az_b\mathbf{x} \times \mathbf{z} \\ &\quad + y_ax_b\mathbf{y} \times \mathbf{x} + y_ay_b\mathbf{y} \times \mathbf{y} + y_az_b\mathbf{y} \times \mathbf{z} \tag{2.7} \\ &\quad + z_ax_b\mathbf{z} \times \mathbf{x} + z_ay_b\mathbf{z} \times \mathbf{y} + z_az_b\mathbf{z} \times \mathbf{z} \\ &= (y_az_b - z_ay_b)\mathbf{x} + (z_ax_b - x_az_b)\mathbf{y} + (x_ay_b - y_ax_b)\mathbf{z} \end{align}$$

所以,在坐标形式下:

$$\mathbf{a} \times \mathbf{b} = (y_az_b - z_ay_b, z_ax_b - x_az_b, x_ay_b - y_ax_b) \tag{2.8}$$

2.4.5 标准正交基和坐标系

管理坐标系是几乎所有图形程序的核心任务之一;其关键在于管理标准正交基。任意一组二维向量 $\mathbf{u}$$\mathbf{v}$ 构成标准正交基,条件是它们正交(成直角)且各自都是单位长度。因此:

$$|\mathbf{u}| = |\mathbf{v}| = 1$$

并且

$$\mathbf{u} \cdot \mathbf{v} = 0$$

在三维空间中,三个向量 $\mathbf{u}$、$\mathbf{v}$ 和 $\mathbf{w}$ 构成标准正交基,如果:

$$|\mathbf{u}| = |\mathbf{v}| = |\mathbf{w}| = 1$$

并且

$$\mathbf{u} \cdot \mathbf{v} = \mathbf{v} \cdot \mathbf{w} = \mathbf{w} \cdot \mathbf{u} = 0$$

如果满足

$$\mathbf{w} = \mathbf{u} \times \mathbf{v}$$

那么这个标准正交基是右手的,否则就是左手的

注意笛卡尔标准标准正交基只是无穷多个可能的标准正交基中的一个。使其特殊的是它和其隐含的原点位置被用于程序内的底层表示。因此,向量 $\mathbf{x}$、$\mathbf{y}$ 和 $\mathbf{z}$ 从不显式存储,标准原点位置 $\mathbf{o}$ 也不存储。全局模型通常存储在这个标准坐标系中,因此它通常被称为全局坐标系

然而,如果我们想使用另一个具有原点 $\mathbf{p}$ 和标准正交基向量 $\mathbf{u}$、$\mathbf{v}$ 和 $\mathbf{w}$ 的坐标系,那么我们需要显式存储这些向量。这样的系统被称为参考系坐标系。例如,在飞行模拟器中,我们可能希望维护一个以飞机机头为原点、标准正交基与飞机对齐的坐标系。同时,我们还会有主标准坐标系(图2.21)。与特定对象(如飞机)关联的坐标系通常称为局部坐标系

Figure2-21

在底层,局部坐标系是以标准坐标存储的。例如,如果 $\mathbf{u}$ 具有坐标 $(x_u, y_u, z_u)$,那么:

$$\mathbf{u} = x_u\mathbf{x} + y_u\mathbf{y} + z_u\mathbf{z}$$

一个位置隐含地包括相对于标准原点的偏移:

$$\mathbf{p} = \mathbf{o} + x_p\mathbf{x} + y_p\mathbf{y} + z_p\mathbf{z}$$

其中 $(x_p, y_p, z_p)$$\mathbf{p}$ 的坐标。

注意,如果我们相对于 $\mathbf{u}$-$\mathbf{v}$-$\mathbf{w}$ 坐标系存储一个向量 $\mathbf{a}$,我们存储一个三元组 $(u_a, v_a, w_a)$,从几何上可以解释为:

$$\mathbf{a} = u_a\mathbf{u} + v_a\mathbf{v} + w_a\mathbf{w}$$

要获得存储在 $\mathbf{u}$-$\mathbf{v}$-$\mathbf{w}$ 坐标系中的向量 $\mathbf{a}$ 的标准坐标,只需回忆 $\mathbf{u}$、$\mathbf{v}$ 和 $\mathbf{w}$ 本身就是用笛卡尔坐标存储的,因此如果显式计算表达式 $u_a\mathbf{u} + v_a\mathbf{v} + w_a\mathbf{w}$ 就已经是笛卡尔坐标了。

要获得存储在标准坐标系中的向量 $\mathbf{b}$$\mathbf{u}$-$\mathbf{v}$-$\mathbf{w}$ 坐标,我们可以使用点积:

$$u_b = \mathbf{u} \cdot \mathbf{b}; \quad v_b = \mathbf{v} \cdot \mathbf{b}; \quad w_b = \mathbf{w} \cdot \mathbf{b}$$

这是有效的,因为我们知道对于某些 $u_b$、$v_b$ 和 $w_b$

$$u_b\mathbf{u} + v_b\mathbf{v} + w_b\mathbf{w} = \mathbf{b}$$

点积可以分离出 $u_b$ 坐标:

$$\begin{align} \mathbf{u} \cdot \mathbf{b} &= u_b(\mathbf{u} \cdot \mathbf{u}) + v_b(\mathbf{u} \cdot \mathbf{v}) + w_b(\mathbf{u} \cdot \mathbf{w}) \\ &= u_b \end{align}$$

这是有效的,因为 $\mathbf{u}$、$\mathbf{v}$ 和 $\mathbf{w}$ 是标准正交的。

使用矩阵管理坐标系变换在第6.2.1节和第6.5节中讨论。

2.4.6 从单个向量构建基

我们经常需要构建一个与给定向量对齐的标准正交基。也就是说,给定一个向量 $\mathbf{a}$,我们希望找到标准正交向量 $\mathbf{u}$、$\mathbf{v}$ 和 $\mathbf{w}$,使得 $\mathbf{w}$ 指向与 $\mathbf{a}$ 相同的方向(Hughes & Möller, 1999),但我们并不特别关心 $\mathbf{u}$$\mathbf{v}$ 是什么。一个向量不足以唯一确定答案;我们只需要一个稳健的过程来找到任何一个可能的基。

可以使用叉积来完成这个任务,如下所示。首先使 $\mathbf{w}$ 成为 $\mathbf{a}$ 方向上的单位向量:

$$\mathbf{w} = \frac{\mathbf{a}}{|\mathbf{a}|}$$

然后选择任何与 $\mathbf{w}$ 不共线的向量 $\mathbf{t}$,并使用叉积构建垂直于 $\mathbf{w}$ 的单位向量 $\mathbf{u}$

$$\mathbf{u} = \frac{\mathbf{t} \times \mathbf{w}}{|\mathbf{t} \times \mathbf{w}|}$$

如果 $\mathbf{t}$$\mathbf{w}$ 共线,分母将为零;如果它们几乎共线,结果将具有低精度。找到与 $\mathbf{w}$ 充分不同的向量的简单过程是:从 $\mathbf{t}$ 等于 $\mathbf{w}$ 开始,将 $\mathbf{t}$ 中幅度最小的分量改为1。例如,如果 $\mathbf{w} = (1/\sqrt{2}, -1/\sqrt{2}, 0)$,那么 $\mathbf{t} = (1/\sqrt{2}, -1/\sqrt{2}, 1)$

一旦获得了 $\mathbf{w}$$\mathbf{u}$,完成基的构建就很简单:

$$\mathbf{v} = \mathbf{w} \times \mathbf{u}$$

使用这种构建的一个例子是表面着色,其中需要一个与表面法线对齐的基,但绕法线的旋转通常并不重要。

2.4.7 从两个向量构建基

上一节中的过程也可以用于绕给定向量的基的旋转很重要的情况。一个常见的例子是为相机构建基:重要的是要有一个向量与相机观察的方向对齐,但相机绕该向量的方向不是任意的,需要以某种方式指定。一旦方向确定下来,基就完全确定了。

完全指定一个坐标系的常见方法是提供两个向量:$\mathbf{a}$(指定 $\mathbf{w}$)和 $\mathbf{b}$(指定 $\mathbf{v}$)。如果已知这两个向量是垂直的,那么通过 $\mathbf{u} = \mathbf{b} \times \mathbf{a}$ 构建第三个向量是一件简单的事情。

为了确保结果基真的是标准正交的,即使输入向量不完全是,建议使用与单向量过程非常相似的程序:

$$\mathbf{w} = \frac{\mathbf{a}}{|\mathbf{a}|}$$

$$\mathbf{u} = \frac{\mathbf{b} \times \mathbf{w}}{|\mathbf{b} \times \mathbf{w}|}$$

$$\mathbf{v} = \mathbf{w} \times \mathbf{u}$$

实际上,当 $\mathbf{a}$$\mathbf{b}$ 不垂直时,这个过程也能很好地工作。在这种情况下,$\mathbf{w}$ 将完全按照 $\mathbf{a}$ 的方向构建,而 $\mathbf{v}$ 被选择为在所有垂直于 $\mathbf{w}$ 的向量中最接近 $\mathbf{b}$ 的向量。

如果 $\mathbf{a}$$\mathbf{b}$ 共线,这个过程就不起作用了。在这种情况下,$\mathbf{b}$ 对于选择垂直于 $\mathbf{a}$ 的哪个方向没有帮助:它垂直于所有这些方向。

在指定相机位置的例子中(第4.3节),我们要构建一个坐标系,使得 $\mathbf{w}$ 平行于相机观察的方向,$\mathbf{v}$ 应该指向相机的顶部。为了使相机保持竖直,我们围绕视线方向构建基,使用正上方向作为参考向量来确定相机绕视线方向的方向。将 $\mathbf{v}$ 设置得尽可能接近正上方,正好符合"保持相机竖直"的直观概念。

2.4.8 基的正交化

有时你可能会发现计算中出现的问题是由于本应正交标准的基中混入了误差——例如,由于计算中的舍入误差,或者基以低精度存储在文件中。

可以使用前一节的过程;简单地使用现有的 $\mathbf{w}$$\mathbf{v}$ 向量重新构建基,将产生一个接近原始基且正交标准的新基。

这种方法对许多应用都很好,但不是最佳的方法。它确实能产生精确正交的向量,对于几乎正交的初始基,结果不会偏离起始点太远。然而,它是非对称的:它"偏爱" $\mathbf{w}$ 胜过 $\mathbf{v}$,$\mathbf{v}$ 胜过 $\mathbf{u}$(其起始值被舍弃)。它选择接近起始基的基,但不能保证选择最接近的正交标准基。当这还不够好时,可以使用SVD(奇异值分解,第5.4.1节)来计算保证最接近原始基的正交标准基。

2.5 积分

关于图形学的一个可能误导人的地方是,它充满了积分,因此人们可能认为必须擅长代数求解积分。这绝对不是事实。图形学中的大多数积分都无法解析求解,因此需要数值求解。完全有可能在图形学领域有一个辉煌的职业生涯,而从未代数求解过一个积分。

虽然你不需要能够代数求解积分,但你确实需要能够阅读它们,以便进行数值求解。在一维情况下,积分通常相当易读。例如,这个积分:

$$\int_{\pi}^{2\pi} \sin(x) dx$$

可以理解为"计算函数$\sin(x)$在$x = \pi$和$x = 2\pi$之间的面积。"计算机科学家可能将这部分:

$$\int_{\pi}^{2\pi} dx$$

视为一个函数调用。我们可以称它为"integrate()"。它接受两个对象:一个函数和一个定义域(区间)。因此整个调用可能是:

float area = integrate(sin(), [pi, 2*pi]);

在更高级的微积分中,我们可能开始在球面上积分,对于图形学来说,令人兴奋的是我们仍然可以用这种方式思考:

float area = integrate(cos(), unit_sphere)

这个函数内部的机制可能不同,但所有积分都有两个要素:

  1. 被积函数
  2. 积分的定义域

通常,诀窍就是仔细解码手头问题的第1和第2部分是什么。这在精神上与从有时令人困惑的文档中正确理解API调用非常相似。

2.5.1 平均值和加权平均值

积分计算事物的总和。长度、面积、体积等等。但它们经常被用来计算平均值。例如,我们可以通过在一个区域(如一个国家)上对高程进行积分来计算该区域的总体积:

float volume = integrate(elevation(), country)

但我们也可以计算平均高程:

float averageElevation = integrate(elevation(), country) / integrate(1, country)

这本质上是"体积除以面积"。这可以抽象为:

float averageElevation = average(elevation, country)

我们也可以计算加权平均值。在这里,我们添加一个权重函数,以在平均值中更多地强调某些点。例如,如果我们想通过温度来强调区域的某些部分(这是相当任意的,我们将在下一节中看到更多与图形学相关的例子):

float weightedAverageElevation =
    integrate(temperature() * elevation(), country) /
    integrate(temperature(), country)

留意这种形式是个好主意;积分中经常包含加权平均值而没有明确指出这一点,有时这可以帮助理解。

数学表达式可以写作:

平均值$$\text{平均高程} = \frac{\int_{\text{country}} \text{elevation}(\mathbf{x}) , d\mathbf{x}}{\int_{\text{country}} 1 , d\mathbf{x}}$$

加权平均值$$\text{加权平均高程} = \frac{\int_{\text{country}} \text{temperature}(\mathbf{x}) \cdot \text{elevation}(\mathbf{x}) , d\mathbf{x}}{\int_{\text{country}} \text{temperature}(\mathbf{x}) , d\mathbf{x}}$$

2.5.2 立体角积分

我们经常看到的一种积分类型的例子是这些形式之一或相关的形式:

float shade = integrate(cos() * f * (), unit_hemisphere)

注意,由于 integrate(cos(), unit_hemisphere) = pi,加权平均版本就是:

float shade = integrate((1/pi) * cos() * f * (), unit_hemisphere)

这个积分更传统的形式是:

$$S = \int_{\mathbf{v} \in H} \frac{1}{\pi} (\mathbf{v} \cdot \mathbf{n}) f(\mathbf{v}) (\mathbf{v} \cdot \mathbf{n}) d\sigma(\mathbf{v})$$

或者使用球面坐标,就像我们可能用来代数求解这些积分一样:

$$S = \int_{\phi=0}^{2\pi} \int_{\theta=0}^{\pi/2} \frac{1}{\pi} f(\theta,\phi) \cos\theta \sin\theta , d\theta , d\phi$$

正弦项是球面坐标的面积修正因子。注意,在图形学中,我们很少需要完整地写出这些,而是在数值求解积分时使用没有显式坐标的更简单形式。

上述特定积分是完美反射漫反射(朗伯)表面的着色值,它也是所有入射颜色的加权平均值。这种结构对于直觉理解非常有用;表面的颜色通常与入射颜色的加权平均值相关。

立体角上的积分几乎总是相同的,但使用各种不同的记号。关键是要认识到这只是记号,并将你看到的记号映射到你最熟悉的记号。这很像读伪代码!

重要说明:在这个积分中:

  • $\mathbf{v} \cdot \mathbf{n}$ 是入射方向与表面法向量的点积,即 $\cos\theta$
  • $f(\mathbf{v})$ 是从方向 $\mathbf{v}$ 入射的光强度
  • $H$ 表示单位半球
  • $d\sigma(\mathbf{v})$ 是立体角微元
  • $\frac{1}{\pi}$ 是归一化因子,确保完美漫反射表面的反射率为1

2.6 密度函数

密度函数在图形学中经常出现(例如,"概率密度函数"),有时它们可能令人惊讶地令人困惑,但掌握它们的确切含义将帮助我们使用它们,并在遇到困惑时摆脱困境。我们知道什么是函数,而密度函数就是返回密度的函数。那么什么是密度呢?密度是"每单位某物"的量,或更正式地说,是一个强度量。例如,你的体重不是密度,它是一个广延量,或者只是某种物质的量,而不是每单位某物的物质量。一个人在一段固定时期内(比如一年)可能增加的体重量是某种物质的量,以千克为单位测量,因此是广延量而不是密度。这个人"每天"或"每小时"增加的体重量是强度量,因此是密度。

作为非密度函数的例子,考虑太阳能电池板在特定一天(2014年7月1日)产生的能量,假设是120千焦耳。这是某种"物质"的量。好吧,这很好,但足够运行我的计算机吗?如果是台式机,我的计算机需要能量密度,或能量的速率,才能持续工作。那么我们如何将这一天的能量转换为能量速率呢?我们可以将其分成时间段。例如,我们可以分成四小时块、两小时块或一小时块,我们会看到速率在一天中发生变化,但同时随着如图2.22所示,时间段越来越短,量也在不断变小。

Figure2-42

随着我们将时间分割得越来越细,我们最终会分解到分钟和秒,并且会获得关于时间变化的更多信息,但箱体的高度会变得如此之小,以至于我们什么都看不到。因此,我们可以做的是根据箱体的宽度重新调整它们的高度,所以 (30kJ)/(0.5 h) = 60 kJ/h。

如果我们使用这个新的"千焦每小时"度量,箱体就不再变短,如图2.23所示。如果我们将这个过程推向极限,使箱体的宽度变为无穷小,我们就得到一条光滑的曲线。

Figure2-42

这条曲线就是密度函数的一个例子。有些人会称其为"能量密度"函数,其中密度所取的维度是时间,在某些情况下会被称为"时间能量密度"函数。因为这个特定的密度非常有用且经常被谈论,所以它有自己的名称——功率,我们不说"焦耳每小时",而是说瓦特。请注意,按照惯例,"瓦特"是焦耳每秒而不是每小时;选择特定单位而不是维度是为了方便。例如,一些物理单位使用米更合理,一些使用千米,一些使用纳米(还有一些像光的光谱辐射亮度在同一个量中同时使用米和纳米,所以当你发现自己困惑时,这不是你的错)。

综合起来,(1) 密度总是某种比率,你说"每单位Y有多少X"或"每Y有多少X",比如"每小时多少千米"(说"每单位长度多少千米"会很奇怪,但如果大家都同意默认的长度单位是什么,这就说得通了),(2) 密度函数是返回密度的函数

密度函数本身对于比较两个不同点的相对集中度很有用。例如,通过我们在时间上定义的能量密度函数(功率),我们可以说"下午2点的功率是上午9点的两倍"。但我们使用它们的另一种方式是计算区域内的总量。例如,要计算下午2点到下午4点之间产生的能量,我们只需积分:

$$E = \int_{2\text{pm}}^{4\text{pm}} P(t) , dt$$

许多积分都是这种"对密度函数积分"的形式,但这一点没有明确说明。如果你能理清一个积分是否在处理某个区间或区域内密度函数的"质量",有时会让事情更清楚。

关键要点

  • 密度函数描述的是强度量(每单位某物的量)
  • 通过积分密度函数可以得到广延量(总量)
  • 在图形学中,这种概念广泛应用于概率密度函数、辐射量等

2.7 曲线和曲面

曲线的几何,特别是曲面的几何,在图形学中起着核心作用。在这里我们回顾2D和3D空间中曲线和曲面的基础知识。

2.7.1 2D隐式曲线

直观地说,曲线是一组可以在纸上不抬笔连续绘制的点的集合。描述曲线的一种常见方法是使用隐式方程。二维隐式方程的形式为:

$$f(x,y) = 0$$

函数 $f(x,y)$ 返回一个实数值。使该值为零的点 $(x,y)$ 在曲线上,而使该值非零的点不在曲线上。例如,假设 $f(x,y)$ 是:

$$f(x,y) = (x-x_c)^2 + (y-y_c)^2 - r^2 \tag{2.9}$$

其中 $(x_c, y_c)$ 是一个2D点,$r$ 是一个非零实数。如果我们令 $f(x,y) = 0$,满足这个等式的点在以 $(x_c, y_c)$ 为圆心、$r$ 为半径的圆上。这被称为"隐式"方程的原因是曲线上的点 $(x,y)$ 不能从方程中直接计算出来,而是必须通过求解方程来确定。因此,曲线上的点不是由方程显式生成的,而是隐含地蕴藏在方程中的某个地方。

Figure2-22

有趣的是,$f$ 对所有的 $(x,y)$ 都有值。我们可以将 $f$ 看作地形,海平面位于 $f = 0$(图2.22)。海岸线就是隐式曲线,$f$ 的值就是海拔高度。另一个需要注意的是,曲线将空间分割为 $f &gt; 0$、$f < 0$ 和 $f = 0$ 的区域。因此,你可以通过计算 $f$ 来判断一个点是否在曲线"内部"。注意对于任何常数 $c$,$f(x,y) = c$ 都是一条曲线,而 $c = 0$ 只是作为约定使用。例如,如果 $f(x,y) = x^2 + y^2 - 1$,改变 $c$ 只会给出以原点为中心的各种圆(图2.23)。

Figure2-23

我们可以使用向量来简化表示法。如果我们有 $\mathbf{c} = (x_c, y_c)$$\mathbf{p} = (x, y)$,那么以 $\mathbf{c}$ 为中心、$r$ 为半径的圆由满足以下条件的位置向量定义:

$$(\mathbf{p} - \mathbf{c}) \cdot (\mathbf{p} - \mathbf{c}) - r^2 = 0$$

这个方程如果代数展开,将得到方程(2.9),但通过几何"阅读"这个方程更容易看出这是一个圆的方程。它的意思是:"圆上的点 $\mathbf{p}$ 具有以下性质:从 $\mathbf{c}$$\mathbf{p}$ 的向量与自身的点积等于 $r^2$。"因为一个向量与自身的点积就是其长度的平方,我们也可以理解为:"圆上的点 $\mathbf{p}$ 具有以下性质:从 $\mathbf{c}$$\mathbf{p}$ 的向量的长度平方为 $r^2$。"

更好的理解是观察到长度平方就是从 $\mathbf{c}$$\mathbf{p}$ 的距离平方,这提示了等价形式:

$$|\mathbf{p} - \mathbf{c}|^2 - r^2 = 0$$

当然,这进一步提示:

$$|\mathbf{p} - \mathbf{c}| - r = 0$$

上式可以理解为"圆上的点 $\mathbf{p}$ 是那些与中心点 $\mathbf{c}$ 距离为 $r$ 的点",这是圆的最佳定义之一。这说明方程的向量形式通常比等价的完整笛卡尔形式(带有 $x$$y$)提供更多的几何直观和洞察。因此,通常建议尽可能使用向量形式。此外,你可以在代码中支持向量类;使用向量形式时代码更清晰。面向向量的方程在实现时也更不容易出错:一旦在代码中实现并调试了向量类型,涉及 $x$、$y$ 和 $z$ 的剪切粘贴错误就会消失。习惯在这些方程中使用向量需要一点时间,但一旦掌握,回报是巨大的。

2.7.2 2D梯度

如果我们将函数 $f(x,y)$ 看作高度场,其中高度 $= f(x,y)$,那么梯度向量指向最大上坡方向,即直接向上的方向。梯度向量 $\nabla f(x,y)$ 由以下公式给出:

$$\nabla f(x,y) = \left(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right)$$

在隐式曲线 $f(x,y) = 0$ 上某点处计算的梯度向量垂直于该点处曲线的切向量。这个垂直向量通常称为曲线的法向量。此外,由于梯度指向上坡方向,它指示了 $f(x,y) &gt; 0$ 区域的方向。

Figure2-24

在高度场的背景下,偏导数和梯度的几何意义比平时更加清晰可见。假设在点 $(a,b)$ 附近,$f(x,y)$ 是一个平面(图2.24)。存在特定的上坡和下坡方向。与此方向成直角的是相对于平面水平的方向。平面与 $f(x,y) = 0$ 平面的任何交线都将沿着水平方向。因此,上坡/下坡方向将垂直于交线 $f(x,y) = 0$。要理解偏导数为什么与此相关,我们需要可视化其几何意义。回忆一维函数 $y = g(x)$ 的常规导数是:

$$\frac{dy}{dx} = \lim_{\Delta x \to 0} \frac{\Delta y}{\Delta x} \equiv \lim_{\Delta x \to 0} \frac{g(x+\Delta x) - g(x)}{\Delta x} \tag{2.10}$$

这测量了 $g$ 的切线斜率(图2.25)。

Figure2-25

偏导数是一维导数的推广。对于二维函数 $f(x,y)$,我们不能像方程(2.10)那样对 $x$ 取同样的极限,因为对于给定的 $x$ 变化,$f$ 可能以多种方式变化。然而,如果我们保持 $y$ 不变,我们可以定义导数的类比,称为偏导数(图2.26):

$$\frac{\partial f}{\partial x} \equiv \lim_{\Delta x \to 0} \frac{f(x+\Delta x, y) - f(x, y)}{\Delta x}$$

Figure2-26

为什么关于 $x$$y$ 的偏导数是梯度向量的分量?同样,几何直观比代数更容易理解。在图2.27中,我们看到向量 $\mathbf{a}$ 沿着 $f$ 不变的路径移动。注意这里的尺度足够小,使得表面高度 $h(x,y) = f(x,y)$ 可以被认为是局部平面的。从图中我们看到向量 $\mathbf{a} = (\Delta x, \Delta y)$

Figure2-27

因为上坡方向垂直于 $\mathbf{a}$,我们知道点积等于零:

$$(\nabla f) \cdot \mathbf{a} \equiv (x_{\nabla}, y_{\nabla}) \cdot (x_a, y_a) = x_{\nabla}\Delta x + y_{\nabla}\Delta y = 0 \tag{2.11}$$

我们也知道 $f$ 在方向 $(x_a, y_a)$ 上的变化等于零:

$$\Delta f = \frac{\partial f}{\partial x}\Delta x + \frac{\partial f}{\partial y}\Delta y \equiv \frac{\partial f}{\partial x}x_a + \frac{\partial f}{\partial y}y_a = 0$$

对于任何垂直的向量 $(x,y)$$(x',y')$,我们知道它们之间的角度是90度,因此它们的点积等于零(回忆点积与两向量间角度的余弦成正比)。因此,我们有 $xx' + yy' = 0$。给定 $(x,y)$,很容易构造与 $(x,y)$ 点积为零的有效向量,最明显的两个是 $(y, -x)$$(-y, x)$;你可以验证这些向量与 $(x,y)$ 的点积确实为零。这个观察的推广是 $(x,y)$ 垂直于 $k(y, -x)$,其中 $k$ 是任何非零常数。这意味着:

$$(x_a, y_a) = k\left(\frac{\partial f}{\partial y}, -\frac{\partial f}{\partial x}\right) \tag{2.12}$$

结合方程(2.11)和(2.12)得到:

$$(x_{\nabla}, y_{\nabla}) = k'\left(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right)$$

其中 $k'$ 是任何非零常数。根据定义,"上坡"意味着 $f$ 的正向变化,所以我们希望 $k' &gt; 0$,而 $k' = 1$ 是一个完全合理的约定。

作为梯度的例子,考虑隐式圆 $x^2 + y^2 - 1 = 0$,梯度向量为 $(2x, 2y)$,表明圆的外部是函数 $f(x,y) = x^2 + y^2 - 1$ 的正区域。注意梯度向量的长度可能因隐式方程中的乘数而不同。例如,单位圆可以用 $Ax^2 + Ay^2 - A = 0$ 描述,其中 $A$ 是任何非零数。这条曲线的梯度是 $(2Ax, 2Ay)$。这将垂直于圆,但长度由 $A$ 决定。对于 $A &gt; 0$,法向量将指向圆外;对于 $A &lt; 0$,它将指向圆内。这种从外向到内向的切换是正确的,因为正区域在圆内切换了。从高度场的角度来看,$h = Ax^2 + Ay^2 - A$,圆位于零高度。对于 $A &gt; 0$,圆围绕着一个凹陷;对于 $A &lt; 0$,圆围绕着一个凸起。当 $A$ 变得更负时,凸起的高度增加,但 $h = 0$ 的圆不变。最大上坡方向不变,但坡度增加。梯度的长度反映了坡度程度的这种变化。因此直观地,你可以将梯度的方向理解为指向上坡,其大小衡量坡度有多陡。

隐式2D直线

直线的熟悉"斜率-截距"形式是:

$$y = mx + b \tag{2.13}$$

这可以很容易地转换为隐式形式(图2.28):

$$y - mx - b = 0 \tag{2.14}$$

Figure2-28

这里 $m$ 是"斜率"(上升与水平距离的比值),$b$ 是直线与 $y$ 轴相交的 $y$ 值,通常称为 $y$ 截距。直线也分割了2D平面,但这里"内部"和"外部"可能更直观地称为"上方"和"下方"。

由于我们可以将隐式方程乘以任何常数而不改变它为零的点,对于任何非零 $k$,$kf(x,y) = 0$ 是相同的曲线。这允许同一条直线有几种隐式形式,例如:

$$2y - 2mx - 2b = 0$$

斜率-截距形式有时不便的一个原因是它不能表示某些直线,如 $x = 0$,因为 $m$ 必须是无穷大。因此,更一般的形式往往有用:

$$Ax + By + C = 0 \tag{2.15}$$

其中 $A$、$B$、$C$ 是实数。

假设我们知道直线上的两个点,$(x_0, y_0)$ 和 $(x_1, y_1)$。什么样的 $A$、$B$、$C$ 描述通过这两个点的直线?因为这些点在直线上,它们都必须满足方程(2.15):

$$\begin{align} Ax_0 + By_0 + C &= 0 \\ Ax_1 + By_1 + C &= 0 \end{align}$$

不幸的是,我们有两个方程和三个未知数:$A$、$B$、$C$。这个问题产生是因为隐式方程可以有任意乘数。我们可以为方便起见设 $C = 1$

$$Ax + By + 1 = 0$$

但我们遇到了与斜率-截距形式中无穷斜率情况类似的问题:通过原点的直线需要满足 $A(0) + B(0) + 1 = 0$,这是矛盾的。例如,通过原点的45度直线的方程可以写成 $x - y = 0$,或同样地 $y - x = 0$,甚至 $17y - 17x = 0$,但它不能写成 $Ax + By + 1 = 0$ 的形式。

Figure2-29

当我们遇到这样讨厌的代数问题时,我们尝试用几何直观作为指导来解决问题。如第2.5.2节所讨论的,我们有的一个工具是梯度。对于直线 $Ax + By + C = 0$,梯度向量是 $(A, B)$。这个向量垂直于直线(图2.29),并指向 $Ax + By + C$ 为正的直线一侧。给定直线上的两个点 $(x_0, y_0)$$(x_1, y_1)$,我们知道它们之间的向量指向与直线相同的方向。这个向量就是 $(x_1 - x_0, y_1 - y_0)$,因为它平行于直线,所以它也必须垂直于梯度向量 $(A, B)$。回忆由于隐式的任意缩放性质,有无穷多个 $(A, B, C)$ 描述这条直线。我们需要任何一个有效的 $(A, B, C)$

我们可以从任何垂直于 $(x_1 - x_0, y_1 - y_0)$$(A, B)$ 开始。根据第2.5.2节中的相同推理,这样的向量就是 $(A, B) = (y_0 - y_1, x_1 - x_0)$。这意味着通过 $(x_0, y_0)$$(x_1, y_1)$ 的直线方程是:

$$(y_0 - y_1)x + (x_1 - x_0)y + C = 0 \tag{2.16}$$

现在我们只需要找到 $C$。因为 $(x_0, y_0)$$(x_1, y_1)$ 在直线上,它们必须满足方程(2.16)。我们可以代入任一值并求解 $C$。对 $(x_0, y_0)$ 这样做得到 $C = x_0y_1 - x_1y_0$,因此直线的完整方程是:

$$(y_0 - y_1)x + (x_1 - x_0)y + x_0y_1 - x_1y_0 = 0 \tag{2.17}$$

再次强调,这是通过两点的直线的无穷多个有效隐式方程之一,但这种形式没有除法运算,因此对于有限笛卡尔坐标的点没有数值退化情况。方程(2.17)的一个好处是,当斜率-截距形式存在时,我们总是可以通过将非$y$项移到方程右边并除以$y$项的系数来转换为斜率-截距形式:

$$y = \frac{y_1 - y_0}{x_1 - x_0}x + \frac{x_1y_0 - x_0y_1}{x_1 - x_0}$$

Figure2-30

隐式直线方程的一个有趣性质是它可以用来找到从点到直线的有符号距离。$Ax + By + C$的值与到直线的距离成正比(图2.30)。如图2.31所示,从点到直线的距离是向量$k(A,B)$的长度,即:

$$\text{距离} = k\sqrt{A^2 + B^2} \tag{2.18}$$

Figure2-31

对于点$(x,y) + k(A,B)$,$f(x,y) = Ax + By + C$的值是:

$$\begin{align} f(x + kA, y + kB) &= Ax + kA^2 + By + kB^2 + C \\ &= k(A^2 + B^2) \tag{2.19} \end{align}$$

该方程中的简化是由于我们知道$(x,y)$在直线上,所以$Ax + By + C = 0$。从方程(2.18)和(2.19),我们可以看出从直线$Ax + By + C = 0$到点$(a,b)$的有符号距离是:

$$\text{距离} = \frac{f(a,b)}{\sqrt{A^2 + B^2}}$$

这里"有符号距离"意味着其大小(绝对值)是几何距离,但在直线的一侧,距离是正的,在另一侧是负的。如果你的问题有理由偏好特定一侧为正,你可以在同样有效的表示$f(x,y) = 0$和$-f(x,y) = 0$之间选择。注意如果$(A,B)$是单位向量,那么$f(a,b)$就是有符号距离。我们可以将方程(2.17)乘以一个常数,确保$(A,B)$是单位向量:

$$f(x,y) = \frac{y_0-y_1}{\sqrt{(x_1-x_0)^2 +(y_0-y_1)^2}}x + \frac{x_1-x_0}{\sqrt{(x_1-x_0)^2 +(y_0-y_1)^2}}y + \frac{x_0y_1-x_1y_0}{\sqrt{(x_1-x_0)^2 +(y_0-y_1)^2}} = 0 \tag{2.20}$$

注意直接计算方程(2.20)中的$f(x,y)$给出有符号距离,但建立方程时需要开平方根。隐式直线对三角形光栅化非常有用(第8.1.2节)。2D直线的其他形式在第14章中讨论。

隐式二次曲线

在前一节中,我们看到线性函数$f(x,y)$产生隐式直线$f(x,y) = 0$。如果$f$是关于$x$和$y$的二次函数,其一般形式为:

$$Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0$$

所得到的隐式曲线称为二次曲线。二维二次曲线包括椭圆和双曲线,以及抛物线、圆和直线的特殊情况。

二次曲线的例子包括以$(x_c, y_c)$为圆心、半径为$r$的圆:

$$(x - x_c)^2 + (y - y_c)^2 - r^2 = 0$$

和轴对齐椭圆,其形式为:

$$\frac{(x - x_c)^2}{a^2} + \frac{(y - y_c)^2}{b^2} - 1 = 0$$

其中$(x_c, y_c)$是椭圆的中心,$a$和$b$是短半轴和长半轴(图2.32)。

Figure2-32

2.7.3 三维隐式曲面

正如隐式方程可以用来定义二维曲线一样,它们也可以用来定义三维曲面。与二维情况类似,隐式方程隐式地定义了曲面上的点集:

$$f(x,y,z) = 0$$

任何在曲面上的点$(x,y,z)$作为$f$的参数时结果为零。任何不在曲面上的点结果为非零数。你可以通过计算$f$来检查一个点是否在曲面上,或者通过查看$f$的符号来检查点位于曲面的哪一侧,但你不能总是显式地构造曲面上的点。使用向量记号,我们将这种关于$\mathbf{p} = (x,y,z)$的函数写作:

$$f(\mathbf{p}) = 0$$

2.7.4 隐式曲面的表面法向量

表面法向量(这是光照计算等所需的)是垂直于表面的向量。表面上的每个点都可能有不同的法向量。就像梯度为二维隐式曲线提供法向量一样,隐式曲面上点$\mathbf{p}$处的表面法向量由隐式函数的梯度给出:

$$\mathbf{n} = \nabla f(\mathbf{p}) = \left(\frac{\partial f(\mathbf{p})}{\partial x}, \frac{\partial f(\mathbf{p})}{\partial y}, \frac{\partial f(\mathbf{p})}{\partial z}\right)$$

推理与二维情况相同:梯度指向$f$增长最快的方向,这垂直于表面的所有切线方向,在这些切线方向上$f$保持常数。梯度向量指向$f(\mathbf{p}) > 0$的表面一侧,在给定上下文中我们可以将其视为"进入"表面或"从表面向外"。

如果$f$的特定形式产生向内的梯度,而需要向外的梯度,那么曲面$-f(\mathbf{p}) = 0$与曲面$f(\mathbf{p}) = 0$相同,但梯度方向相反,即:

$$-\nabla f(\mathbf{p}) = \nabla(-f(\mathbf{p}))$$

2.7.5 隐式平面

作为例子,考虑通过点$\mathbf{a}$且表面法向量为$\mathbf{n}$的无限平面。描述该平面的隐式方程为:

$$(\mathbf{p} - \mathbf{a}) \cdot \mathbf{n} = 0 \tag{2.21}$$

注意$\mathbf{a}$和$\mathbf{n}$是已知量。点$\mathbf{p}$是满足方程的任意未知点。在几何意义上,这个方程表示"从$\mathbf{a}$到$\mathbf{p}$的向量垂直于平面法向量"。如果$\mathbf{p}$不在平面内,那么$(\mathbf{p} - \mathbf{a})$不会与$\mathbf{n}$成直角(图2.33)。

Figure2-33

有时我们希望得到通过点$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$的平面的隐式方程。该平面的法向量可以通过计算平面内任意两个向量的叉积来求得。其中一个这样的叉积是:

$$\mathbf{n} = (\mathbf{b} - \mathbf{a}) \times (\mathbf{c} - \mathbf{a})$$

这使我们能够写出隐式平面方程:

$$(\mathbf{p} - \mathbf{a}) \cdot ((\mathbf{b} - \mathbf{a}) \times (\mathbf{c} - \mathbf{a})) = 0 \tag{2.22}$$

这个方程的几何解读是:由$\mathbf{p} - \mathbf{a}$、$\mathbf{b} - \mathbf{a}$和$\mathbf{c} - \mathbf{a}$定义的平行六面体的体积为零,即它们共面。只有当$\mathbf{p}$与$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$在同一平面内时,这才成立。这个方程的完整笛卡尔表示由行列式给出(这在第5.3节中有更详细的讨论):

$$\begin{vmatrix} x - x_a & y - y_a & z - z_a \\ x_c - x_a & y_c - y_a & z_c - z_a \\ x_b - x_a & y_b - y_a & z_b - z_a \end{vmatrix} = 0 \tag{2.23}$$

这个行列式可以展开(参见第5.3节关于行列式展开的方法)为包含许多项的冗长形式。

方程(2.22)和(2.23)是等价的,比较它们很有启发性。方程(2.22)易于几何解释,并能产生高效的代码。此外,如果利用已调试好的叉积和点积代码,相对容易避免编译成错误代码的排版错误。方程(2.23)同样易于几何解释,如果实现了高效的$3 \times 3$行列式函数,它也将是高效的。如果有函数determinant(a,b,c)可用,它也容易实现而不会出现拼写错误。如果你将行列式函数重命名为volume,其他人阅读你的代码会特别容易。因此,方程(2.22)和(2.23)都能很好地映射到代码中。

将任一方程完全展开为$x$、$y$和$z$分量的形式很可能产生拼写错误。这种拼写错误很可能能够编译通过,因此特别令人困扰。这是简洁数学产生简洁代码而冗长数学产生冗长代码的绝佳例子。

三维二次曲面

正如二变量的二次多项式定义了二维中的二次曲线一样,关于$x$、$y$和$z$的二次多项式定义了三维中的二次曲面。例如,球面可以写为:

$$f(\mathbf{p}) = (\mathbf{p} - \mathbf{c})^2 - r^2 = 0$$

轴对齐的椭球面可以写为:

$$f(\mathbf{p}) = \frac{(x - x_c)^2}{a^2} + \frac{(y - y_c)^2}{b^2} + \frac{(z - z_c)^2}{c^2} - 1 = 0$$

从隐式曲面构造三维曲线

人们可能希望能够用$f(\mathbf{p}) = 0$的形式创建隐式三维曲线。然而,所有这样的曲线都只是退化的曲面,在实践中很少有用。三维曲线可以通过两个同时满足的隐式方程的交集来构造:

$$\begin{cases} f(\mathbf{p}) = 0 \\ g(\mathbf{p}) = 0 \end{cases}$$

例如,三维直线可以由两个隐式平面的交线形成。通常,使用参数曲线更加方便;它们将在以下章节中讨论。

2.7.6 二维参数曲线

参数曲线由单个参数控制,该参数可以看作是沿曲线连续移动的一种索引。这样的曲线具有以下形式:

$$\begin{pmatrix} x \ y \end{pmatrix} = \begin{pmatrix} g(t) \ h(t) \end{pmatrix}$$

这里$(x, y)$是曲线上的一点,$t$是影响曲线的参数。对于给定的$t$值,由函数$g$和$h$确定某个点。对于连续的$g$和$h$,$t$的微小变化将产生$x$和$y$的微小变化。因此,当$t$连续变化时,点会扫描出一条连续曲线。这是一个良好的特性,因为我们可以使用参数$t$来显式构造曲线上的点。

通常我们可以用向量形式写出参数曲线:

$$\mathbf{p} = \mathbf{f}(t)$$

其中$\mathbf{f}$是一个向量值函数,$\mathbf{f}: \mathbb{R} \rightarrow \mathbb{R}^2$。这样的向量函数可以生成非常简洁的代码,因此应该尽可能使用它们。

我们可以将曲线视为位置关于时间的函数。曲线可以延伸到任何地方,并且可能形成环路和自相交。我们还可以认为曲线在任何点都有一个速度。例如,点$\mathbf{p}(t)$在$t = -2$附近移动缓慢,而在$t = 2$和$t = 3$之间移动迅速。即使曲线并不描述一个运动点,讨论参数曲线时也经常使用这种"运动点"的术语。

2D参数直线

通过点$\mathbf{p}_0 = (x_0, y_0)$和$\mathbf{p}_1 = (x_1, y_1)$的二维参数直线可以写为:

$$\begin{pmatrix} x \ y \end{pmatrix} = \begin{pmatrix} x_0 + t(x_1 - x_0) \ y_0 + t(y_1 - y_0) \end{pmatrix}$$

由于$x$和$y$的公式具有相似的结构,我们可以对$\mathbf{p} = (x, y)$使用向量形式(图2.34):

$$\mathbf{p}(t) = \mathbf{p}_0 + t(\mathbf{p}_1 - \mathbf{p}_0)$$

Figure2-34

你可以用几何形式理解这个公式:"从点$\mathbf{p}_0$开始,向$\mathbf{p}_1$移动一定距离,该距离由参数$t$决定。"这种形式的一个良好特性是$\mathbf{p}(0) = \mathbf{p}_0$且$\mathbf{p}(1) = \mathbf{p}_1$。由于点随$t$线性变化,$\mathbf{p}_0$和$\mathbf{p}_1$之间$t$的值衡量了两点间的分数距离。$t < 0$的点位于$\mathbf{p}_0$的"远"侧,$t > 1$的点位于$\mathbf{p}_1$的"远"侧。

参数直线也可以简单地描述为一个点$\mathbf{o}$和一个向量$\mathbf{d}$:

$$\mathbf{p}(t) = \mathbf{o} + t\mathbf{d}$$

当向量$\mathbf{d}$具有单位长度时,直线按弧长参数化。这意味着$t$是沿直线距离的精确度量。任何参数曲线都可以按弧长参数化,这显然是一种非常方便的形式,但并非所有曲线都能解析地转换为这种形式。

二维参数圆

圆心为$(x_c, y_c)$、半径为$r$的圆具有参数形式:

$$\begin{pmatrix} x \ y \end{pmatrix} = \begin{pmatrix} x_c + r\cos\phi \ y_c + r\sin\phi \end{pmatrix}$$

为了确保曲线上的每个点都有唯一的参数$\phi$,我们可以限制其定义域:$\phi \in [0, 2\pi)$或$\phi \in (-\pi, \pi]$或任何其他长度为$2\pi$的半开区间。

轴对齐椭圆可以通过分别缩放$x$和$y$的参数方程来构造:

$$\begin{pmatrix} x \ y \end{pmatrix} = \begin{pmatrix} x_c + a\cos\phi \ y_c + b\sin\phi \end{pmatrix}$$

2.7.7 3D参数曲线

三维参数曲线的运作方式与二维参数曲线非常相似:

$$\begin{align} x &= f(t), \\ y &= g(t), \\ z &= h(t). \end{align}$$

例如,绕$z$轴的螺旋线可以写为:

$$\begin{align} x &= \cos t, \\ y &= \sin t, \\ z &= t. \end{align}$$

与二维曲线一样,如果我们想要控制曲线的起点和终点,函数$f$、$g$和$h$定义在定义域$D \subset \mathbb{R}$上。用向量形式我们可以写为:

$$\begin{bmatrix} x \ y \ z \end{bmatrix} = \mathbf{p}(t)$$

本章我们只详细讨论三维参数直线。一般的三维参数曲线将在第15章中更广泛地讨论。

三维参数直线

三维参数直线可以写为二维参数直线的直接扩展,例如:

$$\begin{align} x &= 2 + 7t, \\ y &= 1 + 2t, \\ z &= 3 - 5t. \end{align}$$

这种写法很繁琐,也不便于转换为代码变量,因此我们将其写成向量形式:

$$\mathbf{p} = \mathbf{o} + t\mathbf{d}$$

在这个例子中,$\mathbf{o}$和$\mathbf{d}$分别为:

$$\mathbf{o} = (2, 1, 3)$$ $$\mathbf{d} = (7, 2, -5)$$

注意这与二维情况非常相似。可视化这条直线的方法是想象直线通过点$\mathbf{o}$且平行于向量$\mathbf{d}$。给定任意$t$值,都能得到直线上的某个点$\mathbf{p}(t)$。例如,当$t = 2$时,$\mathbf{p}(t) = (2, 1, 3) + 2(7, 2, -5) = (16, 5, -7)$。这个一般概念与二维情况相同(图2.30)。

Figure2-30

与二维情况一样,线段可以通过三维参数直线和区间$t \in [t_a, t_b]$来描述。两点$\mathbf{a}$和$\mathbf{b}$之间的线段由$\mathbf{p}(t) = \mathbf{a} + t(\mathbf{b} - \mathbf{a})$给出,其中$t \in [0, 1]$。这里$\mathbf{p}(0) = \mathbf{a}$,$\mathbf{p}(1) = \mathbf{b}$,而$\mathbf{p}(0.5) = (\mathbf{a} + \mathbf{b})/2$是$\mathbf{a}$和$\mathbf{b}$的中点。

射线(ray)或半直线(half-line)是具有半开区间(通常为$[0, \infty)$)的三维参数直线。从现在开始,我们将把所有直线、线段和射线都称为"射线"。这种做法虽然不够严格,但符合通用习惯,并使讨论更加简洁。

2.7.8 三维参数曲面

参数化方法可以用来定义三维空间中的曲面,方式与定义曲线非常相似,不同之处在于需要两个参数来处理曲面的二维区域。这些曲面的形式为:

$$\begin{align} x &= f(u,v), \\ y &= g(u,v), \\ z &= h(u,v). \end{align}$$

或者用向量形式表示:

$$\begin{bmatrix} x \ y \ z \end{bmatrix} = \mathbf{p}(u,v)$$

Figure2-35

示例:例如,地球表面上的一个点可以用经度和纬度这两个参数来描述。如果我们将原点定义在地球中心,设$r$为地球半径,那么以原点为中心的球坐标系(图2.35)让我们可以推导出参数方程:

$$\begin{align} x &= r\cos\phi\sin\theta, \\ y &= r\sin\phi\sin\theta, \\ z &= r\cos\theta. \end{align} \quad (2.24)$$

理想情况下,我们希望将此写成向量形式,但对于这种特定的参数形式来说并不可行。

我们还希望能够从给定的$(x,y,z)$找到对应的$(\theta,\phi)$。如果我们假设$\phi \in (-\pi,\pi]$,使用公式(2.2)中的atan2函数可以很容易做到这一点:

$$\theta = \arccos\left(\frac{z}{\sqrt{x^2 + y^2 + z^2}}\right)$$

对于隐式曲面,函数$f$的导数给出了曲面法向量。对于参数曲面,$\mathbf{p}$的导数同样提供了关于曲面几何的信息。

考虑函数$\mathbf{q}(t) = \mathbf{p}(t, v_0)$。该函数定义了一条参数曲线,它是通过改变$u$而保持$v$固定在值$v_0$时得到的。这条曲线称为等参数曲线(isoparametric curve,有时简称"isoparm"),位于曲面内。$\mathbf{q}$的导数给出了曲线的切向量,由于曲线位于曲面内,向量$\mathbf{q}'$也位于曲面内。由于它是通过改变$\mathbf{p}$的一个参数得到的,向量$\mathbf{q}'$是$\mathbf{p}$关于$u$的偏导数,我们将其记为$\mathbf{p}_u$。类似的论证表明,偏导数$\mathbf{p}_v$给出了当$u$保持常数时等参数曲线的切向量,这是曲面的第二个切向量。

因此,$\mathbf{p}$的导数在曲面上任意一点给出了两个切向量。曲面的法向量可以通过这两个向量的叉积得到:由于两者都与曲面相切,它们的叉积垂直于两个切向量,因此是曲面的法向量。叉积的右手定则提供了一种方法来决定曲面的哪一侧是正面或外侧;我们将使用以下约定,即向量

$$\mathbf{n} = \mathbf{p}_u \times \mathbf{p}_v$$

指向曲面的外侧。

2.7.9 曲线和曲面总结

隐式曲线和曲面:二维隐式曲线或三维隐式曲面由二变量或三变量的标量值函数定义,$f : \mathbb{R}^2 \to \mathbb{R}$ 或 $f : \mathbb{R}^3 \to \mathbb{R}$,曲面由函数值为零的所有点组成:

$$S = {\mathbf{p} | f(\mathbf{p}) = 0}$$

参数曲线:二维或三维参数曲线由单变量的向量值函数定义,$\mathbf{p} : D \subset \mathbb{R} \to \mathbb{R}^2$ 或 $\mathbf{p} : D \subset \mathbb{R} \to \mathbb{R}^3$,当$t$在整个定义域$D$上变化时,曲线被扫描出来:

$$S = {\mathbf{p}(t) | t \in D}$$

参数曲面:三维参数曲面由二变量的向量值函数定义,$\mathbf{p} : D \subset \mathbb{R}^2 \to \mathbb{R}^3$,曲面由定义域中所有点$(u,v)$的像组成:

$$S = {\mathbf{p}(u,v) | (u,v) \in D}$$

对于隐式曲线和曲面,法向量由$f$的导数(梯度)给出,切向量(对于曲线)或切向量组(对于曲面)可以通过构造基底从法向量推导得出。

对于参数曲线和曲面,$\mathbf{p}$的导数给出切向量(对于曲线)或切向量组(对于曲面),法向量可以通过构造基底从切向量推导得出。

2.8 线性插值

线性插值可能是图形学中最常见的数学运算。我们已经看到了位置线性插值的例子,用于在二维和三维中形成线段,其中两个点$\mathbf{a}$和$\mathbf{b}$与参数$t$相关联,形成直线$\mathbf{p} = (1-t)\mathbf{a} + t\mathbf{b}$。这是插值,因为当$t = 0$和$t = 1$时,$\mathbf{p}$恰好经过$\mathbf{a}$和$\mathbf{b}$。这是线性插值,因为权重项$t$和$1-t$是$t$的线性多项式。

另一个常见的线性插值是在$x$轴上的一组位置之间进行:$x_0, x_1, \ldots, x_n$,对于每个$x_i$我们都有一个关联的高度$y_i$。我们希望创建一个连续函数$y = f(x)$来插值这些位置,使得$f$经过每个数据点,即$f(x_i) = y_i$。对于线性插值,点$(x_i, y_i)$通过直线段连接。很自然地使用参数直线方程来表示这些段。参数$t$就是$x_i$和$x_{i+1}$之间的小数距离:

$$f(x) = y_i + \frac{x - x_i}{x_{i+1} - x_i}(y_{i+1} - y_i) \quad (2.26)$$

因为权重函数是$x$的线性多项式,所以这是线性插值。

上述两个例子具有线性插值的通用形式。我们创建一个变量$t$,当从数据项$A$移动到数据项$B$时,$t$从0变化到1。中间值就是函数$(1-t)A + tB$。注意到公式(2.26)具有这种形式,其中:

$$t = \frac{x - x_i}{x_{i+1} - x_i}$$

2.9 三角形

三角形在2D和3D图形程序中都是基本的建模图元。通常,颜色等信息会被标记到三角形的顶点上,然后在三角形内部进行插值。使这种插值操作变得简单直接的坐标系统称为重心坐标(barycentric coordinates);我们将从零开始推导这个概念。我们还将讨论2D三角形,这是在2D屏幕上绘制其图像之前必须理解的基础知识。

2.9.1 2D三角形

如果我们有一个由2D点$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$定义的2D三角形,我们可以首先求出它的面积:

$$\text{area} = \frac{1}{2}\begin{vmatrix} x_b-x_a & x_c-x_a \ y_b-y_a & y_c-y_a \end{vmatrix}$$

$$= \frac{1}{2}(x_ay_b + x_by_c + x_cy_a - x_ay_c - x_by_a - x_cy_b) \tag{2.27} $$

这个公式的推导可以在第5.3节中找到。如果点$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$按逆时针顺序排列,这个面积将为正值,否则为负值。

Figure2-56

在图形学中,我们经常希望为每个三角形顶点分配一个属性(如颜色),并在三角形内部平滑插值该属性的值。有多种方法可以实现这一点,但最简单的方法是使用重心坐标。理解重心坐标的一种方式是将其视为非正交坐标系统,这在第2.4.2节中有简要讨论。这样的坐标系统如图2.36所示,其中坐标原点是$\mathbf{a}$,从$\mathbf{a}$到$\mathbf{b}$和$\mathbf{c}$的向量是基向量。有了这个原点和基向量,任何点$\mathbf{p}$都可以写成:

$$p = a + \beta(b-a) + \gamma(c-a) \tag{2.28} $$

注意我们可以重新整理等式(2.28)中的项,得到:

$$p = (1-\beta-\gamma)a + \beta b + \gamma c$$

人们通常定义一个新变量$\alpha$来改善等式的对称性:

$$\alpha \equiv 1-\beta-\gamma$$

这产生了等式:

$$p(\alpha,\beta,\gamma) = \alpha a + \beta b + \gamma c \tag{2.29} $$

约束条件为:

$$\alpha + \beta + \gamma = 1 \tag{2.30}$$

重心坐标起初看起来像是抽象且不直观的构造,但它们被证明是强大且方便的。你可能会发现这样想很有用:在一个有两组平行街道但这两组街道不成直角的城市中,街道地址是如何工作的。自然的系统本质上就是重心坐标,你会很快习惯它们。

重心坐标是为平面上的所有点定义的。重心坐标的一个特别好的特性是,当且仅当以下条件成立时,点$\mathbf{p}$在由$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$形成的三角形内:

$$0 < \alpha < 1,$$ $$0 < \beta < 1,$$ $$0 < \gamma < 1.$$

如果其中一个坐标为零,其他两个在零和一之间,那么你在边上。如果两个坐标为零,那么另一个为一,你就在顶点上。重心坐标的另一个好特性是等式(2.29)实际上以平滑的方式混合了三个顶点的坐标。同样的混合系数$(\alpha,\beta,\gamma)$可以用来混合其他属性,如颜色,我们将在下一章中看到。

给定一个点$\mathbf{p}$,我们如何计算它的重心坐标?一种方法是将等式(2.28)写成关于未知数$\beta$和$\gamma$的线性系统,求解,然后设$\alpha = 1-\beta-\gamma$。这个线性系统是:

$$\begin{bmatrix} x_b-x_a & x_c-x_a \ y_b-y_a & y_c-y_a \end{bmatrix} \begin{bmatrix} \beta \ \gamma \end{bmatrix} = \begin{bmatrix} x_p-x_a \ y_p-y_a \end{bmatrix} \tag{2.31}$$

虽然代数求解等式(2.31)很直接,但计算直接的几何解往往更有成效。

Figure2.39

重心坐标的一个几何性质是它们是距离通过三角形边的直线的带符号缩放距离,如图2.39中$\beta$所示。回忆第2.7.2节,对于直线$f(x,y) = 0$,计算其方程$f(x,y)$会返回从$(x,y)$到该直线的缩放带符号距离。同样回忆,如果$f(x,y) = 0$是某条特定直线的方程,那么对于任何非零$k$,$kf(x,y) = 0$也是同一条直线的方程。改变$k$会缩放距离并控制直线的哪一边具有正的带符号距离,哪一边具有负的带符号距离。我们希望选择$k$使得,例如,$kf(x,y) = \beta$。由于$k$只是一个未知数,我们可以用一个约束来强制实现这一点,即在点$\mathbf{b}$处我们知道$\beta = 1$。因此,如果直线$f_{ac}(x,y) = 0$通过点$\mathbf{a}$和$\mathbf{c}$,那么我们可以按如下方式计算点$(x,y)$的$\beta$:

$$\beta = \frac{f_{ac}(x,y)}{f_{ac}(x_b,y_b)}\tag{2.32}$$

我们可以用类似的方式计算$\gamma$和$\alpha$。为了提高效率,通常明智的做法是直接计算其中两个重心坐标,然后使用等式(2.30)计算第三个。

为了找到通过$\mathbf{p}_0$和$\mathbf{p}_1$的直线的这种"理想"形式,我们首先可以使用第2.7.2节的技术来找到通过顶点的一些有效隐式直线。等式(2.17)给出:

$$f_{ab}(x,y) \equiv (y_a-y_b)x+(x_b-x_a)y +x_ay_b-x_by_a = 0$$

注意$f_{ab}(x_c,y_c)$可能不等于1,所以它可能不是我们寻求的理想形式。通过除以$f_{ab}(x_c,y_c)$我们得到:

$$\gamma = \frac{(y_a-y_b)x+(x_b-x_a)y +x_ay_b-x_by_a}{(y_a-y_b)x_c +(x_b-x_a)y_c +x_ay_b-x_by_a}$$

除法的存在可能会让我们担心,因为它引入了除零的可能性,但对于面积不接近零的三角形,这种情况不会发生。$\alpha$和$\beta$有类似的公式,但通常只需要其中一个:

$$\beta = \frac{(y_a-y_c)x+(x_c-x_a)y +x_ay_c-x_cy_a}{(y_a-y_c)x_b +(x_c-x_a)y_b +x_ay_c-x_cy_a}$$

$$\alpha = 1-\beta-\gamma$$

Figure2.40

计算重心坐标的另一种方法是计算如图2.40所示的子三角形的面积$A_a$、$A_b$和$A_c$。重心坐标遵循以下规则:

$$\alpha = A_a/A,$$ $$\beta = A_b/A,$$ $$\gamma = A_c/A,$$

其中$A$是三角形的面积。注意$A = A_a + A_b + A_c$,所以它可以通过两次加法而不是完整的面积公式来计算。如果允许面积为带符号的,这个规则对于三角形外的点仍然成立。其原因如图2.41所示。注意这些是带符号的面积,只要对$A$和子三角形$A_a$、$A_b$和$A_c$使用相同的带符号面积计算,就会被正确计算。

Figure2.41

2.9.2 3D三角形

重心坐标的一个奇妙之处在于它们几乎可以透明地扩展到3D。如果我们假设点$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$是3D点,那么我们仍然可以使用表示:

$$\mathbf{p} = (1-\beta-\gamma)\mathbf{a} +\beta\mathbf{b} +\gamma\mathbf{c}$$

现在,当我们改变$\beta$和$\gamma$时,我们扫过一个平面。

Figure2.42

三角形的法向量可以通过计算三角形平面内任意两个向量的叉积来找到(图2.42)。最容易的方法是使用三条边中的两条作为这些向量,例如:

$$\mathbf{n} = (\mathbf{b}-\mathbf{a})\times(\mathbf{c}-\mathbf{a})\tag{2.34}$$

注意这个法向量不一定是单位长度,并且它遵循叉积的右手定则。

三角形的面积可以通过计算叉积的长度来求得:

$$\text{area} = \frac{1}{2}|(\mathbf{b}-\mathbf{a})\times(\mathbf{c}-\mathbf{a})|\tag{2.35}$$

注意这不是带符号的面积,因此不能直接用于计算重心坐标。然而,我们可以观察到,具有"顺时针"顶点顺序的三角形的法向量指向的方向与同一平面内具有"逆时针"顶点顺序的三角形的法向量相反。

回顾一下:

$$\mathbf{a} \cdot \mathbf{b} = |\mathbf{a}| |\mathbf{b}| \cos\phi$$

其中$\phi$是两个向量之间的角度。如果$\mathbf{a}$和$\mathbf{b}$平行,那么$\cos\phi = \pm 1$,这提供了一个测试向量是否指向相同或相反方向的方法。

这个性质,结合方程(2.33)、(2.34)和(2.35),建议使用以下公式:

$$\alpha = \frac{\mathbf{n} \cdot \mathbf{n}_a}{|\mathbf{n}|^2},$$

$$\beta = \frac{\mathbf{n} \cdot \mathbf{n}_b}{|\mathbf{n}|^2},$$

$$\gamma = \frac{\mathbf{n} \cdot \mathbf{n}_c}{|\mathbf{n}|^2},$$

其中$\mathbf{n}$是用顶点$\mathbf{a}$、$\mathbf{b}$和$\mathbf{c}$计算的方程(2.34);$\mathbf{n}_a$是用顶点$\mathbf{b}$、$\mathbf{c}$和$\mathbf{p}$计算的方程(2.34),以此类推,即:

$$\mathbf{n}_a = (\mathbf{c}-\mathbf{b})\times(\mathbf{p}-\mathbf{b}),$$ $$\mathbf{n}_b = (\mathbf{a}-\mathbf{c})\times(\mathbf{p}-\mathbf{c}),$$ $$\mathbf{n}_c = (\mathbf{b}-\mathbf{a})\times(\mathbf{p}-\mathbf{a}).$$

2.10 离散概率

概率研究包含随机结果的事物,而离散概率是指存在有限个随机结果的情况。一个经典例子是六面骰子,其中骰子从集合${1,2,3,4,5,6}$中随机取值,当你投掷它时,每个结果出现的概率相等。某个特定结果的概率是该结果发生的频率比例。任何事情发生的总频率比例为1。每次投掷时,每个面出现的概率都是$\frac{1}{6}$。

随机性中比较令人困惑的一个方面是区分随机结果(要么尚未发生,要么已经发生但我们不知道结果)和已经投掷后的骰子。随机变量是一个没有已知值的单一数值,但它将从已知的可能性集合中以已知的概率取值。这里的"变量"一词来自数学,与编程中的"变量"直接相关。随机变量的一个例子是$X$,其中"$X$ = 骰子的最终结果"。变量可以使用任何符号;大写字母$X$在数学中经常用作随机变量的符号,就像"i"和"j"在计算机科学中经常用作循环变量一样。计算机程序对随机变量有相当直接的应用:

int X = rand_from(1,6)

其中$X$是一个我们不知道其值的变量,但我们知道当运行程序时,我们将得到六个值中的一个,每个值的概率都是$\frac{1}{6}$,这直接对应于随机变量"$X$ = 骰子的最终结果"的情况。

随机变量有两个经常使用的性质:期望值和方差。随机变量$X$的期望值,有时称为期望,通常记作$E[X]$或$E(X)$,或许更好的称呼是"期望平均值",但实际上不这么叫,所以不要这样说,否则会让熟悉标准术语的人感到困惑。这就是在所有"投掷骰子"的平行宇宙中$X$所取的平均值。可以通过将每个结果乘以其概率然后相加来计算:

$$E[X] = \frac{1}{6}(1) + \frac{1}{6}(2) + \frac{1}{6}(3) + \frac{1}{6}(4) + \frac{1}{6}(5) + \frac{1}{6}(6) = 3.5$$

所以如果我们对大量投掷骰子的结果求平均,我们会"期望"得到接近3.5的值。

说"我期望骰子出现3.5"听起来并不荒谬,当你知道它只能出现整数时,但这个术语或许是不幸的选择。这个术语在各个领域都是相当标准的,所以尽管存在缺陷,只要努力内化它,你就不会在与其他领域的人讨论这个话题时遇到沟通问题。

期望值告诉我们随机变量的趋势方向,但它不能告诉我们这种趋势需要多长时间才会出现,也不能告诉我们它偏离平均值的幅度有多大。例如,一个有3个1和3个6的骰子仍然具有3.5的期望值,但其"偏离均值"会比常规骰子更大。那么我们如何衡量变异的幅度呢?

一种方法是测量与3.5的平均偏差,但如果我们包含符号,那么平均偏差为零,因为投出1时的$-2.5$偏差会被投出6时的$+2.5$偏差抵消。我们可以取绝对差值,但这在实践中存在问题(包含绝对值的代数运算具有挑战性)以及一些理论问题。在实践中,人们更倾向于使用平均平方偏差,称之为方差:

$$V(X) = \text{average}((X - E[X])^2)$$

由于它是统计性的,这个平均值就是期望值,所以

$$V(X) = E[(X - E[X])^2]$$

对于骰子的情况,$E[X] = 3.5$,$X - E[X]$的值分别是$-2.5, -1.5, -0.5, 0.5, 1.5, 2.5$,因此$(X - E[X])^2$的值分别是$6.25, 2.25, 0.25, 0.25, 2.25, 6.25$,因此$X$的方差,通常记作$V(X)$,等于$\frac{17.5}{6}$。

通过对方差公式进行代数变换,可以得到一个有时更方便的形式:

$$V(X) = E[X^2] - (E[X])^2$$

期望值和方差有一些经常被使用的代数性质。

例如,假设我们有两个随机变量$X$和$Y$,并定义一个变量$Z = X + Y$。那么$E[Z]$是多少?结果是:

$$E[Z] = E[X + Y] = E[X] + E[Y]$$

令人惊讶的是,即使$X$和$Y$不是"统计独立的"(对于我们骰子的情况,它们可能以某种方式相互影响),这个公式仍然成立。在一个极端的例子中,我们可以观察第一个骰子,然后将第二个骰子设置为与第一个相同的值。这个公式仍然适用!这是非常强大的性质,在程序中经常作为一个未明说的性质被使用。

方差具有相同的行为,但仅当$X$和$Y$独立时:

$$V(Z) = V(X + Y) = V(X) + V(Y)$$

一个反例表明这个公式对于相关的$X$和$Y$不一定适用:假设你投掷$X$,然后将$Y$设置为该骰子的相对面,所以对于$X=1$选择$Y=6$,如果$X=2$选择$Y=5$,对于$X=3$选择$Y=4$,等等。$Z$的值总是7,因此方差为零。但是$X$的方差不是0,显然也不是独立和会产生的$2 \times \frac{17.5}{6}$。

方差的一个缺点是由于平方运算,它不太直观。因此人们经常使用方差的平方根,称为标准差,通常记作$\sigma(X)$。所以:

$$\sigma(X) = \sqrt{V(X)}$$

对于$\sigma(X + Y)$没有很好的公式,所以方差的吸引力(存在好的公式)变得更加明显。注意对于骰子的例子,标准差$\sigma = \sqrt{\frac{17.5}{6}} \approx 1.7$。这"大约是"与均值3.5的平均距离,但略有不同,因为实际的平均绝对距离是1.5。所以虽然在实践中将标准差视为平均绝对偏差的直觉几乎总是无害的,但最好至少在心里记住它们是不同的。

2.11 ## 连续概率

在图形学中,我们经常使用能够取一个范围内数值的随机变量。这些通常称为连续随机变量。好消息是离散随机变量的几乎所有内容都能延续过来:术语、期望值定义和公式,以及方差定义和公式。然而,有一个重大区别:连续随机变量取任何特定值的概率都是零。

假设你有一个在0到10之间的均匀随机变量$X$:

X = continuous_random_from(-2.3, 10.9)

获得值1.7、$\pi$或$e$的概率都是相等的。问题在于精确获得1.7的概率为零。

好消息是密度函数解决了这个问题。就像焦耳每秒的情况一样,我们可以在一维情况下使用单位长度的概率。在上述$X = \text{continuous random from}(-2.3, 10.9)$的例子中,我们测量概率的维度是长度。如果长度以某个未指定的单位表示,我们只知道从零到十的范围,那么我们会说概率是"单位长度"的测量。

2.12 蒙特卡洛积分

第2.5节讨论了如何"理解"积分并将其抽象为一个integrate()函数。但是我们如何实际实现这个函数呢?在图形学中最常见的方法是使用蒙特卡洛积分。蒙特卡洛积分的代数推导通常显得丑陋和令人生畏。但如果我们看这个函数:

float shade = average(f(), hemisphere)

我们的直觉就能找到正确答案。在半球上选取一堆随机点$v_i$,计算$f(v_i)$并对它们求平均值,例如:

float sum = 0.0
N = 10000; // 或用户设置的其他大数
for (int i = 1; i <= N; i++) {
    vec3 v = random_point_on_hemisphere();
    sum = sum + f(v);
}
average = sum / N;

就是这么简单!现在你需要一个在单位半球上选取随机点的函数。最简单的方法是"拒绝采样法",首先通过反复在单位立方体中选取三个均匀随机数来在单位球中均匀选取点:

do {
    X = random_from(-1,1);
    Y = random_from(-1,1);
    Z = random_from(-1,1);
} while (X*X + Y*Y + Z*Z > 1);

然后根据需要翻转Z以使其位于半球中:

if (Z < 0) Z = -Z;

接着,将点投影到单位半球上:

v = unit_vector(X, Y, Z);

这是处理平均值的方法。但对于一般的积分呢?回想一下:

$$\text{average}(f(), \text{domain}) = \frac{\text{integrate}(f(), \text{domain})}{\text{integrate}(1, \text{domain})}$$

所以:

$$\text{integrate}(f(), \text{domain}) = \text{average}(f(), \text{domain}) \times \text{integrate}(1, \text{domain})$$

在半球的情况下,$\text{integrate}(1, \text{domain})$就是面积,即$2\pi$。

因此,蒙特卡洛积分通常是随机点的平均值乘以一个常数(域的大小——长度、面积等)。

2.12.1 重要性采样

当我们想要计算随机平均值的函数在其高值和低值之间有很大变化时,将样本集中在某些区域,然后用权重纠正非均匀性,这样做可能对我们有利。概率密度函数为我们提供了合适的工具:如果我们知道样本的PDF,那就是该区域"过度采样"程度的直接度量。如果我们使用非均匀样本,那么我们可以得到:

$$\text{积分} = \text{average_of_nonuniform_samples}\left(\frac{f()}{p()}, \text{domain}\right)$$

这个公式的一个巧妙之处在于它对均匀随机样本也同样适用。在这种情况下,PDF $p() = \frac{1}{\text{integrate}(1, \text{domain})}$,所以域的"大小"被编码在PDF中。

对于任何给定的蒙特卡洛重要性采样问题,我们遵循一个相当程式化的方法,至少在开始时是这样:

  1. 识别函数$f()$和积分域(例如,单位球面上的点或三角形上的点)。

  2. 选择在该域上生成随机样本$x_i$的方法,并确保有一种方法来计算每个样本的PDF $p(x_i)$

  3. 对许多$x_i$计算比值$\frac{f(x_i)}{p(x_i)}$的平均值。这是我们对积分的估计。

一个巧妙的特点是任何$p()$都可以使用,你都会收敛到正确答案(需要注意的是,在$f()$非零的地方$p()$必须非零)。你使用哪个$p()$仅仅影响你的估计收敛的速度。因此,我们通常从一个常数$p()$开始来调试我们的代码。

常见问题解答

• 为什么没有向量除法?

事实证明,对于向量来说,没有"好的"除法类比。然而,通过详细研究这个问题,可以引出四元数的概念(参见章节注释中引用的Hoffmann的书)。

• 对于超过三边的多边形,是否存在像重心坐标那样简洁的东西?

不幸的是,没有。即使是凸四边形也要复杂得多。这是三角形在图形学中成为如此常见几何图元的原因之一。

• 3D直线是否有隐式形式?

没有。然而,两个3D平面的交线定义了一条3D直线,所以3D直线可以用两个同时的3D隐式方程来描述。

• 准蒙特卡洛(QMC)或蓝噪声采样与蒙特卡洛采样有什么关系?

蒙特卡洛的核心思想是你可以对一堆"公平"的样本求平均值来估计真实平均值。这里,公平可以在统计意义上来理解。但是一些样本集合即使不是随机的,也可以被证明是"公平"的。准蒙特卡洛就是这样的集合之一,它们具有明显的确定性结构,这种结构不是随机的,但在形式上是均匀的(这种均匀性不是统计上的),这些集合通常比随机集合有更好的收敛性。蓝噪声样本集合对样本添加约束以避免聚集,就像QMC集合一样,可以在不完全随机的情况下提高收敛性。

在实践中,大多数技术都是使用蒙特卡洛形式化方法开发的,因为数学处理更容易,然后在代码中插入QMC或蓝噪声点,基于经验上的信心,即在实践中均匀性就是所需要的全部。