贝塞尔曲线
文章参考于:https://www.jianshu.com/p/0c9b4b681724 https://gameinstitute.qq.com/community/detail/129188
贝赛尔曲线的前世今生:
贝塞尔曲线,这个命名规则一眼看上去大概是一个叫贝塞尔的数学家发明的。但,贝塞尔曲线依据的最原始的数学公式,是在1912年在数学界广为人知的伯恩斯坦多项式。简单理解,伯恩斯坦多项式可以用来证明,在[ a, b ] 区间上所有的连续函数都可以用多项式来逼近,并且收敛性很强,也就是一致收敛。再简单点,就是一个连续函数,你可以将它写成若干个伯恩斯坦多项式相加的形式,并且,随着 n→∞,这个多项式将一致收敛到原函数,这个就是伯恩斯坦斯的逼近性质。
时光荏苒岁月如梭,镜头切换到了1959年。当时就职于雪铁龙的法国数学家 Paul de Casteljau 开始对伯恩斯坦多项式进行了图形化的尝试,并且提供了一种数值稳定的德卡斯特里奥(de Casteljau) 算法。(多数理论公式是建立在大量且系统的数学建模基础之上研究的规律性成果)根据这个算法,就可以实现 通过很少的控制点,去生成复杂的平滑曲线,也就是贝塞尔曲线。
但贝塞尔曲线的声名大噪,不得不提到1962年就职于雷诺的法国工程师皮埃尔·贝塞尔(Pierre Bézier),他使用这种方法来辅助汽车的车体工业设计(最早计算机的诞生则是为了帮助美国海军绘制弹道图),并且广泛宣传(典型的理论联系实际并获得成功的示例),因此大家称为贝塞尔曲线 。
贝赛尔曲线的数学理论:
既然贝赛尔曲线的本质是通过数学计算公式去绘制平滑的曲线,那就可以通过数学工具进行实际求证以及解释说明。当然对其进行数学求证就没必要了,因为这些伟大的数学家们已经做过了,这里只是解释说明:
- 步骤一:在平面内选3个不同线的点并且依次用线段连接。如下所示..
-
步骤二:在AB和BC线段上找出点D和点E,使得 AD/AB = BE/BC
-
步骤三:连接DE,在DE上寻找点F,F点需要满足:DF/DE = AD/AB = BE/BC
-
步骤四:最最重要的!根据DE线段和计算公式找出所有的F点,记住是所有的F点,然后将其这些点连接起来。那,连接规则是什么?以上图为例,第一个连接点是A-F,第二连接点是A-F1(这个F1必须满足DF1/DE = AD/AB = BE/BC)以此类推,直到最后连接上C点,下面上一个动图加深理解:
-
可能有些朋友还是不理解,那么这个GIF我截下其中的一张图说明,如下图:
动图里的P0、P1、P2分别代表的是上图的:P0 == A;P1 == B;P2 == C。那么这个黑色点,代表的就是F点,绿色线段的2个端点(P0-P1线段上的绿色点,代表是就是D点,P0-P2线段上的绿色点,代表是就是E点)。线段上面点的获取,必须要满足等比关系。
关于贝赛尔曲线的基本数学理论大概就是上面的内容。两个线段根据等比关系找点的贝塞尔曲线,一般也称为二阶贝塞尔曲线。
贝赛尔曲线的N阶拓展(三阶贝塞尔与N阶贝塞尔曲线)
刚才说到,上面的贝赛尔曲线一般称为二阶贝塞尔曲线,既然是二阶贝塞尔曲线,那肯定有三阶贝塞尔曲线、四阶贝赛尔曲线等等。其实三阶贝塞尔与四阶贝赛尔曲线以及N阶贝赛尔曲线曲线的规则都是一样的,都是先在线段上找点,这个点必须要满足等比关系,然后依次连接,下面是三阶贝赛尔曲线的解释说明:
-
步骤一:三阶贝赛尔曲线,简单理解就是在平面内选4个不同线的点并且依次用线段连接(也就是三条线)。如下所示
-
步骤二:同二阶贝塞尔曲线一样首先需要在线段上找对应的点(E、F、G),对应的点必须要符合等比的计算规则,计算规则如下:AE/AB = BF/BC = CG/CD;找到对应的点以后接着依次链接EF、FG;接着在EF、FG线段上面继续找点H、I,对应的点依旧要符合等比的计算规则,也就是 EH/EF = FI/FG;最后连接H、I线段,在HI线段上面继续找点J、点J的计算规则需要符合:EH/EF = FI/FG = HJ/HI
-
步骤三:重复步骤二的动作,找到所有的J点,依次将J点连接起来,这样最终完成了三阶贝赛尔曲线。
整一个三阶贝赛尔曲线的动作加起来就是下面的一张动图:
那么四阶贝赛尔曲线的实现步骤也是一样的,平面上先选取5个点(5点4线)、依次选点(满足等比关系)、依次连接、根据计算规则找到所有的点(逐个连接)。。。。。。
貌似都是从二阶贝塞尔曲线说起的,那么一阶贝赛尔又是怎么样的?一阶贝赛尔如图:
可以看到一阶贝赛尔是一条直线!因此,N阶贝赛尔不仅可以画平滑的曲线也可以画直线,因此自定义控件画直线又多了一种可选择的方式,但是一般用贝赛尔主要是画曲线,这里只是提供了一种别的解决思路;另外,在Android属性动画,系统为我们提供了一个PathInterpolator插值器。这个PathInterpolator里面就有贝塞尔曲线的身影。有兴趣的小伙伴也可以去了解一下。
线性公式
给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:
// 线性
Vector3 Bezier(Vector3 p0, Vector3 p1, float t)
{
return (1 - t) * p0 + t * p1;
}
且其等同于线性插值。
二次方公式
二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
// 二阶曲线
Vector3 Bezier(Vector3 p0, Vector3 p1, Vector3 p2, float t)
{
Vector3 p0p1 = (1 - t) * p0 + t * p1;
Vector3 p1p2 = (1 - t) * p1 + t * p2;
Vector3 result = (1 - t) * p0p1 + t * p1p2;
return result;
}
TrueType字型就运用了以贝兹样条组成的二次贝兹曲线。
三次方公式
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
曲线的参数形式为:
// 三阶曲线
Vector3 Bezier(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
Vector3 result;
Vector3 p0p1 = (1 - t) * p0 + t * p1;
Vector3 p1p2 = (1 - t) * p1 + t * p2;
Vector3 p2p3 = (1 - t) * p2 + t * p3;
Vector3 p0p1p2 = (1 - t) * p0p1 + t * p1p2;
Vector3 p1p2p3 = (1 - t) * p1p2 + t * p2p3;
result = (1 - t) * p0p1p2 + t * p1p2p3;
return result;
}
现代的成像系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓。
n次方公式
最后一个,最难的n次方的公式,其实这个只要能把前面两个理解了,最后一个也不难,都是有规律的,就是不停的去计算插值;点1和点2插值,产生出新的点,点2和点3插值,又产生一个点,依次循环,直到最后只剩下一个点。
// n阶曲线,递归实现
public Vector3 Bezier(float t, List<Vector3> p)
{
if (p.Count < 2)
return p[0];
List<Vector3> newp = new List<Vector3>();
for (int i = 0; i < p.Count - 1; i++)
{
Debug.DrawLine(p[i], p[i + 1]);
Vector3 p0p1 = (1 - t) * p[i] + t * p[i + 1];
newp.Add(p0p1);
}
return Bezier(t, newp);
}
// transform转换为vector3,在调用参数为List<Vector3>的Bezier函数
public Vector3 Bezier(float t, List<Transform> p)
{
if (p.Count < 2)
return p[0].position;
List<Vector3> newp = new List<Vector3>();
for (int i = 0; i < p.Count; i++)
{
newp.Add(p[i].position);
}
return Bezier(t, newp);
}
在unity里实现的效果