B样条
B样条曲线(B-spline curves)是一种基于样条函数(spline function)的分段多项式函数,广泛用于计算机图形学、数据拟合和数值分析中。B样条曲线有许多优点,如局部控制、平滑性和灵活性。
1. 基本概念
1.1 节点向量( Knot Vector )
节点向量是一个非递减序列 { t i } \{t_i\} {ti},其中 i i i从 0 0 0到 n + k + 1 n+k+1 n+k+1, n n n是控制点的数量, k k k是B样条曲线的阶数(degree)。节点向量决定了B样条基函数的定义域。
1.2 B样条基函数(Basis Functions)
B样条基函数 B i , k ( t ) B_{i,k}(t) Bi,k(t) 是分段多项式函数,它是定义B样条曲线的重要组成部分。基函数的定义是递归的:
- 零阶基函数(piecewise constant): B i , 0 ( t ) = { 1 t i ≤ t < t i + 1 0 otherwise B_{i,0}(t) = \begin{cases} 1 & t_i \leq t < t_{i+1} \\ 0 & \text{otherwise} \end{cases} Bi,0(t)={10ti≤t<ti+1otherwise
- 递归定义高阶基函数: B i , k ( t ) = t − t i t i + k − 1 − t i B i , k − 1 ( t ) + t i + k − t t i + k − t i + 1 B i + 1 , k − 1 ( t ) B_{i,k}(t) = \frac{t - t_i}{t_{i+k-1} - t_i} B_{i,k-1}(t) + \frac{t_{i+k} - t}{t_{i+k} - t_{i+1}} B_{i+1,k-1}(t) Bi,k(t)=ti+k−1−tit−tiBi,k−1(t)+ti+k−ti+1ti+k−tBi+1,k−1(t)
float B(int i, int k, float t, const std::vector<float>& knots) {
if (k == 1) {
return (knots[i] <= t && t < knots[i+1]) ? 1.0f : 0.0f;
} else {
float coeff1 = (t - knots[i]) / (knots[i + k - 1] - knots[i]);
float coeff2 = (knots[i + k] - t) / (knots[i + k] - knots[i + 1]);
return coeff1 * B(i, k - 1, t, knots) + coeff2 * B(i + 1, k - 1, t, knots);
}
}
2. B样条曲线的数学表达式
给定一个控制点序列 { P i } \{P_i\} {Pi} 和一个节点向量 { t i } \{t_i\} {ti},B样条曲线 P ( t ) P(t) P(t) 的表达式为:
P ( t ) = ∑ i = 0 n P i B i , k ( t ) t ∈ [ t k − 1 , t n + 1 ] P(t)=\sum_{i=0}^{n}P_iB_{i,k}(t) \ \ \ \ t\in[t_{k-1},t_{n+1}] P(t)=∑i=0nPiBi,k(t) t∈[tk−1,tn+1]
其中:
● P i P_i Pi是第 i i i个控制点
● B i , k ( t ) B_{i,k}(t) Bi,k(t)是阶数为 k k k的第 i i i个B样条基函数
3. B样条的类型
- 均匀B样条曲线:当节点沿参数轴均匀等距分布,即 u u ui+1- u u ui=常数>0时,表示均匀B样条函数。
- 准均匀B样条曲线:与均匀B样条曲线的差别在于两端节点具有重复度k,这样的节点矢量定义了准均匀的B样条基。
- 分段Bezier曲线:节点矢量中两端节点具有重复度k,所有内节点重复度尾k-1,这样的节点矢量定义了分段的Bernestein基。B样条曲线用分段Bezier曲线表示后,各曲线段就具有了相对独立性。
- 非均匀B样条曲线:当节点沿参数轴的分布不等距,即 u u ui+1- u u ui ≠ \neq =常数时,表示非均匀B样条函数
4. 绘制代码
void BSplineCurve::calculateKnots()
{
// 均匀B样条
// 控制点个数 n+1
// 阶数k
int n = controlPoints.size() - 1;
int k = degree;
int m = n + k + 1;
knots.resize(m + 1);
// 有m+1个矢量结点
for (int i = 0; i <= m; ++i)
{
knots[i] = float(i) / (m - degree + 1);
}
}
float BSplineCurve::B(int i, int k, float t)
{
if (k == 1)
return (knots[i] <= t && t < knots[i + 1]) ? 1.0f : 0.0f;
else
{
float coeff1 = (t - knots[i]) / (knots[i + k - 1] - knots[i]);
float coeff2 = (knots[i + k] - t) / (knots[i + k] - knots[i + 1]);
return coeff1 * B(i, k - 1, t) + coeff2 * B(i + 1, k - 1, t);
}
}
void BSplineCurve::calculateBSplinePoints()
{
int n = controlPoints.size() - 1;
vertices.clear();
for (int j = 0; j <= 64; ++j)
{
float t = (float)j / 64.0f * (knots[n + 1] - knots[degree]) + knots[degree];
Vertex v;
for (int i = 0; i <= n; ++i)
{
float basis = B(i, degree + 1, t);
v.position += basis * controlPoints[i];
}
vertices.push_back(v);
}
}