深入理解B样条曲线(上)

7,360 阅读5分钟

计算机中绘制曲线,通过贝塞尔曲线已经满足了我们大部分需求,但是其存在某些缺点,比如移动某一个控制点会导致整个曲线发生变化,即无法局部控制曲线的走向。所以 B 样条曲线(B-Spline)为了解决贝塞尔曲线的缺陷应运而生。

不了解贝塞尔曲线的同学,可以去看我以前写的另外一篇文章《深入理解贝塞尔曲线》,后面的内容会假设你已经了解并掌握贝塞尔曲线的相关内容。

什么是 B 样条曲线?

解释 B 样条曲线之前,首先要解释一下什么是样条。样条是通过一组指定点集而生成平滑曲线的柔性带。 简单地说,B 样条曲线就是通过控制点局部控制形状的曲线。不太理解的同学可以通过本文底部的 demo 查看 B 样条曲线中,控制点对曲线绘制的影响。

B 样条曲线比贝塞尔曲线的设计要复杂许多,我们先通过他们的公式大致比较一下贝塞尔曲线与 B 样条曲线的区别:

贝塞尔曲线:

\vec{P(t)} = \sum_{i=0}^{n-1}\vec{p_i}B_{i,n}(t),t\in[0,1]

B 样条曲线:

\vec{P(t)}=\sum_{i=0}^{n-1}\vec{p_i}B_{i,d}(t),\ 其中
t_{min}\leq{t}\leq{t_{max}},
2\leq{d}\leq{n}

先简单介绍一下上述公式的组成:

  • \vec{P(t)}表示曲线上的点坐标向量。
  • n为控制点\vec{p_i}数量。
  • \vec{p_i}为控制点坐标(i 从 0 开始)。
  • B_{i,n}(t)为控制点坐标影响权重的多项式系数(式中 i 代表坐标的索引,n 代表多项式最高的幂数)。
  • d影响 B 样条曲线的次数:d - 1就是曲线的次数。
  • t是绘制曲线时的取值。

仔细观察这两个公式,我们可以看到以下的相同点:

  • 都是求和公式。
  • 都有一个B_{i,x}(_t)的多项式系数(式中贝塞尔曲线 x=n,B 样条曲线 x=d)。

可以看出有以下几个不同点:

  • 贝塞尔曲线的多项式幂数与控制点数量一致,而 B 样条曲线的多项式幂数更自由。
  • 贝塞尔曲线t的取值为固定的[0,1],而 B 样条曲线是在最大和最小节点值之间。

计算多项式

公式中其他值其实都比较清晰,问题的关键是需要搞清楚那个多项式B_{i,n}(t)是什么!

计算这个多项式,我们使用到了Cox-deBoor 递归公式,公式内容如下(需要注意一点,如果遇到分母为 0的情况时,需要特殊处理为整体值为 0):

B_{k,1}(u)=
\left\{\begin{matrix}
1, & u_k{\leq}u{\leq}u_{k+1} \\
\\
0, & 其他
\end{matrix}\right.
B_{k,d}(u)=
\frac{u-u_k}{u_{k+d-1}-u_k}B_{k,d-1}(u)+\frac{u_{k+d}-u}{u_{k+d}-u_{k+1}}B_{k+1,d-1}(u)

从这个公式可以看出,递归公式的差异主要体现在u_ku_{k+1}的取值上。需要注意的是节点的数量是由参数dn决定的,数量等于n + d。根据这些节点u的取值,可以划分为一下三种类型:

  • 均匀周期性(uniform)
  • 开放均匀性(open uniform)
  • 非均匀性(non-uniform)

想必大家现在已经非常头晕了吧 🤪,这都什么乱七八糟的,全是公式,完全看不懂啊!如果想讲清楚 B 样条曲线是非常困难的,因为其种类繁多,参数控制也非常自由。所以我还是通过讲解计算过程的栗子 🌰 来让大家更好地理解吧!

种类一: 均匀周期性 B 样条曲线

顾名思义,均匀周期性 B 样条曲线,就是节点u取值是均匀的,比如[-1, -0.5, 0, 0.5, 1],只要每个相邻的值间隔是相同的就可以了。不过为了方便计算,一般取值都是从 0 开始,并且间隔为 1。比如[0, 1, 2, 3, 4, 5, 6, 7, 8]

均匀取节点值会导致每个多项式函数是周期性分布的,下面我会通过一个均匀二次 B 样条的计算过程,帮助大家理解这个周期性

1.确定公式参数

因为是二次 B 样条曲线,所以我们可以确定以下参数:

  • 曲线为二次,所以d - 1 = 2,所以d = 3
  • 控制点数量我们手动取值为 4(也可以取值为其他的值),即n = 4
  • 节点u的数量为d + n,取值为 7,所以节点范围为:[0, 1, 2, 3, 4, 5, 6]

确定这几个值后,我们就可以开始计算多项式 B 的值了。下面是迭代公式:

B_{k,1}(u)=
\left\{\begin{matrix}
1, & u_k{\leq}u{\leq}u_{k+1} \\
& & & ····(公式1)\\
0, & 其他
\end{matrix}\right.
B_{k,d}(u)=
\frac{u-u_k}{u_{k+d-1}-u_k}B_{k,d-1}(u)+\frac{u_{k+d}-u}{u_{k+d}-u_{k+1}}B_{k+1,d-1}(u)   ····(公式2)

2.计算常量值的多项式

首先根据上面的公式 1,我们可以先得出常量值的多项式B_{0,1}(u)B_{1,1}(u)B_{2,1}(u)B_{3,1}(u):

  • k = 0, d = 1时:
B_{0,1}(u)=
\left\{\begin{matrix}
1, & 0{\leq}u{\leq}1(即u_0{\leq}u{\leq}u_{1}) \\
\\
0, & 其他
\end{matrix}\right.
  • k = 1, d = 1时:
B_{1,1}(u)=
\left\{\begin{matrix}
1, & 1{\leq}u{\leq}2(即u_1{\leq}u{\leq}u_{2}) \\
\\
0, & 其他
\end{matrix}\right.
  • k = 2, d = 1时:
B_{2,1}(u)=
\left\{\begin{matrix}
1, & 2{\leq}u{\leq}3(即u_2{\leq}u{\leq}u_{3}) \\
\\
0, & 其他
\end{matrix}\right.
  • k = 3, d = 1时:
B_{3,1}(u)=
\left\{\begin{matrix}
1, & 3{\leq}u{\leq}4(即u_3{\leq}u{\leq}u_{4}) \\
\\
0, & 其他
\end{matrix}\right.

因为 B 样条曲线公式为:

\vec{P(t)}=\sum_{i=0}^{n-1}\vec{p_i}B_{i,d}(t)\\
将n=4和d=3带入公式\\
= \vec{p_0}B_{0,3}(t) + \vec{p_1}B_{1,3}(t) + \vec{p_2}B_{2,3}(t) + \vec{p_3}B_{3,3}(t)

从上式可以很轻松地看出,我们需要知道以下几个多项式的函数:

  • B_{0,3}(t)
  • B_{1,3}(t)
  • B_{2,3}(t)
  • B_{3,3}(t)

所以我们现在依次求解这些多项式的函数。

3.计算非常量的多项式

B_{0,3}(t)的值(即k=0,d=3):

B_{0,3}(u)=
\frac{u-u_0}{u_2-u_0}B_{0,2}(u)+\frac{u_3-u}{u_3-u_1}B_{1,2}(u)\\
= \frac{u}{2}B_{0,2}(u)+\frac{3-u}{3-1}B_{1,2}(u)\\
= \frac{1}{2}uB_{0,2}(u) + \frac{1}{2}(3-u)B_{1,2}(u)

从上式中得出,我们需要得到B_{0,2}(u)B_{1,2}(u)的值。

B_{0,2}(t)的值(即k=0,d=2):

B_{0,2}(u)=
\frac{u-u_0}{u_{1}-u_0}B_{0,1}(u)+\frac{u_{2}-u}{u_{2}-u_{1}}B_{1,1}(u)\\
= \frac{u}{1}B_{0,1}(u)+\frac{2-u}{1}B_{1,1}(u)\\
= uB_{0,1}(u) + (2-u)B_{1,1}(u)\\

将B_{0,1}和B_{1,1}带入方程式\\

= \left\{\begin{matrix}
u, & 0{\leq}u{\leq}1\\
\\
2-u, & 1{\leq}u{\leq}2\\
\\
0, & 其他
\end{matrix}\right.

B_{1,2}(t)的值(即k=1,d=2):

B_{1,2}(u)
=\frac{u-u_1}{u_2-u_1}B_{1,1}(u)+\frac{u_3-u}{u_3-u_2}B_{2,1}(u)\\
=\frac{u-1}{2-1}B_{1,1}(u)+\frac{3-u}{3-2}B_{2,1}(u)\\
=(u-1)B_{1,1}(u)+(3-u)B_{2,1}(u)\\
将B_{1,1}和B_{2,1}带入方程式\\
=\left\{\begin{matrix}
u-1, & 1{\leq}u{\leq}2\\
\\
3-u, & 2{\leq}u{\leq}3\\
\\
0, & 其他
\end{matrix}\right.

得出B_{0,3}(u)

继续迭代,我们就可以得出B_{0,3}(u)了:

B_{0,3}(u)
= \frac{1}{2}uB_{0,2}(u) + \frac{1}{2}(3-u)B_{1,2}(u)\\

将关系式B_{0,2}和B_{1,2}带入\\

= \left\{\begin{matrix}
\frac{1}{2}u^2, & 0{\leq}u{\leq}1\\
\\
\frac{1}{2}u(2-u)+\frac{1}{2}(3-u)(u-1), & 1{\leq}u{\leq}2\\
\\
\frac{1}{2}(3-u)^2, & 2{\leq}u{\leq}3\\
\\
0, & 其他
\end{matrix}\right.

B_{1,3}(t), B_{2,3}(t), B_{3,3}(t)的值

这三个多项式的求解过程与B_{0,3}(t)一致,有兴趣的小伙伴可以自己计算一下,加深理解。这里我仅给出最后的结果。

B_{1,3}(u)
= \left\{\begin{matrix}
\frac{1}{2}(u-1)^2, & 1{\leq}u{\leq}2\\
\\
\frac{1}{2}(u-1)(3-u)+\frac{1}{2}(4-u)(u-2), & 2{\leq}u{\leq}3\\
\\
\frac{1}{2}(4-u)^2, & 3{\leq}u{\leq}4\\
\\
0, & 其他
\end{matrix}\right.
B_{2,3}(u)
= \left\{\begin{matrix}
\frac{1}{2}(u-2)^2, & 2{\leq}u{\leq}3\\
\\
\frac{1}{2}(u-2)(4-u)+\frac{1}{2}(5-u)(u-3), & 3{\leq}u{\leq}4\\
\\
\frac{1}{2}(5-u)^2, & 4{\leq}u{\leq}5\\
\\
0, & 其他
\end{matrix}\right.
B_{3,3}(u)
= \left\{\begin{matrix}
\frac{1}{2}(u-3)^2, & 3{\leq}u{\leq}4\\
\\
\frac{1}{2}(u-3)(5-u)+\frac{1}{2}(5-u)(u-3), & 4{\leq}u{\leq}5\\
\\
\frac{1}{2}(6-u)^2, & 5{\leq}u{\leq}6\\
\\
0, & 其他
\end{matrix}\right.

将四个函数分别绘制到坐标系中,大家就可以看到图像形状是完全相同的,只是在 x 轴方向做了平移,如下图所示。

B_{0,3}(u):

B_{1,3}(u):

B_{2,3}(u):

B_{3,3}(u):

4.确定曲线方程。

之前已经计算出了曲线的方程式,如下:

\vec{P(t)}
= \vec{p_0}B_{0,3}(t) + \vec{p_1}B_{1,3}(t) + \vec{p_2}B_{2,3}(t) + \vec{p_3}B_{3,3}(t)

将上面的多项式带入到方程,就可以得到最终的曲线方程。然后将 t 从 0 取至 6,获得的点集就是我们想要的均匀周期性 B 样条曲线。

从多项式的取值,我们就可以看出 B 样条曲线的一个特点,就是控制点不会影响整个曲线的绘制,只是局部影响曲线的走向。

最后

我写了一个线上的demo,用于展示均匀周期性 B 样条曲线的绘制,有兴趣的同学可以去看一下。

下篇我将会介绍剩下两种 B 样条曲线的绘制过程,敬请期待!