在20世纪50年代和60年代在雷诺公司工作期间,皮埃尔·贝塞尔(Pierre Bézier)开发了用于设计汽车车身的软件系统。他的程序利用了Paul de Casteljau之前开发的数学方程组,后者曾为竞争对手雪铁龙汽车制造商[BE72,DC63]工作。de Casteljau方程仅使用几个标量参数描述曲线,同时使用一种高明的的递归算法,称为“de Casteljau算法”,就可以生成任意精度的曲线。现在它们分别被称为“贝塞尔曲线”和“贝塞尔曲面”,这些方法通常用于高效地对各种曲面3D物体进行建模。
参考这个
二次贝塞尔曲线由一组参数方程定义,方程组中使用3个控制点指定特定的曲线的形状,每个控制点都是2D空间中的一个点。考虑图11.1中所示的一组3个点[p0,p1,p2]。
通过引入参数t,我们可以构建一个用来定义曲线的参数方程组。表示从一个控制点到另一控制点间线段距离的分数。对于在线段上的点,t的值在[0…1]的范围内。图11.2显示了一个这样的值:t=0.75,分别应用于连接
p0-p1,p1-p2的线段。通过t在两条原始线段上定义了两个新点p01(t)和p12(t)。我们对连接两个新点p01(t)和p12(t)
的线段重复该过程,产生点P(t),其中沿线段p01(t)和p12(t)在t=0.75得到点P(t)。P(t)是最终得到的曲线上的点,因此用大写字母P表示。
针对各种t值收集大量的点P(t),则会产生一条曲线,如图11.3所示。采样的t的参数值越多,生成的点P(t)越多,得到的曲线则越平滑。
现在可以导出二次贝塞尔曲线的分析定义。首先,我们注意到连接两个点pa和pb的线段pa-pb上的任意点p可以用参数t表示如下:
使用该等式,我们解出点p01和p12(分别在p0-p1,p1-p2上的点)如下:
同理,在这两点所连接的线段上的点可以表示为:
分解并重新合并各项可得:
其中
因此,我们通过控制点的加权和解出曲线上的任意点。加权函数B通常被称为“混合函数”(尽管名称“B”实际上源自Sergei Bernstein [BE16],他首先描述了这个多项式族)。请注意,混合函数的形式都是二次的,这就是为什么得到的曲线称为二次贝塞尔曲线。
我们现在将曲线模型扩展到4个控制点,就会得到一个三次贝塞尔曲线,如图11.4所示。与二次曲线相比,三次贝塞尔曲线能够定义的形状更加丰富,而二次曲线仅限于定义凹形。
同二次曲线时的情形,我们可以推导出三次贝塞尔曲线的解析定义:
曲线上的点则是:
使用p12-23和p01-12的定义替换等式中的项,再合并得:
渲染贝塞尔曲线时,可以使用许多不同的技术。其中一种方法是,使用固定的增量,在0.0~1.0范围内,迭代增加得出t的后继值。
例如,当增量为0.1时,我们可以使用t值为0.0、0.1、0.2、0.3等的循环。对于t的每个值,计算贝塞尔曲线上的对应点,并绘制连接连续点的一系列线段,如图11.5中的算法所述。
另一种方法是使用de Casteljau算法递归地将曲线对半细分,其中,在每个递归步骤t=1/2。图11.6展示了左侧曲线细分后的新三次控制点(q0,q1,q2,q3),以绿色显示(见彩插)。该算法由de Casteljau提出(完整推导见[AS14])。
算法见图11.7。该算法重复将曲线段细分为两半的过程,直到每个曲线段足够直,进一步的细分不会产生实际的好处。在极限情况下(随着生成的控制点越来越靠近),曲线段本身实际上与第一个控制点和最后一个控制点(
q0和q3)之间的线段相同。因此,可以通过比较从第一控制点到最后一个控制点的距离与连接4个控制点的3条线段的长度之和来确定曲线段是否“足够直”:
当D1-D2小于一个足够小的阈值时,进一步的细分就没有意义了。
de Casteljau算法有一个有趣的特性,它可以在不使用之前描述的混合函数的情况下,生成曲线上所有的点。同时请注意,p(1/2)处的中心点是“共享”的,即它既是左细分中最右的控制点,也是右细分中最左的控制点。它可以使用t=1/2处的混合函数或使用由de Casteljau导出的公式(q2+r1)/2来计算。
另请注意,图11.7中所示的subdivide()
函数假定传入的参数 p、q和r是“引用”参数,因此,图11.7上方列出的drawBezierCurve
函数对于subdivide()
的调用,导致subdivide()
函数中的计算修改了调用中所传的实际参数。
贝塞尔曲线定义了曲线(在2D或3D空间中),而贝塞尔曲面定义了3D空间中的曲面。将我们在曲线中看到的概念扩展到曲面,需要将参数方程组中的参数个数从一个扩展到两个。对于贝塞尔曲线,我们将参数称为t。对于贝塞尔曲面,我们将参数称为u和v。曲线由点P(t)组成,而曲面将由点 P(u,v)组成,如图11.8所示。
对于二次贝塞尔曲面,每个轴u和v上有3个控制点,总共9个控制点。图11.9(见彩插)使用蓝色展示了一组共9个控制点(通常称为控制点“网格”)的示例,以及相应的曲面(红色)。
网格中的9个控制点标记为pij,其中i和j分别代表u和v方向上的索引。每组3个相邻控制点(例如(p00,p01,p02))会定义一条贝塞尔曲线。然后将表面上的点P(u,v)定义为两个混合函数的和,一个在u方向,一个在v方向。则用于构建贝塞尔曲面的两个混合函数的形式遵循先前为贝塞尔曲线给出的方法:
接下来生成构成贝塞尔曲面的点P(u,v)。对于每个控制点pij将其与第i个混合函数在u处的值相乘,再与第j个混合函数在v处的值相乘。最后将所有控制点的结果求和,生成贝塞尔曲面上的点 P(u,v)
组成贝塞尔曲面的生成点集有时会称为补丁。术语“补丁”有时 会让人感到困惑,我们稍后在研究曲面细分着色器时会看到(对于实 际实现贝塞尔曲面非常有用)。因为通常控制点组成的网格才称为 “补丁”。
从二次曲面到三次曲面需要使用更大的网格——4×4而非3×3。 图11.10(见彩插)显示了16控制点网格(蓝色)和相应曲面(红色)的示例。
同上,我们可以通过组合三次贝塞尔曲线的相关混合函数来推导表面上的点P(u,v)的公式:
其中:
渲染贝塞尔曲面也可以通过递归细分[AS14]完成,方法是交替地将曲面沿每个维度分成两半,如图11.11所示。每个细分产生4个新的控制点网格,每个网格包含16个点,这些点定义了曲面的一个象限。
当渲染贝塞尔曲线时,我们在曲线“足够直”时停止细分。而对于贝塞尔曲面,我们在曲面“足够平坦”时停止递归。一种实现方法是,确保子象限控制网格上所有递归生成的点,距由该网格的4个角点中的3个定义的平面的距离,都小于一个允许的范围。点(x,y,z)与平面(A,B,C,D)之间的距离d为:
如果小于某个足够小的阈值,则我们停止细分过程,并简单地使d用子象限网格的4个角的控制点来绘制两个三角形。
对于贝塞尔曲线,OpenGL管线的细分阶段为基于图11.5中的迭代算法渲染贝塞尔曲面提供了一种有吸引力的替代方法。其策略是让曲面细分生成一个大的顶点网格,然后使用混合函数将这些顶点重新定位到贝塞尔曲面上,由三次贝塞尔控制点指定。我们在第12章中实现了这一点。
本章重点介绍参数贝塞尔曲线和曲面的数学基础。我们推迟了在OpenGL中呈现其中任何一个的实现,因为实现它们需要适当的曲面细分着色器知识作为载体,我们将在下一章中进行介绍。我们还跳过了一些推导过程,例如递归细分算法。
在3D图形中,使用贝塞尔曲线建模对象有许多优点。首先,理论上,这些物体可以任意缩放,并且仍然保持光滑的表面而不“像素化”。其次,许多由复杂曲线组成的物体可以使用贝塞尔控制点集合进行更有效的存储,而不是存储数千个顶点。
除计算机图形和汽车外,贝塞尔曲线还有许多实际应用。在桥梁设计中也可以找到它们的身影,例如耶路撒冷的Chords Bridge[CB16]。类似的技术也用于构建TrueType字体,因此可以将其缩放到任意大小,或者将视角任意拉近观看,而字体边缘始终保持平滑。