本章由wzy翻译,感谢wzy对本章的用心翻译。

翻译人:wzy
翻译人联系方式:838168242@qq.com
代为发布人与审核人:StubbornHuang

2 五花八门的数学

许多图形学只是将数学直接转化为代码。数学越清晰,生成的代码就越清晰;我们这本书的主要内容是使用适合的数学来完成这份工作。我们这一章,会回顾高中和大学中学习的各种各样的数学工具,而且,本章被设计出来的目的不是作为教程,而是作为参考。这可能看起来是一个乱炖(东北菜,什么都有的炖菜)式的话题,事实上也确实如此;每个主题的选择都是因为它在“标准”数学课程中可能有点不太寻常,是因为它在图形学中具有中心重要性的地位,或者是因为它通常不被从几何角度来看待。除了对书中使用的符号进行回顾外,本章还强调了标准本科课程中有时会被忽略的一些要点,例如三角形上的重心坐标。本章无意对材料进行严格处理;相反,我们更强调直观感受和几何理解。对线性代数的讨论,我们会推迟到第五章,也就是在讨论变换矩阵之前。同时我们鼓励读者浏览本章,以熟悉本章所涵盖的主题,并根据需要进行查阅。本章末尾的练习可能有助于确定哪些主题需要复习。

2.1 集合和映射

映射,也称为函数,是数学和编程的基础。与程序中的函数一样,数学中的映射采用某一种类型的参数,并将其映射(返回)到特定类型的对象。在程序中,我们会谈到“类型”;在数学中,我们会讨论集合。当我们有一个对象是某个集合的成员时,我们使用∈ 来表示,例如:

a∈S

可以理解为“a是集合S中的一个成员”。给定任意两个集合A和B,我们可以通过取这两个集合的笛卡尔积来创建第三个集合,表示为A×B。该集合A×B由所有可能的有序对(a,b)组成,其中a∈ A和b∈ B。作为缩写,我们使用符号A^2表示A×A。我们可以扩展笛卡尔积,从三个集合中创建一组包括所有可能的有序三元组,以此类推,从任意多个集合中创建任意长有序元组。

​ 平常值得关注的集合包括:

R---实数集
R^+---非负实数集(包括零)
R^2---实二维平面上的有序对
R^n---n维笛卡尔空间中的点集
Z---整数集
S^2---单位球体上的三维点集(R^3中的点)

请注意,尽管S^2由嵌入三维空间的点组成,但它们位于可以用两个变量参数表示的曲面上,因此可以将其视为二维集。映射符号使用箭头和冒号,例如:

f : R → Z,

这可以理解为“有一个名为f的函数,它将实数作为输入,并将其映射为整数。”在这里,箭头前面的集合称为函数的域,右侧的集合称为目标。计算机程序员可能更习惯使用以下表达:“有一个名为f的函数,它有一个实数参数,并返回一个整数。”换句话说,上述集合表示的方式等同于通用的编程表示法:

integer f(real) ← equivalent → f : R → Z.

因此,冒号-箭头符号可以看作是一种编程语法。就这么简单。

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

2.1.1 逆映射

如果我们有一个函数f:A→ B,可能存在一个反函数f^{−1}:B→ 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)。一组骑手和马之间的双射表示每个人都骑一匹马,并且每匹马都被骑着。这两个函数是骑手(马)和马(骑手)。他们是彼此的反函数。非双射的函数是没有逆函数的(图2.2)。双射的一个例子是f:R→ R、 f(x)=x^3。反函数是f^{−1}(x)=\sqrt[3]{x},这个例子表明,标准符号可能有点笨拙,因为x在ff^{-1}中都用作虚拟变量。有时使用不同的虚拟变量会更直观,y=f(x)和x=f^{−1}(y)。这将产生更直观的y=x3x=\sqrt[3]y。一个没有逆函数的例子是sqr:R→ R, 其中sqr(x)=x^2。这有两个原因:首先x^2=(−x)^2,其次域中没有成员可以映射到目标的负部分。请注意,如果我们将定义域和值域限制为R^+,就可以定义一个逆。这时,\sqrt{x}才是一个有效的逆。

2.1.2 区间

我们通常希望指定一个函数来处理数值受限的实数。其中一个约束方式是指定区间。区间的一个例子是介于0和1之间的实数,不包括0或1。我们表示为(0,1)。因为它不包括其端点,所以这被称为开放区间。相应的闭合区间(包含其端点)用方括号表示:[0,1]。这种表示法可以是混合着用,即[0,1)包括0,但不包括1。当写入区间[a,b]时,我们其实是默认假设a≤ b, 图2.3显示了表示间隔的三种常用方法。区间的笛卡尔积经常被用到。例如,要表示点x是在三维的单位立方体中,我们记作x∈ [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)。

2.1.3 对数

虽然现在不像计算器出现之前那样流行,但是在有指数项的方程中对数还是很有用的。根据定义,每个对数都有一个底a。x的“以a为底的对数”写为\log_{a} x,并定义为“a必须升高到这个指数,才能得到x”,即:

y = \log_{a} x ⇔ 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 = log_a b log_b x.

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

\ln x \equiv \log_e x.

请注意“\equiv” 符号可理解为”在定义上是等效“。与\pi一样,特殊的数字e出现在大量的情景中。在许多领域中使用除e之外的特定基数进行运算时,在其表示中省略了基数,即\log x。例如,天文学家经常使用基数10,理论计算机科学家经常使用基数2。由于计算机图形学借鉴了许多领域的技术,我们将避免这种缩写。

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

\frac{d}{dx}\log_ax=\frac{1}{x\ln a}\\
\frac{d}{dx}a^x=a^x\ln a

上述常数乘数仅适用于a=e

2.2 求解二次方程组

二次方程的形式如下:

Ax^2 + Bx + C = 0

其中x是实未知量,ABC是已知常数。如果你将其想成是一个y=Ax^2+Bx+C构成的二维图,那么解就是与y=0相交时x的值。因为y=Ax^2+Bx+C是一条抛物线,根据抛物线是否错过、擦过或撞击x轴,将有零个、一个或两个实解(图2.5)。

​ 为了用分析的方法求解二次方程,我们首先将等式除以A:

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

然后我们“配方”:

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

将常数部分移到右侧并取平方根,则:

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

两侧同时减去B/(2A)并用分母2A整合,得到熟悉的形式:

x=\frac{-B\pm \sqrt{B^2-4AC}}{2A}

这里的“±”符号表示有两种解,一种是加号,另一种是减号。从而,3±1等于“2或4”。请注意,决定实际解的数量的条件是:

D\equiv B^2-4AC

这叫做二次方程的判别式。

例如,2x^2+6x+4=0的根是x=−1x=−2,方程x^2+x+1没有实解。这些方程的判别式为D=4D=−3,因此我们能够预计出解的数量。在程序中,通常最好先计算D,如果D为负,则返回无根,而不取平方根。

2.3 三角学

在图形学中,我们在许多情况下都会使用基本的三角函数。通常情况下,它并不太花哨,记住基本定义通常会有所帮助。

2.3.1 角度

虽然我们觉得角度的存在是理所当然的,但我们应该回到它们的定义上来,这样我们就可以把角度的概念推广到球体上。两条半直线(源自原点的无限光线)或方向之间形成一个角度,必须使用某种约定来决定在它们之间创建角度,这里有两种可能得方法,如图2.6所示。角度由其在单位圆上切割的弧段长度定义。一个常见的约定是使用较小的弧长,角度的符号则由指定两条射线的顺序确定。使用该约定,所有角度都在范围[−π, π]内。

​ 这些角度中的每一个都是由两个方向“切割”的单位圆弧的长度。因为单位圆的周长是2π,所以两个可能的角度之和为2π。这些弧长的单位是弧度。另一种常用的单位是度,其中整个圆是360度。因此,一个弧度为π的角度是180度,通常记作180^。。度和弧度之间的转换是:

\begin{array}{c}
degrees = \frac{180}{\pi}radians\\
radians = \frac{\pi}{180}degrees
\end{array}

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,上式很容易得到。我们定义\phi的正弦和余弦,以及其他基于比率的三角表达式为:

\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\\

这些定义支持我们设置极坐标,极坐标中的点被表示为距原点的距离和相对于正x轴的有符号角度(图2.8)。注意,习惯上角度\phi∈ (−π、 π],且正角度是与正x轴成逆时针的方向。这种逆时针映射为正数的约定是任意的,但它在图形学中的许多情景中使用,因此值得提交到内存中。

​ 三角函数是周期函数,可以以任何角度作为参数。例如,\sin(A)=\sin(A+2π)。这意味着当与R域一起考虑时,函数是不可逆的。通过限制标准反函数的值域可以避免这个问题,并且几乎在所有现代数学库中都是以标准方式进行的(例如,(Plauger,1991))。定义域和值域如下:

asin:[-1,1]\rightarrow[-\pi/2,\pi/2]\\
acos:[-1,1]\rightarrow[0,\pi]\\
atan:R\rightarrow[-\pi/2,\pi/2]\\
atan2:R^2\rightarrow[-\pi,\pi]\tag{2.2}

最后一个函数atan2(s,c)通常非常有用。它采用与sina成比例的s值和按相同因子缩放cos A并返回A的c值。假定该因子为正。理解这一点的一种方法是,它返回极坐标系中二维笛卡尔点(s,c)的角度(图2.9)。

2.3.3 有用的恒等式

本节列出了各种有用的三角恒等式,无需推导。

\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 \\
\sin^2A+\cos^2A=1\\
\sec^2A-\tan^2A=1\\
\csc^2A-\cot^2A=1\\
\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}\\
\tan2A=\frac{2\tan A}{1-\tan^2A}
\sin^2{A/2}=(1-\cos A)/2\\
\cos^2{A/2}=(1+\cos A)/2
\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\\

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

\frac{\sin A}{a}=\frac{\sin B}{b}=\frac{\sin C}{c}\\
c^2=a^2+b^2-2ab\cos C\\
\frac{a+b}{a-b}=\frac{\tan\frac{A+B}{2}}{\tan\frac{A-B}{2}}

三角形的面积也可以根据以下边长计算:

triangle \ \ \ area=\frac{1}{4}\sqrt{(a+b+c)(-a+b+c)(a-b+c)(a+b-c)}

2.4 向量

向量描述长度和方向。它可用箭头表示。如果两个向量具有相同的长度和方向,即使我们认为它们位于不同的位置,它们也是相等的(图2.11)。尽可能多地,你应该把向量看作方向,而不是坐标或数字。在某些情况下,我们必须在程序中将向量表示为数字,但即使在代码中,它们也应该作为对象进行操作,只有低级向量操作才应该知道它们的数字表示(DeRose,1989)。向量将用粗体字符表示,例如||a||表示向量A的长度。单位向量是长度为一的任意向量。零向量是长度为零的向量。零向量的方向未定义。

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

2.4.1 向量的运算

与实数有关的大多数常用运算,对于向量来说也具有。两个向量相等的充要条件是它们具有相同的长度和方向。根据平行四边形规则添加两个向量。该规则规定,两个向量之和是通过将任一向量的尾部与另一个向量的头部相连接放置而得到的(图2.12)。和向量是由两个向量开始,按顺序,最终能组成“完整三角形”的向量。平行四边形是按任意顺序求和形成的。这强调了向量加法是可交换的:

a + b = b + a.

请注意,平行四边形规则只是形式化了我们关于位移的直觉。想象一下沿着一个向量走,从尾巴到头部,然后再沿着另一个向量走。净位移就是平行四边形对角线。还可以为向量添加一个负号:−a(图2.13)是与a长度相同但方向相反的向量。这也允许我们定义减法:

b-a\equiv -a+b

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

a+(b-a)=b

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

2.4.2 向量的笛卡尔坐标

一个二维向量可以写成两个不平行的非零向量的组合。这两个向量的这种特性称为线性独立性。两个线性独立的向量构成二维基,因此这些向量被称为基向量。例如,向量c可以表示为两个基向量a和b的组合(图2.15):

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

请注意,权重a_cb_c是唯一的。如果两个向量是正交的,即它们彼此成直角,这样的基尤其有用。如果它们还是单位向量,那就更有用了。如果我们假设已知两个这样的“特殊”向量x和y,那么我们可以使用它们来表示笛卡尔坐标系中的所有其他向量,其中每个向量表示为两个实数。例如,一个向量就可以被表示为:

a=x_a x+y_a y

其中x_ay_a是二维向量a的实数笛卡尔坐标(图2.16)。注意,这在概念上与方程(2.3)没有任何不同,只是在那里基向量不是正交向量。但笛卡尔坐标系有几个优点。例如,根据毕达哥拉斯定理,a的长度是:

||a||=\sqrt{x^2_a+y^2_a}

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

​ 习惯上,我们将a的坐标写为有序对(x_a,y_a)或列矩阵:

a=\begin{bmatrix}
x_a\\
y_a\end{bmatrix}

我们使用的形式取决于排版的便利性。我们还偶尔将向量写入行矩阵,我们将用a^T表示:

a^T=\begin{bmatrix}
x_a&
y_a\end{bmatrix}

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

2.4.3 点积

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

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

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

​ 点积也可以用来求一个向量到另一个向量的投影。这是以直角投影到向量b上的向量a的长度(图2.18):

a\rightarrow b=||a||\cos{\phi}=\frac{a\cdot b}{||b||} \tag{2.5}

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

a\cdot b = b\cdot a\\
a\cdot(b+c) = a\cdot b+a\cdot c\\
(ka)\cdot b =a\cdot(kb)=ka\cdot b \tag{2.6}

如果二维向量ab用笛卡尔坐标表示,我们可以利用x·x=y·y=1x·y=0来推导它们的点积:

a\cdot b=(x_a x+y_a y)\cdot(x_bx+y_by)\\
=x_ax_b(x\cdot x)+x_ay_b(x\cdot y)+x_by_a(y\cdot x)+y_ay_b(y\cdot y)\\
=x_ax_b+y_ay_b

三维向量也类似:

a\cdot b=x_ax_b+y_ay_b+z_az_b

2.4.4 叉乘

叉积a×b通常仅用于三维向量;广义叉积在章节注释中给出的参考文献中进行了讨论。叉积返回的是,与进行叉积的两个向量都垂直的3D向量。而所得到的向量的长度与sin\phi有关:

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

重点是||a×b||等于向量a和b形成的平行四边形的面积。此外,a×b垂直于ab(图2.19)。请注意,对于这样一个向量,只有两个可能的方向。根据定义,x、y和z轴方向上的向量由下式给出:

x = (1, 0, 0), \\y = (0, 1, 0),\\ z = (0, 0, 1),

我们设定了一个惯例,x×y必须在正负z方向上。这个选择有点武断,但标准的假设是:

z = x × y.

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

x × y = +z,\\ y × x = −z,\\ y × z = +x, \\z × y = −x, \\z × x = +y, \\x × z = −y.

由于sin\phi的性质,我们也知道向量叉积它自身,结果就是零向量,所以x×x=0。请注意,叉积是不可交换的,即x×y \neq y×x。细心的读者可能会注意到,上述讨论不允许我们清楚地描绘笛卡尔坐标轴之间的关系。更具体地说,如果我们把xy轴放在人行道上,x指向东方,y指向北方,那么z指向天空还是地面?通常的是将z轴指向天空。这被称为右手坐标系。这个名字来自于用右手手掌和手指“抓住”x并向y方向旋转的记忆方式。矢量z应该与拇指对齐。如图2.20所示。

​ 叉积拥有良好的性质:

a\times (b+c)=a\times b+a\times c\\
a\times(kb)=k(a\times b)

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

a\times b = -(b\times a)

在笛卡尔坐标系中,我们可以使用易于理解的展开来计算叉积:

a\times b =(x_a x+y_a y+z_az)\times(x_bx+y_by+z_bz)\\
=x_ax_bx\times x+x_ay_bx\times y+x_az_bx\times z\\
+y_ax_by\times x+y_ay_by\times y+y_az_by\times z\\
+z_ax_bz\times x+z_ay_bz\times y+z_az_bz\times z\\
=(y_az_b-z_ay_b)x+(z_ax_b-x_az_b)y+(x_ay_b-y_ax_b)z\tag{2.7}

所以,坐标形式就是:

a\times 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 正交基与坐标系

管理坐标系是几乎所有图形程序的核心任务之一;关键是管理正交基。任何两个二维向量uv的集合构成正交基,其前提是它们正交(成直角),且各自为单位长度。因此:

||u||=||v||=1

并且

u\cdot v = 0

在三维空间中,三个向量u,v,w构成一组正交基的前提是:

||u||=||v||=||w||=1

并且

u · v = v · w = w · u = 0.

这组正交基是右手系的:

w=u\times v

除此之外,是左手系的。

​ 请注意,笛卡尔标准正交基只是无限多个可能正交基中的一个。它的特殊之处在于,它及其隐含的原点位置可用于程序中的低级表示。因此,向量x、y和z永远不会显式存储,标准原点位置o也不会显式存储。全局模型通常存储在此标准坐标系中,因此通常称为全局坐标系。但是,如果我们想使用另一个坐标系,它包含原点p和正交基向量u、v和w,那么我们会显式地存储这些向量。这种系统称为参考系或坐标系。例如,在飞行模拟器中,我们可能希望保持一个坐标系,原点位于飞机机头,正交基保持与飞机对齐。我们将同时拥有主标准坐标系(图2.21)。与特定对象(如平面)关联的坐标系通常称为局部坐标系。

​ 在较低级别,局部坐标系存储在标准坐标中。例如,如果u有坐标(x_u,y_u,z_u)

u = x_ux + y_uy + z_uz.

位置隐式包含与标准原点的偏移:

p = o + x_px + y_py + z_pz,

其中(x_p, y_p, z_p)是p点的坐标。

​ 注意,如果我们存储一个关于u-v-w坐标系的向量a,我们存储一个三元组(u_a,v_a,w_a),我们可以将其几何解释为:

a = u_au + v_av + w_aw

要获得存储在u-v-w坐标系中的向量a的标准坐标,只需回忆一下uvw本身是以笛卡尔坐标存储的,因此表达式u_au+v_av+w_aw已经是笛卡尔坐标。为了获得存储在标准坐标系中的向量bu-v-w坐标,我们可以使用点积:

u_b = u · b; v_b = v · b; w_b = w · b.

这是因为我们知道对于一些u_bv_bw_b

u_bu + v_bv + w_bw = b,

点积分解了u_b坐标:

u · b = u_b(u · u) + v_b(u · v) + w_b(u · w) \\= u_b

之所以如此,这是因为uvw是正交的。

​ 第6.2.1节和第6.5节将讨论使用矩阵管理坐标系的变化。

2.4.6 从单个向量构造基

通常我们需要一个与给定向量对齐的正交基。也就是说,给定一个向量a,我们需要一组正交的uvw,使得w指向a的同一方向(Hughes&M¨oller,1999),但我们并不特别关心uv是什么。一个向量不足以唯一地确定答案;我们需要一个强大的程序,可以找到任何一个可能的基。

​ 这可以使用叉积来完成。首先使w成为a方向上的单位向量:

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

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

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

如果tw共线,分母将消失,如果它们几乎共线,结果的精度将很低。找到与w完全不同的向量的一个简单步骤是,从t等于w开始,并将t的最小幅值分量改为1。例如,如果w=(1/\sqrt{2},-1/\sqrt{2},0),那么t=(1/\sqrt{2},-1/\sqrt{2},1)。一旦w和u被确定了,完成这组基就很简单了:

v = w × u.

使用此形式的情形的一个示例是曲面投影,其中需要与曲面法线对齐的基,但围绕法线的旋转通常不重要。

2.4.7 从两个向量构造基

当基围绕给定向量旋转比较重要时,上一节中的程序也可用。一个常见的例子是为摄影机建立基:让一个向量与摄影机的观察方向对齐很重要,但摄影机围绕该向量的方向不是任意的,需要以某种方式指定。一旦固定了方向,就完全确定了基。

​ 完全指定坐标系的常用方法是通过提供两个向量a(指定w)和b(指定v)。如果已知两个向量垂直,则通过u=b×a构造第三个向量就很简单了。

​ 为了确保,即使输入向量不完全是正交的,生成的基也是正交的,建议使用与单向量过程类似的过程:

w=\frac{a}{||a||}\\
u=\frac{b\times w}{||b\times w||}\\
v=w\times u

事实上,当ab不垂直时,这个程序工作得也很好。在这种情况下,w将精确地沿a的方向构造,并且v被选择为垂直于w的所有向量中与b最近的向量。

​ 如果ab是共线的,此过程将不起作用。在这种情况下,b对于选择垂直于a的方向是没有帮助:因为它垂直于所有可能的方向。

​ 在指定摄像机位置的示例(第4.3节)中,我们希望构造一个坐标系,该坐标系的w与摄像机的观察方向平行,v应指向摄像机的顶部。为了使相机垂直定向,我们围绕视图方向建立基,使用垂直向上方向作为参考向量来确定相机围绕视图方向的朝向。将v设置为尽可能接近垂直,与“将相机保持垂直”的直观概念完全匹配。

2.4.8 Squaring Up a Basis

有时,您可能会在计算种发现一些问题,这些问题是由假定为正交的基引起的,但由于计算中的舍入误差,或由于基存储在文件中的精度较低,因此出现了错误。

​ 这时我们可以使用上一节的程序;简单地使用现有的w和v向量重新构造产生一个新的正交基,并且与旧基接近。

​ 这种方法适用于许多应用程序,但不是最好的。它确实产生精确的正交向量,并且对于几乎正交的原始基,结果不会偏离起始点太远。然而,它是不对称的:它“偏爱”w胜过v,偏爱v胜过u(其起始值被丢弃)。它选择一个接近原始基的基,但不能保证选择的是最接近的正交基。当这不能够满足需求时,可以使用SVD(第5.4.1节)计算正交基,该正交基保证最接近原始基。

2.5 曲线和曲面

曲线(尤其是曲面)的几何图形在图形中起着核心作用,这里我们将回顾二维和三维空间中曲线和曲面的基础知识。

2.5.1 二维隐式曲线

直观地说,曲线是一组无需提起笔,在一张纸上绘制的点。描述曲线的常用方法是使用隐式方程。二维隐式方程的形式为:

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)不能立即从方程中计算出来,而必须通过解方程来确定。因此,曲线上的点不是由方程显式生成的,而是隐含在方程中的某个地方。

​ 有趣的是,f确实包含所有(x,y)的值。我们可以将f视为一个地形,海平面为f=0(图2.22)。海岸线就是隐式曲线。f的值是高度。另外需要注意的是,曲线将空间划分为f>0、f<0f=0的区域。所以你可以通过计算f来确定一个点是否在曲线的“内部”。注意,f(x,y)=c是关于任意常数c的曲线,c=0仅仅是习惯。例如,如果f(x,y)=x^2+y^2−1,不同的c给出的是以坐标原点为中心的各种半径的圆(图2.23)。

​ 我们可以用向量来简化公式。如果我们有c=(x_c,y_c)p=(x,y),那么我们的圆心为c,半径为r的圆应满足:

(p − c) · (p − c) − r^2 = 0

这个方程,如果用代数展开,将得到方程(2.9),但如果通过几何来“理解”方程,就更容易看出这是一个圆的方程。它可以这么理解,“圆上的点p具有以下特性:当从c指向p的向量与自身点乘时,值为r^2。”因为一个向量与自身点乘时,结果就是它自身长度的平方,我们也可以将方程解读为“圆上的点p具有以下性质:从c指向p的向量具有平方长度r^2。”

​ 更好的是,我们观察到,向量长度的平方只是从c到p的距离的平方,这表明了等效形式:

||p-c||^2-r^2=0

当然,这也意味着

||p-c||-r=0

上述内容可以理解为“圆上的点p距离中心点c的距离为r”,这和其他圆的定义是一样的。这说明方程的向量形式通常比x和y的等效完全的笛卡尔形式更能体现几何性和直观性。因此,在可能的情况下,通常建议使用向量形式。此外,您可以在代码中支持向量类;使用向量形式时,代码会更清晰明了。面向向量的表达式在执行时也不太容易出错:一旦在你的代码中执行和调试向量类型,涉及x、y和z的剪切和粘贴错误就会消失。要适应这些方程中的向量需要一点时间,但一旦你掌握了窍门,回报会很大。

2.5.2 二维梯度

如果我们将函数f(x,y)视为高度=f(x,y)的高度场,则梯度向量所指的方向就是爬升最快的方向。梯度向量∇f(x,y)由下式给出:

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

隐式曲线f(x,y)=0上某一点处的梯度向量与该点处曲线的切线向量垂直。这种垂直的向量通常称为曲线的法向量。此外,由于梯度是指向爬升方向的,这就隐含了f(x,y)>0的区域的方向。在高度场中,偏导数和梯度的几何意义比通常更为好理解。假设在点(a,b)附近,f(x,y)是一个平面(图2.24)。现有一个特定的上坡和下坡方向。与该方向成直角的方向与平面水平。平面和f(x,y)=0平面之间的任何交点都将位于水平方向。因此,上坡/下坡方向将垂直于相交线f(x,y)=0。要了解偏导数为什么与此有关,我们需要将其几何意义直观的展现出来。回想一下,一维函数y=g(x)的常规导数是:

\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}

这是g曲线切线的斜率。

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

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

为什么关于x和y的偏导数是梯度向量的分量?同样,几何比代数更具有直观性。在图2.27中,我们看到向量a沿着f上固定的路径移动。请注意,这也是在一个足够小的尺寸下,表面高度(x,y)=f(x,y)可以被视为局部平面。从图中,我们可以看到向量a=(Δx,Δy)

​ 因为爬升方向垂直于向量a,我们知道相互垂直的两个向量的点积等于零:

(∇f) · a ≡ (x_∇, y_∇) · (x_a, y_a) = x_∇Δx + y_∇Δy = 0. \tag{2.11}

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

\triangle f=\frac{\partial f }{\partial x}\triangle x+\frac{\partial f }{\partial y}\triangle 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(\frac{\partial f}{\partial y},-\frac{\partial f}{\partial x}) \tag{2.12}

结合等式(2.11)和(2.12)得出:

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

其中k '是任意非零常数。根据定义,“上坡”意味着f的正变化,所以我们希望k '>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>0,法线将指向圆的外侧,如果A<0,法线将指向圆的内侧。这个从外向内的开关是应该的,因为正区域在圆内切换。就高度场视图而言,h=Ax^2+Ay^2− A, 圆是在零高度上。对于A>0,圆包围的区域会凹陷;对于A<0,圆包围的区域会凸起。随着A变得更负,凹凸的高度也随之增加,但h=0圆不会改变。爬升最快的方向不变,但坡度增加。梯度的长度反映了坡度的这种变化。所以直观地说,你可以认为梯度的方向是指向上坡的,它的大小是度量坡度的。

隐式二维线

我们所熟悉的,直线的“斜率截距”式是:

y = mx + b.\tag{2.13}

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

y − mx − b = 0.\tag{2.14}

这里m是“斜率”(上升与行走的比率),b是直线穿过y轴时的y值,通常称为y截距。这条线也将二维平面分割开来,但这里的“内部”和“外部”可能更直观地称为“上方”和“下方”。

​ 因为我们可以将一个隐式方程乘以任何常数,而不改变它为零的点,所以对于任意非零kkf(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):

Ax_0 + By_0 + C = 0,\\
Ax_1 + By_1 + C = 0.

不幸的是,我们有两个方程和三个未知数: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的形式写出。

​ 每当我们遇到这样讨厌的代数问题时,我们可以尝试着,让几何直观来指导我们解决这些问题。如第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)开始。这样的向量就是(A,B)=(y_0− y_1,x_1− x_0),根据与第2.5.2节相同的推理。这意味着通过(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}

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

distance = k\sqrt{A^2+B^2} \tag{2.18}

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

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

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

distance = \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}}+\frac{x_1-x_0}{\sqrt{(x_1-x_0)^2+(y_0-y_1)^2}}\\
=\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节)。第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)是椭圆的中心,ab是短半轴和长半轴(图2.32)。

2.5.3 三维隐式曲面

正如隐式方程可用于定义二维曲线一样,它们也可用于定义三维曲面。与二维中一样,隐式方程式隐式的定义了曲面上的一组点:

f(x, y, z)=0

当曲面上的任何点(x,y,z)作为参数,输入到f中,结果都会得到零。不在曲面上的点都会得到除零以外的数字。可以通过计算f来检查点是否位于曲面上,也可以通过查看f的符号来检查点位于曲面的哪一侧,但不能总是在曲面上显式构造点。使用向量表示法,我们将向量p=(x,y,z)的函数写成:

f(p)=0.

2.5.4 隐式曲面的法线

曲面法线(用于照明计算等)是垂直于曲面的向量。曲面上的每个点可能具有不同的法向量。与梯度在二维中为隐式曲线提供法线的方式相同,隐式曲面上点p处的曲面法线由隐式函数的梯度给出:

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

推导的方法与二维的情况相同:梯度指向f增长最快的方向,垂直于与曲面相切的所有方向,其中f保持不变。梯度向量指向f(p)>0的一侧,其中,在给定条件下,我们可以将其视为“进入”曲面或“离开”曲面。如果特定形式的f产生了向内的梯度,但我们需要向外的梯度,那么曲面−f(p)=0与曲面f(p)=0拥有相同大小的梯度,但方向相反,即,−∇f(p)=∇(−f(p))

2.5.5 隐式平面

作为一个例子,考虑一个无限的平面通过点a,拥有一个表面法向量n。描述该平面的隐式方程如下所示:

(p − a) · n = 0. \tag{2.21}

请注意,a和n是已知量。点p是满足方程的任意未知点。用几何术语来说,这个方程表示“从a到p的向量垂直于平面的法线。”如果p不在平面内,那么(p− a) 不会与n成直角(图2.33)。

​ 有时我们需要一个通过点a、b和c的平面的隐式方程。通过取平面中任意两个向量的叉积,可以找到该平面的法线。一个这样的交叉积是:

n = (b − a) × (c − a).

这样,我们就可以写出我们的隐式方程:

(p − a) · ((b − a) × (c − a)) = 0. \tag{2.22}

从几何角度理解,这个方程表达的意思是,由p− ab− a、 和c− a三个向量定义的平行六面体的体积为零,即它们是共面的。只有当p与a、b和c位于同一平面时,这才成立。行列式给出了完整的笛卡尔表示(第5.3节对此进行了更详细的讨论):

\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}

行列式可以展开(展开行列式的原理见第5.3节)成包含许多项的庞大形式。

​ 等式(2.22)和(2.23)是等效的,比较它们之间的区别是有指导意义的。方程(2.22)易于几何解释,并将产生高效的代码。此外,如果利用调试过的叉积和点积代码,则相对容易避免编译成错误代码的排版错误。方程(2.23)也很容易从几何角度解释,并且如果实现了高效的3×3行列式函数,则方程(2.23)也将是高效的。如果函数行列式(a,b,c)可用,那么它也很容易无错误的执行。如果您重命名行列式函数,其他人将特别容易阅读您的代码。所以方程(2.22)和(2.23)都能很好地映射到了代码中。将任一方程完全展开为x、y和z分量,都可能产生打字错误。这样的打字错误很可能会被编译,因此特别令人讨厌。这是例子表明,一个干净的数学产生干净的代码,臃肿的数学产生臃肿的代码。

三维二次曲面

正如拥有两个变量的二次多项式定义二维中的二次曲线一样,拥有三个变量x、y和z的二次多项式定义三维中的二次曲面。例如,球体可以写成:

f(p)=(p − c)^2 − r^2 = 0,

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

f(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(p)=0的形式创建隐式三维曲线。然而,所有这些曲线都只是退化曲面,在实践中很少有用。我们可以从两个隐式方程的交点来构造三维曲线:

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

例如,可以从两个隐式平面的交点形成三维线。通常,使用参数化曲线会更方便;我们将在下面几节讨论这些问题。

2.5.6 二维参数曲线

参数曲线由单个参数控制,该参数可视为沿着曲线连续移动的一种索引。它的形式如下:

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

这里(x,y)是曲线上的一个点,t是影响曲线的参数。对于给定的t,将由函数gh生成一些点。对于连续的gh,t的微小变化将产生xy的微小变化。因此,当t不断变化时,点在一条连续的曲线中被扫出。这是一个很好的特性,因为我们可以使用参数t显式地构造曲线上的点。平时我们可以写一条向量形式的参数曲线,

p = f(t)

其中f是向量值函数,f:R→ R^2。这样的向量函数可以生成非常干净的代码,因此应该尽可能使用它们。

​ 我们可以将曲线的位置视为时间的函数。这条曲线可以走到任何地方,可以自行循环和交叉。我们也可以认为曲线在每个点都有一个速度。例如,点p(t)在t=−2附近缓慢移动,并且在t=2和t=3之间快速移动。这种类型的“移动点”通常在讨论参数曲线时使用,即使曲线未描述移动点。

二维参数直线

通过点p_0=(x_0,y0)p_1=(x_1,y_1)的二维参数线可以写成:

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

由于x和y的公式具有类似的结构,我们可以使用p=(x,y)的向量形式(图2.34):

p(t) = p_0 + t(p_1 − p_0).

您可以将其以几何形式理解为:“从点p_0开始,向p_1走一段距离,距离由参数t确定。”这种形式的一个很好的特点是p(0)=p_0p(1)=p_1。由于点随t线性变化,在p_0p_1之间的t值度量点与点之间距离的比例关系。t<0的点位于p_0的“远”侧,t>1的点位于p_1的“远”侧。

​ 参数线也可以仅由点o和向量d描述:

p(t) = o + t(d)

当向量d具有单位长度时,直线就是弧长被参数化的。这意味着t可以精确测量直线上的距离。任何参数曲线都可以参数化弧长,这显然是一种非常方便的形式,但并非所有曲线都可以解析转换。

二维参数圆

以(x_c,y_c)为圆心,r为半径的圆,的参数形式是:

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

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

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

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

2.5.7 三维参数曲线

三维参数曲线和二维参数曲线类似:

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

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

x = cost,\\
y = sin t,\\
z = t.

同二维曲线一样,如果你想要控制曲线从哪开始,在哪结束,就要确定函数f,g,h的定义域D R。向量形式如下:

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

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

三维参数直线

三维参数直线可以作为二维参数直线的直接延伸写出,例如,

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

这样写很麻烦,无法很好地转换为代码变量,因此我们将以向量形式编写:

p = o + td,

其中,对于本例,o和d由下式给出

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

请注意,这与二维的情况非常相似。可以想象为,这条线穿过o并与d平行。给定t的任何值,就可以得到直线上的点p(t)。例如,在t=2时,p(t)=(2,1,3)+2(7,2,−5) = (16, 5, −7). 这与二维是相同的(图2.30)。

​ 与二维的情况一样,线段可以由三维参数线和区间t∈ [t_a,t_b]来描述。两点a和b之间的线段由p(t)=a+t(b− a) 与区间t∈ [0, 1]描述.。这里p(0)=ap(1)=bp(0.5)=(a+b)/2是a和b之间的中点。

​ 射线是具有半开放间隔的三维参数化直线,通常为[0,\infty). 从现在开始,我们将所有直线、线段和射线称为“光线”,这么做很不严谨,但符合常见用法,使讨论更简单。

2.5.8 三维参数曲面

参数化方法可用于定义三维空间中的曲面,与定义曲线的方式大致相同,只是有两个参数用于处理曲面的二维区域。这些曲面具有以下形式:

x = f(u, v),\\ y = g(u, v), \\z = h(u, v).

向量形式:

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

Example.

例如,地球表面的一个点可以通过假装来描述,以证明地球是完全球形的。两个参数经度和纬度。如果我们将原点定义为地球的中心,并将r设为地球的半径,那么以原点为中心的球面坐标系(图2.35)就可以导出参数方程:

x=r\cos\phi\sin\theta\\
y=r\sin\phi\sin\theta\\
z=r\cos\theta \tag{2.24}

理想情况下,我们希望以向量形式编写,但对于这种特殊的参数形式来说是不可行的。

​ 对于给定的(x,y,z),我们希望能够找到对应的(\theta,\phi)。如果我们假设\phi∈ (−π、 π],使用等式(2.2)中的atan2函数很容易做到这一点:

\theta=a\cos({z/\sqrt{x^2+y^2+z^2}})\\
\phi=atan2(y,x) \tag{2.25}

​ 对于隐式曲面,函数f的导数给出了曲面法线。对于参数化曲面,p的导数还提供了有关曲面几何体的信息。

​ 考虑函数q(t)=p(t,v_0)。该函数定义了一条参数曲线,该曲线通过保持v固定在值v_0,同时改变u来获得。该曲线称为位于曲面中的等参曲线(有时简称“等参线”)。q的导数给出了一个与曲线相切的向量,由于曲线位于曲面中,向量q '也位于曲面中。因为它是通过改变p的一个参数得到的,所以向量q 'pu的偏导数,我们将标识为p_u。类似的,偏导数p_v给出了等参曲线在保持u不变时的切线,这是曲面的第二个切线向量。

​ 然后,因为p的导数在曲面上的任意点上给出两个切向量。可以通过取这些向量的叉积来找到曲面的法线:因为两者都与曲面相切,所以它们的叉积(垂直于两条切线)与曲面垂直。叉积的右手法则提供了一种确定哪一侧是曲面的前面(或者说是外侧)的方法;我们将使用向量:

n = p_u × p_v

指向曲面的外部。

2.5.9 曲线和曲面的总结

二维中的隐式曲线或三维中的隐式曲面,是由两个或三个变量的标量值函数f:R^2→ Rf:R^3→ R,而曲面是由函数为零的所有点组成:

S = \{p |f(p)=0.\}

二维或三维参数曲线由一个变量p的向量值函数定义,p:D⊂ R→ R^2p:D⊂ R→ R^3,当t在整个D上变化时,曲线被扫掠出来:

S = \{p(t)|(u, v) ∈ D \}.

​ 对于隐式曲线和曲面,法向量由f的导数(梯度)给出,切线向量(对于曲线来说)或向量(对于曲面来说)可通过构造基向量从法向量导出。

​ 对于参数曲线和曲面,p的导数给出切线向量(对于曲线)或向量(对于曲面),通过构造基向量,可以从切线导出法向量。

6 线性插值法

也许图形中最常见的数学运算就是线性插值。我们已经看到了一个示例,就是关于位置的线性插值,在二维和三维中构造线段,其中两点a和b与参数t关联以形成线p=(1− t) a+tb。之所以称为插值,是因为p正好在t=0和t=1处通过a和b。它是线性插值,因为加权项t1− tt的线性多项式。

​ 另一种常见的线性插值是在x轴上的一组位置之中:x_0,x_1……x_n,对于每个x_i,我们有一个相关的高度y_i。我们打算创建一个连续函数y=f(x),对这些位置进行插值,使f穿过每个数据点,即f(x_i)=y_i。对于线性插值,点(x_i,y_i)通过直线段连接。对这些线段使用参数化直线方程是很自然的。参数t只是x_ix_{i+ 1}之间的比例:

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

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

​ 上述两个示例具有线性插值的常见形式。当我们从数据项A移动到数据项B时,我们创建了一个从0到1的变量t。中间值就是函数(1− t) A+tB。注意等式(2.26)的形式为

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

2.7 三角形

三角形在二维和三维中,都是许多图形程序中的基本初始建模。通常,诸如颜色之类的信息会标记到三角形顶点上,并且这些信息会在整个三角形上进行插值。使这种插值变得清晰明了的坐标系称为重心坐标系;我们将从头开始开发这些产品。我们还将讨论二维三角形,在我们可以在二维屏幕上绘制它们的图片之前,必须先了解它们。

2.7.1 二维三角形

如果我们有一个由2D点a、b和c定义的2D三角形,我们可以首先找到它的面积:

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节。如果点a、b和c按逆时针顺序排列,则该面积将有一个正号,否则为负号。

​ 通常在图形中,我们希望在每个三角形顶点指定一个属性(如颜色),并在整个三角形上平滑插入该属性的值。有多种方法可以做到这一点,但最简单的是使用重心坐标。这是一种将重心坐标系视为非正交坐标系,如第2.4.2节所述。这种坐标系如图2.36所示,其中坐标原点为a,从a到b和从a到c的向量为基向量。有了这个原点和这些基向量,任何点p都可以写成:

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

值得注意的是,我们可以对方程(2.28)中的项进行重新排序,以得到:

p = (1 − β − γ)a + βb + γ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}

​ 重心坐标起初看起来像是一个抽象的、不直观的构造,但事实证明它们是强大而方便的。你可能会发现,在一个有两组平行街道,但这些街道不是直角的城市里,街道地址是如何工作的,这是很有用的。自然的系统本质上就是重心坐标,你会很快习惯它们。为平面上的所有点定义重心坐标。重心坐标的一个特别好的特征是,当满足以下条件时,点p位于由a、b和c构成的三角形内,

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

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

​ 给定一个点p,我们如何计算它的重心坐标?一种方法是将方程(2.28)写成一个含有未知量β和γ的线性系统,设α=1− β − γ. 并求解。那个线性系统如下:

\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)很简单,但直接计算出的几何解通常是富有成效的。重心坐标的一个几何特性是,它们是从它所代表的直线到三角形边的带正负号的缩放间隔,如图2.37中的β所示。回想第2.5.2节,为直线f(x,y)=0计算方程式f(x,y),返回从(x,y)到直线的带正负号的间隔。还记得,如果f(x,y)=0是特定直线的方程式,那么对于任何非零k,kf(x,y)=0也是如此。更改系数k将会缩放间隔并控制直线的哪一侧具有正符号间隔,哪一侧具有负符号间隔。我们希望k满足,kf(x,y)=β。因为k只有一个未知量,我们可以用一个约束来强制它,即在点b处时,β=1。因此,如果直线f_{ac}(x,y)=0穿过a和c之间,那么我们可以计算点(x,y)的β,如下所示:

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

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

​ 为了通过p_0p_1找到该线的“理想”形式,我们可以首先使用第2.5.2节的技术,通过顶点找到一些有效的隐式线。方程式(2.17)给出了:

f_{ab}(x, y) ≡ (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}

除法的存在可能会让我们担心,因为它引入了被零除的可能性,但对于面积不接近零的三角形,这是不可能发生的。α和β有类似的公式,但通常只需要一个:

\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

计算重心坐标的另一种方法是计算亚三角形的面积A_aA_bA_c,如图2.38所示。重心坐标遵从下面的规则:

α = A_a/A,\\
β = A_b/A,\\
γ = A_c/A,\tag{2.33}

其中A是三角形的面积。请注意,A=A_a+A_b+A_c,因此可以使用两个加法计算,而不是使用全面积公式。如果允许对三角形以外的点进行标记,则此规则仍然适用。其原因如图2.39所示。请注意,这些是有符号的面积,只要对A和子三角形A_aA_bA_c使用相同的有符号面积进行计算,就可以正确得出结果。

2.7.2 三维三角形

重心坐标的一个奇妙之处是,它们几乎可以完美地延伸到3D。如果我们假设点a、b和c是3D的,那么我们仍然可以使用该表达式:

p = (1 − β − γ)a + βb + γc

现在,当我们改变β和γ时,我们可以扫出一个平面。

​ 三角形的法向量可以通过取三角形平面内任意两个向量的叉积得到(图2.40)。最容易使用的是三角形三条边中的两条作为这些向量,例如,

n = (b − a) × (c − a). \tag{2.34}

请注意,该法向量遵循右手交积规则,但不一定是单位长度。

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

area = \frac{1}{2} ||(b − a) × (c − a)|| . \tag{2.35}

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

a\cdot b=||a|| \ ||b||\cos\phi

其中\phi是向量之间的夹角。如果a和b是平行的,那么cosφ=±1,通过这个可以测试,向量的指向是相同的,还是相反的。这与方程式(2.33)、(2.34)和(2.35)一起,得到了下列公式:

\alpha = \frac{n\cdot n_a}{||n||^2}\\
\beta = \frac{n\cdot n_b}{||n||^2}\\
\gamma = \frac{n\cdot n_c}{||n||^2}

式中,n是用顶点a、b和c,通过方程(2.34)计算得到的;n_a是用顶点b、c和p,经过方程(2.34)计算得到的,即:

n_a = (c − b) × (p − b),\\ n_b = (a − c) × (p − c), \\n_c = (b − a) × (p − a). \tag{2.36}

常见问题

  • 为什么没有向量乘法?

事实证明,向量的除法并没有“好”的类比。然而,可以通过详细研究这个问题来激发四元数(参见章节注释中引用的霍夫曼的书)。

  • 对于三条边以上的多边形,有没有像重心坐标那样清晰的东西?

不幸的是,没有。即使是凸四边形也要复杂得多。这就是为什么三角形在图形中是如此常见的几何图元。

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

没有。但是,两个三维平面的交点确定了一条三维的线,因此三维线可以由两个隐式三维方程联立来描述。