网站创建方法,中国源码资源网,海尔网站建设策划书,wordpress cos存储文章目录资料援引贝塞尔曲线的用途一阶贝塞尔#xff08;bezier#xff09;曲线二阶贝塞尔#xff08;bezier#xff09;曲线三阶贝塞尔#xff08;bezier#xff09;曲线高阶贝塞尔#xff08;bezier#xff09;曲线三阶贝塞尔曲线求插值#xff08;Slerp#xff09…
文章目录资料援引贝塞尔曲线的用途一阶贝塞尔bezier曲线二阶贝塞尔bezier曲线三阶贝塞尔bezier曲线高阶贝塞尔bezier曲线三阶贝塞尔曲线求插值Slerp资料援引
B站视频wow神奇的贝塞尔曲线
博客贝塞尔曲线简单介绍
知乎曲线篇: 贝塞尔曲线 贝塞尔曲线的用途
基于对汽车的的车身结构进行流体化设计而诞生处理视频状态点之间的图像变化随心所欲绘制曲线比如 一阶贝塞尔bezier曲线 如上P0P_0P0、P1P_1P1 两点构成了一条线段而我们可以通过一个函数——线性插值lerp来根据一个 ttt 值t∈[0,1]t \in [0,1]t∈[0,1] 得到线段上一点 PPP图中一直在滑动的点。而 PPP 的运动轨迹红线便是一阶贝塞尔线段曲线。线性插值的数学形式一阶贝塞尔曲线公式为
Plerp(P0P1t)(1−t)P0tP1Plerp(P_0P_1t)(1-t)P_0 tP_1Plerp(P0P1t)(1−t)P0tP1
一阶贝塞尔曲线有两个端点P0P_0P0、P1P_1P1 0个控制点。 二阶贝塞尔bezier曲线 如上假设现在有点 P2P_2P2 它与 P1P_1P1 构成了新的线段我们得到两个 一阶插值点Q1Q_1Q1、Q2Q_2Q2它们构成了绿色线段值得注意的是两个插值点具有相同的 ttt 值。
而此时我们在绿色线段上生成一个 二阶插值点PPP并让它具有 与两个一阶插值点相同的ttt 值。 那么该点的运动轨迹就是 二阶贝塞尔曲线。其公式推导为
绿色线段左端点的运动轨迹
Q1(1−t)P0tP1Q_1 (1-t)P_0 tP_1 Q1(1−t)P0tP1
绿色线段右端点的运动轨迹
Q2(1−t)P1tP2Q_2 (1-t)P_1 tP_2Q2(1−t)P1tP2
二阶贝塞尔曲线公式
P(1−t)Q1tQ2P (1-t)Q_1 tQ_2P(1−t)Q1tQ2 (1−t)((1−t)P0tP1)t((1−t)P1tP2)(1-t)((1-t)P_0 tP_1) t((1-t)P_1 tP_2)(1−t)((1−t)P0tP1)t((1−t)P1tP2) (1−t)2P02t(t−1)P1t2P2(1-t)^2P_02t(t-1)P_1t^2P_2(1−t)2P02t(t−1)P1t2P2
二阶贝塞尔曲线有两个端点P0P_0P0、P2P_2P2一个控制点P1P_1P1。 三阶贝塞尔bezier曲线 经过对一阶、二阶贝塞尔曲线的研究学习我们能知道贝塞尔曲线通过在两点之间再采点的方式实现降阶每一次选点都是一次的降阶。
P0P_0P0、P1P_1P1、P2P_2P2、P3P_3P3 通过生成插值点 Q1Q_1Q1、Q2Q_2Q2、Q3Q_3Q3 来构成二阶贝塞尔绿色线段在此基础上生成插值点 O1O_1O1、O2O_2O2 来构成一阶贝塞尔蓝色线段之后以 O1O_1O1、O2O_2O2 上的插值点 PPP 的运动轨迹来生成三阶贝塞尔曲线。
公式推导过程同二阶贝塞尔曲线因此不做赘述直接贴出公式
P(1−t)3P03t(1−t)2P13t2(1−t)P2t3P3P(1-t)^3P_03t(1-t)^2P_13t^2(1-t)P_2t^3P_3P(1−t)3P03t(1−t)2P13t2(1−t)P2t3P3
三阶贝塞尔曲线有两个端点P0P_0P0、P3P_3P3两个控制点P1P_1P1、P2P_2P2。 高阶贝塞尔bezier曲线
四阶贝塞尔曲线示意图 五阶贝塞尔曲线示意图 高阶贝塞尔曲线公式
P(t)∑i0nPiBi,n(t)t∈[0,1]P(t)\sum_{i0}^{n}P_iB_{i,n}(t)t \in [0,1]P(t)i0∑nPiBi,n(t)t∈[0,1]
Bi,n(t)Cniti(1−t)n−in!i!(n−i)!ti(1−t)n−i【i0,1,...,n】B_{i,n}(t)C_n^it^i(1-t)^{n-i}\frac{n!}{i!(n-i)!}t^i(1-t)^{n-i}【i0,1,...,n】Bi,n(t)Cniti(1−t)n−ii!(n−i)!n!ti(1−t)n−i【i0,1,...,n】 三阶贝塞尔曲线求插值Slerp
在熟悉了贝塞尔曲线的相关概念之后我们来了解一下它的具体应用。通常它的应用场景是 已知两个端点和两个控制点的情况下根据 动画进度向量 PxP_xPx 求 ttt再由 ttt 确认的曲线求 PyP_yPy。 回顾一下三阶贝塞尔曲线公式 P(1−t)3P03t(1−t)2P13t2(1−t)P2t3P3P(1-t)^3P_03t(1-t)^2P_13t^2(1-t)P_2t^3P_3P(1−t)3P03t(1−t)2P13t2(1−t)P2t3P3
公式中的 P0P_0P0、P1P_1P1 等都是二维向量由两个一维向量 PxP_xPx 和 PyP_yPy 构成。而我们根据 ttt 求 PPP本质上是根据 ttt 来求一个坐标 (x,y)(x,y)(x,y)。因此可将公式拆解在两个一维向量上 y(1−t)3Py03t(1−t)2Py13t2(1−t)Py2t3Py3y(1-t)^3P_{y0}3t(1-t)^2P_{y1}3t^2(1-t)P_{y2}t^3P_{y3}y(1−t)3Py03t(1−t)2Py13t2(1−t)Py2t3Py3
x(1−t)3Px03t(1−t)2Px13t2(1−t)Px2t3Px3x(1-t)^3P_{x0}3t(1-t)^2P_{x1}3t^2(1-t)P_{x2}t^3P_{x3}x(1−t)3Px03t(1−t)2Px13t2(1−t)Px2t3Px3
而由于我们在处理动画时通常起点 P0P_0P0 和终点 P3P_3P3 都是可以确定的【P0(0,0)、P3(1,1)P_0(0,0)、P_3(1,1)P0(0,0)、P3(1,1)】因此上述公式可以化简为以 xxx 举例yyy 同理
x3t(1−t)2Py13t2(1−t)Py2t3x3t(1-t)^2P_{y1}3t^2(1-t)P_{y2}t^3x3t(1−t)2Py13t2(1−t)Py2t3
完全展开 3Py1t−6Py1t23Py1t33Py2t2−3Py2t3t33P_{y1}t-6P_{y1}t^23P_{y1}t^33P_{y2}t^2-3P_{y2}t^3t^33Py1t−6Py1t23Py1t33Py2t2−3Py2t3t3
提取三次方系数 aaa a3Py1−3Py21a3P_{y1}-3P_{y2}1a3Py1−3Py21
提取二次方系数 bbb b3Py2−6Py1b3P_{y2}-6P_{y1}b3Py2−6Py1
提取一次方系数 ccc c3Py1c3P_{y1}c3Py1
将公式简化为 xat3bt2ctxat^3bt^2ctxat3bt2ct
移动 xxx将公式变为一元三次方程 at3bt2ct−x0at^3bt^2ct-x0at3bt2ct−x0
此时就可以通过卡尔丹公式根据 xxx 求出来 ttt。之后根据 ttt 可以求得 yyy y(1−t)3Py03t(1−t)2Py13t2(1−t)Py2t3Py3y(1-t)^3P_{y0}3t(1-t)^2P_{y1}3t^2(1-t)P_{y2}t^3P_{y3}y(1−t)3Py03t(1−t)2Py13t2(1−t)Py2t3Py3
代码实现
double SlerpWithCubicBazier(double pX1, double pY1, double pX2, double pY2, double x) {// x为动画进度并不是tt只是一个参数先根据x求t再由t确认的曲线上求y// 参考 https://github.com/gre/bezier-easing/blob/master/src/index.jsdouble t 0.0;if (x 0.0) {t 0.0;}else if (x 1.0) {t 1.0;}else {// x (1-t)^3*P0x 3*(1-t)^2*t*P1x 3*(1-t)*t^2*P2x t^3*P3x// 提取系数double a 0.0 3 * pX1 - 3 * pX2 1.0;double b 3 * 0.0 - 6 * pX1 3 * pX2;double c 0.0 3 * pX1;// 公式可化简为 x at^3 bt^2 ct// 转换为基于 t 的一元三次方程at^3 bt^2 ct - x 0double d 0 - x;// 那么就可以通过 SolveCubic 函数根据a、b、c、d四个系数来求解一元三次方程的一个实根// 可能该一元三次方程的根不止一个但不重要即使有多个根我们也只需要其中之一且要求这个根是在 01 之间的符合 t 的取值范围要求如果没有根/没有符合要求的根我们会返回 -1double tTemp SolveCubic(a, b, c, d);if (tTemp -1) {return -1;}t tTemp;}// Gy(t) P0*(1-t)^3 3*P1*t*(1-t)^2 3*P2*t^2*(1-t) P3*t^3 t[0,1]// PY00.0 PY31.0double coef1 0.0 * (1.0 - t) * (1.0 - t) * (1.0 - t);double coef2 pY1 * 3 * t * (1.0 - t) * (1.0 - t);double coef3 pY2 * 3 * t * t * (1.0 - t);double coef4 1.0 * t * t * t;double gt coef1 coef2 coef3 coef4;return gt;
}SolveCubic 函数的具体实现如下值得注意的是并不能简单的将此函数的作用等同于求解一元三次方程本函数的本质作用是贝塞尔曲线中根据 x 求出 t这两者有什么区别呢举个具体的例子下面的代码第 5 行有这样的语句
if (d 0) return 0;在 d0 的情况下普通一元三次方程是可以继续求解的但是在 SlerpWithCubicBazier 调用 SolveCubic 时是以 d0−xd0-xd0−x 的形式传值的d0d0d0 代表 x0x0x0此时曲线位于起点t 的值可以确定无需通过解方程获得即 t0t0t0。
double FCPSolveCubic(double a, double b, double c, double d) {/* a0 视为一元二次方程式 */if (a 0) return FCPSolveQuadratic(b, c, d);/* d0表明x0则t0*/if (d 0) return 0;/* 将三次方的系数变为1方便后续判别式中的计算即不用考虑 a^2 这一项了 */b / a;c / a;d / a;/* q和r对应求根公式中的p和qdis即是求根公式的判别式 △ */double q (3.0 * c - FCPSquared(b)) / 9.0;double r (-27.0 * d b * (9.0 * c - 2.0 * FCPSquared(b))) / 54.0;double disc FCPCubed(q) FCPSquared(r);double term1 b / 3.0;if (disc 0) {/* 运用卡尔丹公式求得一个实根 */double s r sqrtf(disc);s (s 0) ? - FCPCubicRoot(-s) : FCPCubicRoot(s);double t r - sqrtf(disc);t (t 0) ? - FCPCubicRoot(-t) : FCPCubicRoot(t);double result -term1 s t;if (result 0 result 1) return result;} else if (disc 0) {double r13 (r 0) ? - FCPCubicRoot(-r) : FCPCubicRoot(r);double result -term1 2.0 * r13;if (result 0 result 1) return result;result -(r13 term1);if (result 0 result 1) return result;} else {q -q;double dum1 q * q * q;dum1 acosf(r / sqrtf(dum1));double r13 2.0 * sqrtf(q);double result -term1 r13 * cos(dum1 / 3.0);if (result 0 result 1) return result;result -term1 r13 * cos((dum1 2.0 * M_PI) / 3.0);if (result 0 result 1) return result;result -term1 r13 * cos((dum1 4.0 * M_PI) / 3.0);if (result 0 result 1) return result;}return -1;
}上面代码中用到的知识
一元三次方程判别式
△q24p227△ \frac{q^2}{4} \frac{p^2}{27}△4q227p2
标准型方程中卡尔丹公式的一个实根