很多时候,图形学就是将数学翻译成代码。数学越简洁,代码就越简洁。本章将回顾各种数学工具,涵盖高中和大学,但不做严格处理,而是强调几何直觉。线性代数的内容到第六章再做介绍。
集合与映射
映射,或函数,是数学和编程的基础。数学中的映射,接收一个某种类型的参数,然后将它映为某种类型的对象。程序中的类型在数学中用集合表示。
集合、映射、逆映射、区间(intervals)、对数(logarithms)等相关内容可参考高中数学教材。
二次(quadratic)方程求解
二次方程:
Ax2+Bx+C=0
求根公式如下:
x=2A−B±B2−4AC
程序中可先计算判别式(discriminant),再根据判别式的正负决定是否需要开方。求根公式还有另一种更健壮的形式:
x=−B∓B2−4AC2C
使用该公式,可根据一次项系数的正负计算出其中一个根,而无需考虑二次项系数是否为 0。
三角学(trigonometry)
角(angles)
来自同一源点的两条射线构成一个角,角的大小由射线在单位圆上所截弧长决定。这种度量方式称为弧度(radians),它可以和角度(degrees)互相转换。由于两条射线将平面分割成了两个部分,因此需要约定哪一部分才是所定义的角。一种常见的约定是,取较小的那段弧长作为角的大小,射线的声明顺序决定角的正负。这种约定下,所有的角都在区间 [−π,π] 上。
三角函数
函数 atan2(s,c) 在图形学中很常用,它的返回值是坐标为 (c,s) 的点的辐角主值。
逆时针为角的正方向。
三角函数相关内容可参考中学数学教材。
立体角(solid angles)和球面三角学(spherical trigonometry)
三角形可以定义在曲面上,如球面。球面三角形(spherical triangles)以大圆(great circles)弧为边,对这种三角形的研究称为球面三角学。
对于计算机图形学,更重要的是立体角(solid angles)。物体相对于原点所占据的视场范围就是它的立体角。立体角在单位球面上所围成的面积就是它的球面度(steradians)。
向量(vector)
向量是一种具有大小和方向的量。有时在程序中,向量需要用数字表示,但即便如此,它们也应该被当做对象来使用,只有底层的向量操作才需要知道相关的数值表示。
向量可以存储偏移(offset)——也称位移(displacement),也可以存储位置(location/position/point)。位置可以用另一个位置加上一个位移来表示。通常约定一个原点,所有位置可以存储为相对于原点的偏移。位置不是向量,也不是偏移,它只是可以用向量或偏移来存储;偏移也不是位置。
常用的向量间乘法有点乘(dot product)、叉乘(cross product)。
- 点乘常用于计算两向量夹角余弦,也可用于计算一个向量对另一个向量的投影;
- 叉乘通常仅用于三维向量。
向量叉乘通常不满足结合律 a×(b×c)=(a×b)×c
向量相关的数学知识可参考高中数学教材。
正交基(orthonormal bases)与坐标系(coordinate frames)
管理坐标系是图形程序的核心任务之一,其中的关键在于管理正交基。图形学中定义了两种坐标系:规范坐标系(canonical coordinate system)和参照标架(frame of reference)——也称坐标标架(coordinate frame)。
这里将 canonical 翻译为规范,原因是书中曾提到 “The word 'canonical' crops up again——it means something arbitrarily chosen for convenience.”。
规范坐标系最特殊,它的原点和正交基不予存储,程序中用它做底层表示。由于全局模型通常存储在规范坐标系中,因此规范坐标系也称为全局坐标系(global coordinate system)或世界坐标系(world coordinate system)。
除规范坐标系以外,其它的坐标系均称为参照标架,它们的原点和正交基必须显式存储。坐标系可以固连到某一物体上,这样的参照标架也称为局部坐标系(local coordinate system)。
规范坐标系、参照标架和局部坐标系就是理论力学中的定系、动系和固系。
规范坐标系和参照标架中的坐标可以利用以下两点进行转换:
- 两坐标系的正交基之间的关系
- 坐标就是矢量对正交基的投影
构造正交基
根据单个向量构造正交基
根据单个向量 a 构造一组正交基 u、v、w,使得 w 与 a 平行。这种情形下,我们并不关心 u、v 的方向,一个典型的应用场景是表面着色,在这一场景中,我们只关注法向量。下面是构造正交基的具体步骤:
- 首先,构造单位向量 w=a/∥a∥;
- 然后,选取与 w 不共线的向量 t,构造与 w 垂直的单位向量 u=t×w/∥t×w∥;
- 构造 v=w×u。
上面第二步中,如果向量 t 与 w 近平行,则会出现精度问题。解决该问题的一个简单方法是,将 w 中绝对值最小的分量改为 1,其余分量保持不变从而构成向量 t。易知,这种方法所得到的 ∥t×w∥ 取值范围:[2(3−1)/3,33/4]≈[0.345,1.299]。
根据两个向量构造正交基
有时候,我们关心正交基 u、v 的取向,如:为相机(camera)建立一个标架。确定一个标架的常用方法是提供两个向量 a、b。这种情况下,上面处理单向量的方法也是可取的:
⎩⎨⎧wuv=∥a∥a=∥b×w∥b×w=w×u
上述公式适用于一般情形,只要 a、b 不平行。对于 a、b 不垂直的情形,所得出的 v 是所有与 a 垂直的向量中离 b 最近的。
摆正(squaring up)正交基
有时候,计算机的舍入误差(rounding error)或正交基存储的精度过低可能会引起一些问题。为了解决这些问题,可以使用上面的方法把原始向量基中的 w、v 分别当作 a、b 重新生成一组新的正交基。然而,这种正交化方法是不对称的,w 优先级高于 v,v 优先级高于 u。也可以使用 SVD(Singular Value Decomposition,奇异值分解)对原始向量基正交化。
积分(integration)
图形学中大多数积分都无法解析求解,因此需要数值积分。真正需要的不是手算积分,而是明白积分的含义以便于数值求解。积分可能有多种形式,但它们都有:
- 被积函数
- 积分区域
这两部分通常作为参数传入 integrate 函数中:
float area = integrate(cos(), unit-sphere)
图形学中的积分常用于计算平均(average)以及加权平均(weighted average)。有时也会涉及对立体角积分,比如,物体表面的颜色通常与入射光颜色(incident colors)的加权平均有关。
float averageElevation = integrate(elevation(), country) / integrate(1, country)
float shade = integrate(cos() * f(), unit-hemisphere) / integrate(cos(), unit-hemisphere)
积分相关内容可参考高等数学教材。
密度函数(density function)
图形学中经常出现密度函数。密度是一种强度量(intensive quantity),而非广延量(extensive quantity)。一般意义上,密度是单位度量下的广延量。图形学中使用的度量不仅限于空间,还可以是时间、立体角等。也就是说,在图形学中,物理量的速率——单位时间内的物理量,如功率,也可以作为密度。
热力学统计物理中,广延量是与物质数量成正比的物理量,这里的广延量和强度量可以看作是相关概念的推广。
密度函数具有以下两点特征:
- 密度是某种比值,即单位 Y 内 X 的量
- 密度函数是一种返回值为密度的函数
密度函数的用处:
- 比较不同位置的浓度
- 计算一个区域内的总量
图形学中很多积分都涉及密度函数。
曲线(curves)与曲面(surfaces)
曲线和曲面在图形学中占据核心地位。
下面将 2D 空间中的曲线简称为 2D 曲线,3D 曲面、3D 曲线等术语的含义类似。
梯度(gradient)
以二元函数为例,高度场 f(x,y) 的梯度定义如下:
∇f=(∂x∂f,∂y∂f)
梯度方向是增速最快的方向,梯度大小是该方向的增速。梯度在隐式曲线 f(x,y)=0 上的取值,就是曲线上该点的法向量(normal vector),与曲线上该点的切向量(tangent vector)垂直,方向指向 f>0 的区域。
梯度、偏导的具体内容可参考高等数学教材。
隐式方程(implicit equation)
隐式方程是一种常用的描述曲线的方式。
2D 曲线
2D 曲线的隐式方程具有如下形式:
f(x,y)=0
该方程可以看作高度场(height field)z=f(x,y) 在 xoy 平面上的截线。之所以称为“隐式”,是因为曲线上的点没有在方程中直接表示出来,必须解方程才能得到。
2D 曲线把整个平面分成三个区域:f>0、f<0、f=0。因此,通过计算 f 可以知道点和曲线间的方位关系,如内外、上下等。这一关系通过高度场可以更加明显地看出来。
曲线的隐式方程也可以用向量来表示,比如圆的方程:
∥p−c∥−r=0
一个方程的向量形式通常蕴含着更加丰富的几何直觉。而面向向量(vector-oriented)的方程在代码实现上也更少出错,比如:涉及 x、y、z 复制粘贴的错误直接消失了。
2D 直线的斜截式(slope-intercept):
一般式:
Ax+By+C=0
给定直线上两点 (x0,y0)、(x1,y1),直线的一般式:
(y0−y1)x+(x1−x0)y+x0y1−x1y0=0
点 (a,b) 到直线 f(x,y)=Ax+By+C=0 的有符号距离(signed distance)为:
A2+B2f(a,b)
2D 隐式二次曲线(implicit quadric curves)方程如下:
Ax2+Bxy+Cy2+Dx+Ey+F=0
上式可以描述的曲线有:椭圆(ellipses)、双曲线(hyperbolas)、抛物线(parabolas)、圆(circles)、直线(lines)。
其它形式的直线方程、二次曲线相关内容可参考高中数学教材。
3D 曲面与 3D 曲线
3D 曲面的隐式方程及其向量形式:
f(p)=f(x,y,z)=0
其中,p=(x,y,z)。计算 f(x,y,z) 可以知道点 p 是否在曲面上,或者根据符号判断在曲面的哪一侧。
曲面上任一点法向量可以通过隐函数的梯度来得到:
n=∇f(p)=(∂x∂f,∂y∂f,∂z∂f)
该向量指向 f(p)>0 的区域,该区域和曲面之间的方位关系(如内外、上下等)需结合具体情况来分析。
3D 平面的点法式方程如下:
(p−a)⋅n=0
其中,n 是法向量,a 是平面上给定的一个点。
过 a、b、c 三点的平面隐式方程如下:
(p−a)⋅((b−a)×(c−a))=0
上式的几何意义是,由 p−a、b−a、c−a 所构成的平行六面体(parallelepiped)体积为 0,或者说三个向量共面(coplanar)。该式也等价于下面的行列式:
x−xaxb−xaxc−xay−yayb−yayc−yaz−zazb−zazc−za=0
其中,p=(x,y,z)。若存在封装完好的关于向量的 determinant(a,b,c) 函数(或命名为 volume),使用上式也可以避免笔误。唯独使用 x、y、z 的展开式编程是最不可取的。数学的简洁性决定了代码的简洁性。
与 2D 情形类似,关于 x、y、z 的二次多项式定义了 3D 二次曲面。如球面:
f(p)=(p−c)2−r2=0
形式 f(p)=0 的方程能产生的 3D 曲线都只是退化曲面(degenerate surfaces),实践中很少使用。3D 曲线一般表示为两个 3D 曲面的交线:
{f(p)=0g(p)=0
参数方程
2D 曲线
2D 参数曲线(parametric curves)方程如下:
[xy]=[g(t)h(t)]
其中,t 是决定整条曲线的参数,(x,y) 是曲线上的点。参数方程的向量形式如下:
p=f(t)
其中,f:R↦R2 是向量值函数。对 f(t) 求导可以得到曲线的切向量。讨论参数曲线时,通常会把 t 看作时间,把曲线看作轨迹。
过点 p0=(x0,y0)、p1=(x1,y1) 的 2D 直线参数方程:
[xy]=[x0+t(x1−x0)y0+t(y1−y0)]
等价的向量形式为:
p(t)=p0+t(p1−p0)
其中,p=(x,y)。参数 t 的取值决定了点 p 的位置:
t<00<t<1t>1⇔p0 以外⇔p0 与 p1 之间⇔p1 以外
参数直线也可以由一个点 o 和一个向量 d 来描述:
p(t)=o+td
当 d 的长度为 1 时,直线是弧长参数化的(arc-length parameterized)。此时,t 就是直线上的精确距离。
圆心 (xc,yc)、半径为 r 的圆的参数方程:
[xy]=[xc+rcosϕyc+rsinϕ]
为了保证点的参数唯一,可限制 ϕ 的取值范围。
齐轴椭圆(axis-aligned ellipse)参数方程:
[xy]=[xc+acosϕyc+bsinϕ]
3D 曲面和 3D 曲线
曲面需要两个参数来确定所占据的区域,3D 曲面的参数方程及其向量形式如下:
xyz=p(u,v)=f(u,v)g(u,v)h(u,v)
曲面就是函数 p:R2↦R3 的取值范围。例如:球面可以经度(longitude)和纬度(latitude)作为参数,也可以球坐标系中的天顶角 θ 和方位角 ϕ 作为参数。
曲线 q(t)=p(t,v0) 称为等参数曲线(isoparametric curve)。它的导数 pu 给出了曲面的一个切向量。同理,可以构造出曲面的法向量:
n=pu×pv
一般约定该法向量指向曲面外部。
3D 曲线参数方程及其向量形式:
xyz=p(t)=f(t)g(t)h(t)
通过限制定义域可以控制曲线的起点和终点。
3D 直线参数方程的向量形式:
p=o+td
其中,o 是直线上的点,d 是直线方向向量。将参数范围限制为 [ta,tb]、[0,∞),可以得到线段(line segment)、射线(ray, half-line)。点 a、b 间的线段可以表示为:
p(t)=a+t(b−a),t∈[0,1]
线性插值(linear interpolation)
线性插值是图形学中最常用的数学操作。线性插值就是用一个变量 t∈[0,1],使数据 A 线性过渡到数据 B,中间值为:
I(t)=(1−t)A+tB
由于 I(0)=A、I(1)=B,所以称为插值;又因为权重项 1−t、t 是关于 t 的线性多项式,所以称为线性插值。
三角形(triangles)
2D、3D 三角形是许多图形程序中的基础模型。图形学中通常希望把一个属性(如颜色)分配到三角形顶点(vertices)上,然后平滑地插值到三角形内部。而重心坐标(barycentric coordinates)让这种插值变得更加简单直接。理解 2D 三角形有助于在 2D 屏幕上绘制图形。
2D 三角形
由点 a=(xa,ya)、b=(xb,yb)、c=(xc,yc) 所构成的 2D 三角形的有向面积(signed area):
Area=21xb−xayb−yaxc−xayc−ya
其中,a、b、c 的顺序决定了 Area 的正负:
- 逆时针,Area>0
- 顺时针,Area<0
任选 △abc 的一个顶点以及相关联的两条边建立一个标架,再根据线性代数的基础知识可以知道,平面上任一点 p 均可以唯一分解为:
p=αa+βb+γc,α+β+γ=1
其中,(α,β,γ) 就是点 p 的重心坐标。
重心坐标具有以下性质:
- 点 p 在 △abc 内部 ⇔ 0<α,β,γ<1
- 点 p 在 △abc 边上(不包括顶点)⇔ α、β、γ 中有一个为 0,另外两个位于开区间 (0,1) 上
- 点 p 在 △abc 顶点上 ⇔ α、β、γ 中有两个为 0
重心坐标 (α,β,γ) 可作为系数混合其它属性,如颜色。
如何计算重心坐标
计算重心坐标可以使用代数方法,也可以使用几何方法。代数方法可以是求解线性方程组:
[b−ac−a][βγ]=[p−a]
一种几何方法是,先考虑以下事实:
- fac(x,y)、β(x,y) 均正比于点 (x,y) 到直线 fac(x,y)=0 的有符号距离
- β(xb,yb)=1
其中,fac(x,y)=0 是过点 a、c 的直线。将这两条事实结合起来可以得到:
β(x,y)=fac(xb,yb)fac(x,y)
根据过两点直线方程的一般式可得:
⎩⎨⎧γβα=(ya−yb)xc+(xb−xa)yc+xayb−xbya(ya−yb)x+(xb−xa)y+xayb−xbya=(ya−yc)xb+(xc−xa)yb+xayc−xcya(ya−yc)x+(xc−xa)y+xayc−xcya=1−β−γ
还有一种几何方法是,通过计算面积来得到重心坐标:
⎩⎨⎧αβγ=Aa/A=Ab/A=Ac/A
其中,A=Aa+Ab+Ac 是 △abc 的面积;Aa、Ab、Ac 分别是 △bcp、△cap、△abp 的有向面积,它们(如 Ab)的正负取决于相应的顶点(如 b)和点 p 是否在对边(如 ac)的同侧。
3D 三角形
重心坐标可以直接推广到 3D 情形。此时,点 p 位于 △abc 所确定的平面上:
p=(1−β−γ)a+βb+γc
由于 3D 隐式直线使用联立方程组表示,相比较而言,通过面积来计算重心坐标更加方便:
⎩⎨⎧αβγ=∥n∥2n⋅na=∥n∥2n⋅nb=∥n∥2n⋅nc
其中,向量 n、na、nb、nc 分别用于表征 △abc、△bcp、△cap、△abp 的有向面积,面积的正负反映在向量方向上:
⎩⎨⎧n=(b−a)×(c−a)na=(c−b)×(p−b)nb=(a−c)×(p−c)nc=(b−a)×(p−a)
概率
原文中关于离散概率的说法 “discrete probability refers to when there is a finite number of random outcomes” 有问题。离散型随机变量的取值不一定只有有限多个,也可以是无穷多个,比如所有整数。
随机变量 X(random variable)、离散概率、连续型随机变量、期望 E(X)(expectation)、方差 V(X)(variance)、标准差 σ(X)(standard deviation)等内容可参考高中数学教材或大学概率论教材。
蒙特卡洛积分(Monte Carlo Integration)
图形学中实现积分的最常用的方法就是蒙特卡洛。蒙特卡洛方法通常把一个积分表示为一个平均值与常数的乘积:
integrate(f(), domain) = average(f(), domain) * integrate(1, domain)
根据大数定理,通过选取一系列服从 domain 上均匀分布的随机数 v,并对 f(v) 求平均可以得到 average(f(), domain) 的近似值。而生成随机数 v 的最简单的方法就是舍选法(rejection method):先在简单区域中生成随机数,然后舍弃那些不在目标区域的,留下的就是所需要的随机数。
重要性采样(importance sampling)
如果随机变量 f(v) 涨落较大,那么最好集中在某些区域采样,并用权重来修正 f 的不均匀性。这种依照对积分贡献大小来决定采样权重的方法叫做重要性采样。
integrate = average_of_nonuniform_samples(f() / p(), domain)
蒙特卡洛重要性采样一般按以下步骤进行:
- 确定被积函数 f(x) 和积分区域;
- 找一个方法生成积分区域上满足概率密度函数(probability density function,简称为 PDF)p(x) 的随机数 x;
- 生成一系列随机数 xi,计算 f(xi)/p(xi) 的平均值。
任何概率密度函数 p(x) 都可以得到正确结果,只要保证 f(x) 非零的区域,p(x) 也非零。p(x) 的选取只影响蒙特卡洛积分的收敛速度。