OpenGL中点画圆法绘制抛物线y=ax²+bx+c

安装glut库

这篇博客的编译器是VS2017,vs2019的安装步骤也一样:glut

中点画圆法

这篇博客写得挺详细的,也介绍了速度更快的Bresenham算法:中点画圆法

思路分析

首先,利用导数找出斜率为1的点,从这点开始将曲线分开两段处理,斜率小于1的曲线利用y作为增量计算,斜率大于1的曲线利用x作为增量计算。
其次,由于 y = a x 2 + b x + c y=ax^2+bx+c y=ax2+bx+c关于函数对称轴 x = − a 2 b x=\cfrac{-a}{2b} x=2ba对称,所以只要画出右边的图像,再进行取对称点操作就能得到左边图像,从而拼出整个图像。
注意 y = a x 2 + b x + c y=ax^2+bx+c y=ax2+bx+c有3个参数,下面两种参数变化的情况会对程序产生较大影响

情况分析

a=0

此时,曲线会退化成直线 y = b x + c y=bx+c y=bx+c

a=0且b=0

此时,曲线会退化成一条平行于x轴的直线 y = c y=c y=c

判别式推导

在这里插入图片描述
原函数变为 f ( x , y ) = a x 2 + b x + c − y f(x,y)=ax^2+bx+c-y f(x,y)=ax2+bx+cy
0 < x < − a 2 b 0<x<\cfrac{-a}{2b} 0<x<2ba时,利用y作为增量
对任意一点 d 0 ( x p , y p ) d_0(xp,yp) d0(xp,yp),有
d p = F ( M ) = F ( x p + 1 , y p + 0.5 ) = a ( x p + 1 ) 2 + b ( x p + 1 ) + c − ( y p + 0.5 ) d_p=F(M)=F(x_p+1,y_p+0.5)=a(x_p+1)^2+b(x_p+1)+c-(y_p+0.5) dp=F(M)=F(xp+1,yp+0.5)=a(xp+1)2+b(xp+1)+c(yp+0.5)
d p < 0 d_p<0 dp<0则应取 p 1 p_1 p1作为下一像素,而且再下一像素的判别式为:
d p + 1 = F ( x p + 2 , y p + 0.5 ) = a ( x p + 2 ) 2 + b ( x p + 2 ) + c − ( y p + 0.5 ) d_{p+1}=F(x_p+2,y_p+0.5)=a(x_p+2)^2+b(x_p+2)+c-(y_p+0.5) dp+1=F(xp+2,yp+0.5)=a(xp+2)2+b(xp+2)+c(yp+0.5)
此时, Δ d = d p + 1 − d p = a ( 2 x + 3 ) + b \Delta d=d_{p+1}-d_p=a(2x+3)+b Δd=dp+1dp=a(2x+3)+b
d p > 0 d_p>0 dp>0则应取 p 2 p_2 p2作为下一像素,而且再下一像素的判别式为:
d p + 1 = F ( x p + 1 , y p + 1.5 ) = a ( x p + 1 ) 2 + b ( x p + 1 ) + c − ( y p + 1.5 ) d_{p+1}=F(x_p+1,y_p+1.5)=a(x_p+1)^2+b(x_p+1)+c-(y_p+1.5) dp+1=F(xp+1,yp+1.5)=a(xp+1)2+b(xp+1)+c(yp+1.5)
此时, Δ d = d p + 1 − d p = a ( 2 x + 3 ) + b − 1 \Delta d=d_{p+1}-d_p=a(2x+3)+b-1 Δd=dp+1dp=a(2x+3)+b1

在这里插入图片描述
同理,当 x > − a 2 b x>\cfrac{-a}{2b} x>2ba时,利用x作为增量
对任意一点 d 0 ( x p , y p ) d_0(xp,yp) d0(xp,yp),有
d p = F ( M ) = F ( x p + 0.5 , y p + 1 ) = a ( x p + 0.5 ) 2 + b ( x p + 0.5 ) + c − ( y p + 1 ) d_p=F(M)=F(x_p+0.5,y_p+1)=a(x_p+0.5)^2+b(x_p+0.5)+c-(y_p+1) dp=F(M)=F(xp+0.5,yp+1)=a(xp+0.5)2+b(xp+0.5)+c(yp+1)
d p < 0 d_p<0 dp<0则应取 p 1 p_1 p1作为下一像素,而且再下一像素的判别式为:
d p + 1 = F ( x p + 0.5 , y p + 2 ) = a ( x p + 0.5 ) 2 + b ( x p + 0.5 ) + c − ( y p + 2 ) d_{p+1}=F(x_p+0.5,y_p+2)=a(x_p+0.5)^2+b(x_p+0.5)+c-(y_p+2) dp+1=F(xp+0.5,yp+2)=a(xp+0.5)2+b(xp+0.5)+c(yp+2)
此时, Δ d = d p + 1 − d p = a ( 2 x + 2 ) + b − 1 \Delta d=d_{p+1}-d_p=a(2x+2)+b-1 Δd=dp+1dp=a(2x+2)+b1
d p > 0 d_p>0 dp>0则应取 p 2 p_2 p2作为下一像素,而且再下一像素的判别式为:
d p + 1 = F ( x p + 1.5 , y p + 1 ) = a ( x p + 1.5 ) 2 + b ( x p + 1.5 ) + c − ( y p + 1 ) d_{p+1}=F(x_p+1.5,y_p+1)=a(x_p+1.5)^2+b(x_p+1.5)+c-(y_p+1) dp+1=F(xp+1.5,yp+1)=a(xp+1.5)2+b(xp+1.5)+c(yp+1)
此时, Δ d = d p + 1 − d p = − 1 \Delta d=d_{p+1}-d_p=-1 Δd=dp+1dp=1

重要函数实现

通用函数

画点函数

void DrawPoint(int x, int y)
{
	glBegin(GL_POINTS);
	glVertex2i(x, y);
	glEnd();
}

窗口初始化函数

由于OpenGL glut默认的窗口显示范围只有[-1,1],所以要扩大显示区域,我这里扩大到[-35,35]

void InitWindow(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);//RGB渲染通道、双缓冲
	glutInitWindowPosition(100, 100);//窗口位置
	glutInitWindowSize(800, 800);//窗口大小
	id = glutCreateWindow("绘制曲线");//窗口名字
	glPointSize(5);//将像素点调整为5倍大小,方便看清楚
	gluOrtho2D(-35.0, 35.0, -35.0, 35.0);//更改显示区域
	glutKeyboardFunc(KeyBoardHandle);//接收键盘事件
}

键盘事件函数

程序运行后按下任意键即可结束程序,销毁窗口需要用到窗口的id,我用一个全局变量来接收这个id

int id;//窗口id
void KeyBoardHandle(unsigned char key, int x, int y)
{
	glutDestroyWindow(id);//按下任意键销毁窗口
	exit(0);//退出程序
}

绘制函数

这里之所以用一个void函数封装起来的原因是初始化窗口函数glutCreateWindow的参数只接受void(*func)(),即没有参数的void函数指针。这也导致如果想将用户的输入数据传给myBreasehamCurve方法只能使用全局变量接受用户的输入数据。

void myWork()
{
	myBreasehamCurve(-0.25, 5, 0);
}

主函数

int main(int argc, char* argv[]) 
{
	InitWindow(argc, argv);
	glutDisplayFunc(&myWork);
	glutMainLoop();
	return 0;
}

绘制函数实现

a=0的情况

这也是Bresenham两点一线算法

void BreasehamDrawLine(int x0, int y0, int x1, int y1)
{
	glColor3f(1.0f, 1.0f, 1.0f);
	int s1 = x1 > x0 ? 1 : -1;
	int s2 = y1 > y0 ? 1 : -1;
	int dx, dy, e, x, y;
	bool bigOne = false;
	dx = abs(x1 - x0);
	dy = abs(y1 - y0);
	if (dx < dy)
	{
		Swap(dx, dy);
		bigOne = true;
	}
	e = 2 * dy - dx;
	x = x0;
	y = y0;
	for (int i = 0; i <= dx; i++)
	{
		DrawPoint(x, y);
		if (e >= 0)
		{
			if (!bigOne)
				y += s2;
			else
				x += s1;
			e -= 2 * dx;
		}
		if (!bigOne)
			x += s1;
		else
			y += s2;
		e += 2 * dy;
	}
	glFlush();
	glutSwapBuffers();
}

a=0,b=0的情况

void myBreasehamCurve_c(double c)//a,b为0时
{
	
	for (int i = -35; i < 35; i++)
		DrawPoint(i, c);
	glFlush();
	glutSwapBuffers();
}

一般情况

由于int参数的拟合度不高,效果太差,所以这里使用double参数
只要用x减去2倍x到对称轴的距离即可得到另一个x的坐标

void myBreasehamCurve(double a, double b, double c)
{
	bool isAZ = true;//a为正数时
	if (a == 0 && b == 0)
	{
		myBreasehamCurve3(c);
		return;
	}
	else if (a < 0)
	{
		a = -a;
		isAZ = false;
	}
	else if (a == 0)
	{
		BreasehamDrawLine(-30, -30 * b + c, 30, 30 * b + c);
		return;
	}

	double x = 0, y = 0;
	double d = a + b + c - 0.5;
	double lastX = 0;
	double sx;
	double cutPoint = -b / 2 / a;
	double symmetry = cutPoint;//对称轴的位置
	//斜率大于0而小于1时
	while (x < cutPoint)
	{
		if (d < 0)
			d += a * (2 * x + 3) + b;
		else
		{
			d += a * (2 * x + 3) + b - 1;
			y++;
		}
		x++;
		sx = abs(x - symmetry);//x到对称轴的绝对值
		if (!isAZ)
		{
			DrawPoint(x, -y);
			DrawPoint(x - 2 * sx, -y);
		}
		else
		{
			DrawPoint(x, y);
			DrawPoint(x - 2 * sx, y);
		}
	}
	//斜率大于1时
	x = cutPoint;
	y = c - b * b / a / 4;
	d = a / 4 + b / 2 + c - 1;
	while (y <= 30 && y > -30)
	{
		if (d >= 0)
			d -= 1;
		else
		{
			d += a * (2 * x + 2) + b - 1;
			x++;
		}
		y++;
		sx = abs(x - symmetry);
		if (!isAZ)
		{
			DrawPoint(x, -y);
			DrawPoint(x - 2 * sx, -y);
		}
		else
		{
			DrawPoint(x, y);
			DrawPoint(x - 2 * sx, y);
		}
	}
	glFlush();
	glutSwapBuffers();
}

运行效果

在这里插入图片描述

后记

理论上如果能用整型计算是速度最快的,但我考虑到参数a如果是整型的话,这个图像会很苗条,再使用中点画圆算法之后恐怕会出现一条线。如果有大佬研究出整型的计算方案的话,请务必在评论区告诉我,感激不尽!!

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页

打赏作者

d幻月b

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值