美学贝塞尔曲线的霍比算法

霍比算法是一种将曲线拟合到平面上一系列点上的技术,使得该曲线依次经过所有点。生成的曲线看起来平滑,并倾向于形成愉悦、放松的形状。

以下是一个交互式示例——尝试拖动其中一些点。

霍比算法可用于创建简单但有效的用户输入系统,用于操作曲线。用户只需放置点,软件即可自动将曲线拟合到这些点上,形成一个合理平滑且不令人意外的形状。

霍比曲线与计算机图形学中另一种常见的曲线——贝塞尔曲线(Bézier curves)密切相关。事实上,霍比曲线就是贝塞尔曲线,而霍比算法只是选择一条经过给定点且具有平滑圆润形状的特定贝塞尔曲线的技术。本文剩余部分将讨论贝塞尔曲线与霍比曲线之间的关系,以及为何在某些应用中可能更倾向于使用霍比曲线。

本文互动示例中使用的Hobby算法的JavaScript实现在ISC许可证下开放。我已对其进行了详细注释,希望它能作为在其他语言中实现Hobby算法的参考。

曲线

贝塞尔曲线(发音:BEH-zee-ay)是一种在计算机图形学中广泛使用的曲线类型。

贝塞尔曲线由一系列控制点 P0 至 Pn 定义。点 P0 和 Pn 构成曲线的端点。其他控制点影响曲线的形状,但不一定位于曲线上。值 n 称为曲线的阶数。最常用的贝塞尔曲线是二次曲线(阶数 2)和三次曲线(阶数 3)。

需注意,阶数为n的贝塞尔曲线包含n+1个控制点;例如,三次贝塞尔曲线由四个点定义。

贝塞尔曲线是参数曲线,即其形状由一个称为t的参数的函数定义。通过在区间[0, 1]内代入t的值,即可得到曲线上的一个点。

定义贝塞尔曲线形状的函数可以被视为在相邻控制点之间进行重复的线性插值。我们首先使用参数t在每对相邻控制点之间进行线性插值,以找到位于它们之间的中间点。然后我们将这些点作为新的控制点,并再次在每对相邻点之间进行线性插值。每次操作都会减少一个点。经过多次迭代后,最终只剩下一个点。该点位于曲线上。

这种构造贝塞尔曲线的方法称为德卡斯特尔约算法。虽然它易于理解,但计算效率不高。更好的方法是将曲线转换为伯恩斯坦多项式形式,然后进行评估。

通过让t从0到1变化,我们可以扫出曲线的形状。下图是一个使用上述技术扫出的三次贝塞尔曲线的示例。

如需更详细的贝塞尔曲线介绍,请参阅Freya Holmér的精彩视频

样条曲线

样条曲线是一系列相连的曲线。贝塞尔样条曲线是由贝塞尔曲线组成的样条曲线。

在下面的示例中,样条中的每条曲线都是三次贝塞尔曲线。样条通过一组称为结点的锚点连接。每个结点是某一贝塞尔曲线段的末端和另一段的起点。在每对结点之间有两个控制点,用于影响该段的形状。通常将这些中间控制点表示为“手柄”,并用线段将其与相邻结点连接。您可能在使用Inkscape、Illustrator或Photoshop中的钢笔工具时见过此类界面。

样条曲线可以是开放的或闭合的(P0 == Pn),但本文中的示例均为开放样条曲线。

您可能注意到我们还添加了一个额外约束:在每个结点周围,相邻的控制点是对称的。这确保了样条曲线的平滑性。更具体地说,它确保了样条曲线是C1连续的,即其一阶导数连续(即没有尖锐的拐角)。你可以创建违反此约束的样条曲线(按住Shift键拖动控制点即可尝试)。但本文剩余部分将主要关注符合某种平滑性定义的曲线。

贝塞尔样条作为输入系统的局限性

许多软件使用贝塞尔样条作为输入系统,以便用户操作曲线。虽然这提供了强大的表达能力,但使用起来也颇具挑战性。特别是,为了生成一条令人满意的曲线而调整控制点,往往需要大量调整。

在某些情况下,为了使界面更加易用和快捷,牺牲一些灵活性可能是更明智的选择。实现这一点的一种方法是仅要求用户选择结点的位置,并根据某种倾向于产生美观曲线的度量标准,自动计算手柄的适当位置。

自然三次贝塞尔样条

选择一串结点对应的控制点位置的一种方法是要求生成的样条的二阶导数连续。这意味着如果一个粒子以恒定速度沿样条运动,它不会经历任何突然的加速度变化。这被称为C2连续性。一个C2连续的三次贝塞尔样条被称为自然三次样条。

事实上,对于任何一组结点,都存在唯一一条符合这些结点且C2连续的贝塞尔样条曲线,而确定该样条曲线所需的控制点位置相对简单。1 以下是一个示例,其中自然三次样条曲线会自动计算以拟合给定的一组结点。

对于像这样的开放曲线,我们需要为每个端点指定额外的约束条件以确保唯一解。常见的选择是要求端点的曲率为零。

生成的样条在数学上是光滑的,对于许多应用来说这非常有用。但我对这些曲线感兴趣主要是出于美学考虑,而自然三次样条的外观并不完全符合我的期望。在许多配置中,样条显得扭曲,存在曲率极高的局部区域。在我看来,这看起来不自然,因为如果将一根弹性杆强行穿过这些结点,它会采取一种曲率变化较小的更放松的形状。

似乎通过要求曲率连续,我们也在样条曲线上产生了较大的曲率变异性。放松这一约束是否能得到更令人愉悦的曲线?

Hobby算法

Hobby算法2是一种替代技术,用于根据一组结点选择控制点位置,以生成贝塞尔样条曲线。与上述自然三次样条不同,霍比算法不要求真实曲率的连续性,而是生成具有模拟曲率连续性(真实曲率的一阶近似)的样条。

在此示例中,您可以观察到霍比样条与自然三次样条之间的差异。

与上述自然三次样条类似,开放的霍比样条需要两个额外约束(每个端点一个)以获得唯一解。

这些参数称为ω0和ω1,决定了每个端点的旋度。当旋度接近零时,样条在端点附近趋于直线。当旋度接近一时,它趋于圆弧。

在此示例中,omega滑块同时控制ω0和ω1,但理论上它们可以独立设置。

Hobby样条的连续性

与自然三次样条不同,Hobby样条并非C2连续。事实上,它们甚至不是C1连续。如果你在上述示例中启用“显示控制点”,你会发现它们在结点处不对称,而这是C1连续性所要求的。但每个结点处的控制点始终直接相对放置,这赋予曲线一个与C1连续性密切相关的属性,称为G1连续性。

要理解C1连续性和G1连续性的区别,想象沿曲线移动一个粒子,通过以恒定速率改变输入参数t。为了使曲线具有C1连续性,粒子的速度向量在移动过程中必须连续变化。但对于G1连续性,只需速度向量的方向连续即可;向量的长度允许突然变化。G1曲线看起来与C1曲线同样平滑;只有当你使用t驱动沿曲线移动时才能察觉差异。

因此,Hobby 曲线看起来仍然平滑,尽管从数学上讲,它们在对 t 的第一导数上不一定是连续的。与自然三次样条曲线相比,它们的曲率变化要小得多,这使得它们具有愉悦的圆润形状。

Hobby 样条曲线的其他属性

与自然三次贝塞尔样条曲线类似,Hobby 样条曲线生成的曲线会经过所有输入点。这使得样条在用户界面中创建曲线时非常直观。霍比算法可以非常高效地计算(线性时间),这意味着用户界面可以在用户拖动结点时,每帧轻松重新计算曲线。而且霍比曲线“编译”为普通的贝塞尔曲线,这些曲线可以高效渲染,并在图形库中广泛支持。

使用Hobby样条作为用户输入机制的主要缺点是,它们不如无约束贝塞尔曲线灵活。用户无法创建带有锐角的曲线,也无法有意沿曲线长度方向改变曲率。Hobby曲线也不稳定:移动一个结点可能会导致生成的曲线“突然”变成完全不同的形状,而自然三次样条曲线不会出现这种情况。移动一个结点会影响整个曲线(尽管效果在结点附近最为明显)。相比之下,在无约束贝塞尔曲线中,移动一个结点仅影响该结点相邻的曲线段。

结论

总体而言,霍比曲线比无约束贝塞尔曲线灵活性更低,但能以更少 effort 创建美观的形状。由于霍比曲线本质上只是手柄位置自动设置的贝塞尔曲线,因此保留了贝塞尔曲线的所有优势:易于程序化处理、渲染高效且广泛兼容。

如果你对在自己的项目中实现 Hobby 的算法感兴趣,可以查看我用于创建本文中交互式可视化的 源代码。该代码在 ISC 许可证条款下免费使用,并且注释非常详细,以便你需要将其移植到其他语言时使用。


  1. 维基百科对计算自然三次样条函数(通过一组结点)的算法有详细描述。需注意,该链接文章使用“样条函数”(spline)一词,而本文中我一直使用“曲线”(curve)来指代相同概念。
  2. 霍比,约翰·D.,“光滑且易于计算的插值样条”,《离散与计算几何》,1986年, 第1卷