Li Jiaheng's blog
2170 字
11 分钟
贝塞尔曲线
2024-04-24

贝塞尔曲线的内容很基础,只是国内的各种图形学教材都容易把这部分讲的特别“复杂”(大量数学公式推导证明),新手极其不友好。这里不把证明作为重点,而只关注是什么。
同时,也为了整理一下我以前看的一些东西,本文会从三次曲线开始。

从三次曲线开始#

曲线的参数化简单理解为用一个参数方程表示曲线,这样可以通过控制参数来控制曲线,也可以通过遍历参数获得曲线上每一个点的精确位置。我们知道可以用多项式去逼近函数,所以可以用多项式作为参数方程的表达式。
为了统一,参数范围一般是0~1.
三次多项式定义的曲线在操作灵活性和计算简单性方面比较平均.

Q(t)=a+bt+ct2+dt3\bold Q(t) = \bold a+\bold bt+\bold ct^2+ \bold dt^3

注意除了参数之外的字母都是向量,表示x,y,z分量。
用矩阵表示为:

Q(t)=[abcd][1tt2t3]Q(t)=CT(t)\begin{aligned} \bold Q(t) &= \begin{bmatrix} \bold a & \bold b & \bold c & \bold d \end{bmatrix} \begin{bmatrix} 1 \\ t \\ t^2 \\ t^3 \end{bmatrix} \\ \bold Q(t) &= \bold C \bold T(t) \end{aligned}

对于三次曲线的求导也非常简单,因为 C\bold C 是常数矩阵,所以直接对向量 T(t)\bold T(t) 求导即可.

ddtQ(t)=C[012t3t2]\begin{aligned} \frac{d}{dt}\bold Q(t)=\bold C \begin{bmatrix} 0 \\ 1 \\ 2t \\ 3t^2 \end{bmatrix} \end{aligned}

我们希望有办法控制系数矩阵 C\bold C 中的值,因而我们引入若干控制点,对这些控制点定义不同的约束条件,比如,以某两个点为起点、终点,以某两个点之间的斜率为曲线起点的导等等,这样能通过控制这些控制点来比较直观地修改曲线。
为了计算上能够更“通用”,往往用如下方式表示一组约束条件,直接对“约束条件”做加权和”:

Q(t)=i=14(ai+bit+cit2+dit3)gi\bold Q(t) = \sum_{i=1}^4 (a_i+b_it+c_it^2+d_it^3) \bold g_i

其中 g 是约束条件,往往直接是控制点本身,或者是由控制点能够直接得到的,每个 g 都是三维向量。
上式用矩阵表示,为:

Q(t)=[g1g2g3g4][a1a2a3a4b1b2b3b4c1c2c3c4d1d2d3d4][1tt2t3]Q(t)=GMT(t)\begin{aligned} \bold Q(t)&=\begin{bmatrix} \bold g_1 & \bold g_2 & \bold g_3 & \bold g_4 \end{bmatrix} \begin{bmatrix} a_1 & a_2 & a_3 &a_4 \\ b_1 & b_2 & b_3 &b_4 \\ c_1 & c_2 & c_3 &c_4 \\ d_1 & d_2 & d_3 &d_4 \end{bmatrix} \begin{bmatrix} 1 \\ t \\ t^2 \\ t^3 \end{bmatrix} \\ \bold Q(t) &= \bold G \bold M \bold T(t) \end{aligned}

其中 GM\bold G \bold M 相当于前面的系数矩阵 C\bold C,对于特定的控制点,G 是确定的,需要求解 M 使得 Q(t) 满足规定的约束条件,从而得到由这些控制点和约束条件决定的三次曲线 Q。

Hermite曲线#

Hermite 曲线是对上述三次曲线经过四个控制点进行约束后得到的结果。四个控制点 g0,g1,g2,g3g_0, g_1, g_2, g_3的约束规则是:

  • g0,g3g_0, g_3 分别是曲线的起点和终点。
  • g1,g0g_1, g_0 形成的直线的斜率是起点的导数。
  • g2,g3g_2, g_3 形成的直线的斜率是终点的导数。

可见,g1,g2g1,g2的存在只是提供了一种控制起点终点导数的直观方式。后面将直接用P1,P2,T1,T2\bold P_1, \bold P_2, \bold T_1, \bold T_2 分别表示起点终点、起点导数和终点导数。
直接用 P1,P2,T1,T2\bold P_1, \bold P_2, \bold T_1, \bold T_2 作为控制条件,作为上面的 G,代入上面的式子有:

Q(t)=[P1P2T1T2]MT(t)\bold Q(t)=\begin{bmatrix} P_1 & P_2 & T_1 & T_2 \end{bmatrix} \bold M \bold T(t)

需要满足上面前面所述的约束条件,即起点终点和导数。

Q(0)=P1Q(1)=P2ddtQ(0)=T1ddtQ(1)=T2\begin{aligned} \bold Q(0) &= P_1 \\ \bold Q(1) &= P_2 \\ \frac{d}{dt}\bold Q(0) &= T_1 \\ \frac{d}{dt}\bold Q(1) &= T_2 \\ \end{aligned}

上面四个方程写成矩阵的形式:

[P1P2T1T2]=[P1P2T1T2]M[1100011101020103]\begin{bmatrix} P_1 & P_2 & T_1 & T_2 \end{bmatrix} = \begin{bmatrix} P_1 & P_2 & T_1 & T_2 \end{bmatrix} \bold M \begin{bmatrix} 1 & 1 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 0 & 2 \\ 0 & 1 & 0 & 3 \end{bmatrix}

容易解得 Hermite 曲线的 MH\bold M_H:

MH=[1100011101020103]1\begin{aligned} \bold M_H = \begin{bmatrix} 1 & 1 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 0 & 2 \\ 0 & 1 & 0 & 3 \end{bmatrix}^{-1} \end{aligned}

Hermite 曲线 QH(t)\bold Q_H(t):

QH(t)=[P1P2T1T2]MHT(t)QH(t)=[P1P2T1T2][1100011101020103]1T(t)\begin{aligned} \bold Q_H(t)&=\begin{bmatrix} P_1 & P_2 & T_1 & T_2 \end{bmatrix} \bold M_H \bold T(t) \\ \bold Q_H(t)&=\begin{bmatrix} P_1 & P_2 & T_1 & T_2 \end{bmatrix} \begin{bmatrix} 1 & 1 & 0 & 0 \\ 0 & 1 & 1 & 1 \\ 0 & 1 & 0 & 2 \\ 0 & 1 & 0 & 3 \end{bmatrix}^{-1} \bold T(t) \end{aligned}

给定四个控制点,即可得到 P1,P2,T1,T2,然后应用上式即可得到 Hermite 曲线。

贝塞尔曲线#

贝塞尔一般的来说先从数学定义开始,然后证明一些性质,最后才介绍怎么算出贝塞尔曲线上各个点。GAMES101 从是什么的角度入手,先介绍贝塞尔曲线是怎么画出来的,最后稍微提及其数学表达。
这里仍旧从数学表达式入手,贝塞尔曲线的表达式本身不难,仅仅以知道是什么为目的的话,并没有很大难度。

贝塞尔曲线解决的问题是,给定一系列控制点 {b0,b1,,bn}\left\{ b_0, b_1, \dots,b_n\right\},用这些控制点控制一条曲线。
贝塞尔曲线通过对 Bernstein基函数 加权求和的方式定义。

QB(t)=i=0nBi,n(t)biBi,n(t)=(ni)ti(1t)i\begin{aligned} \bold Q_B(t) &= \sum_{i=0}^nB_{i,n}(t) b_i \\ B_{i,n}(t)&=\begin{pmatrix} n \\ i \end{pmatrix}t^i(1-t)^i \end{aligned}

可见,对 Bernstein基函数加权求和,权重即为控制点坐标本身,而Bernstein基函数为 (t+(1t))n(t+(1-t))^n 的二项式展开中的项。

贝塞尔曲线的性质。#

1、起点终点
QB(0)=b0,QB(1)=b1Q_B(0) = b_0, Q_B(1)=b_1
起点的切线方向和 b0b1b_0b_1 方向一致,终点的切线方向和 bn1bnb_{n-1}b_n方向一致。

2、降阶(导数)
QB(t)=ni=0n1bi(Bi1,n1(t)Bi,n1(t))Q_B'(t)=n\sum_{i=0}^{n-1}b_i(B_{i-1, n-1}(t)-B_{i,n-1}(t))
QB(t)=n(n1)i=0n2(bi+22bi+1+bi)Bi,n1(t)Q_B''(t)=n(n-1)\sum_{i=0}^{n-2}(b_{i+2}-2b_{i+1}+b_i)B_{i,n-1}(t)

3、凸包性。
贝塞尔曲线一定处在其所有控制点形成的凸包的范围内。

4、(仿射变换)几何不变性
贝塞尔曲线只与控制点位置有关,与具体使用那个坐标轴无关。对所有控制点做同一个仿射变换,最后得到的贝塞尔曲线和变换之前是一样的。
只对仿射变换有效,对于其它如投影变换等,没有这个性质。

5、不具备局部可控性
修改贝塞尔曲线的任意控制点,整条线都会发生变化。
在控制点较多的时候,曲线会变得相当平,难以较大幅度改变。

de … 算法#

这个算法解决了贝塞尔曲线怎么画出来的问题,即根据参数 t 快速地找到这个 t 对应的贝塞尔曲线上的点坐标。
首先,把所有原本的控制点定义为 bi0b_i^0,对于参数值 t,取 bik=(1t)bik1+tbi+1k1b_i^k=(1-t)b_{i}^{k-1}+tb_{i+1}^{k-1},即 bikb_i^k 是线段 bik1bi+1k1b_{i}^{k-1}b_{i+1}^{k-1}上的一点,使得该点到左右端点长度的比例是 bik1bikbikbi+1k1=t1t\frac{b_{i}^{k-1}b_i^k}{b_i^kb_{i+1}^{k-1}}=\frac{t}{1-t},即 1/t 分位点。
然后将 bik,bi+1kb_i^k, b_{i+1}^k 连起来形成一条新的线段,然后重复上述过程。
可见,上标 k 每+1,得到的点的个数就-1. 重复上述过程,最终得到唯一的 b0nb_0^n 的坐标,就是 QB(t)Q_B(t) 即参数值 t 对应的贝塞尔曲线上的坐标。

这是一个动态规划算法。

QB(t)=b0nbik={(1t)bik1+tbi+1k1,k0bi,k=0\begin{aligned} \bold Q_B(t) &= b_0^n \\ b_i^k &= \begin{cases} \begin{aligned} (1-t)b_{i}^{k-1}+tb_{i+1}^{k-1} , \quad k &\neq 0 \\ b_i, \quad k &= 0 \end{aligned} \end{cases} \end{aligned}

就是不停地取 1/t 分位点,然后把相邻的新点连起来,再取分位点。。。一直到最后只剩一个点。

贝塞尔曲面#

曲线用一个参数 t 就可以了,而曲面需要用参数 u, v。而控制点也构成一个 m*n 的 “网格”。参数 {b00bmn}\left\{b_{00}\dots b_{mn} \right\}

QB(u,v)=i+jnBi,j,n(u,v)buvBi,j,n(u,v)=(nij)uivj(1uv)nij\begin{aligned} \bold Q_B(u,v) &= \sum_{i+j \leq n}B_{i,j,n}(u,v) b_{uv} \\ B_{i,j,n}(u,v)&=\begin{pmatrix} n \\ ij \end{pmatrix}u^iv^j(1-u-v)^{n-i-j} \end{aligned}

其中高阶的 Bernstein基函数 就是 (u+v+(1uv))n(u+v+(1-u-v))^n 的广义二项式展开的项。

贝塞尔曲面的算法就是进行了两趟 de…算法。
给定 (u, v),对于 u 方向,对每个 u 方向上的控制点(n列,每列m个)以u为 t 算出自己的贝塞尔曲线点,在以这些点为新的贝塞尔曲线控制点,再以 v 为 t 进行一次贝塞尔曲线求解得到最终的 (u,v) 对应的曲面上的点。
就是非常直观的行列分解

Picewise 贝塞尔曲线#

由于贝塞尔曲线没有局部性,所以往往用分段贝塞尔曲线来近似实现局部性。将控制点分组,每组若干个控制点(一般是三四个),上一组最后一个控制点和下一组第一个控制点重合。这样每组控制点的贝塞尔曲线能首尾相接地拼合在一起,并且不同组的控制点之间互不干扰,实现近似的局部性。

贝塞尔曲线
https://namisntimpot.github.io/posts/cg/theory/贝塞尔曲线/
作者
Li Jiaheng
发布于
2024-04-24
许可协议
CC BY-NC-SA 4.0