样条曲线
在 3D 游戏开发和角色动画中,如何让一个物体、摄像机,或者角色的肢体摆脱生硬的直线运动,划出一条丝滑、自然的弧线?
答案就是:样条曲线,Spline。
什么是样条曲线?
简单来说,样条曲线(Spline)是由多项式分段定义出来的曲线。
所谓多项式,形式上并不神秘:就是一个变量使用不同的指数进行计算,然后把结果加总。比如一个典型的三次多项式:
这里的
当
在图形学中,最常登场的样条曲线大致有三类:
- B-样条(B-splines)
- 贝塞尔曲线(Bezier)
- 埃尔米特样条(Hermite splines)
它们的底层都是多项式,只是控制方式不同。
贝塞尔曲线很适合做美术编辑和路径设计,因为它的控制点非常直观,也可以用 De Casteljau 算法通过几何方式构造。B-样条适合做更平滑、更稳定的多段曲线。本文的主角是工业动画系统里非常常见的立方埃尔米特样条(Cubic Hermite Spline)。
为什么叫 Cubic?因为曲线公式里
埃尔米特样条
一段立方埃尔米特样条由 4 个参数决定:
- 起点
v_0 - 终点
v_1 - 起点切线
m_0 - 终点切线
m_1
可以把它想象成这样:
v_0 和v_1 决定曲线从哪里来、到哪里去。m_0 决定曲线离开起点时的方向和速度。m_1 决定曲线到达终点时的方向和速度。
立方埃尔米特样条的公式是:
为了看得更清楚,我们把四个多项式单独命名:
于是公式可以写成:
这四个函数就叫 Hermite 基函数。
这些基函数到底在干什么?它们不是随便凑出来的,而是为了同时满足 4 个约束:
也就是说:
- 曲线必须经过起点。
- 曲线必须经过终点。
- 曲线在起点的导数必须等于起点切线。
- 曲线在终点的导数必须等于终点切线。
C1 连续
立方埃尔米特样条很容易做到 C1 连续。
设第一段 Hermite 曲线为:
第二段 Hermite 曲线为:
两段曲线共享:连接点
可视化
为了更方便观察,我们用 Python 实现同样的逻辑,并使用 Matplotlib 画 3D 曲线。
- def hermite(v0, m0, v1, m1, t):
- """Cubic Hermite interpolation, matching the C++ GLM formula."""
- # Hermite 曲线使用 4 个输入控制形状:
- # v0: 起点位置,v1: 终点位置,m0: 起点切线,m1: 终点切线。
- # t 是插值进度,范围通常是 [0, 1]。
- t2 = t * t
- t3 = t2 * t
- # 四个三次 Hermite 基函数。
- # h00/h01 负责让曲线经过端点 v0/v1;
- # h10/h11 负责让端点导数等于切线 m0/m1。
- h00 = 2.0 * t3 - 3.0 * t2 + 1.0
- h10 = t3 - 2.0 * t2 + t
- h01 = -2.0 * t3 + 3.0 * t2
- h11 = t3 - t2
- return h00 * v0 + h10 * m0 + h01 * v1 + h11 * m1