贝塞尔曲线是图形学中非常重要的知识,是绘制曲线以及曲面的基础,在很多地方都有着非常广泛的应用,比如Photoshop里的钢笔工具,字体设计,各种过渡动画等等。本文将记录贝塞尔曲线的原理公式,以及使用Three.js中的贝塞尔曲线API进行简单的心形绘制。
贝塞尔曲线详解
贝塞尔曲线就是在起始点和终止点之间,设置控制点,通过控制点的移动来控制曲线的形状。根据控制点数量的不同,可以将贝塞尔曲线分为一阶曲线、二阶曲线、三阶曲线……等等。
一阶曲线
一阶曲线非常简单,因为没有控制点,所以就是一条从起始点到终止点的线段。
二阶曲线
二阶曲线由起始点、终止点和一个控制点组成。贝塞尔曲线由以下规则形成:对于[0, 1]内任何t,在
b
0
b
1
b_0b_1
b0b1上取一个点
b
0
1
b_0^1
b01,满足
b
0
b
0
1
b_0b_0^1
b0b01和
b
0
1
b
1
b_0^1b_1
b01b1的关系为
t
:
1
−
t
t : 1-t
t:1−t,同样的,在在
b
1
b
2
b_1b_2
b1b2上取一个点
b
1
1
b_1^1
b11,满足
b
1
b
1
1
b_1b_1^1
b1b11和
b
1
1
b
2
b_1^1b_2
b11b2的关系为
t
:
1
−
t
t : 1-t
t:1−t。最后将
b
0
1
b
1
1
b_0^1b_1^1
b01b11相连,在
b
0
1
b
1
1
b_0^1b_1^1
b01b11上继续取一点满足
b
0
1
b
0
2
:
b
0
2
b
1
1
=
t
:
1
−
t
b_0^1b_0^2:b_0^2b_1^1=t:1-t
b01b02:b02b11=t:1−t。所有的t所形成的
b
0
2
b_0^2
b02形成一道曲线,这道曲线就是贝塞尔曲线。对应的点的坐标在向量表示下很容易表示出来:
b
0
1
⃗
(
t
)
=
(
1
−
t
)
b
0
⃗
+
t
b
⃗
1
b
1
1
⃗
(
t
)
=
(
1
−
t
)
b
1
⃗
+
t
b
⃗
2
b
0
2
⃗
(
t
)
=
(
1
−
t
)
b
0
1
⃗
+
t
b
⃗
1
1
⇒
b
0
2
⃗
(
t
)
=
(
1
−
t
)
2
b
⃗
0
+
2
t
(
1
−
t
)
b
1
⃗
+
t
2
b
⃗
2
\vec{b^1_0}(t)=(1-t)\vec{b_0}+t\vec b_1 \\ \vec{b^1_1}(t)=(1-t)\vec{b_1}+t\vec b_2 \\ \vec{b^2_0}(t)=(1-t)\vec{b_0^1}+t\vec b_1^1 \\ \Rightarrow \vec{b_0^2}(t)=(1-t)^2\vec b_0 + 2t(1-t)\vec{b_1}+t^2\vec b_2
b01(t)=(1−t)b0+tb1b11(t)=(1−t)b1+tb2b02(t)=(1−t)b01+tb11⇒b02(t)=(1−t)2b0+2t(1−t)b1+t2b2
三阶以及多阶曲线
类似于二阶曲线,三阶以及多阶贝塞尔曲线也是采用同样的方法,不断进行迭代,每一个t确定一个点,最后形成一条曲线。
计算贝塞尔曲线有一个对于任意阶通用的公式:德卡斯特里奥算法 (De Casteljau’s algorithm),是一种递归的计算贝塞尔曲线的方法:
b
⃗
n
(
t
)
=
b
⃗
0
n
(
t
)
=
∑
j
=
0
n
b
⃗
j
B
j
n
(
t
)
, where
b
⃗
j
is the Bernstein polynomial
Bernstein polynomial:
B
t
n
(
t
)
=
(
n
t
)
t
i
(
1
−
t
)
n
−
i
\vec b^n(t)=\vec b_0^n(t)=\sum_{j=0}^n\vec b_jB_j^n(t)\text{, where }\vec b_j \text{ is the Bernstein polynomial} \\ \text{Bernstein polynomial: }B^n_t(t)=\tbinom{n}{t}t^i(1-t)^{n-i}
bn(t)=b0n(t)=j=0∑nbjBjn(t), where bj is the Bernstein polynomialBernstein polynomial: Btn(t)=(tn)ti(1−t)n−i
使用Three.js中的贝塞尔曲线绘制心形
绘制圆形
我们首先可以通过贝塞尔曲线绘制出一个圆形,心形可以在圆形的基础上进行一定的控制点的调整就可以绘制而成。
从上图中可以看出,一个圆形可以被分成四个弧线,每条弧线可以通过一个三阶贝塞尔曲线绘制而成(两个控制点),控制点的位置在geometry - How to create circle with Bézier curves? - Stack Overflow已经有人计算好了,我们可以直接使用(这里的计算也并不是特别复杂,简单的数学知识就可以计算出来)。由此,我们可以通过贝塞尔曲线创建出一个圆的形状:
const x = 0, y = 0;
const radius = 30;
const c = 0.551915024494 * radius;
const heartShape = new THREE.Shape()
.moveTo(x, y+radius)
.bezierCurveTo(x+c,y+radius, x+radius, y+c, x+radius, y)
.bezierCurveTo(x+radius,y-c, x+c, y-radius, x, y-radius)
.bezierCurveTo(x-c,y-radius, x-radius, y-c, x-radius, y)
.bezierCurveTo(x-radius,y+c, x-c, y+radius, x, y+radius)
bezierCurveTo()
是Three.js中的贝塞尔曲线的API,它接受六个参数,前四个分别是两个控制点的x,y坐标,最后两个是终止点的x,y坐标。这里只需要设定好参数,使用四段贝塞尔曲线一一绘制出来就可以了。
绘制心形
心形其实就是在圆形的基础上移动了三个控制点的位置,将y轴上上方的起始点稍向下移动,以及下方左右两个控制点向上移动,就可以绘制出一个心形,代码以及效果图如下所示。
const x = 0, y = 0;
const radius = 30;
const c = 0.551915024494 * radius;
const heartShape = new THREE.Shape()
.moveTo(x, y+radius/3)
.bezierCurveTo(x+c,y+radius, x+radius, y+c, x+radius, y)
.bezierCurveTo(x+radius,y-c, x+c, y-radius/2, x, y-radius)
.bezierCurveTo(x-c,y-radius/2, x-radius, y-c, x-radius, y)
.bezierCurveTo(x-radius,y+c, x-c, y+radius, x, y+radius/3)
参考资料
GAMES101-现代计算机图形学入门-闫令琪_哔哩哔哩_bilibili
德卡斯特里奥算法 - 维基百科,自由的百科全书 (wikipedia.org)
Path#bezierCurveTo – three.js docs (threejs.org)
AndroidNote_Path_BezierGcsSloop/AndroidNote (github.com)
geometry - How to create circle with Bézier curves? - Stack Overflow