第4章 B样条曲线:从基函数到分类
B样条曲线,说白了就是贝塞尔曲线的“升级版”。
我刚入行时,用贝塞尔曲线做自由曲面,最头疼的就是——动一个控制点,整条曲线都跟着抖。后来换了B样条,才真正体会到什么叫“局部控制”。这一章,我就带你把这个核心工具吃透。
4.1 B样条基函数的定义
B样条基函数,是整条曲线的“砖瓦”。它不像伯恩斯坦多项式那样全局作用,而是只在局部区间有值。
定义上,我们通常用Cox-de Boor递推公式:
给定节点向量 U = {u₀, u₁, ..., uₘ},p次B样条基函数定义为:
当 p = 0 时:
Nᵢ,₀(u) = 1, 若 uᵢ ≤ u < uᵢ₊₁
Nᵢ,₀(u) = 0, 其他
当 p > 0 时:
Nᵢ,ₚ(u) = (u - uᵢ)/(uᵢ₊ₚ - uᵢ) · Nᵢ,ₚ₋₁(u)
+ (uᵢ₊ₚ₊₁ - u)/(uᵢ₊ₚ₊₁ - uᵢ₊₁) · Nᵢ₊₁,ₚ₋₁(u)
这个公式看着有点吓人,但拆开看其实就两件事:线性插值 + 递归。0次基函数就是个“开关”——在某个区间内是1,外面是0。高次基函数,就是低次基函数的加权组合。
4.2 B样条基函数的性质
这些性质决定了B样条为什么好用。我挑几个最关键的说说:
- 局部支撑性: Nᵢ,ₚ(u) 只在区间 [uᵢ, uᵢ₊ₚ₊₁) 上非零。动一个控制点,只影响附近几段曲线。
- 非负性: 所有基函数值 ≥ 0。这保证了曲线的“凸包性”。
- 单位分解性: 在任意参数 u 处,所有基函数之和为1。曲线不会“飘”。
- 连续性: p次B样条基函数在节点处具有 Cᵖ⁻¹ 连续性。节点重复度越高,连续性越低。
避坑指南: 我曾经在项目里用B样条做叶片曲面,节点重复度没处理好,结果曲面在接缝处出现了“鼓包”。后来查了半天才发现——重复度每增加1,连续性就降1阶。这个细节,做工程时一定要算清楚。
4.3 节点向量
节点向量,是B样条曲线的“骨架”。它决定了基函数在参数轴上的分布方式。
一个典型的节点向量长这样:
U = {0, 0, 0, 0.2, 0.4, 0.6, 0.8, 1, 1, 1}
节点向量的长度 m、控制点个数 n+1、曲线次数 p,三者满足:
m = n + p + 2
节点向量里的值,可以重复。重复的节点叫“重节点”。重节点会让基函数在该处的连续性降低。
| 节点类型 | 说明 | 典型用途 |
|---|---|---|
| 简单节点 | 节点值只出现一次 | 标准B样条曲线 |
| 重节点 | 节点值出现多次 | 控制曲线端点或尖角 |
| 两端重节点 | 首尾节点重复 p+1 次 | 使曲线通过首尾控制点 |
4.4 B样条曲线的分类
根据节点向量的分布方式,B样条曲线分为三类。我按实际工程中的使用频率来排:
4.4.1 均匀B样条
节点向量中的节点等距分布。比如:
U = {0, 0.2, 0.4, 0.6, 0.8, 1.0}
均匀B样条的基函数形状都一样,只是平移。计算简单,但曲线在两端的行为不太好控制。
我的看法: 均匀B样条适合做快速原型验证。但正式产品里,我很少用它——因为两端不通过控制点,做曲面拼接时很麻烦。
4.4.2 准均匀B样条
节点向量首尾重复 p+1 次,内部节点均匀分布。比如三次准均匀B样条:
U = {0, 0, 0, 0, 0.25, 0.5, 0.75, 1, 1, 1, 1}
这是工程中最常用的类型。它既保留了均匀B样条内部的光滑性,又让曲线通过首尾控制点。
4.4.3 非均匀B样条
节点向量中的节点分布不均匀,且可能有重节点。这是最通用的形式。
非均匀B样条可以做到:
- 在某个区域集中更多节点,让曲线在该处更“灵活”
- 通过重节点制造尖角或C⁰连续
- 精确控制曲线形状的局部细节
说白了,均匀和准均匀都是非均匀的特例。但非均匀的节点向量需要手动设计,对工程师的经验要求更高。
4.5 三类B样条的对比
| 类型 | 节点分布 | 端点行为 | 计算复杂度 | 工程适用性 |
|---|---|---|---|---|
| 均匀 | 等距 | 不通过端点 | 低 | 快速原型 |
| 准均匀 | 首尾重复+内部等距 | 通过端点 | 中 | 产品级曲面 |
| 非均匀 | 任意分布 | 可控制 | 高 | 复杂形状 |
4.6 知识结构图
下面这张图,把B样条曲线的核心知识点串起来了:
4.7 一个简单的代码示例
下面这个Python代码,演示了如何计算三次均匀B样条基函数的值:
import numpy as np
def basis_function(i, p, u, knots):
"""计算第i个p次B样条基函数在u处的值"""
if p == 0:
return 1.0 if knots[i] <= u < knots[i+1] else 0.0
else:
# 第一项
denom1 = knots[i+p] - knots[i]
term1 = 0.0
if denom1 != 0:
term1 = (u - knots[i]) / denom1 * basis_function(i, p-1, u, knots)
# 第二项
denom2 = knots[i+p+1] - knots[i+1]
term2 = 0.0
if denom2 != 0:
term2 = (knots[i+p+1] - u) / denom2 * basis_function(i+1, p-1, u, knots)
return term1 + term2
# 均匀节点向量,三次B样条
knots = [0, 0.2, 0.4, 0.6, 0.8, 1.0]
u = 0.5
for i in range(3): # 计算前3个基函数
val = basis_function(i, 3, u, knots)
print(f"N_{i},3({u}) = {val:.4f}")
工程建议: 实际项目中,别自己手写基函数计算。直接用现成的库,比如 SciPy 的 BSpline 或者 NURBS-Python。但理解递推公式的原理,能帮你调试节点向量时快速定位问题。
嗯,这一章的内容就到这儿。B样条基函数、节点向量、三种分类——这三样东西,是后面学NURBS和自由曲面的基础。你先把它们理清楚,后面就顺了。