计算机图形学基础-02-数学杂项

《Fundamentals of Computer Graphics》5th(计算机图形学基础/虎书),中文翻译。

第 2 章 Miscellaneous Math 数学杂项

计算机图形学的很大一部分都是将数学直接转化为代码。数学越清晰,生成的代码就越清晰;因此,本书的许多内容集中在使用最合适的数学来完成工作。本章回顾了高中和大学数学中的各种工具,旨在更多地作为参考资料而不是教程使用。它可能看起来像是一堆杂乱无章的主题,确实如此;每个主题之所以被选择,是因为它在“标准”数学课程中有些不寻常,因为它在图形学中非常重要,或者因为它通常不能从几何角度来处理。除了建立本书中使用的符号的回顾外,本章还强调了几点,这些点有时会被忽略在标准的本科课程中,比如三角形的重心坐标。本章并不旨在对这些材料进行严格的处理;相反,强调直觉和几何解释。线性代数的讨论推迟到第 6 章,在讨论变换矩阵之前进行。鼓励读者浏览本章,熟悉涵盖的主题,并根据需要参考它。本章末尾的练习可能有助于确定哪些主题需要复习。

2.1 集合和映射 (Sets and Mappings)

映射,也称为函数,是数学和编程的基础。与程序中的函数类似,数学中的映射接受一个类型的参数,并将其映射到(返回)特定类型的对象。在程序中,我们说“类型”;在数学中,我们会确定集合。当我们有一个属于集合的对象时,我们使用符号、in 。例如

aSa \in S

可以解读为“a 是集合 S 的成员”。给定任意两个集合 A 和 B,我们可以通过取两个集合的笛卡尔积来创建第三个集合,表示为 A×B。这个集合 A×B 由所有可能的有序对 (a, b) 组成,其中 a \in A 且 b \in B。作为简写,我们使用符号 A2 来表示 A×A。我们可以扩展笛卡尔积,从三个集合中创建所有可能的有序三元组的集合,以及从任意多个集合中创建任意长的有序元组的集合。

常见的集合包括:

  • R\Bbb{R} —— 实数;
  • R+\Bbb{R}^+ —— 非负实数(包括零);
  • R2\Bbb{R}^2 —— 实二维平面中的有序对;
  • Rn\Bbb{R}^n —— n 维笛卡尔空间中的点;
  • Z\Bbb{Z} —— 整数;
  • S2S^2 —— 球面上的 3D 点(R3\Bbb{R}^3 中的点)。

请注意,虽然 S2S^2 由嵌入三维空间中的点组成,但它位于可用两个变量参数化的表面上,因此它可以被视为一个二维集合。映射的符号使用箭头和冒号,例如

f:RZf:\Bbb{R}→\Bbb{Z}

你可以将其解读为“存在一个名为 f 的函数,它以实数作为输入,将其映射到一个整数。” 在这里,箭头之前的集合称为函数的定义域,右侧的集合称为目标。计算机程序员可能更习惯以下等效语言:“存在一个名为 f 的函数,它有一个实数参数并返回一个整数。”换句话说,上面的集合符号等价于通用编程符号:

integerf(real)equivalentf:RZinteger f(real)←equivalent→f:\Bbb{R}→\Bbb{Z}

因此,冒号箭头符号可以被视为一种编程语法。如此简单。

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

2.1.1 逆映射 (Inverse Mappings)

如果我们有一个函数 f:ABf:A⟼B,则可能存在一个逆函数 f1:BAf^{-1}:B⟼A,它由规则 f1(b)=af^{-1}(b)=a 定义,其中 b=f(a)b=f(a)。这个定义仅在每个 b\in B 都是某个点的像(即,值域等于目标)且只有一个这样的点(即,只有一个 a 使得 f(a)=b)时才起作用。这样的映射或函数被称为双射。双射将 A 中的每个 a 映射到唯一的 B 中的 b,并且对于每个 B 中的 b,恰好有一个 A 中的 a 满足 f(a)=bf(a)=b(图 2.1)。一组骑手和马之间的双射表示每个人骑一匹马,每匹马都会被骑。两个函数分别为 rider(horse)rider(horse)horse(rider)horse(rider)。它们是彼此的逆函数。不是双射的函数没有逆函数(图 2.2)。

fig11_1.jpg

图 2.1,一个双射 ff 和其逆函数 f1f^{-1}。请注意,f1f^{-1} 也是一个双射。

fig11_1.jpg

图 2.2 函数 gg 没有逆函数,因为定义域 dd 的两个元素映射到 EE 中的同一个元素。函数 hh 没有逆函数,因为 FF 中的元素 TT 没有任何一个 dd 的元素映射到它。

一个双射的例子是 f:RRf:\Bbb{R}⟼\Bbb{R},其中 f(x)=x3f(x)=x^3。其逆函数为 f1(x)=x3f^{–1}(x)=\sqrt[3]{x}。这个例子表明,标准符号表示法可能有些笨拙,因为 x 在 fff1f^{–1} 中都被用作虚拟变量。有时使用不同的虚拟变量更直观,例如 y=f(x)y=f(x)x=f1(y)x=f^{-1}(y)。 这产生了更直观的 y=x3y=x^3x=y3x=\sqrt[3]{y}。没有逆函数的一个例子是 sqr:RRsqr:R⟼R,其中 sqr(x)=x2sqr(x)=x^2。这是因为两个原因:首先,x2=(x)2x^2=(–x)^2,其次,定义域的成员没有映射到目标的负部分。请注意,如果我们将定义域和目标限制为 R+,那么仍然可以定义一个逆函数。在这种情况下,x\sqrt[]{x} 是一个有效的逆函数。

2.1.2 区间 (Intervals)

通常,我们希望指定一个函数处理数值受限的实数集合。其中一种限制是指定一个区间。例如,一个区间是指在零和一之间(不包括零或一)的实数集合,用(01)(0,1)表示。因为它不包括端点,所以这被称为开区间。相应的闭区间,它包含其端点,用方括号表示:[01][0,1]。这种标记可以混合使用;即 [01)[0,1) 包括零但不包括一。在写一个区间 [ab][a,b] 时,我们假设 aba≤b。常见的三种表示区间的方式如图 2.3 所示。区间的笛卡尔积经常被使用。例如,要表示 3D 单位立方体中的点 x,我们说 x[0,1]3x \in [0, 1]3

fig11_1.jpg

图 2.3,表示从 a 到 b 的区间,包括 b 但不包括 a,有三种等效的方法。

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

fig11_1.jpg

图 2.4,在 [3,5)[3,5)[4,6][4,6] 上执行区间运算。

2.1.3 对数 (Logarithms)

虽然对数在计算器出现前并不像今天这么普遍,但它们在涉及指数项的方程问题中通常很有用。根据定义,每个对数都有一个底数 a。xx 的“以 a 为底”的对数写作 logaxlog_ax,并被定义为“使 a 的多少次幂等于 x”,即:y=logaxay=xy=log_ax⇔a^y=x

请注意,“以 a 为底的对数”和“将 a 提高到幂次函数的函数”是彼此的反函数。这个基本定义有几个结果:

aloga(x)=xa^{log_a(x)}=x

loga(ax)=xlog_a(a^x)=x

loga(xy)=logax+logaylog_a(xy)=log_ax+log_ay

loga(x/y)=logaxlogaylog_a(x/y)=log_ax−log_ay

logax=logablogbxlog_ax=log_ab log_bx

当我们将微积分应用于对数时,通常会出现特殊的数字 e=2.718...e=2.718...ee 为底的对数被称为自然对数。我们采用常见的简写 lnln 来表示它:

lnxlogexlnx≡log_ex

请注意,“≡”符号可以读作“根据定义等价”。像 ππ 一样,特殊数字 ee 在许多情况下都会出现。许多领域使用除 ee 之外的特定底数进行计算,并在它们的符号中省略底数,即 logxlogx。例如,天文学家经常使用 10 作为底数,理论计算机科学家经常使用 2 作为底数。因为计算机图形学从许多领域借鉴技术,所以我们将避免使用这种简写。

对数和指数的导数说明为什么自然对数是“自然”的:

ddxlogax=1xlna\frac{d}{dx}log_ax=\frac{1}{xlna}

ddxax=axlna\frac{d}{dx}a^x=a^{x}lna

上面的常数系数仅在 a=ea=e 时为 1。

2.2 解二次方程

一个二次方程的形式为:

Ax2+Bx+C=0Ax^2+Bx+C=0

其中 xx 是实数未知数,AABBCC 是已知常数。如果将它想象成一个二维坐标系中的图像,其中 y=Ax2+Bx+Cy = Ax^2 + Bx + C,则解就是 yy 中“零点”的 xx 值。因为 y=Ax2+Bx+Cy = Ax^2 + Bx + C 是一条抛物线,所以根据它是否与 xx 轴相交(如图 2.5),会有零个、一个或两个实数解。

fig11_1.jpg

图 2.5。二次方程根的几何解释是抛物线与 xx 轴的交点。

为了解析地求解二次方程,我们首先将其除以 AA

x2+BAx+CA=0x^2+{B \over A}x+{C \over A}=0

然后,我们“配方”,将项分组:

(x+B2A)2B24A2+CA=0.\left(x+\frac{B}{2 A}\right)^{2}-\frac{B^{2}}{4 A^{2}}+\frac{C}{A}=0 .

将常数部分移到右边并取平方根得到

x+B2A=±B24A2CA.x+\frac{B}{2 A}= \pm \sqrt{\frac{B^{2}}{4 A^{2}}-\frac{C}{A}} .

两边同时减去 B/(2A)B/(2A),然后将分母为 2A2A 的项分组,得到常见的形式:

x=B±B24AC2A(2.1)x=\frac{-B \pm \sqrt{B^{2}-4 A C}}{2 A} \tag{2.1}

一个健壮的实现将使用等价表达式 2C/(BB24AC)2 C /\left(-B \mp \sqrt{B^{2}-4 A C}\right) 来计算根之一,具体取决于 BB 的符号(练习 7)。

这里,“±±”符号表示有两个解,一个是正号,一个是负号。因此,3±13 ± 1 等于“二或四”。注意,决定实数解数量的项是

DB24ACD≡B^2−4AC,

它被称为二次方程的判别式。如果 D>0D>0,存在两个实数解(也称为根)。如果 D=0D=0,则存在一个实数解(“双重”根)。如果 D<0D<0,则不存在实数解。

例如,2x2+6x+4=02x2 +6x +4 = 0 的根为 x=1x = –1x=2x = –2,而方程 x2+x+1x2 + x+1 没有实数解。这些方程的判别式分别为 D=4D = 4D=3D = –3,因此我们可以得到预期的解数量。在程序中,通常最好先计算 DD,如果 DD 为负数,则不要取平方根并返回“无解”。

2.3 三角学 (trigonomtry)

在计算机图形学中,我们在许多情况下使用基本三角学知识。通常,这并不是什么高深的东西,而且记住基本定义往往会有所帮助。

2.3.1 角度 (Angles)

尽管我们在某种程度上认为角度是理所当然的,但我们还是应该回到它们的定义,以便我们可以将角度的概念扩展到球面上。一个角度由两个半线(从原点发出的无限射线)或方向之间形成,并且必须使用某种约定来确定它们之间所创建的角度的两种可能性,如图 2.6 所示。一个角度由它在单位圆上切出的弧段长度定义。一种常见的约定是使用较小的弧长,并通过指定两个半线的顺序来确定角度的符号。使用这种约定,所有角度都在区间 [π,π][–π, π] 内。

fig11_1.jpg

图 2.6。两个半线将单位圆分为两个弧段。任何一个弧段的长度都是两个半线之间的有效角度。我们可以使用较小长度的约定,或者指定某个顺序的两个半线,并在第一个到第二个半线的逆时针方向上扫过的弧决定角度ϕ的那个。

这些角度是由两个方向所“切割”的单位圆弧的长度。因为单位圆的周长是 2π,所以可能的两个角度的和为 2π。这些弧长的单位是弧度。另一个常见的单位是度数,其中圆的周长是 360°360°。因此,一个角度为 ππ 弧度是 180°180°,通常表示为 180°180°。度数和弧度之间的转换为

 角度 =180π 弧度  弧度 =π180 角度 \begin{array}{l} \text { 角度 }=\frac{180}{\pi} \text { 弧度 } \\ \text { 弧度 }=\frac{\pi}{180} \text { 角度 } \end{array}

2.3.2 三角函数

给定一个直角三角形,其边长分别为 aaoohh,其中 hh 是最长的一条边(始终位于直角的对面),或称为斜边,通过勾股定理可得到一个重要关系式:

a2+o2=h2a^2+o^2=h^2

fig11_1.jpg

你可以从图 2.7 中看出这是真的,其中大正方形的面积为 (a+o)2(a+o)^2,四个三角形的总面积为 2ao2ao,中心正方形的面积为 h2h^2

因为三角形和内部正方形均匀地分割了较大的正方形,所以我们有 2ao+h2=(a+o)22ao + h^2 = (a+o)^2,可以很容易地将其变换为上述形式。

我们定义正弦和余弦函数,以及其他基于比率的三角函数表达式:

sinϕo/hcscϕh/ocosϕa/hsecϕh/atanϕo/acotϕa/o.\begin{aligned} \sin \phi & \equiv o / h \\ \csc \phi & \equiv h / o \\ \cos \phi & \equiv a / h \\ \sec \phi & \equiv h / a \\ \tan \phi & \equiv o / a \\ \cot \phi & \equiv a / o . \end{aligned}

这些定义允许我们建立极坐标系,其中一个点编码为距离原点的距离和相对于正向 xx 轴的符号角度(如图 2.8)。注意,角度约定在 ϕ(ππ]\phi \in (–π,π] 范围内,并且正角度是逆时针方向从正向 xx 轴开始。这种逆时针映射到正数的惯例是任意的,但它在计算机图形学中的许多场景中都被使用,因此值得记忆。

fig11_1.jpg

图 2.8。点 (xa,ya)=(1,3)(x_a,y_a)=(1,\sqrt{3}) 的极坐标为 (ra,ϕa)=(2π/3)(r_a,\phi_a)=(2,π/3)

三角函数是周期性的,并且可以采用任何角度作为参数。例如,sin(A)=sin(A+2π)sin(A)=sin(A+2π)。这意味着当考虑到定义域 R\Bbb{R} 时,这些函数不具有可逆性。通过限制标准反函数的范围来避免这个问题,在几乎所有现代数学库中都是以标准方式完成的(例如 Plauger(1991))。域和范围如下:

asin:[1,1][π/2,π/2]acos:[1,1][0,π];atan:R[π/2,π/2];atan2:R2[π,π](2.2)\begin{array}{l} \operatorname{asin}:[-1,1] \mapsto[-\pi / 2, \pi / 2] \\ \operatorname{acos}:[-1,1] \mapsto[0, \pi] ; \\ \operatorname{atan}: \mathbb{R} \mapsto[-\pi / 2, \pi / 2] ; \\ \operatorname{atan} 2: \mathbb{R}^{2} \mapsto[-\pi, \pi] \end{array} \tag{2.2}

最后一个函数,atan2(s,c)\text{atan2}(s,c) 常常非常有用。它使用与 sinA\text{sin} A 成比例的 ss 值和以相同因子缩放 cosA\text{cos} Acc 值,返回 AA。假定该因子为正数。一种思考方法是,它返回一个在极坐标系(Figure 2.9)中的 2D 笛卡尔点 (s,c)(s,c) 的角度。

fig11_1.jpg

图 2.9。函数 atan2(s,c)\text{atan2}(s,c) 返回角度 AA,通常在图形处理中非常有用。

2.3.3 有用的恒等式

本节列出了各种有用的三角恒等式,不包括推导过程。

平移恒等式:

sin(A)=sinAcos(A)=cosAtan(A)=tanAsin(π/2A)=cosAcos(π/2A)=sinAtan(π/2A)=cotA\begin{aligned} \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 \end{aligned}

勾股恒等式:

sin2A+cos2A=1sec2Atan2A=1csc2Acot2A=1\begin{aligned} \sin ^{2} A+\cos ^{2} A & =1 \\ \sec ^{2} A-\tan ^{2} A & =1 \\ \csc ^{2} A-\cot ^{2} A & =1 \end{aligned}

半角恒等式:

sin(A+B)=sinAcosB+sinBcosAsin(AB)=sinAcosBsinBcosAsin(2A)=2sinAcosAcos(A+B)=cosAcosBsinAsinBcos(AB)=cosAcosB+sinAsinBcos(2A)=cos2Asin2Atan(A+B)=tanA+tanB1tanAtanBtan(AB)=tanAtanB1+tanAtanBtan(2A)=2tanA1tan2A\begin{aligned} \sin (A+B) & =\sin A \cos B+\sin B \cos A \\ \sin (A-B) & =\sin A \cos B-\sin B \cos A \\ \sin (2 A) & =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 (2 A) & =\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 (2 A) & =\frac{2 \tan A}{1-\tan ^{2} A} \end{aligned}

半角恒等式:

sin2(A/2)=(1cosA)/2cos2(A/2)=(1+cosA)/2\begin{array}{l} \sin ^{2}(A / 2)=(1-\cos A) / 2 \\ \cos ^{2}(A / 2)=(1+\cos A) / 2 \end{array}

积的恒等式:

sinAsinB=(cos(A+B)cos(AB))/2sinAcosB=(sin(A+B)+sin(AB))/2cosAcosB=(cos(A+B)+cos(AB))/2\begin{aligned} \sin A \sin B & =-(\cos (A+B)-\cos (A-B)) / 2 \\ \sin A \cos B & =(\sin (A+B)+\sin (A-B)) / 2 \\ \cos A \cos B & =(\cos (A+B)+\cos (A-B)) / 2 \end{aligned}

以下恒等式适用于任意三角形,其中边长为 a、b 和 c,分别与其相对的角度为 A、B 和 C(Figure 2.10):

sinAa=sinBb=sinCc (sin 函数定律) c2=a2+b22abcosC (cos 函数定律) a+bab=tan(A+B2)tan(AB2) (tan 函数定律)\begin{array}{r} \frac{\sin A}{a}=\frac{\sin B}{b}=\frac{\sin C}{c} \space \text{(sin 函数定律)} \\ \space \\ c^{2}=a^{2}+b^{2}-2 a b \cos C \space \text{(cos 函数定律)} \\ \space \\ \frac{a+b}{a-b}=\frac{\tan \left(\frac{A+B}{2}\right)}{\tan \left(\frac{A-B}{2}\right)} \space \text{(tan 函数定律)} \end{array}

三角形面积也可以用这些边长表示:

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

fig11_1.jpg

图 2.10。三角形定律的几何学。

2.3.4 实角与球面三角学

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

对于计算机图形学来说更为重要的是实角。虽然角度可以让我们量化像“我视野中的两极之间有多大分离”这样的事情,但实角让我们量化像“那架飞机占据了我的视野有多少面积。”对于传统角度,我们将极点投影到单位圆上并在单位圆上测量它们之间的弧长。我们经常使用角度,以至于我们中的许多人可能会忘记这个定义,因为现在所有这些都是如此直观。实角同样简单,但可能会更令人困惑,因为我们大多数人成年后才学习它们。对于实角,我们将“看到”飞机的可见方向投影到单位球面上并测量其面积。这个面积就是实角,就像弧长是角度一样。而角度是用弧度表示的,总和为 2π(单位圆的总长度),实角则用立体弧度表示,总和为 4π(单位球面的总面积)。

2.4 向量

fig11_1.jpg

图 2.11。这两个向量相同,因为它们具有相同的长度和方向。

向量描述了一个长度和一个方向。它可以用箭头有用地表示。如果两个向量具有相同的长度和方向,即使我们认为它们位于不同的位置(图 2.11),它们也是相等的。尽可能地,您应该将向量视为箭头,而不是坐标或数字。在某些时候,我们必须将向量表示为程序中的数字,但即使在代码中,它们也应被视为对象,并且只有低级向量操作应了解其数字表示(DeRose,1989)。向量将表示为粗体字符,例如 aa。向量的长度表示为 a||a||。单位向量是长度为 11 的任何向量。零向量是长度为零的向量。零向量的方向未定义。

向量可用于表示许多不同的事物。例如,它们可用于存储偏移量,也称为位移。如果我们知道“宝藏被埋在秘密会面地点东两步和北三步的地方”,那么我们知道了偏移量,但我们不知道从哪里开始。向量还可以用于存储位置,另一个词是位置或点。位置可以表示为相对于另一个位置的位移。通常,有一个已知的起始位置,从中存储所有其他位置作为偏移量。请注意,位置不是向量。正如我们将要讨论的那样,您可以添加两个向量。但是,除非在计算位置的加权平均值时它是一个中间操作(Goldman,1985),否则通常不会有意义地添加两个位置。添加两个偏移量是有意义的,因此这是偏移量是向量的原因之一。但是这强调了位置不是偏移量;它是从特定起始位置的偏移量。仅仅使用偏移量本身并不能描述位置。

2.4.1 向量运算

fig11_1.jpg

图 2.12。通过将它们头对尾排列来相加两个向量。这可以按任何顺序完成。

fig11_1.jpg

图 2.13。向量 a-a 具有与向量 aa 相同的长度但相反的方向。

向量具有我们与实数相关的大多数常规算术操作。当且仅当两个向量具有相同的长度和方向时,它们相等。根据平行四边形法则相加两个向量。该法则指出,通过将其中一个向量的尾部放在另一个向量的头部(图 2.12),可以找到两个向量的和。和向量是“完成由两个向量开始的三角形”的向量。平行四边形通过以任意顺序进行求和而形成。这强调了向量加法是可交换的:

a+b=b+aa+b = b+a

请注意,平行四边形法则只是正式化偏移量的直觉。想象一下沿着一个向量从头到尾走,然后沿着另一个向量走。净位移量就是平行四边形对角线。您还可以为向量创建一个单目负号:-a(图 2.13)是一个与 a 长度相同但方向相反的向量。这使我们也可以定义减法:

baa+bb−a ≡ −a+b

您可以使用平行四边形可视化向量减法(图 2.14)。我们可以写

fig11_1.jpg

图 2.14。向量减法只是将第二个参数相反地加入向量加法。

a+(ba)=ba+(b−a) = b

向量也可以相乘。实际上,涉及向量的几种产品。首先,我们可以通过将其乘以实数 k 来缩放向量。

这仅仅是将向量的长度乘以 k 而不改变其方向。例如,3.5a3.5a 是一个与 aa 方向相同但长度是 aa3.53.5 倍的向量。我们稍后在本节中讨论涉及两个向量的点积和叉积以及涉及三个向量的行列式,在第 6 章中介绍。

2.4.2 向量的笛卡尔坐标

2D 向量可以写成任意两个非平行向量的组合。这两个向量的此特性称为线性独立性。两个线性独立的向量形成一个 2D 基础,因此这些向量被称为基础向量。例如,向量 c\bold c 可以表示为两个基础向量 a\bold ab\bold b 的组合(图 2.15):

c=aca+bcb(2.3)\bold{c} = a_c\bold{a}+b_c\bold{b} \tag{2.3}

fig11_1.jpg

请注意,重量 ac\bold {ac}bc\bold {bc} 是唯一的。如果这两个向量垂直,即它们相互垂直,则基数特别有用。如果它们还是单位向量,则更有用,此时它们是正交归一化的。如果我们假设我们已知这两个“特殊”向量 xxyy,则可以使用它们在笛卡尔坐标系中表示所有其他向量,其中每个向量表示为两个实数。例如,向量 a\bold a 可以表示为

a=xax+yay\bold a=x_a\bold x+y_a\bold y

其中 xax_ayay_a 是 2D 向量 aa 的实数笛卡尔坐标(图 2.16)。请注意,从概念上讲,这与方程式(2.3)没有什么不同,基础向量不是正交归一化的。但是笛卡尔坐标系具有几个优点。例如,根据勾股定理,向量的长度为

fig11_1.jpg

图 2.16。用于向量的 2D 笛卡尔基础。

a=xa2+ya2\|\mathbf{a}\|=\sqrt{x_{a}^{2}+y_{a}^{2}}

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

按照惯例,我们将 aa 的坐标写为有序对 (xaya)(xa,ya) 或列矩阵:

a=[xaya]\mathbf{a}=\left[\begin{array}{l} x_{a} \\ y_{a} \end{array}\right]

我们使用的形式取决于排版上的方便性。我们偶尔还会将向量写成行矩阵,我们将其表示为 aT\bold a^{\mathrm{T}}

aT=[xaya]\mathbf{a}^{\mathrm{T}}=\left[\begin{array}{ll} x_{a} & y_{a} \end{array}\right]

我们还可以用笛卡尔坐标表示 3D,4D 等向量。对于 3D 情况,我们使用与 x\bold xy\bold y 都正交的基向量 z\bold z

2.4.3 点积

将两个向量相乘的最简单方法是点积。a\bold ab\bold b 的点积表示为 ab\bold a·\bold b,通常称为标量积,因为它返回一个标量。点积返回与它们的长度和它们之间的角度ϕ相关的值(图 2.17):

ab=abcosϕ(2.4)\mathbf{a} \cdot \mathbf{b}=\|\mathbf{a}\|\|\mathbf{b}\| \cos \phi \tag{2.4}

fig11_1.jpg

图 2.17。点积与长度和角度有关,是图形学中最重要的公式之一。

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

点积还可用于找到一个向量在另一个向量上的投影。这是向量 a 在垂直投影于向量 b 上的长度 a→b(图 2.18):

ab=acosϕ=abb.(2.5)\mathbf{a} \rightarrow \mathbf{b}=\|\mathbf{a}\| \cos \phi=\frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{b}\|} . \tag{2.5}

fig11_1.jpg

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

ab=ba,a(b+c)=ab+ac,(ka)b=a(kb)=kab(2.6)\begin{array}{c} \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{array} \tag{2.6}

如果用笛卡尔坐标表示 2D 向量 aabb,则可以利用 xx=yy=1\bold x · \bold x = \bold y · \bold y = 1xy=0\bold x · \bold y = 0 来推导它们的点积为

ab=(xax+yay)(xbx+yby)=xaxb(xx)+xayb(xy)+xbya(yx)+yayb(yy)=xaxb+yayb\begin{aligned} \mathbf{a} \cdot \mathbf{b} & =\left(x_{a} \mathbf{x}+y_{a} \mathbf{y}\right) \cdot\left(x_{b} \mathbf{x}+y_{b} \mathbf{y}\right) \\ & =x_{a} x_{b}(\mathbf{x} \cdot \mathbf{x})+x_{a} y_{b}(\mathbf{x} \cdot \mathbf{y})+x_{b} y_{a}(\mathbf{y} \cdot \mathbf{x})+y_{a} y_{b}(\mathbf{y} \cdot \mathbf{y}) \\ & =x_{a} x_{b}+y_{a} y_{b} \end{aligned}

同样,在 3D 中我们可以找到

ab=xaxb+yayb+zazb.\mathbf{a} \cdot \mathbf{b}=x_{a} x_{b}+y_{a} y_{b}+z_{a} z_{b}.

2.4.4 叉积

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

a×b=absinϕ\|\mathbf{a} \times \mathbf{b}\|=\|\mathbf{a}\|\|\mathbf{b}\| \sin \phi

a×b||\bold a × \bold b|| 的大小等于由向量 a\bold ab\bold b 形成的平行四边形的面积。此外,a×b\bold a×\bold b 垂直于 a\bold ab\bold b(图 2.19)。请注意,这样的向量仅有两个可能的方向。根据定义,沿着 xx-yy-zz- 轴方向的向量由以下公式给出:

x=(1,0,0)y=(0,1,0)z=(0,0,1)\begin{array}{l} \mathbf{x}=(1,0,0) \\ \mathbf{y}=(0,1,0) \\ \mathbf{z}=(0,0,1) \end{array}

我们约定 x×y\bold x × \bold y 必须沿正负 z\bold z 方向。这种选择有点随意,但通常假设

z=x×y\bold z = \bold x × \bold y

fig11_1.jpg

图 2.19。叉积 a×b\bold a×\bold b 是一个垂直于两个 3D 向量 a\bold ab\bold b 的 3D 向量,其长度等于所示平行四边形的面积。

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

x×y=+zy×x=zy×z=+xz×y=xz×x=+yx×z=y\begin{array}{l} \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{array}

由于 sinϕ\text{sin}\phi 属性,我们还知道向量叉积本身是零向量,因此 x×x=0\bold x × \bold x = 0 等等。请注意,叉积不是可交换的,即,x×yy×x\bold x×\bold y≠\bold y×\bold x。细心的观察者会注意到上述讨论并没有使我们能够绘制笛卡尔坐标轴之间如何关联的明确图像。更具体地说,如果将 x\bold xy\bold y 放在人行道上,其中 x\bold x 指向东方,y\bold y 指向北方,则 z\bold z 指向天空还是地面?通常假设 zz 指向天空。这被称为右手坐标系。这个名字来自“拿”右手掌和手指旋转 x\bold x 朝着 y\bold y。矢量 z\bold z 应该与您的大拇指对齐。这在图 2.20 中说明。

fig11_1.jpg

图 2.20。叉积的“右手法则”。想象一下将你的右手掌和手指放在 a\bold ab\bold b 在其尾部相接的地方,然后将 a\bold a 的箭头向 b\bold b 推。您延伸的右手拇指应指向 a×b\bold a×\bold b

叉积具有很好的性质

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

a×(kb)=k(a×b)\mathbf{a} \times(k \mathbf{b})=k(\mathbf{a} \times \mathbf{b})

然而,右手定则的一个结果是

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

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

a×b=(xax+yay+zaz)×(xbx+yby+zbz)=xaxbx×x+xaybx×y+xazbx×z+yaxby×x+yayby×y+yazby×z+zaxbz×x+zaybz×y+zazbz×z=(yazbzayb)x+(zaxbxazb)y+(xaybyaxb)z.(2.7)\begin{aligned} \mathbf{a} \times \mathbf{b}= & \left(x_{a} \mathbf{x}+y_{a} \mathbf{y}+z_{a} \mathbf{z}\right) \times\left(x_{b} \mathbf{x}+y_{b} \mathbf{y}+z_{b} \mathbf{z}\right) \\ = & x_{a} x_{b} \mathbf{x} \times \mathbf{x}+x_{a} y_{b} \mathbf{x} \times \mathbf{y}+x_{a} z_{b} \mathbf{x} \times \mathbf{z} \\ & +y_{a} x_{b} \mathbf{y} \times \mathbf{x}+y_{a} y_{b} \mathbf{y} \times \mathbf{y}+y_{a} z_{b} \mathbf{y} \times \mathbf{z} \\ & +z_{a} x_{b} \mathbf{z} \times \mathbf{x}+z_{a} y_{b} \mathbf{z} \times \mathbf{y}+z_{a} z_{b} \mathbf{z} \times \mathbf{z} \\ = & \left(y_{a} z_{b}-z_{a} y_{b}\right) \mathbf{x}+\left(z_{a} x_{b}-x_{a} z_{b}\right) \mathbf{y}+\left(x_{a} y_{b}-y_{a} x_{b}\right) \mathbf{z} . \end{aligned} \tag{2.7}

因此,在坐标形式中,

a×b=(yazbzayb,zaxbxazb,xaybyaxb)(2.8)\mathbf{a} \times \mathbf{b}=\left(y_{a} z_{b}-z_{a} y_{b}, z_{a} x_{b}-x_{a} z_{b}, x_{a} y_{b}-y_{a} x_{b}\right) \tag{2.8}

2.4.5 标准正交基和坐标系

标准正交基是一组向量集合,其中每个向量都与其他向量垂直(正交),并且每个向量的长度是 1。 因此,这些向量可以被用来表示空间中的任何向量,而不会添加额外的自由度或产生任何冗余的信息。

管理坐标系几乎是所有图形程序的核心任务之一;关键在于管理标准正交基。任何一组二维向量 u\bold uv\bold v,只要它们正交(垂直)并且长度为单位长度,就构成了一个标准正交基。因此,

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

并且

uv=0\mathbf{u}·\mathbf{v}=0

在三维中,如果有三个向量 u\bold uv\bold vw\bold w 满足

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

并且

uv=vw=wu=0\mathbf{u} \cdot \mathbf{v}=\mathbf{v} \cdot \mathbf{w}=\mathbf{w} \cdot \mathbf{u}=0

只要这个标准正交基是右手系的,以下式子就成立。

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

否则就是左手系的。

请注意,笛卡尔标准正交基只是无限多可能标准正交基之一。它的特殊之处在于它及其隐含的原点位置用于程序中的低级表示。因此,向量 x\bold xy\bold yz\bold z 从未显式存储,标准原点位置 o\bold o 也没有。全局模型通常存储在这个标准坐标系中,因此通常称为全局坐标系。但是,如果我们想要使用另一个具有原点 p\bold p 和标准正交基向量 u\bold uv\bold vw\bold w 的坐标系,则我们会明确地存储这些向量。这样的系统称为参考系 (frame of reference) 或坐标系 (coordinate frame)。例如,在飞行模拟器中,我们可能希望维护一个坐标系,其原点位于飞机的鼻部,标准正交基与飞机对齐。同时,我们会有主标准坐标系(图 2.21)。与特定对象相关联的坐标系,例如飞机,通常称为局部坐标系 (local coordinate system)。

在低级别 (low level) 上,局部坐标系以标准坐标存储。例如,如果 u\bold u 具有坐标 (xu,yu,zu)(x_u,y_u,z_u),则

u=xux+yuy+zuz\mathbf{u}=x_{u} \mathbf{x}+y_{u} \mathbf{y}+z_{u} \mathbf{z}

位置隐式包括对标准原点的偏移:

p=o+xpx+ypy+zpz\mathbf{p}=\mathbf{o}+x_{p} \mathbf{x}+y_{p} \mathbf{y}+z_{p} \mathbf{z}

其中 (xp,yp,zp)(x_p,y_p,z_p)p\bold p 的坐标。

请注意,如果我们相对于 uvw\bold u-\bold v-\bold w 框架存储一个向量 a\bold a,我们将存储一个三元组 (ua,va,wa)(u_a,v_a,w_a),我们可以将其几何地解释为

a=uau+vav+waw\mathbf{a}=u_{a} \mathbf{u}+v_{a} \mathbf{v}+w_{a} \mathbf{w}

要获得存储在 uvw\bold u-\bold v-\bold w 坐标系中的向量 a\bold a 的标准坐标,只需记住 uuvvww 本身以笛卡尔坐标存储,因此如果明确计算表达式 uau+vav+wawu_a\bold u + v_a\bold v + w_a\bold w,则该表达式已经在笛卡尔坐标中。要获取以标准坐标系存储的向量 b\bold buvw\bold u-\bold v-\bold w 坐标,可以使用点积:

ub=ub;vb=vb;wb=wbu_{b}=\mathbf{u} \cdot \mathbf{b} ; \quad v_{b}=\mathbf{v} \cdot \mathbf{b} ; \quad w_{b}=\mathbf{w} \cdot \mathbf{b}

fig11_1.jpg

图 2.21。始终存在一个主坐标系,其原点为 o\bold o,标准正交基为 x\bold xy\bold yz\bold z。这个坐标系统通常被定义为与全局模型对齐,因此经常被称为“全局”或“世界”坐标系。这个原点和基向量从未明确存储。所有其他向量和位置都以与全局框架相关的坐标存储。与飞机相关联的坐标系以全局坐标明确存储。

这是因为我们知道对于某些 ubu_bvbv_bwbw_b

ubu+vbv+wbw=bu_b\bold u+v_b\bold v+w_b\bold w=\bold b

点积隔离了 ubu_b 坐标:

ub=ub(uu)+vb(uv)+wb(uw)=ub\bold u·\bold b=u_b(\bold u·\bold u)+v_b(\bold u·\bold v)+w_b(\bold u·\bold w)=u_b

这是因为 u\bold uv\bold vw\bold w 是正交归一 (orthonormal) 的。

使用矩阵管理坐标系更改在第 7.2.1 节和第 7.5 节中讨论。

2.4.6 从单个向量构建基

Constructing a Basis from a Single Vector

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

这可以使用叉积来完成。首先,将 w\bold w 设置为指向 a\bold a 的单位向量:

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

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

u=t×wt×w\mathbf{u}=\frac{\mathbf{t} \times \mathbf{w}}{\|\mathbf{t} \times \mathbf{w}\|}

当然,同样的过程可以用于以任何顺序构造三个向量;只需注意叉积的顺序,以确保基是右手的。

如果 t\bold tw\bold w 共线,则分母将消失,如果它们几乎共线,则结果精度较低。找到一个与 w\bold w 足够不同的向量的简单过程是,将 t\bold t 设置为等于 w\bold w,并将最小幅度分量更改为 11。例如,如果 w=(1/2,1/2,0)\bold w=(1/\sqrt{2},−1/\sqrt{2},0),则 t=(1/2,1/2,1)\bold t=(1/\sqrt{2},−1/\sqrt{2},1)。一旦有了 w\bold wu\bold u,完成基就很简单:

v=w×u\bold v=\bold w×\bold u

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

对于严肃的生产代码,皮克斯的研究人员最近开发了一种相当卓越的方法,可以从两个向量构建向量,其紧凑性和效率令人印象深刻 (Duff et al., 2017)。他们提供了经过实战检验的代码,鼓励读者使用它,因为在整个行业中没有出现“陷阱”。

2.4.7 从两个向量构建基

Constructing a Basis from Two Vectors

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

一个完整指定框架的常见方法是提供两个向量 a\bold a(用于指定 w\bold w)和 b\bold b(用于指定 v\bold v)。如果已知这两个向量相互垂直,则可以通过 u=b×a\bold u = \bold b × \bold a 简单地构造第三个向量。

u=a×b\bold u = \bold a × \bold b 也可以产生一个标准正交基,但它是左手的。

为确保生成的基向量组是正交归一的,即使输入向量不完全满足条件,建议采用与单向量处理类似的过程:

w=aau=b×wb×w,v=w×u.\begin{aligned} \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} . \end{aligned}

实际上,当 a\bold ab\bold b 不垂直时,这个过程也可以很好地工作。在这种情况下,向量 w\bold w 将恰好沿着 a\bold a 的方向构建,并且在所有垂直于 w\bold w 的向量中选择最接近 b\bold b 的向量作为 v\bold v

如果您希望我设置 w\bold wv\bold v 为两个非垂直的方向,则需要进行某些调整,以确保该方案仍然适用。具体来说,我将按照您的要求设置 w\bold wv\bold v,除此之外只对 v\bold v 进行最小的更改,以确保它实际上是垂直于 w\bold w 的。

a\bold ab\bold b 共线时,这个过程无法工作。在这种情况下,b\bold b 不能帮助我们选择垂直于 a\bold a 的方向:它垂直于所有这些方向。

如果 a\bold ab\bold b 平行,计算将出现错误吗?

在指定相机位置(第 4.3 节)的示例中,我们希望构建一个基座,其中 w\bold w 平行于相机所看方向,而 v\bold v 应该指向相机的顶部。为了使相机垂直,我们围绕视角方向建立基向量组,使用垂直向上的方向作为参考向量来确定相机在视角方向周围的方向。将 v\bold v 设置为尽可能接近垂直向上的方向,恰好符合“保持相机水平”的直观概念。

2.4.8 将基座正交化

Squaring Up a Basis

偶尔,您可能会发现由于舍入误差或者基座以低精度存储在文件中等原因,导致基座虽然应该是正交归一的但产生了误差,从而影响了计算结果。

可以使用上一节中的方法:只需使用现有的 w\bold wv\bold v 向量重新构建基座,就会生成一个新的正交归一的基座,与旧的基座相似。

这种方法对于许多应用程序来说是很好的选择,它可以产生准确的正交向量,对于初始基座接近正交的情况,结果不会偏离初始点太远。但是,它是不对称的:它“偏爱” w\bold w 而不是 v\bold v,偏爱 v\bold v 而不是 u\bold u(其起始值被丢弃)。它选择一个接近初始基座的基座,但不能保证选择最接近的正交基座。当这还不够好时,可以使用 SVD(第 6.4.1 节)来计算一个保证最接近原始基座的正交基座。

2.5 积分 (Integration)

关于图形学可能会引人误解的一件事是,它充满了积分,因此人们可能会认为必须擅长代数解积分。但实际上并非如此。大多数图形学中的积分都无法通过解析方法求解,因此需要使用数值方法进行求解。在图形学中完全可以有一份优秀的职业生涯,而不用代数解决一个积分。

虽然您不需要能够代数解积分,但是需要能够阅读它们以便进行数值计算。在一维中,积分通常很容易阅读。例如,这个积分:

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

可以理解为“计算函数 sin(x)\text{sin}(x)x=πx = πx=2πx = 2π 之间的面积。” 计算机科学家可能会将其视为:

π2πdx\int_{\pi}^{2 \pi} d x

作为函数调用,我们可以将其称为“integrate()”。它接受两个对象:一个函数和一个定义域(区间)。因此,整个调用可能是这样的:

1
float area = integrate(sin(),[pi2pi])。

在更高级的微积分中,我们可能会开始对球体进行积分,而对于图形学来说,重要的是我们仍然可以这样思考:

1
float area = integrate(cos()),单位球体)

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

  1. 被积函数
  2. 积分域。

通常,关键是仔细解码出问题所涉及的 1 和 2。这与从有时令人困惑的文档中正确获取 API 调用非常相似。

2.5.1 平均值和加权平均值

积分计算的是总数。长度、面积、体积等等。但它们经常用于计算平均值。例如,我们可以通过在区域上积分高程(如国家)来计算该区域的总体积。

1
float volume = integrate(elevation(),country)

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

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

这基本上是“将体积除以面积”。这可以抽象为

1
2
Float averageElevation =
average(elevation,country)

我们也可以计算加权平均值。在这里,我们添加加权函数以强调平均值中的某些点比其他点更重要。例如,如果我们想根据温度突出显示部分区域的平均高程(这相当任意,并且我们将在下一节中看到更多与图形学相关的示例):

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

最好留心这种形式;通常积分包含加权平均值,而不明确指出它,有时可以帮助理解。

2.5.2 固体角上的积分

我们经常看到的一种积分类型是以下形式或类似形式之一:

1
2
float shade = integrate(cos()* f *(),
unit-hemisphere)

请注意,由于 integrate(cos(),unit-hemisphere)= pi,加权平均版本只是

1
2
float shade = integrate((1 / pi)* cos()* f *(),
unit-hemisphere)

该积分的更传统形式为

S=vH1π(vn)f(v)(vn)dσ(v)S=\int_{v \in H} \frac{1}{\pi}(v \cdot n) f(v)(v \cdot n) d \sigma(v)

或者使用球面坐标,我们可以代数地解决这样的积分:

S=ϕ=02πθ=0π1π(vn)f(θ,ϕ)cosθsinθdθdϕS=\int_{\phi=0}^{2 \pi} \int_{\theta=0}^{\pi} \frac{1}{\pi}(v \cdot n) f(\theta, \phi) \cos \theta \sin \theta d \theta d \phi

正弦项用作球面坐标的面积修正因子。请注意,在图形学中,我们很少需要将其全部写出,并且会使用简单的形式而没有显式的坐标来数值求解积分。

以上特定积分是完美反射(漫反射)表面的遮挡率,也是所有入射颜色的加权平均值。这种结构对直觉非常有帮助;表面颜色通常与入射颜色的加权平均值相关。

固体角上的积分几乎总是相同的,但使用各种符号表示。关键是要认识到这只是符号,将您看到的符号映射到您最熟悉的符号。这很像阅读伪代码!

2.6 密度函数

密度函数在图形学中经常出现(例如,“概率密度函数”),有时它们会令人困惑,但准确理解它们是什么将帮助我们使用它们,并在遇到困惑时进行导航。我们知道函数是什么,而密度函数只是返回密度的函数。那么密度是什么?密度是“每单位某物”的量,或更正式地说是一种强度量。例如,您的体重不是密度,而是广延量,或只是一些东西的数量,而不是每单位某物的数量。一个人在一段时间内(比如一年)可能增加的体重是某个东西的数量,以千克为单位衡量,因此是广延量,而不是密度。一个人每天或每小时所增加的体重量是强度量,因此是密度。

作为非密度函数的例子,请考虑给定日期(例如 2014 年 7 月 1 日)太阳能电池板产生的能量量,假设为 120 千焦耳。这是一些“东西”的数量。好吧,但这足以运行我的计算机吗?如果是台式机,则需要能量密度或能量速率才能继续工作。那么,我们如何将那一天的能量转换为能量速率?我们可以将其分成时间段。例如,我们可以按四小时块、两小时块或一小时块进行划分,并且我们会发现速率在白天改变,但数量不断缩短,如图 2.22 所示。

fig11_1.jpg

图 2.22。随着在一定时间间隔内产生的能量量的直方图降低时间间隔,箱子的高度也会下降(在极限情况下随着宽度趋近于零而变为零高度)。

随着时间的分割越来越细,我们最终会到达分钟和秒,并且会获得更多有关时间变化的信息,但是箱子的高度将变得如此小,以至于我们看不到任何东西。因此,我们可以根据它们的宽度重新调整其箱子的高度,因此(30kJ)/(0.5 小时)= 60 kJ / h。如果我们使用这种新的“每小时千焦”度量标准,则箱子不再缩短,如图 2.23 所示。如果我们将此过程推向宽度成为无穷小的极限,我们会得到一个平滑曲线。

这条曲线是密度函数的一个例子。它可能被某些人称为“能量密度”函数,在其中密度所涉及的维度是时间,在某些情况下可能被称为“时间能量密度”函数。由于这种特定的密度非常有用并且经常讨论,因此它拥有自己的名称“功率”,而不是说“每小时焦耳”,我们说瓦特。请注意,“瓦特”是按照惯例每秒的焦耳而不是每小时,而不是按维度选择特定的单位。例如,一些物理单位与米有更多的意义,一些与千米有更多的意义,一些与纳米米有更多的意义(对于光的光谱辐射这样的少量使用米和纳米表达同一数量,因此当您感到困惑时,这并不是您的错)。

将所有这些放在一起,(1)密度始终是某种比率,您会说“每单位 Y 有多少 X”或“每 Y 有多少 X”,例如“每小时多少公里”(如果每个人默认同意长度单位,则说“每单位长度有多少公里”将是奇怪的,但是有意义),以及(2)密度函数是返回密度的函数。

fig11_1.jpg

图 2.23。如果我们将能量除以箱子的宽度,随着我们进一步分割,它会变得更加详细。

密度函数本身对于比较两个不同点的相对浓度很有用。例如,使用定义在时间(功率)上的能量密度函数,我们可以说“下午 2 点的功率是早上 9 点的两倍”,例如。但是,我们可以使用另一种方法来计算区域中的总量。例如,要计算下午 2 点到下午 4 点之间产生了多少能量,我们只需进行积分:

E=2pm4pmP(t)dtE=\int_{2 p m}^{4 p m} P(t) d t

许多积分都属于这种“积分密度函数”的类型,但没有明确说明。如果你揭示一个积分是否在某个区间或区域中处理密度函数的“质量”,有时会使事情更加清晰。

2.7 曲线和曲面

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

2.7.1 2D 隐式曲线

直观地说,曲线是可以在纸上绘制而不抬笔的一组点。描述曲线的常见方式是使用隐式方程。二维中的隐式方程具有以下形式:

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

函数 f(x,y)f(x,y) 返回实值。这个值为零的点 (x,y)(x,y) 在曲线上,值为非零的点不在曲线上。例如,假设 f(x,y)f(x,y)

f(x,y)=(xxc)2+(yyc)2r2(2.9)f(x, y)=\left(x-x_{c}\right)^{2}+\left(y-y_{c}\right)^{2}-r^{2} \tag{2.9}

其中 (xc,yc)(x_c,y_c) 是二维点,r 是非零实数。如果我们取 f(x,y)=0f(x,y)= 0,则这个等式成立的点在以 (xc,yc)(x_c,y_c) 为圆心、半径为 rr 的圆上。之所以称其为“隐式”方程,是因为曲线上的点不能直接从方程计算出来,而必须通过解方程来确定。因此,曲线上的点并不是显式地由方程生成的,而是暗藏在方程中。

有趣的是,ff 对所有 (x,y)(x,y) 都有值。我们可以将 ff 视为地形,在 f=0f = 0 处具有海平面(图 2.24)。岸边是隐含曲线。 ff 的值是高度。另一个要注意的事情是,曲线将空间分割成 f>0f> 0f<0f<0f=0f = 0 的区域。因此,您需要评估 ff 以决定点是否“在”曲线内。请注意,f(x,y)=cf(x,y)= c 是任何常数 cc 的曲线,而 c=0c = 0 仅被用作惯例。例如,如果 f(x,y)=x2+y21f(x,y)= x^2 + y^2-1,则变化的 cc 仅给出以原点为中心的各种圆(图 2.25)。

fig11_1.jpg

图 2.24. 隐函数 f(x,y)=0f(x,y) = 0 可以被视为高度场,其中 ff 是高度(顶部)。高度为零的路径是隐式曲线(底部)。

fig11_1.jpg

图 2.25. 隐函数定义了任何常数值的曲线,其中零是通常的约定。

我们可以使用向量来压缩符号。如果我们有 c=(xc,yc)\bold c =(x_c,y_c)p=(x,y)\bold p =(x,y),那么我们的以中心 c\bold c 和半径 rr 定义的圆由满足以下条件的位置向量定义:

(pc)(pc)r2=0(\bold p-\bold c)·(\bold p-\bold c)-r^2 = 0

如果代数展开此方程,将得到式(2.9),但通过“阅读”几何形式的方程,更容易看出这是一个圆的方程。它读作:“圆上的点 p\bold p 具有以下属性:从 c\bold cp\bold p 的向量与自身点乘的值为 r2r^2。”因为向量与自身点乘只是其长度平方,所以我们也可以将方程解释为:“圆上的点 p\bold p 具有以下属性:从 c\bold cp\bold p 的向量具有平方长度 r2r^2。”

最好的方法是观察到平方长度只是从 c\bold cp\bold p 的距离的平方,这表明等效形式为

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

当然,这又建议了

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

以上可以理解为,“圆上的点 p\bold p 是距离中心点 c\bold crr 的点”,这是一种很好的圆的定义。这说明向量形式的方程通常比具有 xxyy 的完整笛卡尔形式提供更多的几何和直觉信息。因此,通常建议尽可能使用向量形式。此外,您可以在代码中支持向量类;当使用向量形式时,代码更清晰。基于向量的方程也在实现中更少出错:一旦在代码中实现和调试了向量类型,涉及 xxyyzz 的复制粘贴错误将消失。需要一点时间来适应这些方程中的向量,但是一旦掌握了它,收益是巨大的。

2.7.2 二维梯度

如果我们将函数 f(x,y)f(x,y) 视为高度场,其中高度等于 f(x,y)f(x,y),则梯度向量指向最大上坡的方向,即直线上升。梯度向量 f(x,y)∇f(x,y) 由以下表示:

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

在隐含曲线 f(x,y)=0f(x,y)= 0 上评估的梯度向量垂直于曲线在该点的切向量。这个垂直向量通常被称为曲线的法向量。此外,由于梯度指向上山方向,它指示了 f(x,y)>0f(x,y)> 0 区域的方向。

在高度场的背景下,偏导数和梯度的几何意义更加明显。假设在点 (a,b)(a,b) 附近,f(x,y)f(x,y) 是一个平面(图 2.26)。有一个特定的上山和下山方向。对于这个方向的直角方向,是相对于平面水平的方向。平面和 f(x,y)=0f(x,y)= 0 平面之间的任何交点都将在水平的方向上。因此,上山/下山的方向将垂直于交线 f(x,y)=0f(x, y) = 0。要看到偏导数与此有关的原因,我们需要想象其几何意义。回忆一下 1D 函数 y=g(x)y = g(x) 的常规导数为

dydxlimΔx0ΔyΔx=limΔx0g(x+Δx)g(x)Δx.(2.10)\frac{d y}{d x} \equiv \lim _{\Delta x \rightarrow 0} \frac{\Delta y}{\Delta x}=\lim _{\Delta x \rightarrow 0} \frac{g(x+\Delta x)-g(x)}{\Delta x} . \tag{2.10}

fig11_1.jpg

图 2.26. 在 (x,y)=(a,b)(x,y)=(a,b) 附近,表面高度 = f(x,y)f(x,y) 是局部平面。梯度是向上的方向在高度 = 0 平面上的投影。

这测量了切线的斜率,如图 2.27 所示。

fig11_1.jpg

图 2.27。1D 函数的导数测量曲线切线的斜率。

偏导数是 1D 导数的推广。对于 2D 函数 f(x,y)f(x,y),我们无法像公式(2.10)中对 xx 进行相同的限制,因为对于给定的 xx 变化,ff 可以以许多方式发生变化。但是,如果我们保持 yy 不变,我们可以定义一个导数的类比,称为偏导数(图 2.28):

fxlimΔx0f(x+Δx,y)f(x,y)Δx\frac{\partial f}{\partial x} \equiv \lim _{\Delta x \rightarrow 0} \frac{f(x+\Delta x, y)-f(x, y)}{\Delta x}

为什么关于 xxyy 的偏导数是梯度向量的分量?再次,几何上比代数更有明显的洞见。在图 2.29 中,我们看到向量 aa 沿着 ff 不变的路径行进。注意,这又是足够小的尺度,使得表面高度 (x,y)=f(x,y)(x,y)= f(x,y) 可以被视为局部平面。从图中,我们可以看出向量 a=(ΔxΔy)a =(Δx,Δy)

由于上山方向与 a\bold a 垂直,我们知道点积等于零:

(f)a(x,y)(xa,ya)=xΔx+yΔy=0(2.11)(\nabla f) \cdot \mathbf{a} \equiv\left(x_{\nabla}, y_{\nabla}\right) \cdot\left(x_{a}, y_{a}\right)=x_{\nabla} \Delta x+y_{\nabla} \Delta y=0 \tag{2.11}

fig11_1.jpg

图 2.28。函数 ff 关于 xx 的偏导数必须保持 yy 不变以具有唯一值,如黑点所示。空心点显示其他不保持 yy 恒定的 ff 值。

我们还知道沿 (xaya)(x_a,y_a) 方向的 ff 变化为零:

Δf=fxΔx+fyΔyfxxa+fyya=0\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)(x,y)(x,y),我们知道它们之间的角度为 90 度,因此它们的点积等于零(回想一下点积与两个向量之间夹角的余弦成比例)。因此,我们有 xx+yy=0xx+yy=0。给定 (x,y)(x,y),很容易构造有效的向量,其与 (x,y)(x,y) 的点积等于零,最明显的是 (y,x)(y,-x)(y,x)(-y,x);您可以验证这些向量与 (x,y)(x,y) 给出了期望的零点积。该观察的一个推广是 (x,y)(x,y) 垂直于 k(y,x)k(y,-x) ,其中 kk 是任意非零常数。这意味着

(xa,ya)=k(fy,fx)(2.12)(x_a, y_a) = k(\frac{\partial f}{\partial y},-\frac{\partial f}{\partial x}) \tag{2.12}

fig11_1.jpg

图 2.29。向量 aa 指向 ff 没有变化的方向,因此垂直于梯度向量 f∇f

结合公式(2.11)和(2.12)可得

(x,y)=k(fx,fy)(x_\nabla,y_\nabla)=k'(\frac{\partial f}{\partial x},\frac{\partial f}{\partial y})

其中 kk 是任意非零常数。“上山”定义为 ff 的正变化,因此我们希望 k>0k>0,并且 k=1k=1 是一个完全良好的约定。

作为梯度的一个例子,考虑隐式圆 x²+y²1=0x² + y² - 1 = 0,其梯度向量为 (2x,2y)(2x, 2y),表明圆的外部是函数 f(x,y)=x²+y²1f(x, y) = x² + y² - 1 的正区域。请注意,梯度向量的长度可能会因隐式方程中的乘法器而异。例如,单位圆可以由 $$Ax² + Ay² - A = 0$ 描述,其中 AA 是任何非零 AA。该曲线的梯度为 (2Ax,2Ay)(2Ax, 2Ay)。这将是圆的法向量,但其长度由 AA 确定。对于 A>0A> 0,法向量将从圆外指向外部,而对于 A<0A <0,它将指向内部。这种从外向内的开关是正确的,因为正区域在圆内部切换。从高度场的观点来看,h=Ax²+Ay²Ah = Ax² + Ay² - A,圆是零高度。对于 A>0A> 0,圆围绕一个凹陷,而对于 A<0A <0,圆围绕一个隆起。随着 AA 变得更为负数,隆起的高度增加,但 h=0h = 0 的圆不会改变。最大上坡的方向不会改变,但是斜率增加。梯度的长度反映了这种斜坡程度的变化。因此,您可以直观地认为梯度的方向指向上坡,其大小测量斜坡的程度。

隐式 2D 线

线的熟悉的“斜率截距”形式为

y=mx+b(2.13)y=mx+b \tag{2.13}

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

ymxb=0(2.14)y-mx-b=0 \tag{2.14}

在这里,mm 是“斜率”(上升与移动之比),bb 是线穿过 yy 轴的 yy 值,通常称为 yy 截距。该线还将 2D 平面分割成两部分,但在这里,“内部”和“外部”可能更直观地称为“上方”和“下方”。

fig11_1.jpg

图 2.30。2D 线可以用方程 ymxb=0y - mx - b = 0 描述。

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

2y2mx2b=02y-2mx-2b=0

斜率截距形式有时会很笨拙的原因之一是它不能表示某些线,例如 x=0x = 0,因为 mm 将必须无限大。因此,更一般的形式通常很有用:

Ax+By+C=0(2.15)Ax+By+C=0 \tag{2.15}

其中 AABBCC 是实数。

假设我们知道线上的两个点 (x0,y0)(x_0,y_0)(x1,y1)(x_1,y_1)。什么 AABBCC 描述通过这两个点的线?因为这些点位于该线上,它们都必须满足方程(2.15):

Ax0+By0+C=0,Ax1+By1+C=0Ax_0+By_0+C=0, \\ Ax_1+By_1+C=0

不幸的是,我们有两个方程和三个未知数:AABBCC。出现这个问题是因为我们可以使用隐式方程具有的任意乘法器。我们可以方便地设置 C=1C = 1

Ax+By+1=0Ax+By+1=0

但是我们在斜率截距形式的无限斜率情况中遇到了类似的问题:通过原点的线需要具有 A(0)+B(0)+1=0A(0)+B(0)+1 = 0,这是一个矛盾。例如,通过原点的 45° 线的方程可以写成 xy=0x - y = 0,或者 yx=0y - x = 0,或者甚至是 17y17x=017y - 17x = 0,但不能写成 Ax+By+1=0Ax + By + 1 = 0 的形式。

每当我们有这种棘手的代数问题时,我们尝试使用几何直觉作为指导来解决问题。正如第 2.7.2 节中讨论的那样,我们拥有的一种工具是梯度。对于线 Ax+By+C=0Ax + By + C = 0,梯度向量为 (A,B)(A,B)。该向量垂直于线(图 2.31),并指向 Ax+By+CAx + By + C 为正的线的一侧。给定线上的两个点 (x0,y0)(x_0,y_0)(x1,y1)(x_1,y_1),我们知道它们之间的向量指向与线相同的方向。该向量只是 x1x0y1y0(x_1-x_0,y_1-y_0),因为它与线平行,所以它必须也垂直于梯度向量 (A,B)(A,B)。回想一下,由于隐式函数的任意缩放属性,有无限多个 (A,B,C)(A,B,C) 可以描述该线。我们想要任何一个有效的 (A,B,C)(A,B,C)

fig11_1.jpg

图 2.31。梯度向量 (A,B)(A,B) 垂直于隐式线 Ax+By+C=0Ax + By + C = 0

我们可以从任何与 (x1x0y1y0)(x_1-x_0,y_1-y_0) 垂直的 (A,B)(A,B) 开始。出于与第 2.7.2 节相同的原因,这样的向量只是 (A,B)=(y0y1x1x0)(A,B)=(y_0-y_1,x_1-x_0)。这意味着通过 (x0,y0)(x_0,y_0)(x1,y1)(x_1,y_1) 的线的方程为

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

现在我们只需要找到 CC。因为 (x0,y0)(x_0,y_0)(x1,y1)(x_1,y_1) 在该线上,所以它们必须满足方程(2.16)。我们可以将任一值代入并解出 CC。对 (x0,y0)(x_0,y_0) 这样做会得到 C=x0y1x1y0C = x_0y_1 - x_1y_0,因此,线的完整方程是

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

再次说明,这是穿过两个点的无限有效隐式方程之一,但这种形式没有除法操作,因此对于具有有限笛卡尔坐标的点没有数值退化情况。方程(2.17)的好处是我们可以通过将非 yy 项移至方程的右侧并除以 yy 项的乘数来始终转换为斜率截距形式(存在时):

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

隐式线方程的一个有趣特性是它可以用于找到点到线的符号距离。Ax+By+CAx + By + C 的值与与该线的距离成比例(图 2.32)。如图 2.33 所示,从点到线的距离是向量 k(A,B)k(A,B) 的长度,其中

miss

 distance =kA2+B2(2.18)\text { distance }=k \sqrt{A^{2}+B^{2}} \tag{2.18}

fig11_1.jpg

图 2.32。隐式函数 f(x,y)=Ax+By+Cf(x,y)= Ax + By + C 的值是符号距离与常数成比例。

对于点 (x,y)+k(A,B)(x,y)+ k(A,B)f(x,y)=Ax+By+Cf(x,y)= Ax + By + C 的值为

f(x+kA,y+kB)=Ax+kA2+By+kB2+C=k(A2+B2)(2.19)\begin{aligned} f(x+k A, y+k B) & =A x+k A^{2}+B y+k B^{2}+C \\ & =k\left(A^{2}+B^{2}\right) \end{aligned} \tag{2.19}

fig11_1.jpg

图 2.33。向量 k(A,B)k(A,B) 连接到最靠近不在该线上的点的线上的点 (x,y)(x,y)。距离与 kk 成比例。

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

 Distance =f(a,b)A2+B2.\text { Distance }=\frac{f(a, b)}{\sqrt{A^{2}+B^{2}}} .

这里,“有符号距离”意味着其大小(绝对值)是几何距离,但一侧的距离为正,另一侧的距离为负。如果您的问题有某些原因选择一个特定的边为正,可以在等效的表示 f(x,y)=0f(x,y)= 0f(x,y)=0-f(x,y)= 0 之间选择。请注意,如果 (a,b)(a,b) 是单位向量,则 f(a,b)f(a,b) 是有符号距离。我们可以将式(2.17)乘以一个常数,以确保 (a,b)(a,b) 是单位向量:

f(x,y)=y0y1(x1x0)2+(y0y1)2x+x1x0(x1x0)2+(y0y1)2y+x0y1x1y0(x1x0)2+(y0y1)2=0.(2.20)\begin{aligned} f(x, y)= & \frac{y_{0}-y_{1}}{\sqrt{\left(x_{1}-x_{0}\right)^{2}+\left(y_{0}-y_{1}\right)^{2}}} x+\frac{x_{1}-x_{0}}{\sqrt{\left(x_{1}-x_{0}\right)^{2}+\left(y_{0}-y_{1}\right)^{2}}} y \\ & +\frac{x_{0} y_{1}-x_{1} y_{0}}{\sqrt{\left(x_{1}-x_{0}\right)^{2}+\left(y_{0}-y_{1}\right)^{2}}}=0 . \end{aligned} \tag{2.20}

请注意,在式(2.20)中直接评估 f(x,y)f(x,y) 会得到有符号距离,但它需要平方根来设置方程。隐式线将被证明对于三角形光栅化(第 9.1.2 节)非常有用。第 13 章讨论了其他形式的 2D 线。

隐式二次曲线

在前一节中,我们看到线性函数 f(x,y)f(x,y) 导致隐式线 f(x,y)=0f(x,y)= 0。如果 ff 改为 xxyy 的二次函数,具有通用形式

Ax2+Bxy+Cy2+Dx+Ey+F=0A x^{2}+B x y+C y^{2}+D x+E y+F=0

则产生的隐式曲线称为二次曲线。二维二次曲线包括椭圆和双曲线,以及抛物线,圆和线的特殊情况。

二次曲线的示例包括以中心 (xc,yc)(x_c,y_c) 和半径 rr 为圆的情况,

(xxc)2+(yyc)2r2=0(x-x_c)^2 + (y-y_c)^2 - r^2=0

以及形式为轴向对齐的椭圆

(xxc)2a2+(yyc)2b21=0\frac{(x-x_c)^2}{a^2}+\frac{(y-y_c)^2}{b^2}-1=0

尝试将 a=b=ra = b = r 设置在椭圆方程中并与圆方程进行比较,

其中 (xc,yc)(x_c,y_c) 是椭圆的中心,aabb 是长短轴(图 2.34)。

fig11_1.jpg

图 2.34。以中心 (xc,yc)(x_c,y_c) 和长短轴长度为 aabb 的椭圆。

2.7.3 3D 隐式曲面

与隐式方程可以用于定义 2D 曲线一样,它们也可以用于定义 3D 曲面。与 2D 一样,隐式方程隐式地定义在表面上的一组点:

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

当点 (x,y,z)(x,y,z) 在表面上时,将其作为 ff 的参数给出的结果为零。任何不在表面上的点都会产生与零不同的某个数字。您可以通过评估 ff 来检查点是否在表面上,或者您可以检查点位于表面的哪一侧,看 ff 的符号,但您不能总是显式构造表面上的点。使用向量表示法,我们将写成 p=(x,y,z)\bold p =(x,y,z) 的这种函数

f(p)=0f(\bold p)=0

2.7.4 隐式曲面的表面法线

表面法线(在光照计算等方面需要)是垂直于表面的向量。表面上的每个点可能具有不同的法线向量。与 2D 情况下梯度提供对隐式曲线的法线相同,对于隐式曲面上的点 p\bold p 处的表面法线由隐式函数的梯度给出

n=f(p)=(f(p)x,f(p)y,f(p)z)n=\nabla f(\bold p)=(\frac{\partial f(\bold p)}{\partial x},\frac{\partial f(\bold p)}{\partial y},\frac{\partial f(\bold p)}{\partial z})

推理与 2D 情况相同:梯度指向 ff 最快的增加方向,该方向垂直于所有切线方向,在这些方向中 ff 保持不变。梯度向量指向表面的 f(p)>0f(\bold p)> 0 一侧,我们可以将其视为在表面“内部”或在给定上下文中从表面“外部”进入。如果特定形式的 f 创建了向内的梯度,并且需要向外的梯度,则表面 f(p)=0-f(\bold p)= 0 与表面 f(p)=0f(\bold p)= 0 相同,但具有方向相反的梯度,即 f(p)=(f(p))-∇f(\bold p)= ∇(-f(\bold p))

2.7.5 隐式平面

举个例子,考虑穿过点 aa 并具有表面法线 nn 的无限平面。描述该平面的隐式方程为

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

请注意,aann 是已知数量。点 p\bold p 是满足该方程的任何未知点。在几何术语中,这个方程表示“从 aapp 的向量垂直于平面法线”。如果 pp 不在平面上,则 (pa)(\bold p-a)nn 不成直角(图 2.35)。

fig11_1.jpg

有时,我们希望得到通过点 aabbcc 的平面的隐式方程。可以通过求平面内任意两个向量的叉积获得该平面的法线。其中一个叉积是

n=(ba)×(ca)\mathbf{n}=(\mathbf{b}-\mathbf{a}) \times(\mathbf{c}-\mathbf{a})

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

(pa)((ba)×(ca))=0.(2.22)(\mathbf{p}-\mathbf{a}) \cdot((\mathbf{b}-\mathbf{a}) \times(\mathbf{c}-\mathbf{a}))=0 . \tag{2.22}

一种几何方法是将该方程读作“由 pa\bold p-\bold aba\bold b-\bold aca\bold c-\bold a 定义的平行六面体的体积为零;即它们共面。”只有当 p\bold p 在与 a\bold ab\bold bc\bold c 相同的平面上时,才可能成立。其完整笛卡尔表示由 determinant 给出(这在第 6.3 节中有更详细的讨论):

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

行列式可以展开(请参见第 6.3 节展开行列式的机制)以得到带有许多项的膨胀形式。

方程(2.22)和(2.23)是等价的,并且对比它们很有启示性。方程(2.22)在几何上易于解释,并将产生有效的代码。而且,如果利用已调试的叉积和点积代码,则避免排版错误并编译为不正确代码相对容易。方程(2.23)在几何上也易于解释,并且只要实现了高效的 3×33×3 行列式函数,就会很有效。如果可用一个函数 determinant(A,B,C),则还可以轻松地实现它。如果将行列式函数重命名为 volume,则其他人阅读您的代码将特别容易。因此,Equations(2.22)和(2.23)都可以映射到代码中。任一方程的完整展开为 xxyyzz 分量可能会产生排版错误。这样的错误可能编译,因此可能特别棘手。这是干净数学生成干净代码和臃肿数学生成臃肿代码的绝佳例子。

3D 二次曲面

正如二元二次多项式定义 2D 四次曲线一样,xxyyzz 的二次多项式定义 3D 二次曲面。例如,一个球可以写成

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

一个轴对齐的椭球体可以写成

f(p)=(xxc)2a2+(yyc)2b2+(zzc)2c21=0.f(\mathbf{p})=\frac{\left(x-x_{c}\right)^{2}}{a^{2}}+\frac{\left(y-y_{c}\right)^{2}}{b^{2}}+\frac{\left(z-z_{c}\right)^{2}}{c^{2}}-1=0 .

从隐式曲面中创建 3D 曲线

有人可能希望使用形式 f(p)=0f(\bold p)=0 创建隐式 3D 曲线。然而,所有这些曲线都只是退化曲面,在实践中很少有用。可以从两个同时隐式方程的交点构造 3D 曲线:

f(p)=0,g(p)=0f(\bold p)=0, \\ g(\bold p)=0

例如,可以从两个隐式平面的交点形成 3D 直线。通常,使用参数曲线更方便;它们在以下部分中讨论。

2.7.6 2D 参数曲线

参数曲线由单个参数控制,该参数可以被认为是沿着曲线连续移动的索引。这样的曲线具有形式

[xy]=[g(t)h(t)]\left[\begin{array}{l} x \\ y \end{array}\right]=\left[\begin{array}{l} g(t) \\ h(t) \end{array}\right]

这里,(x,y)(x,y) 是曲线上的点,tt 是影响曲线的参数。对于给定的 tt,将通过函数 gghh 确定一些点。对于连续的 gghhtt 的小变化将产生 xxyy 的小变化。因此,当 tt 不断变化时,在连续曲线上扫过点。这是一个很好的特征,因为我们可以使用参数 tt 来显式地构造曲线上的点。通常,我们可以以向量形式编写参数曲线,

其中 ff 是一个向量值函数,f:RR2f: \mathbb{R} \mapsto \mathbb{R}^{2}。这些向量函数可以生成非常干净的代码,因此应尽可能使用它们。

我们可以将带有时间戳记的位置看作曲线。曲线可以走到任何地方,可以循环并交叉自身。我们还可以认为曲线在任何点都具有速度。例如,点 p(t)\bold p(t)t=2t = –2 附近缓慢移动,在 t=2t = 2t=3t = 3 之间快速移动。即使曲线不描述移动的点,这种“移动点”词汇也经常用于讨论参数曲线时使用。

2D 参数线

通过点 p0=(x0,y0)\bold p_0=(x_0, y_0)p1=(x1,y1)\bold p_1=(x_1,y_1) 的 2D 参数线可以写成

[xy]=[x0+t(x1x0)y0+t(y1y0)]\left[\begin{array}{l} x \\ y \end{array}\right]=\left[\begin{array}{c} x_{0}+t\left(x_{1}-x_{0}\right) \\ y_{0}+t\left(y_{1}-y_{0}\right) \end{array}\right]

因为 xxyy 的公式具有如此相似的结构,所以我们可以使用向量形式表示 p=(x,y)p=(x,y)(图 2.36):

p(t)=p0+t(p1p0).\mathbf{p}(t)=\mathbf{p}_{0}+t\left(\mathbf{p}_{1}-\mathbf{p}_{0}\right) .

fig11_1.jpg

你可以将其读作“从点 p0\bold p_0 开始,并沿着参数 tt 确定的距离移动到 p1\bold p_1。”这种形式的一个好的特性是 p(0)=p0\bold p(0)=\bold p_0p(1)=p1\bold p(1)=\bold p_1。由于该点随 tt 线性改变,因此 p0\bold p_0p1\bold p_1 之间的 tt 值测量点之间的分数距离。tt 小于 00 的点位于 p0\bold p_0 的“远”侧,tt 大于 11 的点位于 p1\bold p_1 的“远”侧。

参数线也可以描述为一个点 o\bold o 和一个向量 d\bold d

p(t)=o+t(d)\bold p(t)=\bold o+t(\bold d)

当向量 d\bold d 具有单位长度时,该线是弧长参数化。这意味着 tt 是沿线的距离的确切度量。任何参数曲线都可以是弧长参数化的,这显然是一种非常方便的形式,但并非所有曲线都可以解析地转换。

2D 参数圆

具有中心 (xc,yc)(x_c,y_c) 和半径 rr 的圆具有参数形式:

[xy]=[xc+rcosϕyc+rsinϕ]\left[\begin{array}{l} x \\ y \end{array}\right]=\left[\begin{array}{l} x_{c}+r \cos \phi \\ y_{c}+r \sin \phi \end{array}\right]

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

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

[xy]=[xc+acosϕyc+bsinϕ]\left[\begin{array}{l} x \\ y \end{array}\right]=\left[\begin{array}{l} x_{c}+a \cos \phi \\ y_{c}+b \sin \phi \end{array}\right]

2.7.7 3D 参数曲线

3D 参数曲线的工作方式与 2D 参数曲线类似:

x=f(t),y=g(t),z=h(t)x=f(t), \\ y=g(t), \\ z=h(t)

例如,围绕 zz 轴的螺旋线可写为

x=cost,y=sint,z=tx=\text{cos}t, \\ y=\text{sin}t, \\ z=t

与 2D 曲线一样,如果我们想要控制曲线的起始和结束位置,则函数 ffgghh 在域 DRD⊂R 上定义。以向量形式,我们可以写成

[xyz]=p(t)\left[\begin{array}{l} x \\ y \\ z \end{array}\right]=\mathbf{p}(t)

在本章中,我们仅详细讨论 3D 参数线。一般的 3D 参数曲线在第 15 章中更全面地讨论。

参数曲线是 p:RR3\mathbf{p}: \mathbb{R} \rightarrow \mathbb{R}^{3} 的范围。

3D 参数线

3D 参数线可以写成 2D 参数线的直接扩展,例如,

x=2+7t,y=1+2t,z=35tx=2+7t, \\ y=1+2t, \\ z=3−5t

这很麻烦,不易于转换为代码变量,因此我们将其以向量形式写出:

p=o+td\bold p=\bold o+t\bold d

其中,在此示例中,o\bold od\bold d 由以下给出:

o=(2,1,   3)d=(7,2,5)\bold o=(2,1,\space\space\space 3) \\ \bold d=(7,2,−5)

请注意,这与 2D 情况非常相似。可视化这一点的方法是想象该直线通过 o\bold o 并且平行于 d\bold d。给定任何 tt 值,您都可以在直线上获得某些点 p(t)\bold p(t) 。例如,在 t=2t = 2 时,p(t)=(2,1,3)+2(7,2,–5)=(16,5,–7)p(t) = (2,1,3) + 2(7,2,–5)=(16,5,–7)。这个概念与二维相同(图 2.36)。

与 2D 一样,线段可以由 3D 参数线和间隔 t[ta,tb]t\in [t_a,t_b] 描述。两点之间的线段由 p(t)=a+t(ba)\bold p(t)=\bold a+t(\bold b–\bold a) 给出,其中 t[0,1]t\in [0,1]。在这里,p(0)=a\bold p(0)=\bold ap(1)=b\bold p(1)=\bold bp(0.5)=(a+b)/2\bold p(0.5)=(\bold a+\bold b)/2,即 a\bold ab\bold b 之间的中点。

射线或半线是具有半开区间(通常为 [0)[0,∞))的 3D 参数线。从现在开始,我们将所有线、线段和射线称为“射线”。这有些邋遢,但对应于常见的用法,使讨论更简单。

3D 参数曲面

参数方法可以用来定义 3D 空间中的曲面,方式与定义曲线类似,只不过需要两个参数来描述曲面的二维区域。这些曲面具有如下形式:

x=f(u,v),y=g(u,v),z=h(u,v).\begin{array}{l} x=f(u, v), \\ y=g(u, v), \\ z=h(u, v) . \end{array}

或者以向量形式表示,

[xyz]=p(u,v)\left[\begin{array}{l} x \\ y \\ z \end{array}\right]=\mathbf{p}(u, v)

参数曲面是函数 p:R2R3\mathbf{p}: \mathbb{R}^{2} \rightarrow \mathbb{R}^{3} 的范围。

例如,地球表面上的点可以用经度和纬度两个参数来描述。如果我们将原点定义为地球的中心,并让 rr 为地球的半径,则以原点为中心的球面坐标系(图 2.37)使我们能够推导出参数方程

x=rcosϕsinθ,y=rsinϕsinθ,z=rcosθ.(2.24)\begin{array}{l} x=r \cos \phi \sin \theta, \\ y=r \sin \phi \sin \theta, \\ z=r \cos \theta . \end{array} \tag{2.24}

为了方便起见,假设地球是完全球形的。

这里的θ和ϕ根据您的背景可能会看起来相反;这些符号的使用因学科而异。在本书中,我们始终假定公式(2.24)中使用的θ和ϕ的含义,并在图 2.37 中描述。

理想情况下,我们希望以向量形式书写此内容,但对于此特定参数形式并不可行。

我们还希望能够找到给定 (x,y,z)(x,y,z)(θ,ϕ)(θ,\phi)。如果我们假设 ϕ(ππ] \phi\in (–π,π],则可以使用方程(2.2)中的 atan2atan2 函数轻松完成此操作:

θ=acos(z/x2+y2+z2)ϕ=atan2(y,x).(2.25)\begin{array}{l} \theta=\operatorname{acos}\left(z / \sqrt{x^{2}+y^{2}+z^{2}}\right) \\ \phi=\operatorname{atan} 2(y, x) . \end{array} \tag{2.25}

对于隐式曲面,函数 ff 的导数给出了曲面法向量。对于参数曲面,p\bold p 的导数也提供有关曲面几何的信息。

考虑函数 q(t)=p(t,v0)\bold q(t)=\bold p(t,v_0)。该函数通过在保持 vv 固定为 v0v_0 的值的情况下变化 uu 来定义一个参数曲线。这条曲线称为等参曲线(或有时简称“等参”),它位于曲面内。 q\bold q 的导数给出了曲线的切向量,由于曲线位于曲面内,因此向量 q\bold q' 也位于曲面内。由于它是通过变化 p\bold p 的一个参数而获得的,因此向量 q\bold q'p\bold puu 偏导数,我们将其表示为 pu\bold p_u。类似的论证表明,对 vv 求偏导数 pv\bold p_v 给出了常数 u 等参曲线的切向量,它是曲面的第二个切向量。

fig11_1.jpg

图 2.37。球面坐标系的几何形状。

因此,p\bold p 的导数在曲面上任意一点都给出了两个切向量。曲面的法向量可以通过取这些向量的叉积来找到:由于两个向量都切于曲面,它们的叉积,即垂直于两个切向量的向量,是曲面的法向量。右手法则为叉积提供了一种确定前面或外部的曲面的方法;我们将使用向量

n=pu×pv\bold n=\bold p_u×\bold p_v

指向曲面的外部。

2.7.9 曲线和曲面总结

在二维中隐式曲线或三维中曲面是由两个或三个变量的标量值函数 f:R2R\mathbf{f}: \mathbb{R}^2 \rightarrow \mathbb{R}f:R3R\mathbf{f}: \mathbb{R}^3 \rightarrow \mathbb{R} 定义的,曲面包括所有使函数为零的点:

S={pf(p)=0}.S=\{\mathbf{p} \mid f(\mathbf{p})=0\} .

在二维或三维中参数曲线由一个变量的向量值函数 p:DRR2 or p:DRR3\mathbf{p}: D \subset \mathbb{R} \rightarrow \mathbb{R}^{2} \text { or } \mathbf{p}: D \subset \mathbb{R} \rightarrow \mathbb{R}^{3} 定义,并且当 ttDD 的所有值上变化时,曲线被扫过:

在三维中,参数曲面由两个变量的向量值函数 p:DR2R3\mathbf{p}: D \subset \mathbb{R}^2 \rightarrow \mathbb{R}^{3} 定义,曲面包括域内所有点 (u,v)(u,v) 的图像:

S={p(t)tD}.S=\{\mathbf{p}(t) \mid t \in D\} .

对于隐式曲线和曲面,法向量由 ff 的导数(梯度)给出,切向量(对于曲线)或向量(对于曲面)可以通过构建基础从法向量推导出。

对于参数曲线和曲面,p\bold p 的导数给出切向量(对于曲线)或向量(对于曲面),并且可以通过构建基础从切向量推导出法向量。

2.8 线性插值

在计算机图形学中,最常见的数学运算可能是线性插值。我们已经看到了在线段 2D 和 3D 中通过位置进行线性插值的示例,其中两个点 aabb 与参数 tt 相关联以形成线段 p=(1t)a+tbp=(1-t)a+tb。这是插值,因为 pp 恰好在 t=0t=0t=1t=1 时穿过 aabb。这是线性插值,因为权重项 tt1t1-ttt 的线性多项式。

另一个常见的线性插值是在 xx 轴上一组位置之间:x0x1...xnx_0、x_1、...,x_n,对于每个 xix_i,我们都有一个关联的高度 yiy_i。我们希望创建一个连续的函数 y=f(x)y=f(x),它插值这些位置,使得 ff 经过每个数据点,即 f(xi)=yif(x_i)=y_i。对于线性插值,点 (xi,yi)(x_i,y_i) 由直线段连接。对于这些段,使用参数化线方程是自然的。参数 tt 只是 xix_ixi+1x_i+1 之间的分数距离:

f(x)=yi+xxixi+1xi(yi+1yi)(2.26)f(x)=y_{i}+\frac{x-x_{i}}{x_{i+1}-x_{i}}\left(y_{i+1}-y_{i}\right) \tag{2.26}

由于权重函数是 xx 的线性多项式,因此这是线性插值。

上述两个示例具有相同的线性插值形式。我们创建一个变量 tt,当从数据项 AA 移动到数据项 BB 时,tt 变化从 0011。中间值只是函数 (1t)A+tB(1-t)A+tB。请注意,方程(2.26)具有此形式,

t=xxixi+1xi.t=\frac{x-x_{i}}{x_{i+1}-x_{i}} .

2.9 三角形

在许多图形程序中,2D 和 3D 中的三角形是基本建模基元。通常,诸如颜色之类的信息被标记在三角形顶点上,并且该信息在三角形上进行插值。使这种插值变得简单的坐标系称为重心坐标;我们将从头开始开发这些内容。我们还将讨论 2D 三角形,在我们可以在 2D 屏幕上绘制它们的图片之前必须理解它们。

2.9.1 2D 三角形

如果我们有由 2D 点 aabbcc 定义的 2D 三角形,我们首先可以找到它的面积:

 Area =12xbxaxcxaybyaycya=12(xayb+xbyc+xcyaxaycxbyaxcyb)(2.27)\begin{aligned} \text { Area } & =\frac{1}{2}\left|\begin{array}{ll} x_{b}-x_{a} & x_{c}-x_{a} \\ y_{b}-y_{a} & y_{c}-y_{a} \end{array}\right| \\ & =\frac{1}{2}\left(x_{a} y_{b}+x_{b} y_{c}+x_{c} y_{a}-x_{a} y_{c}-x_{b} y_{a}-x_{c} y_{b}\right) \end{aligned} \tag{2.27}

此公式的推导可以在第 6.3 节中找到。如果点 aabbcc 按逆时针顺序排列,则该区域将具有正号,否则为负号。

在图形学中,通常希望在每个三角形顶点处分配一种属性(例如颜色),并平滑地插值该属性的值。有各种方法可以实现这一点,但最简单的方法是使用重心坐标。重心坐标的一种思考方式是作为非正交坐标系,如 2.4.2 节中简要讨论的那样。这样的坐标系在图 2.38 中显示,其中坐标原点是 aa,从 aabbcc 的向量是基向量。用该原点和基向量,任何点 p\bold p 都可以写成

p=a+β(ba)+γ(ca)(2.28)\mathbf{p}=\mathbf{a}+\beta(\mathbf{b}-\mathbf{a})+\gamma(\mathbf{c}-\mathbf{a}) \tag{2.28}

fig11_1.jpg

图 2.38。一个由点 a\bold ab\bold bc\bold c 定义的 2D 三角形可以用来建立一个以 a\bold a 为原点,以 (ba)(\bold b-\bold a)(ca)(\bold c-\bold a) 为基向量的非正交坐标系。然后,一个点被表示为有序对 (β,γ)(β,γ)。例如,点 p=(2.00.5)\bold p=(2.0,0.5),即 p=a+2.0(ba)+0.5(ca)\bold p=\bold a+2.0(\bold b-\bold a)+0.5(\bold c-\bold a)

请注意,我们可以重新排列方程(2.28)中的项,以得到

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

通常人们定义一个新变量 αα 来改善方程的对称性:

α1βγ\alpha \equiv 1-\beta-\gamma

这导致方程

p(α,β,γ)=αa+βb+γc,(2.29)\mathbf{p}(\alpha, \beta, \gamma)=\alpha \mathbf{a}+\beta \mathbf{b}+\gamma \mathbf{c}, \tag{2.29}

并满足约束条件

α+β+γ=1(2.30)α+β+γ=1 \tag{2.30}

乍一看,重心坐标似乎是一个抽象而不直观的构造,但它们实际上是强大而方便的。您可能会发现,在一个城市中,如果有两组平行街道,但这些街道不是成直角,那么街道地址将如何工作。自然系统本质上就是重心坐标系,您很快就会习惯于使用它们。重心坐标可以定义在平面上的所有点上。重心坐标的特别好处是点 p\bold p 在由 a\bold ab\bold bc\bold c 形成的三角形内,当且仅当

0<α<1,0<β<1,0<γ<10<α<1, \\ 0<β<1, \\ 0<γ<1

如果其中一个坐标为零,另外两个在零和一之间,则您位于边缘上。如果两个坐标为零,则另一个为一,您就在一个顶点上。重心坐标的另一个好处是方程(2.29)实际上以平滑的方式混合了三个顶点的坐标。相同的混合系数 (αβγ)(α,β,γ) 可以用于混合其他属性,如颜色,我们将在下一章中看到。

给定一个点 pp,我们如何计算它的重心坐标?一种方法是将方程(2.28)写成一个线性系统,未知数为 ββγγ,解出并设置 α=1βγα=1−β−γ。该线性系统为

[xbxaxcxaybyaycya][βγ]=[xpxaypya](2.31)\left[\begin{array}{ll} x_{b}-x_{a} & x_{c}-x_{a} \\ y_{b}-y_{a} & y_{c}-y_{a} \end{array}\right]\left[\begin{array}{l} \beta \\ \gamma \end{array}\right]=\left[\begin{array}{l} x_{p}-x_{a} \\ y_{p}-y_{a} \end{array}\right] \tag{2.31}

虽然代数解方程(2.31)很容易,但通常通过计算直接几何解决方案会更有成效。

重心坐标的一个几何性质是它们是三角形边缘上线段的带符号缩放距离,如图 2.39 所示。回想一下,在 2.7.2 节中,对于线 f(xy)=0f(x,y) = 0,评估方程 f(xy)f(x,y) 返回从 (x,y)(x,y) 到该线的缩放带符号距离。还记得如果 f(x,y)=0f(x,y)=0 是特定线的方程,则对于任何非零 kkkf(xy)=0kf(x,y) = 0 也是一条直线的方程。改变 kk 会缩放距离,并控制哪侧的线具有正符号距离,哪侧具有负符号距离。我们想选择 kk,使得例如 kf(xy)=βkf(x,y) = β。由于 kk 只有一个未知数,我们可以通过一个约束强制这样做,即在点 bb 处,我们知道 β=1β=1。因此,如果 fac(xy)=0fac(x,y)=0 的线通过 aacc 两点,则我们可以按以下方式计算点 (xy)(x,y)ββ

β=fac(x,y)fac(xb,yb),(2.32)\beta=\frac{f_{a c}(x, y)}{f_{a c}\left(x_{b}, y_{b}\right)}, \tag{2.32}

我们可以以类似的方式计算 γγαα。为了提高效率,通常明智的做法是直接计算两个重心坐标,并使用方程(2.30)计算第三个坐标。

要找到通过 p0\bold p_0p1\bold p_1 的“理想”形式的线,我们可以首先使用 2.7.2 节的技术找到一些经过顶点的有效隐式线。公式(2.17)给出了

fab(x,y)(yayb)x+(xbxa)y+xaybxbya=0f_{a b}(x, y) \equiv\left(y_{a}-y_{b}\right) x+\left(x_{b}-x_{a}\right) y+x_{a} y_{b}-x_{b} y_{a}=0

请注意,fab(xc,yc)f_{ab}(x_c,y_c) 可能不等于 11,因此它可能不是我们所寻求的理想形式。通过除以 fab(xc,yc)f_{ab}(x_c,y_c),我们得到

γ=(yayb)x+(xbxa)y+xaybxbya(yayb)xc+(xbxa)yc+xaybxbya\gamma=\frac{\left(y_{a}-y_{b}\right) x+\left(x_{b}-x_{a}\right) y+x_{a} y_{b}-x_{b} y_{a}}{\left(y_{a}-y_{b}\right) x_{c}+\left(x_{b}-x_{a}\right) y_{c}+x_{a} y_{b}-x_{b} y_{a}}

fig11_1.jpg

图 2.39。重心坐标 ββ 是通过 aacc 的线段的带符号缩放距离。

除法的存在可能会令我们担忧,因为它引入了可能出现被零除的情况,但是对于面积不接近零的三角形,这种情况不会发生。ααγγ 有类似的公式,但通常只需要一个:

β=(yayc)x+(xcxa)y+xaycxcya(yayc)xb+(xcxa)yb+xaycxcya,α=1βγ.\begin{array}{l} \beta=\frac{\left(y_{a}-y_{c}\right) x+\left(x_{c}-x_{a}\right) y+x_{a} y_{c}-x_{c} y_{a}}{\left(y_{a}-y_{c}\right) x_{b}+\left(x_{c}-x_{a}\right) y_{b}+x_{a} y_{c}-x_{c} y_{a}}, \\ \alpha=1-\beta-\gamma . \end{array}

计算重心坐标的另一种方法是计算如图 2.40 所示的子三角形的面积 AaA_a, AbA_bAcA_c。重心坐标遵守规则

α=Aa/Aβ=Ab/Aγ=Ac/A(2.33)\begin{array}{l} \alpha=A_{a} / A \\ \beta=A_{b} / A \\ \gamma=A_{c} / A \end{array} \tag{2.33}

fig11_1.jpg

其中 AA 是三角形的面积。请注意,A=Aa+Ab+AcA = A_a + A_b + A_c,因此可以用两个加法而不是完整的面积公式来计算。如果允许区域具有符号,则该规则仍然适用于三角形外的点。其原因如图 2.41 所示。请注意,这些是有符号面积,并且只要对 AA 和子三角形 AaA_aAbA_bAcA_c 使用相同的有符号面积计算,就会计算正确。

fig11_1.jpg

图 2.41。所示的两个三角形的面积都是底边乘高度的一半,因此相同,任何一个顶点在 β=0.5β = 0.5 线上的三角形都是如此。高度并因此面积与 ββ 成比例。

2.9.2 三维三角形

关于重心坐标的一个奇妙之处是它们几乎透明地扩展到了三维。如果我们假设点 aabbcc 是三维的,那么我们仍然可以使用表示式

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

现在,随着 ββγγ 的变化,我们横跨一个平面。

三角形的法向量可以通过取三角形所在平面上任意两个向量的叉积来找到(图 2.42)。最简单的方法是使用其中的两个边作为这些向量,例如

n=(ba)×(ca)(2.34)\mathbf{n}=(\mathbf{b}-\mathbf{a}) \times(\mathbf{c}-\mathbf{a}) \tag{2.34}

fig11_1.jpg

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

三角形的面积可以通过取叉积的长度来找到:

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

请注意,这不是有符号面积,因此不能直接用于计算重心坐标。但是,我们可以观察到,顶点顺序“顺时针”的三角形的法向量指向与同一平面中顶点顺序“逆时针”的三角形的法向量相反的方向。回想一下,

ab=abcosϕ,\mathbf{a} \cdot \mathbf{b}=\|\mathbf{a}\|\|\mathbf{b}\| \cos \phi,

其中ϕ是向量之间的夹角。如果 aabb 平行,则 cosϕ=±1\text{cos}\phi = ±1,这给出了一种测试向量是否指向相同或相反方向的方法。结合公式(2.33)-(2.35),这提示了公式

α=nnan2 β=nnbn2, γ=nncn2\begin{array}{l} \alpha=\frac{\mathbf{n} \cdot \mathbf{n}_{a}}{\|\mathbf{n}\|^{2}}\\ \space \\ \beta=\frac{\mathbf{n} \cdot \mathbf{n}_{b}}{\|\mathbf{n}\|^{2}},\\ \space \\ \gamma=\frac{\mathbf{n} \cdot \mathbf{n}_{c}}{\|\mathbf{n}\|^{2}} \end{array}

其中 n\bold n 是使用顶点 a\bold ab\bold bc\bold c 计算的公式(2.34);na\bold n_a 是使用顶点 b\bold bc\bold cp\bold p 计算的公式(2.34),等等,即

na=(cb)×(pb),nb=(ac)×(pc),nc=(ba)×(pa).(2.36)\begin{array}{l} \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}) . \end{array} \tag{2.36}

2.10 离散概率

概率研究的是包括随机结果的事物,而离散概率是指存在有限数量的随机结果的情况。一个经典的例子是一个六面骰子,其中骰子从 {1,2,3,4,5,6}\{1,2,3,4,5,6\} 中随机取一个值,每个结果的概率相等。某种结果发生的概率是该结果出现的时间比例。某件事情发生的比例是 1。每次掷骰子都会以六分之一的概率出现。

关于随机性最令人困惑的事情之一是区分一个随机结果和一个已经被投掷过的骰子。随机变量是一个没有已知值,但将在一个已知可能性集合中随机取一个值的单个值。这里的“变量”一词来自数学,与编程中的“变量”直接相关。随机变量的一个例子是 XX,“X=骰子的最终结果X=骰子的最终结果”。变量可以使用任何符号;大写 XX 通常用作数学中的随机变量符号,就像“ii”和“jj”通常用于计算机科学的循环变量一样。计算机程序具有随机变量的非常直接的用途:

 int X= rand_from (1,6)\text { int } \mathrm{X}=\text { rand\_from }(1,6)

其中 XX 是一个变量,我们不知道它的值,但我们知道当运行程序时,我们将获得六个值中的一个,每个值的概率相等,这直接对应于随机变量“X=骰子的最终结果X=骰子的最终结果”。随机变量有两个常用的属性:期望和方差 (expected value and variance)。随机变量 XX 的期望值,通常表示为 EXEXE(X)E(X),更好地称为“期望平均值”,但不要这么说,因为这会让熟悉标准术语的人感到困惑。这只是 XX 在“掷骰子”的所有平行宇宙中取值的平均值。这可以通过将每个结果乘以其概率并相加来计算:

EX=16(1)+16(2)+16(3)+16(4)+16(5)+16(6)=3.5E 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.53.5”这种说法并不是无稽之谈,但术语可能不太合适。尽管存在缺陷,但这个术语在各个领域中是相当标准的,因此尽管尝试内化它,你在与其他领域的人讨论这个话题时就不会有问题了。

期望值告诉我们随机变量趋势的方向,但它并不能告诉我们这种趋势需要多长时间才会发生,也不能告诉我们它偏离平均值的程度。例如,一个有 33113366 的骰子仍然具有期望值为 3.5,但是其“偏差”比传统的骰子大。那么我们如何度量变化的幅度呢?一种方法是测量与 3.53.5 的平均偏差,但如果我们包括符号,那么平均偏差为零,因为掷出 112.5-2.5 抵消了掷出 66+2.5+2.5 的偏差。我们可以取绝对差,但这会带来实际问题(包括绝对值的代数问题很有挑战性),并且存在一些理论问题。在实践中,人们更喜欢计算平均平方偏差,称之为方差:

V(X)=average((XEX)2)V(X)=\operatorname{average}\left((X-E X)^{2}\right)

因为它是统计量,所以该平均值是一个期望,因此

V(X)=E((XE(X))2)V(X)=E\left((X-E(X))^{2}\right)

对于骰子的情况,E(X)=3.5E(X)=3.5XE(X)X-E(X) 的值为 2.5,1.5,0.5,0.5,1.5,2.5-2.5,-1.5,-0.5,0.5,1.5,2.5,因此 (XE(X))2(X-E(X))^2 的值分别为 6.25,2.25,0.25,0.25,2.25,6.256.25,2.25,0.25,0.25,2.25,6.25,因此随机变量 XX 的方差通常表示为 V(X)V(X),是 17.5/617.5/6

方差公式的某些代数运算可以得到一种更方便的形式:

V(X)=E(X2)E(X)2V(X)=E(X^2)−E(X)^2

期望和方差有一些代数上的技巧,在很多应用中被频繁使用。例如,假设我们有两个随机变量 XXYY,并定义一个变量 Z=X+YZ=X+Y。那么 E(Z)E(Z) 的期望值是什么?事实证明,

E(Z)=E(X+Y)=E(X)+E(Y)E(Z)=E(X+Y)=E(X)+E(Y)

令人惊奇的是,即使 XXYY 不是“统计独立”的(对于我们的骰子而言,它们可能以某种方式相互影响),公式仍然适用。在一个极端的例子中,我们可以看一下第一个骰子,然后将第二个骰子设置为与第一个相同。这个公式仍然适用!这非常强大,并且通常作为程序中未声明的属性使用。

方差具有相同的性质,但仅当 XXYY 是独立的时才成立。

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

反例表明,如果 XXYY 是相关的,则该公式不一定适用。假设你掷出 XX,然后将 YY 设置为该骰子的相反面,因此对于 X=1X=1,选择 Y=6Y=6,如果 X=2X=2,则选择 Y=5Y=5,如果 X=3X=3,则选择 Y=4Y=4,等等。ZZ 的值始终为 77,因此方差为零。但是 XX 的方差为 00,并且显然不是独立总和所产生的 2(17.5/6)2(17.5/6)

方差不直观的一个缺点是因为它有平方,因此人们经常使用标准差的平方根,称为标准差,通常表示为 sigma(X)\text{sigma}(X)。所以

σ(X)=V(X)σ(X)=\sqrt{V(X)}

没有关于 σ(X+Y)σ(X+Y) 的好公式,因此方差具有很好的公式的吸引力变得更加明显。请注意,在骰子的例子中,标准偏差=17.5/61.7标准偏差=\sqrt{17.5/6}≈1.7。这“大约”是距离平均值 3.53.5 的平均距离,但略有不同,因为实际的平均绝对距离是 1.51.5。因此,虽然在实践中将标准差视为平均绝对偏差几乎总是没有危险的直觉,但最好至少记住它们是不同的。

2.11 连续概率

在图形学中,我们经常使用可以取一系列值的随机变量,这些通常称为连续随机变量。好消息是,几乎所有关于离散随机变量的内容都适用于连续随机变量:术语、期望值定义和公式以及方差定义和公式。然而,还存在一个重要区别:连续随机变量取任何特定值的概率都是零。假设你有一个在 001010 之间的均匀分布随机变量 XX

X= continuous_random_from (2.3,10.9)X=\text { continuous\_random\_from }(-2.3,10.9)

获得值 1.7 或π或 e 的概率是相等的。问题是获得恰好 1.7 的概率为零。

好消息是,密度函数解决了这个问题。就像每秒焦耳一样,我们可以在一维情况下使用每长度的概率。在上面的例子中,我们测量概率的维度是长度。如果长度以某个未指定的单位表示,并且我们只知道从零到十的范围,则会说概率以“每单位长度”的方式表示。

2.12 蒙特卡罗积分

第 2.5 节讨论了如何“读取”积分并将其抽象为“integrate()”函数。但是我们如何实际实现该函数呢?在图形学中,最常见的方法是使用蒙特卡罗积分。蒙特卡罗积分的代数通常很丑陋和令人生畏。但是如果我们看一下这个函数:

1
float shade = average(f(), hemisphere)

我们的直觉会得出正确的答案。在半球上选取一堆随机点 viv_i 并评估 f(vi)f(v_i) 并将它们平均,例如:

1
2
3
4
5
6
float sum = 0.0
N = 10000; //或用户设置的其他大数字
For (int i = 1 to N)
vec3 v = random_point_on_hemisphere()
sum = sum + f(v)
Average = sum / N

真的那么容易!现在你需要一个函数来在单位半球上选择随机点。最简单的方法是“拒绝法”,它首先通过重复从单位立方体中均匀选择三个随机数字来在单位球中选择点:

1
2
3
4
5
do
X = random_from(-1,1)
Y = random_from(-1,1)
Z = random_from(-1,1)
while (x^2 + y^2 + z^2 > 1)

然后如果需要,在半球中翻转 ZZ 以使其处于半球中:

1
If (Z < 0) Z = -Z

然后,将点投影到单位半球上

1
v = unit_vector(X, Y, Z).

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

1
average(f(), domain) = integrate(f(), domain) / integrate(1, domain)

所以

1
integrate(f(), domain)) = average(f(), domain) * integrate(1, domain)

对于半球而言,integrate(1, domain) 就是其面积,即 2π

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

2.12.1 重要性采样

当我们要对一个具有高低值变化范围很大的函数进行随机平均时,集中样本在某些区域并使用权重纠正不均匀性可能对我们有利。概率密度函数为我们提供了正确的工具:如果我们知道样本的 PDF,则这是该区域“过采样”的直接度量。如果使用非均匀采样,则可以这样获得

1
integrate = average_of_nonuniform_samples(f()/p(), domain).

关于这个公式的一个好处是它也适用于均匀随机样本。在这种情况下,PDF p() = 1 / integrate(1, domain),因此域的“大小”被编码在 PDF 中。

对于任何给定的蒙特卡罗重要性采样问题,我们遵循一种相当公式化的方法,至少是为了开始:

  1. 确定函数 f()f() 和积分域(例如,单位球上的点或三角形上的点)。
  2. 选择一种生成随机样本 xix_i 的方法,并确保有一种方法来评估每个样本的 PDF p(xi)p(x_i)
  3. 对许多 xix_i 的比值 f(xi)/p(xi)f(x_i)/p(x_i) 进行平均。这是我们对积分的估计。

一个好处是可以使用任何 p()p(),并且您将收敛于正确的答案(需要注意的是,在 f()f() 非零时,p()p() 必须非零)。您使用哪个 p()p() 仅影响估计收敛的速度。因此,我们通常从调试代码的角度开始使用常量 p()p()

常见问题解答

为什么没有向量除法?

事实证明,向量除法没有“好”的类比。但是,通过详细考虑这个问题,可以激发出四元数的动机(参见章节注释中提到的霍夫曼的书)。

是否有像重心坐标那样简洁的多边形(三角形以外)?

不幸的是,没有。即使是凸四边形也要复杂得多。这就是为什么在图形学中三角形是常见的几何原语的一个原因。

是否有 3D 线的隐式形式?

没有。但是,两个 3D 平面的交点定义了一条 3D 线,因此可以通过两个同时的隐式 3D 方程来描述 3D 线。

拟蒙特卡罗(QMC)或蓝噪声采样与蒙特卡罗采样有何关系?

蒙特卡罗的核心思想是您可以对大量“公平”的样本进行平均以估计真实平均值。在这里,“公平”可以用统计意义上的方式来表达。但是,即使它们不是随机的,某些样本集也可以被证明是“公平”的。其中之一是拟蒙特卡罗,具有明显的确定性结构,这种结构不是随机的,但在形式上具有统计意义上的均匀性,这些集通常比随机集改进收敛性。蓝色噪声样本集对样本施加限制以避免聚集,并且像 QMC 集一样可以提高收敛性,而不完全是随机的。在实践中,大多数技术都是使用蒙特卡罗形式主义开发的,因为数学更可行,然后在代码中插入 QMC 或蓝噪声点,以经验性信心认为实践中只需要均匀性。

注释

向量分析的历史特别有趣。它在 19 世纪中期主要由格拉斯曼发明,但被忽视并在以后重新发明(Crowe,1994)。现在在图形学领域中,一些研究人员正在基于他的一些思想开发几何代数,这使得格拉斯曼又开始受到关注(Doran&Lasenby,2003)。对于为什么特定的标量和向量乘积在某种意义上是正确的,为什么我们没有常用的向量除法,读者可以在简洁的《关于向量》(Hoffmann,1975)中获得启示。另一个重要的几何工具是四元数,由汉密尔顿在 19 世纪中期发明。四元数在许多情况下都很有用,尤其是涉及方向时(Hanson,2005)。

练习

  1. 一个集合的基数是它包含的元素数量。在 IEEE 浮点表示法(第 1.5 节)下,浮点数的基数是多少?
  2. 是否可能实现一个将 32 位整数映射到 64 位整数并具有明确定义反函数的函数?所有从 32 位整数到 64 位整数的函数都具有明确定义的反函数吗?
  3. 用三个区间的笛卡尔积来指定单位立方体(xxyyzz 坐标均介于 0011 之间,包括边界)。
  4. 如果您可以访问自然对数函数 ln(x)\ln{(x)},请说明如何使用它来实现 log(b,x)\log(b, x) 函数,其中 b 是对数的底数。负 b 值的函数应该怎么做?假设采用 IEEE 浮点实现。
  5. 解二次方程 2x²+6x+4=02x² + 6x + 4 = 0
  6. 实现一个函数,该函数接受二次方程 Ax²+Bx+C=0Ax² + Bx + C = 0 的系数 AABBCC,并计算出两个解。使函数返回有效(非 NaN)解的数量,并填写返回参数,使得两个解中较小的解首先返回。
  7. 证明第 17 页上的二次公式的两种形式是等价的(假设精确运算),并解释如何选择每个根以避免减去几乎相等的浮点数,从而导致精度损失。
  8. 通过反例证明对于三维向量 a\bold ab\bold bc\bold ca×(b×c)!=(a×b)×c\bold a × (\bold b × \bold c) != (\bold a × \bold b) × \bold c 不总是成立。
  9. 给定非平行的三维向量 a\bold ab\bold b,请计算一个右手坐标系,使得 u\bold u 平行于 a\bold av\bold v 在由 a\bold ab\bold b 定义的平面上。
  10. 函数 f(x,y,z)=x²+y3z³f(x,y,z)=x²+y-3z³ 的梯度是什么?
  11. 什么是轴对齐的二维椭圆的参数形式?
  12. 给定三维点 (1,0,0)(1, 0, 0)(0,1,0)(0, 1, 0)(0,0,1)(0, 0, 1),平面的隐式方程式是什么?参数方程式是什么?这个平面的法向量是什么?
  13. 设计一个稳健的过程,以确定线段 a0a1\bold a_0\bold a_1b0b1\bold b_0\bold b_1 是否相交,给定四个二维点 a0\bold a_0a1\bold a_1b0\bold b_0b1\bold b_1
  14. 设计一个稳健的过程来计算一个二维非共线点关于三个二维点的重心坐标。
  15. 计算入门微积分的各种一元积分,并改变样本数量。随着样本数量的增加,答案收敛的速度有多快?