【图形学】双三次贝塞尔曲线绘制方法

本文详细解释了双三次贝塞尔曲面的数学定义,包括由16个控制点构成的矩阵表示形式,以及如何通过三次Bernstein基函数进行计算。同时介绍了如何使用递归方法和编程实现来绘制和细分这种曲面的过程。
摘要由CSDN通过智能技术生成

双三次贝塞尔曲线的定义

双三次贝塞尔曲面是由16个控制点定义的曲面,通常表示为4x4矩阵。
在这里插入图片描述

曲面的公式如下:

p ( u , v ) = ∑ i = 0 3 ∑ j = 0 3 P i , j B i , 3 ( u ) B j , 3 ( v ) , ( u , v ) ∈ [ 0 , 1 ] × [ 0 , 1 ] p(u,v)=\sum_{i=0}^3\sum_{j=0}^3P_{i,j}B_{i,3}(u)B_{j,3}(v),\\(u,v)\in[0,1]\times[0,1] p(u,v)=i=03j=03Pi,jBi,3(u)Bj,3(v),(u,v)[0,1]×[0,1]

其中, P i , j , i , j = 0 , 1 , 2 , 3 P_{i,j},i,j=0,1,2,3 Pi,j,i,j=0,1,2,3​是曲面上的16控制点; B i , 3 ( u ) 和 B j , 3 ( v ) B_{i, 3} ( u )和 B_{j,3}( v ) Bi,3(u)Bj,3(v)是三次Bernstein基函数,定义如下:
{ B 0 , 3 ( u ) = ( 1 − u ) 3 B 1 , 3 ( u ) = 3 u ( 1 − u ) 2 B 2 , 3 ( u ) = 3 u 2 ( 1 − u ) B 3 , 3 ( u ) = u 3 { B 0 , 3 ( v ) = ( 1 − v ) 3 B 1 , 3 ( v ) = 3 v ( 1 − v ) 2 B 2 , 3 ( v ) = 3 v 2 ( 1 − v ) B 3 , 3 ( v ) = v 3 \begin{cases} B_{0,3}(u)=(1-u)^3\\ B_{1,3}(u)=3u(1-u)^2\\ B_{2,3}(u)=3u^2(1-u)\\ B_{3,3}(u)=u^3 \end{cases}\begin{cases} B_{0,3}(v)=(1-v)^3\\ B_{1,3}(v)=3v(1-v)^2\\ B_{2,3}(v)=3v^2(1-v)\\ B_{3,3}(v)=v^3 \end{cases} B0,3(u)=(1u)3B1,3(u)=3u(1u)2B2,3(u)=3u2(1u)B3,3(u)=u3 B0,3(v)=(1v)3B1,3(v)=3v(1v)2B2,3(v)=3v2(1v)B3,3(v)=v3
用矩阵表示公式如下
p ( u , v ) = ( B 0 , 3 ( u ) B 1 , 3 ( u ) B 2 , 3 ( u ) B 3 , 3 ( u ) ) ( P 00 P 01 P 02 P 03 P 10 P 11 P 12 P 13 P 20 P 21 P 22 P 23 P 30 P 31 P 32 P 33 ) ( B 0 , 3 ( v ) B 0 , 1 ( v ) B 0 , 2 ( v ) B 0 , 3 ( v ) ) p(u,v)=\begin{pmatrix}B_{0,3}(u)&B_{1,3}(u)&B_{2,3}(u)&B_{3,3}(u) \end{pmatrix}\begin{pmatrix} P_{00}&P_{01}&P_{02}&P_{03}\\ P_{10}&P_{11}&P_{12}&P_{13}\\ P_{20}&P_{21}&P_{22}&P_{23}\\ P_{30}&P_{31}&P_{32}&P_{33}\end{pmatrix}\begin{pmatrix} B_{0,3}(v)&B_{0,1}(v)&B_{0,2}(v)&B_{0,3}(v) \end{pmatrix} p(u,v)=(B0,3(u)B1,3(u)B2,3(u)B3,3(u)) P00P10P20P30P01P11P21P31P02P12P22P32P03P13P23P33 (B0,3(v)B0,1(v)B0,2(v)B0,3(v))
将Bernstein基数带入得到如下公式
p ( u , v ) = ( u 3 u 2 u 1 ) ( − 1 3 − 3 1 3 − 6 3 0 − 3 3 0 0 1 0 0 0 ) ( P 00 P 01 P 02 P 03 P 10 P 11 P 12 P 13 P 20 P 21 P 22 P 23 P 30 P 31 P 32 P 33 ) ( − 1 3 − 3 1 3 − 6 3 0 − 3 3 0 0 1 0 0 0 ) ( v 3 v 2 v 1 ) p(u,v)=\begin{pmatrix}u^3&u^2&u&1\end{pmatrix}\begin{pmatrix} -1&3&-3&1\\3&-6&3&0\\-3&3&0&0\\1&0&0&0 \end{pmatrix}\begin{pmatrix} P_{00}&P_{01}&P_{02}&P_{03}\\ P_{10}&P_{11}&P_{12}&P_{13}\\ P_{20}&P_{21}&P_{22}&P_{23}\\ P_{30}&P_{31}&P_{32}&P_{33}\end{pmatrix}\begin{pmatrix} -1&3&-3&1\\3&-6&3&0\\-3&3&0&0\\1&0&0&0 \end{pmatrix}\begin{pmatrix}v^3\\v^2\\v\\1\end{pmatrix} p(u,v)=(u3u2u1) 1331363033001000 P00P10P20P30P01P11P21P31P02P12P22P32P03P13P23P33 1331363033001000 v3v2v1

令 U = ( u 3 u 2 u 1 ) , V = ( v 3 v 2 v 1 ) M = ( − 1 3 − 3 1 3 − 6 3 0 − 3 3 0 0 1 0 0 0 ) , P = ( P 00 P 01 P 02 P 03 P 10 P 11 P 12 P 13 P 20 P 21 P 22 P 23 P 30 P 31 P 32 P 33 ) 那么 p ( u , v ) = U M P M T V T 令U=\begin{pmatrix}u^3&u^2&u&1\end{pmatrix},V=\begin{pmatrix}v^3&v^2&v&1\end{pmatrix}\\M=\begin{pmatrix} -1&3&-3&1\\3&-6&3&0\\-3&3&0&0\\1&0&0&0 \end{pmatrix},P=\begin{pmatrix} P_{00}&P_{01}&P_{02}&P_{03}\\ P_{10}&P_{11}&P_{12}&P_{13}\\ P_{20}&P_{21}&P_{22}&P_{23}\\ P_{30}&P_{31}&P_{32}&P_{33}\end{pmatrix} \\那么p(u,v)=UMPM^TV^T U=(u3u2u1),V=(v3v2v1)M= 1331363033001000 ,P= P00P10P20P30P01P11P21P31P02P12P22P32P03P13P23P33 那么p(u,v)=UMPMTVT
对得到的这个式子进行编程就可以绘制出一个双三次贝塞尔曲线。

绘制双贝塞尔曲面

双三次贝塞尔曲面可以看着一个弯曲的四面体,采用四叉树递归划分法进行细分,递归细分次数足够时分割出来的子曲面近似为一个平面四边形,这些平面四边形拼成了双三次贝塞尔曲线。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

递归绘制细分曲面

递归函数

void CBezier::Recursion(CDC* pDC, int ReNumber, CMesh Mesh)
{
	if (0 == ReNumber)
	{
		Tessellation(Mesh);//细分曲面,根据公式求坐标,将(u,v)点转换为(x,y)点
		DrawQuadrilateral(pDC);//绘制小平面四边形
		return;
	}
	else
	{
		CDimension2 Mid = (Mesh.m_BottomLeft + Mesh.m_TopRight) / 2.0;
		CMesh SubMesh[4];//一分为四个
		//左下子长方形
		SubMesh[0].m_BottomLeft = Mesh.m_BottomLeft;
		SubMesh[0].m_BottomRight = CDimension2(Mid.m_u, Mesh.m_BottomLeft.m_v);
		SubMesh[0].m_TopRight = CDimension2(Mid.m_u, Mid.m_v);
		SubMesh[0].m_TopLeft = CDimension2(Mesh.m_BottomLeft.m_u, Mid.m_v);
		//右下子长方形
		SubMesh[1].m_BottomLeft = SubMesh[0].m_BottomRight;
		SubMesh[1].m_BottomRight = Mesh.m_BottomRight;
		SubMesh[1].m_TopRight = CDimension2(Mesh.m_BottomRight.m_u, Mid.m_v);
		SubMesh[1].m_TopLeft = SubMesh[0].m_TopRight;
		//右上子长方形
		SubMesh[2].m_BottomLeft = SubMesh[1].m_TopLeft;
		SubMesh[2].m_BottomRight = SubMesh[1].m_TopRight;
		SubMesh[2].m_TopRight = Mesh.m_TopRight;
		SubMesh[2].m_TopLeft = CDimension2(Mid.m_u, Mesh.m_TopRight.m_v);
		//左上子长方形
		SubMesh[3].m_BottomLeft = SubMesh[0].m_TopLeft;
		SubMesh[3].m_BottomRight = SubMesh[2].m_BottomLeft;
		SubMesh[3].m_TopRight = SubMesh[2].m_TopLeft;
		SubMesh[3].m_TopLeft = Mesh.m_TopLeft;
		Recursion(pDC, ReNumber - 1, SubMesh[0]);//递归绘制4个子曲面
		Recursion(pDC, ReNumber - 1, SubMesh[1]);
		Recursion(pDC, ReNumber - 1, SubMesh[2]);
		Recursion(pDC, ReNumber - 1, SubMesh[3]);
	}
}

求细分曲面四个顶点的坐标

void CBezier::Tessellation(CMesh Mesh)
{
	double M[4][4];//系数矩阵M
	M[0][0] = -1, M[0][1] = 3, M[0][2] = -3, M[0][3] = 1;
	M[1][0] = 3, M[1][1] = -6, M[1][2] = 3, M[1][3] = 0;
	M[2][0] = -3, M[2][1] = 3, M[2][2] = 0, M[2][3] = 0;
	M[3][0] = 1, M[3][1] = 0, M[3][2] = 0, M[3][3] = 0;
	CPoint3 P3[4][4];//曲线计算用控制点数组
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			P3[i][j] = m_CtrPt[i][j];
	LeftMultiplyMatrix(M, P3);//系数矩阵左乘三维点矩阵
	TransposeMatrix(M);//计算转置矩阵
	RightMultiplyMatrix(P3, M);//系数矩阵右乘三维点矩阵
	double u0, u1, u2, u3, v0, v1, v2, v3;//u、v参数的幂
	double u[4] = { Mesh.m_BottomLeft.m_u,Mesh.m_BottomRight.m_u ,Mesh.m_TopRight.m_u ,Mesh.m_TopLeft.m_u };
	double v[4] = { Mesh.m_BottomLeft.m_v,Mesh.m_BottomRight.m_v ,Mesh.m_TopRight.m_v ,Mesh.m_TopLeft.m_v };
	for (int i = 0; i < 4; i++)
	{
		u3 = pow(u[i], 3.0), u2 = pow(u[i], 2.0), u1 = u[i], u0 = 1;
		v3 = pow(v[i], 3.0), v2 = pow(v[i], 2.0), v1 = v[i], v0 = 1;
		CPoint3 Pt = (u3 * P3[0][0] + u2 * P3[1][0] + u1 * P3[2][0] + u0 * P3[3][0]) * v3
			+ (u3 * P3[0][1] + u2 * P3[1][1] + u1 * P3[2][1] + u0 * P3[3][1]) * v2
			+ (u3 * P3[0][2] + u2 * P3[1][2] + u1 * P3[2][2] + u0 * P3[3][2]) * v1
			+ (u3 * P3[0][3] + u2 * P3[1][3] + u1 * P3[2][3] + u0 * P3[3][3]) * v0;
		m_QuadrPoint[i] = Pt;
	}
}

绘制细分曲面,就是知道了四个点绘制四边形

void CBezier::DrawQuadrilateral(CDC* pDC)
{
	CPoint2 ScreenPoint[4];//二维投影点
	for (int nPoint = 0; nPoint < 4; nPoint++)
		ScreenPoint[nPoint] = m_QuadrPoint[nPoint];//正交投影
	CPen NewPen, * pOldPen;
	NewPen.CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
	pOldPen = pDC->SelectObject(&NewPen);
	pDC->MoveTo(ROUND(ScreenPoint[0].m_x), ROUND(ScreenPoint[0].m_y));
	pDC->LineTo(ROUND(ScreenPoint[1].m_x), ROUND(ScreenPoint[1].m_y));
	pDC->LineTo(ROUND(ScreenPoint[2].m_x), ROUND(ScreenPoint[2].m_y));
	pDC->LineTo(ROUND(ScreenPoint[3].m_x), ROUND(ScreenPoint[3].m_y));
	pDC->LineTo(ROUND(ScreenPoint[0].m_x), ROUND(ScreenPoint[0].m_y));
	pDC->SelectObject(pOldPen);
	NewPen.DeleteObject();
}

第二种方法

在这里插入图片描述

在四根贝塞尔曲线p(t),q(t),r(t),s(t)上去相同的t,根据定义可以得到四个点P,Q,R,S,这四个点又可以画一个三次贝塞尔曲线,那么在t从0递增至1的过程中就会产生一系列的三次贝塞尔曲线,这一组曲线就构成了一个曲面。
在这里插入图片描述

需要项目代码的可以评论区留言或者私信

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是使用EasyX图形库绘制三次贝塞尔曲面的示例代码: ``` #include <graphics.h> #include <cmath> const int N = 4; // 控制点矩阵大小 struct Point { int x, y; }; Point controlPoints[N][N]; // 控制点矩阵 const int curvePointsCount = 20; // 每个方向上的曲线点数 Point curvePoints[curvePointsCount][curvePointsCount]; // 存储曲线上的点 // 计算组合数 int binomialCoeff(int n, int k) { int res = 1; if (k > n - k) k = n - k; for (int i = 0; i < k; ++i) { res *= (n - i); res /= (i + 1); } return res; } // 计算Bezier曲线上的点 Point bezierCurvePoint(Point* points, int n, float t) { Point res = { 0, 0 }; for (int i = 0; i < n; i++) { float coeff = binomialCoeff(n - 1, i) * pow(t, i) * pow(1 - t, n - 1 - i); res.x += points[i].x * coeff; res.y += points[i].y * coeff; } return res; } // 计算Bezier曲面上的点 Point bezierSurfacePoint(Point points[N][N], float u, float v) { Point uCurvePoints[N], vCurvePoints[N]; for (int i = 0; i < N; i++) { Point rowPoints[N]; for (int j = 0; j < N; j++) { rowPoints[j] = points[i][j]; } uCurvePoints[i] = bezierCurvePoint(rowPoints, N, u); } for (int i = 0; i < N; i++) { Point colPoints[N]; for (int j = 0; j < N; j++) { colPoints[j] = points[j][i]; } vCurvePoints[i] = bezierCurvePoint(colPoints, N, v); } return bezierCurvePoint(uCurvePoints, N, v); } void drawBezierSurface(Point points[N][N]) { // 计算曲面上的所有点 for (int i = 0; i < curvePointsCount; i++) { for (int j = 0; j < curvePointsCount; j++) { float u = i * 1.0f / (curvePointsCount - 1); float v = j * 1.0f / (curvePointsCount - 1); curvePoints[i][j] = bezierSurfacePoint(points, u, v); } } // 绘制曲面 for (int i = 0; i < curvePointsCount - 1; i++) { for (int j = 0; j < curvePointsCount - 1; j++) { Point p1 = curvePoints[i][j]; Point p2 = curvePoints[i + 1][j]; Point p3 = curvePoints[i + 1][j + 1]; Point p4 = curvePoints[i][j + 1]; line(p1.x, p1.y, p2.x, p2.y); line(p2.x, p2.y, p3.x, p3.y); line(p3.x, p3.y, p4.x, p4.y); line(p4.x, p4.y, p1.x, p1.y); } } } int main() { // 初始化控制点矩阵 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { controlPoints[i][j] = { i * 100, j * 100 }; } } initgraph(640, 480); // 创建绘图窗口 drawBezierSurface(controlPoints); // 绘制曲面 system("pause"); // 暂停程序,等待用户关闭窗口 closegraph(); // 关闭绘图窗口 return 0; } ``` 该示例代码中,我们使用了EasyX图形库来绘制三次贝塞尔曲面。首先初始化了控制点矩阵,然后通过 `drawBezierSurface` 函数计算曲面上的所有点,并将这些点连接起来绘制曲面。在 `main` 函数中,我们创建了一个640x480的绘图窗口,调用 `drawBezierSurface` 函数绘制曲面,并使用 `system("pause")` 暂停程序,等待用户关闭窗口。最后,调用 `closegraph()` 函数关闭绘图窗口。 需要注意的是,该示例代码中使用了 `line` 函数来绘制曲面上的线段,实际上这样绘制曲面比较粗糙,可以通过使用更高级的图形库或者实现更复杂的插值算法绘制更平滑的曲面

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值