第四节:均匀B样条曲线——最朴素的B样条
好,咱们今天聊聊均匀B样条曲线。说实话,这是B样条家族里最“老实”的一个成员。为什么这么说?因为它的节点向量排布特别规整,规整到有点“强迫症”的感觉。
我记得刚入行那会儿,第一次接触B样条就是均匀B样条。当时觉得这东西真简单,每个节点区间长度都一样,计算起来也省事。后来做多了才发现,简单归简单,但坑也不少。咱们今天就把它的底裤扒干净。
4.1 均匀节点向量的定义
先说说什么是均匀节点向量。说白了,就是节点在参数轴上等距分布。
假设我们有 n+1 个控制点,B样条的阶数是 p(次数是 p-1),那么节点向量的长度就是 n+p+2。均匀节点向量的形式长这样:
U = {0, 1, 2, ..., n+p+1}
或者更一般地:
U = {u₀, u₁, u₂, ..., u_{n+p+1}}
其中相邻节点的差是常数:
Δu = u_{i+1} - u_i = 常数
我习惯把第一个节点设为0,最后一个节点设为 n+p+1,这样每个区间长度就是1。当然你也可以归一化到[0,1]区间,但那样计算时反而多了一步除法,我个人不太推荐。
4.2 均匀B样条曲线的构造
构造均匀B样条曲线,核心就是基函数的计算。因为节点是均匀的,基函数可以写成一种“平移不变”的形式。
对于 p 阶均匀B样条,第 i 个基函数 N_{i,p}(u) 其实只依赖于 u - i 这个差值。换句话说,所有基函数形状都一样,只是平移了一下。
咱们看个具体的例子。三次均匀B样条(p=4阶,次数=3)的基函数长这样:
N_{0,4}(u) = (1/6) * u³ , 0 ≤ u < 1
N_{1,4}(u) = (1/6) * (-3u³ + 3u² + 3u + 1) , 0 ≤ u < 1
N_{2,4}(u) = (1/6) * (3u³ - 6u² + 4) , 0 ≤ u < 1
N_{3,4}(u) = (1/6) * (-u³ + 3u² - 3u + 1) , 0 ≤ u < 1
嗯,这里要注意,上面的公式是在每个节点区间[0,1)上定义的。实际计算时,你需要把参数 u 映射到对应的区间。
曲线上的点就是控制点和基函数的线性组合:
C(u) = Σ_{i=0}^{n} N_{i,p}(u) * P_i
其中 P_i 是第 i 个控制点。
4.3 均匀B样条曲线的特点
均匀B样条曲线有几个鲜明的特点,有好有坏,咱们掰开揉碎了说。
4.3.1 优点
- 计算简单高效:基函数可以写成封闭形式,不需要递归计算。你想想看,非均匀B样条每次都要用Cox-de Boor公式递归,而均匀B样条直接套公式就行。
- 矩阵形式优美:每个节点区间内的曲线段可以用一个固定的系数矩阵表示。比如三次均匀B样条,每个区间的曲线段可以写成:
C(u) = [u³ u² u 1] * M * [P_i, P_{i+1}, P_{i+2}, P_{i+3}]^T
其中M是固定的4x4矩阵:
M = (1/6) * [ -1 3 -3 1
3 -6 3 0
-3 0 3 0
1 4 1 0 ]
这个矩阵是固定的!不管你有多少个控制点,矩阵M都不变。这在硬件实现时特别友好。
- 曲线形状可预测:因为节点均匀,曲线在参数空间里是“公平”的,不会出现某些区域特别密、某些区域特别疏的情况。
4.3.2 缺点
- 无法局部加密:这是最大的痛点。如果你想让曲线在某一段更“听话”,比如经过某个特定的点,你没法只调整那一段的节点。你必须增加控制点,而增加控制点会影响整条曲线。
- 端点不插值:均匀B样条默认不经过第一个和最后一个控制点。除非你使用clamped(夹紧)技巧,把首尾节点的重复度设为p。
4.3.3 适用场景
| 场景 | 推荐程度 | 说明 |
|---|---|---|
| 实时轨迹规划 | ★★★★★ | 计算量小,适合嵌入式实时系统 |
| 离线路径平滑 | ★★★★ | 如果不需要局部调整,均匀B样条够用 |
| 需要局部修改 | ★★ | 建议改用非均匀B样条或NURBS |
| 精确插值 | ★★ | 需要配合端点条件或改用插值样条 |
4.4 知识体系总览
下面这张图把均匀B样条的核心知识点串起来了,你可以对照着看:
这张图把均匀B样条的三个核心模块串起来了。从左到右,从定义到构造再到特点,逻辑很清晰。你可以看到,均匀节点向量是基础,它决定了构造方式,而构造方式又决定了曲线的优缺点。
最后说一句,均匀B样条虽然简单,但千万别小看它。我在很多工业项目中都用它做轨迹平滑,效果相当不错。关键是你要知道它的边界在哪里——什么时候该用,什么时候该换别的工具。
公众号:蓝海资料掘金营,微信deep3321