Three.js使用贝塞尔曲线绘制心形

贝塞尔曲线是图形学中非常重要的知识,是绘制曲线以及曲面的基础,在很多地方都有着非常广泛的应用,比如Photoshop里的钢笔工具,字体设计,各种过渡动画等等。本文将记录贝塞尔曲线的原理公式,以及使用Three.js中的贝塞尔曲线API进行简单的心形绘制。

贝塞尔曲线详解

贝塞尔曲线就是在起始点和终止点之间,设置控制点,通过控制点的移动来控制曲线的形状。根据控制点数量的不同,可以将贝塞尔曲线分为一阶曲线、二阶曲线、三阶曲线……等等。

一阶曲线

一阶曲线非常简单,因为没有控制点,所以就是一条从起始点到终止点的线段。

二阶曲线

image-20220210111040096

二阶曲线由起始点、终止点和一个控制点组成。贝塞尔曲线由以下规则形成:对于[0, 1]内任何t,在\(b_0b_1\)上取一个点\(b_0^1\),满足\(b_0b_0^1\)\(b_0^1b_1\)的关系为\(t : 1-t\),同样的,在在\(b_1b_2\)上取一个点\(b_1^1\),满足\(b_1b_1^1\)\(b_1^1b_2\)的关系为\(t : 1-t\)。最后将\(b_0^1b_1^1\)相连,在\(b_0^1b_1^1\)上继续取一点满足\(b_0^1b_0^2:b_0^2b_1^1=t:1-t\)。所有的t所形成的\(b_0^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 \]

三阶以及多阶曲线

类似于二阶曲线,三阶以及多阶贝塞尔曲线也是采用同样的方法,不断进行迭代,每一个t确定一个点,最后形成一条曲线。

image-20220210112920807

计算贝塞尔曲线有一个对于任意阶通用的公式:德卡斯特里奥算法 (De Casteljau's algorithm),是一种递归的计算贝塞尔曲线的方法: \[ \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} \]

使用Three.js中的贝塞尔曲线绘制心形

绘制圆形

我们首先可以通过贝塞尔曲线绘制出一个圆形,心形可以在圆形的基础上进行一定的控制点的调整就可以绘制而成。

aEsuA

从上图中可以看出,一个圆形可以被分成四个弧线,每条弧线可以通过一个三阶贝塞尔曲线绘制而成(两个控制点),控制点的位置在geometry - How to create circle with Bézier curves? - Stack Overflow已经有人计算好了,我们可以直接使用(这里的计算也并不是特别复杂,简单的数学知识就可以计算出来)。由此,我们可以通过贝塞尔曲线创建出一个圆的形状:

1
2
3
4
5
6
7
8
9
10
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轴上上方的起始点稍向下移动,以及下方左右两个控制点向上移动,就可以绘制出一个心形,代码以及效果图如下所示。

1
2
3
4
5
6
7
8
9
10
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)
heart

参考资料

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