贝塞尔曲线(Bezier Curve)在计算机图形领域应用非常广泛,比如我们熟知的 CSS 动画、 Canvas 以及 Photoshop 等都可以看到贝塞尔曲线的身影。
文章目录
- 一、什么是贝塞尔曲线?
- 二、贝塞尔曲线分为哪些类型?
- 三、贝塞尔曲线是如何绘制出来的?
- 四、如何求贝塞尔曲线上的点坐标?
- 五、如何实现一个类似CSS中easing属性的三阶贝塞尔曲线构造函数?
- 六、如何用高阶贝塞尔曲线表示低阶贝塞尔曲线?
一、什么是贝塞尔曲线?
贝塞尔曲线于 1962 年,由法国工程师皮埃尔·贝济埃(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。
贝塞尔曲线主要用于二维图形应用程序中的数学曲线,曲线由起始点,终止点(也称锚点)和控制点组成,通过调整控制点,通过一定方式绘制的贝塞尔曲线形状会发生变化。后面会具体介绍绘制的方法。
在计算机图形学中贝赛尔曲线的运用很广泛,例如Photoshop中的钢笔效果,Flash5的贝塞尔曲线工具,在软件GUI开发中一般也会提供对应的方法来实现贝赛尔曲线,我们熟知的CSS动画过渡时间函数也是通过贝塞尔曲线(三阶贝塞尔曲线)获取的。
二、贝塞尔曲线分为哪些类型?
贝塞尔曲线根据控制点的数量分为:
- 一阶贝塞尔曲线(2 个控制点)
- 二阶贝塞尔曲线(3 个控制点)
- 三阶贝塞尔曲线(4 个控制点)
- n阶贝塞尔曲线(个控制点)
三、贝塞尔曲线是如何绘制出来的?
下图为一个三阶的贝塞尔曲线,包括四个控制点,分别为。
那我们通过控制点是怎么绘制出贝塞尔曲线的呢?
通过上图的三阶贝塞尔曲线举例,基本的步骤如下:
- 四个控制点通过先后顺序进行连接,形成了三条线段,也就是上图中的,然后通过一个参数,该参数的值等于线段上某一个点距离起点的长度除以线段长度。就比如线段上有一个点,此时的值就是,其中位置如下图所示。
- 接下来对每一条线段做同样的操作,得到三个控制点,如下图所示。
- 然后对这三个控制点重复第1步操作,得出两个控制点,如下图所示。
- 最后再使用同样的方法可以得到,最终的一个点,如下图所示,此时这个点就是贝塞尔曲线上的一个点。
通过控制的值,由 0 增加至 1,就绘制出了一条由起点至终点的贝塞尔曲线。
你可以通过下面这个动画直观感受一下绘制的过程:
四、如何求贝塞尔曲线上的点坐标?
1、一阶贝塞尔曲线
对于一阶贝塞尔曲线,我们可以通过几何知识,很容易根据的值得出线段上那个点的坐标:
然后可以得出:
2、二阶贝塞尔曲线
对于二阶贝塞尔曲线,其实你可以理解为:在上利用一阶公式求出点,然后在上利用一阶公式求出点,最后在上再利用一阶公式就可以求出最终贝塞尔曲线上的点。具体推导过程如下:
先求出线段上的控制点。
将上面的公式带入至下列公式中:
得出以下公式:
3、三阶贝塞尔曲线
与二阶贝塞尔曲线类似,可以通过相同的方法得出以下坐标公式:
4、多阶贝塞尔曲线
这里我就直接把阶贝塞尔曲线公式给出来了,有兴趣的同学可以自行研究一下。
即:
公式中的值为,与统计学有关,有兴趣的同学可以看一看我的这篇文章。
其中的值为:
五、如何实现一个类似CSS中easing属性的三阶贝塞尔曲线构造函数?
如果要实现一个这样的三阶贝塞尔曲线,我们需要不仅需要获取到一些曲线上的点,还需要通过x轴获取y轴坐标。
CSS中的easing贝塞尔曲线有一个特点,那就是起点和终点是固定的,也就是分别是。所以未知的点就只有两个,也就是需要传入四个值,并且这四个值的范围需要在内。
所以我们需要创建一个类CubicBezier,它拥有属性controlPoints
:
class CubicBezier {
constructor(x1, y1, x2, y2) {
this.controlPoints = [x1, y1, x2, y2];
}
}
通过上述代码初始化以后,我们还需要根据t(取值范围为)值获取坐标,以及一个曲线上坐标集合的数组。另外还需要使用三阶贝塞尔公式:
因为点坐标为[0, 0],点坐标为为所以公式进而可以写成:
class CubicBezier {
constructor(x1, y1, x2, y2) {
this.controlPoints = [x1, y1, x2, y2];
}
getCoord(t) {
// 如果t取值不在0到1之间,则终止操作
if (t > 1 || t < 0) return;
const _t = 1 - t;
const [ x1, y1, x2, y2 ] = this.controlPoints;
const coefficient1 = 3 * t * Math.pow(_t, 2);
const coefficient2 = 3 * _t * Math.pow(t, 2);
const coefficient3 = Math.pow(t, 3);
const px = coefficient1 * x1 + coefficient2 * x2 + coefficient3;
const py = coefficient1 * y1 + coefficient2 * y2 + coefficient3;
// 结果只保留三位有效数字
return [parseFloat(px.toFixed(3)), parseFloat(py.toFixed(3))];
}
}
利用上述的Bezier类,我们就可以根据两个控制点构建Bezier实例,通过这个实例我们可以根据t值,获取点上的近似值。
那么如果我们想要根据x轴坐标值,来获取y轴坐标时,我们该怎么做呢?
这里我使用了一个近似处理的办法,具体如下:
- 先获取离需要求值点最近的两个点。
- 然后通过这两个点可以得到一个直线方程。
- 最后通过将x轴坐标传入直线方程中,就可以近似求得y轴坐标值了。
所以我们需要进一步改造Bezier构造函数,需要缓存固定数量坐标数组的属性coords
,以及获取coords
的方法getCoordsArray
,最后还有获取y轴坐标的方法getY
,具体的实现方法如下:
class CubicBezier {
constructor(x1, y1, x2, y2) {
const precision = 100;
this.controlPoints = [x1, y1, x2, y2];
this.coords = this.getCoordsArray(precision);
}
getCoord(t) {
// 如果t取值不在0到1之间,则终止操作
if (t > 1 || t < 0) return;
const _t = 1 - t;
const [ x1, y1, x2, y2 ] = this.controlPoints;
const coefficient1 = 3 * t * Math.pow(_t, 2);
const coefficient2 = 3 * _t * Math.pow(t, 2);
const coefficient3 = Math.pow(t, 3);
const px = coefficient1 * x1 + coefficient2 * x2 + coefficient3;
const py = coefficient1 * y1 + coefficient2 * y2 + coefficient3;
// 结果只保留三位有效数字
return [parseFloat(px.toFixed(3)), parseFloat(py.toFixed(3))];
}
getCoordsArray(precision) {
const step = 1 / (precision + 1);
const result = [];
for (let t = 0; t <= precision + 1; t++) {
result.push(this.getCoord(t * step));
}
this.coords = result;
return result;
}
getY(x) {
if (x >= 1) return 1;
if (x <= 0) return 0;
let startX = 0;
for (let i = 0; i < this.coords.length; i++) {
if (this.coords[i][0] >= x) {
startX = i;
break;
}
}
const axis1 = this.coords[startX];
const axis2 = this.coords[startX - 1];
const k = (axis2[1] - axis1[1]) / (axis2[0] - axis1[0]);
const b = axis1[1] - k * axis1[0];
// 结果也只保留三位有效数字
return parseFloat((k * x + b).toFixed(3));
}
}
然后通过下述方式就可以使用我们的CubicBezier
了:
const cubicBezier = new CubicBezier(0.3, 0.1, 0.3, 1);
cubicBezier.getY(0.1); // 0.072
cubicBezier.getY(0.7); // 0.931
我写了一个应用这个
CubicBezier
构造函数的库Animate-Scroll,有兴趣的可以去看一下源码。
六、如何用高阶贝塞尔曲线表示低阶贝塞尔曲线?
一个阶贝塞尔曲线可以通过一个形状完全一致的阶贝塞尔曲线表示。那我们该怎么做,才能获取这个阶贝塞尔曲线呢?
由高阶贝塞尔曲线表示低阶贝塞尔曲线的过程,我们称之为升阶。
我们需要用到这个等式来做升阶。
- 先以二阶升三阶为例,二阶贝塞尔曲线坐标公式为:
将以下等式带入上面这个公式中:
然后得出以下公式:
根据以上结果可以得出控制点由之前的变成了,,和四个控制点了,从而完成了升阶。
- 如果对于任意的n值,我们该如何进行升阶呢?(以下为推导过程,没兴趣的同学可以直接跳转至下面👇的公式)
这里需要进行一些推导(这里的推导需要用到公式,有兴趣的同学可以自己推导一下),因为:
贝塞尔公式可以表示为:
带入上述两个等式,得:
因为当时:
所以该式可以写成:
又因为:
当时:
所以:
将上述两个等式(1)和(2)代入公式(0)中,最终可以得出下面这个升阶公式:
关于贝塞尔曲线基本的内容就差不多讲完了,如果您发现不正确或者有补充的地方,欢迎在评论里指出😊。