贝塞尔曲线(Bezier Curve)在计算机图形领域应用非常广泛,比如我们 CSS 动画、 Canvas 以及 Photoshop 等都可以看到贝塞尔曲线的身影。
贝塞尔曲线类型
贝塞尔曲线根据_控制点_的数量分为:
- 一阶贝塞尔曲线(2 个控制点)
- 二阶贝塞尔曲线(3 个控制点)
- 三阶贝塞尔曲线(4 个控制点)
- n阶贝塞尔曲线(n+1个控制点)
如何绘制出贝塞尔曲线
下图为一个三阶的贝塞尔曲线,包括四个控制点,分别为 P 0 , P 1 , P 2 , P 3 P_0,P_1,P_2,P_3 P0,P1,P2,P3
那我们通过控制点是怎么绘制出贝塞尔曲线的呢?
通过上图的三阶贝塞尔曲线举例,基本的步骤如下:
- 四个控制点通过先后顺序进行连接,形成了三条线段,也就是上图中的 P 0 P 1 , P 1 P 2 , P 2 P 3 P_0P_1,P_1P_2,P_2P_3 P0P1,P1P2,P2P3,然后通过一个参数 t , 其中 t ∈ [ 0 , 1 ] t,其中 t\in[0,1] t,其中t∈[0,1]该参数的值等于线段上某一个点距离起点的长度除以线段长度。就比如 P 0 P 1 P_0P_1 P0P1线段上有一个点 P 0 ′ ,此时 t 的值就是 P_0^{'},此时t的值就是 P0′,此时t的值就是 P 0 P ′ P 0 P 1 \frac{P_0P_{'}}{P_0P_1} P0P1P0P′,其中 P 0 ′ P_0^{'} P0′位置如下图所示。
- 接下来对每一条线段做同样的操作,得到三个控制点 P 0 ′ , P 1 ′ , P 2 ′ P_0^{'},P_1^{'},P_2^{'} P0′,P1′,P2′,如下图所示。
- 然后对这三个控制点重复第1步操作,得出两个控制点 P 0 ′ ′ , P 1 ′ ′ P_0^{''},P_1^{''} P0′′,P1′′,如下图所示。
- 最后再使用同样的方法可以得到,最终的一个点 P 0 ′ ′ ′ P_0^{'''} P0′′′,如下图所示,此时这个点就是贝塞尔曲线上的一个点。
通过控制t的值,由 0 增加至 1,就绘制出了一条由起点P_0至终点P_1的贝塞尔曲线。
通过下面这个动画直观感受一下绘制的过程:
求贝塞尔曲线上的点坐标
贝塞尔曲线的定义
给定n+1个控制点,则n次贝塞尔曲线的定义为
p ( t ) = ∑ i = 0 n P i B i , n ( t ) , t ∈ [ 0 , 1 ] p(t)=\sum_{i=0}^{n} {P_iB_{i,n}(t)},t\in[0,1] p(t)=i=0∑nPiBi,n(t),t∈[0,1]式子中 B i , n ( t ) B_{i,n}(t) Bi,n(t)是贝塞尔基数,表达式为 B i , n ( t ) = C n i t i ( 1 − t ) n − i , i = 0 , 1 , 2... , n B_{i,n}(t)=C_n^it^i(1-t)^{n-i},i=0,1,2...,n Bi,n(t)=Cniti(1−t)n−i,i=0,1,2...,n
1、一阶贝塞尔曲线
对于一阶贝塞尔曲线,我们可以通过几何知识,很容易根据t的值得出线段上那个点的坐标:
p ( t ) = P 0 + ( P 1 − P 0 ) t p(t) = P_0 + (P_1 - P_0)t p(t)=P0+(P1−P0)t
然后可以得出:
p ( t ) = ( 1 − t ) P 0 + t P 1 , t ∈ [ 0 , 1 ] p(t) = (1 - t)P_0 + tP_1,t\in[0,1] p(t)=(1−t)P0+tP1,t∈[0,1]
也可以直接根据定义得出 p ( t ) = ∑ i = 0 1 P i B i , 1 ( t ) = ( 1 − t ) P 0 + t P 1 p(t)=\sum_{i=0}^1{P_iB_{i,1}(t)}=(1-t)P_0+tP_1 p(t)=i=0∑1PiBi,1(t)=(1−t)P0+tP1
2、二阶贝塞尔曲线
对于二阶贝塞尔曲线,其实你可以理解为:在 P 0 P 1 P_0P_1 P0P1上利用一阶公式求出点 P 0 ′ P_0^{'} P0′,然后在
P_1P_2上利用一阶公式求出点 P 1 ′ P_1^{'} P1′,最后在 P 0 ′ P 1 ′ P_0^{'}P_1^{'} P0′P1′ 上再利用一阶公式就可以求出最终贝塞尔曲线上的点 P 0 ′ ′ P_0{''} P0′′。具体推导过程如下:
P 0 ′ = ( 1 − t ) P 0 + t P 1 P_0^{'} = (1 - t)P_0 + tP_1 P0′=(1−t)P0+tP1
P 1 ′ = ( 1 − t ) P 1 + t P 2 P_1^{'} = (1 - t)P_1 + tP_2 P1′=(1−t)P1+tP2
P 0 ′ ′ = ( 1 − t ) P 0 ′ + t P 1 ′ P^{''}_0 = (1 - t)P_0^{'} + tP_1^{'} P0′′=(1−t)P0′+tP1′
整理得到 P 0 ′ ′ = ( 1 − t ) 2 P 0 + 2 t ( 1 − t ) P 1 + t 2 P 2 P_{0}^{''}=(1-t)^2P_0+2t(1-t)P_1+t^2P_2 P0′′=(1−t)2P0+2t(1−t)P1+t2P2
3、三阶贝塞尔曲线
!
与二阶贝塞尔曲线类似,可以通过相同的几何方法得出以下坐标公式:
p ( t ) = ( 1 − t ) 3 P 0 + 3 t ( 1 − t ) 2 P 1 + 3 t 2 ( 1 − t ) P 2 + t 3 P 3 , t ∈ [ 0 , 1 ] p(t) = (1 - t)^3P_0 + 3t(1 - t)^2P_1 + 3t^2(1 - t)P_2 + t^3P_3 , t\in[0, 1] p(t)=(1−t)3P0+3t(1−t)2P1+3t2(1−t)P2+t3P3,t∈[0,1]
应用
三次贝塞尔曲线绘制圆
如图所示,使用三次贝塞尔曲线可以绘制出1/4个圆,那么通过二维变换就可以绘制出一个圆。 假设点 P 0 0 ( 0 , 1 ) 点 P 1 0 ( m , 1 ) 点 P 2 0 ( 1 , m ) 点 P 3 0 ( 1 , 0 ) 假设点P_0^0(0,1)点P_1^0(m,1)点P_2^0(1,m)点P_3^0(1,0) 假设点P00(0,1)点P10(m,1)点P20(1,m)点P30(1,0)
三次贝塞尔曲线的表达式为 p ( t ) = ( 1 − t ) 3 P 0 + 3 t ( 1 − t ) 2 P 1 + 3 t 2 ( 1 − t ) P 2 + t 3 P 3 p(t) = (1 - t)^3P_0 + 3t(1 - t)^2P_1 + 3t^2(1 - t)P_2 + t^3P_3 p(t)=(1−t)3P0+3t(1−t)2P1+3t2(1−t)P2+t3P3
对于圆弧的中点,t=0.5有 ( 2 2 , 2 2 ) = 1 8 P 0 0 + 3 8 P 1 0 + 3 8 P 2 2 + 1 8 P 3 0 (\frac{\sqrt{2}}{2},\frac{\sqrt{2}}{2})=\frac{1}{8}P_0^0+\frac{3}{8}P_1^0+\frac{3}{8}P_2^2+\frac{1}{8}P_3^0 (22,22)=81P00+83P10+83P22+81P30
带入点的坐标可以得到 m = 4 ( 2 − 1 ) 3 ≈ 0.5523 m=\frac{4(\sqrt{2}-1)}{3}\approx0.5523 m=34(2−1)≈0.5523
/*曲线的轨迹可以用直线去拟合也可以用一个个像素点*/
void CBezierCurve3::DrawCurve(CDC* pDC)
{CPen NewPen, *OldPen;NewPen.CreatePen(PS_SOLID, 3, RGB(255, 0,0));OldPen=pDC->SelectObject(&NewPen);CPoint2 p00 = m_p[0], p10 = m_p[1], p20 = m_p[2], p30 = m_p[3];//控制点CPoint2 p01, p11, p21, p02, p12, p03;//插值点double step = 0.001;/*pDC->MoveTo(int(m_p[0].m_x + 0.5), int(m_p[0].m_y + 0.5));*/for (double t = 0.00; t < 1; t += step) {p01 = (1 - t) * p00 + t * p10;p11 = (1 - t) * p10 + t * p20;p21 = (1 - t) * p20 + t * p30;p02 = (1 - t) * p01 + t * p11;p12 = (1 - t) * p11 + t * p21;p03 = (1 - t) * p02 + t * p12;/*pDC->LineTo(int(p03.m_x + 0.5), int(p03.m_y + 0.5));*/auto c = LinearInterP(t, CRGB(1, 0, 0), CRGB(1.0, 0, 1.0));pDC->SetPixelV(p03.m_x, p03.m_y, COLOR(c));}/*pDC->LineTo(int(m_p[3].m_x + 0.5), int(m_p[3].m_y + 0.5));*/pDC->SelectObject(OldPen);NewPen.DeleteObject();
}
#define M 4.0/3*(sqrt(2)-1)
C贝塞尔曲线View::C贝塞尔曲线View() noexcept
{CPoint2 p[4] = { {0,1},{M,1} ,{1,M},{1,0} };for (int i = 0; i < 4; i++) {curve[i].ReadPoint(p);transform[i].SetMatrix(curve[i].m_p, 4);}transform[0].Scale(100, 100);transform[1].Scale(-100,100);transform[2].Scale(-100,-100);transform[3].Scale(100,-100);
}
多条贝塞尔曲线拼接
假设有两段三贝塞尔曲线p(t)和q(t)。曲线的控制点分别是
P 0 , P 1 , P 2 , P 3 , Q 0 , Q 1 , Q 2 , Q 3 P_0,P_1,P_2,P_3,Q_0,Q_1,Q_2,Q_3 P0,P1,P2,P3,Q0,Q1,Q2,Q3如果这两段曲线要拼接,那么就要求 P 2 , Q 0 ( P 3 ) , Q 1 P_2,Q_0(P_3),Q_1 P2,Q0(P3),Q1三点共线。
- 二次贝塞尔曲线绘制爱心
void CBezierCurve2::DrawCurve(CDC* pDC)
{CPen NewPen, * OldPen;NewPen.CreatePen(PS_SOLID, 3, RGB(255, 0, 0));OldPen = pDC->SelectObject(&NewPen);CPoint2 p00 = m_p[0], p10 = m_p[1], p20 = m_p[2];//控制点CPoint2 p01, p11, p02;//插值点double step = 0.00001;/*pDC->MoveTo(int(m_p[0].m_x + 0.5), int(m_p[0].m_y + 0.5));*/for (double t = 0.00; t < 1; t += step) {p01 = (1 - t) * p00 + t * p10;p11 = (1 - t) * p10 + t * p20;p02 = (1 - t) * p01 + t * p11;/*pDC->LineTo(int(p03.m_x + 0.5), int(p03.m_y + 0.5));*/auto c = LinearInterP(t, CRGB(1, 0, 0), CRGB(1.0, 0, 1.0));pDC->SetPixelV(p02.m_x, p02.m_y, COLOR(c));}/*pDC->LineTo(int(m_p[3].m_x + 0.5), int(m_p[3].m_y + 0.5));*/pDC->SelectObject(OldPen);NewPen.DeleteObject();
}
C贝塞尔曲线View::C贝塞尔曲线View() noexcept
{CPoint2 P2[3] = { {0,0},{1.4,1.8},{2,0} };CPoint2 P3[3] = { {2,0},{2.3,-3 * 2.3 + 6},{0,-2.6} };\\P2[1],P2[2](P3[0]),P3[1]三点共线curve2[0].ReadPoint(P2);transform[0].SetMatrix(curve2[0].m_p, 3);transform[0].Scale(100, 100);curve2[1].ReadPoint(P2);transform[1].SetMatrix(curve2[1].m_p, 3);transform[1].Scale(-100, 100);curve2[2].ReadPoint(P3);transform[2].SetMatrix(curve2[2].m_p, 3);transform[2].Scale(100, 100);curve2[3].ReadPoint(P3);transform[3].SetMatrix(curve2[3].m_p, 3);transform[3].Scale(-100, 100);}
需要项目源代码请评论区留言或私信