【图形学】贝塞尔曲线理论与实践

贝塞尔曲线(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

在这里插入图片描述

那我们通过控制点是怎么绘制出贝塞尔曲线的呢?

通过上图的三阶贝塞尔曲线举例,基本的步骤如下:

  1. 四个控制点通过先后顺序进行连接,形成了三条线段,也就是上图中的 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位置如下图所示。

在这里插入图片描述

  1. 接下来对每一条线段做同样的操作,得到三个控制点 P 0 ′ , P 1 ′ , P 2 ′ P_0^{'},P_1^{'},P_2^{'} P0,P1,P2,如下图所示。

在这里插入图片描述

  1. 然后对这三个控制点重复第1步操作,得出两个控制点 P 0 ′ ′ , P 1 ′ ′ P_0^{''},P_1^{''} P0′′,P1′′,如下图所示。

在这里插入图片描述

  1. 最后再使用同样的方法可以得到,最终的一个点 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=0nPiBi,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(1t)ni,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+(P1P0)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)=(1t)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=01PiBi,1(t)=(1t)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^{'} P0P1 上再利用一阶公式就可以求出最终贝塞尔曲线上的点 P 0 ′ ′ P_0{''} P0′′。具体推导过程如下:

P 0 ′ = ( 1 − t ) P 0 + t P 1 P_0^{'} = (1 - t)P_0 + tP_1 P0=(1t)P0+tP1

P 1 ′ = ( 1 − t ) P 1 + t P 2 P_1^{'} = (1 - t)P_1 + tP_2 P1=(1t)P1+tP2

P 0 ′ ′ = ( 1 − t ) P 0 ′ + t P 1 ′ P^{''}_0 = (1 - t)P_0^{'} + tP_1^{'} P0′′=(1t)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′′=(1t)2P0+2t(1t)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)=(1t)3P0+3t(1t)2P1+3t2(1t)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)=(1t)3P0+3t(1t)2P1+3t2(1t)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);

}

在这里插入图片描述

需要项目源代码请评论区留言或私信

  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值