“贝塞尔曲线”无处不在

633 阅读6分钟

最近在图形化上有一些研究,之前在学习CSS的时候就接触到了贝塞尔曲线,当时觉得这个东西自己也用不到,所以就跳过了,现在看来是自己愚钝了噢。

一、应用

名字就已经告诉你了,主要用于曲线、曲面的实现。大到汽车的曲面外观,小到app里的曲线(比如思维导图里的曲线)。只要能用曲线、曲面描述出来的事物,无论静态还是动态的,它都可以跟“贝塞尔曲线”扯上关系。

接下来的内容,我们讨论的范畴固定在2D平面上。

二、只有贝塞尔曲线能描述曲线吗?

非也,其他的函数也可以描述曲线,比如对数函数:

截屏2024-12-28 19.13.27.png

它的曲线大致是下面这个样子:

截屏2024-12-28 20.23.27.png

上面的曲线就是拿canvas画的。那这种思路可以落地吗?比如思维导图里不是有曲线嘛,2个方框用曲线相连。

答案是不可以的。除非你的业务数据的整体趋势是对数形式的,否则很难落地。原因如下:

  • 这种曲线与控制点无关,并且曲线的形状是固定的。

翻译成人话就是不灵活。你让它出现个“U”字形状,或者在任意地方拐个弯,它肯定是实现不了的。

  • 它在实现上限制较多。

我们知道canvas的Y轴是向下的,越往下数值越高。而数学里,y轴是向上的,越往上数值越高。因此第一步就是要做转换;

其次网格里的点与你给出的点差距过大,导致实际上你画出来的曲线,看上去可能就是一条直线,或者视觉效果里坡度不是很明显。比如你的canvas宽高都是300或者100,每个网格的步长都是10,结果你给出的数据最大值都没超过20,那20相比100,20相比300可不就是一条直线。因此这个时候你就要给一个缩放系数,让你的值,扩大N倍去看。

总之实现上限制太多,不仅要考虑网格的步长,还要考虑实际的数据,曲线的系数以及可能存在的缩放系数。

那有没有一个办法,让曲线的生成由控制点来决定的。比如控制点的位置决定曲线的形状?

答案是肯定的,其中有一个比较通用的就是“贝塞尔曲线”。

三、3次贝塞尔曲线?

贝塞尔曲线分很多种,比如2次贝塞尔曲线、3次贝塞尔曲线等等。理论上,你想要多少次,它就可以有多少控制点。

我们这篇文章只讲到3次,这在图形化里够用了。

首先要知道,在整个贝塞尔曲线的概念里,它将所有的点,分为了3类,分别是起点、终点、控制点。

3.1、一次贝塞尔曲线

只有起点、终点,没有控制点,所以一次贝塞尔曲线就是一条直线。

3.2、二次贝塞尔曲线

在有起点、终点的情况下,增加一个控制点。这个就是二次贝塞尔曲线。

从二次开始,就可以根据这个理论来画出曲线了。

bezier_2_big.gif

上面这个动画相信很多人都看过,具体解释如下图:

截屏2024-12-28 22.59.20.png

其中,P0是起点,P2是终点,P1是控制点。

贝塞尔的思路如下:

在由控制点形成的区域内去找“数据点”的过程。这些数据点连成线,最终就可以得到一条相对稳定的曲线。

那怎么找数据点呢?在人为意识思维下,你能在这个区域下任意找出无数点去连成曲线,但是这并不利于计算机绘图。所以为了能让计算机绘图,我们必须给出一套公式,然后根据这个公式去求大量的数据点,然后将它们连成曲线。

这些数据点只需满足以下条件:

假设P0P1、P1P2线段上各有运动点,分别是L1、L2。任意时刻内,在L1L2线段上,必须有一点能够满足以下等式:
(P0L1 / P0P1) === (P1L2 / P1P2) === (L1H1 / L1L2)

至于为什么必须在L1L2上找数据点,那是因为只有L1L2上的数据点,始终都在P0、P1、P2围成的区域内。

二次贝塞尔曲线大致就是这么个意思,但是大家发现没,二次贝塞尔曲线只能是个拱形!

那如何让这个曲线变成“S”型呢?或者说如何让曲线多拐个弯?

3.3、三次贝塞尔曲线

再加一个控制点试试,最开始的时候可能是这个样子:

截屏2024-12-29 11.46.36.png

在这个过程中,P0、P3分别是起点与终点,P1与P2是控制点。在L1、L2、L3不断运动的同时,H1就是我们要找的数据点,P0不断与H1进行连线。

当运动中期的时候,大致是下面这样:

截屏2024-12-29 12.07.31.png

当运动到后期的时候,大致是下面这样:

截屏2024-12-29 12.18.49.png

在过程中,依然将P0与蓝色球不断相连,最终你能得到下面这条曲线:

截屏2024-12-29 12.22.09.png

这就是3次贝塞尔曲线的思路。

四、公式

3次贝塞尔曲线的公式如下:

截屏2024-12-29 12.27.41.png

P0、P1、P2、P3不用解释。这里面有个t,在官方解释里,它是一个区间变量,范围是【0,1】,代表着绿色运动点在各自线段上完成的比率。

有了这个公式,我们就可以让计算机为我们画图了:

// 画曲线
function drawBezierCurve(p0, p1, p2, p3) {
    var nPoints = 100; // 曲线上的点数(同时也控制着曲线是否圆润)
    for (var i = 0; i <= nPoints; i++) {
        var t = i / nPoints;
        var x = (1 - t) * (1 - t) * (1 - t) * p0.x + 3 * (1 - t) * (1 - t) * t * p1.x + 3 * (1 - t) * t * t * p2.x + t * t * t * p3.x;
        var y = (1 - t) * (1 - t) * (1 - t) * p0.y + 3 * (1 - t) * (1 - t) * t * p1.y + 3 * (1 - t) * t * t * p2.y + t * t * t * p3.y;
        if (i === 0) {
            ctx.moveTo(x, y);
        } else {
            ctx.lineTo(x, y);
        }
    }
    ctx.stroke();
}

之后,我们只需要传入控制点的坐标,我们就能够得到一条曲线了。当然在这个过程中,你依然要调整曲线的视觉效果,但是因为你知道了贝塞尔曲线的运作规律,所以你只要根据视觉给出大致控制点的坐标就可以得到你想要的曲线效果,这在开发过程中是很高效,因为这几乎排除了真实数据对绘图带来的影响。

五、最后

好啦,2024年最后一篇文章到这里就结束啦,接下来是去杭州,我们明年再见,拜拜~~