OpenGL中點畫圓法繪製拋物線y=ax²+bx+c

安裝glut庫

這篇博客的編譯器是VS2017,vs2019的安裝步驟也一樣:glut

中點畫圓法

這篇博客寫得挺詳細的,也介紹了速度更快的Bresenham算法:中點畫圓法

思路分析

首先,利用導數找出斜率爲1的點,從這點開始將曲線分開兩段處理,斜率小於1的曲線利用y作爲增量計算,斜率大於1的曲線利用x作爲增量計算。
其次,由於y=ax2+bx+cy=ax^2+bx+c關於函數對稱軸x=a2bx=\cfrac{-a}{2b}對稱,所以只要畫出右邊的圖像,再進行取對稱點操作就能得到左邊圖像,從而拼出整個圖像。
注意y=ax2+bx+cy=ax^2+bx+c有3個參數,下面兩種參數變化的情況會對程序產生較大影響

情況分析

a=0

此時,曲線會退化成直線 y=bx+cy=bx+c

a=0且b=0

此時,曲線會退化成一條平行於x軸的直線 y=cy=c

判別式推導

在這裏插入圖片描述
原函數變爲 f(x,y)=ax2+bx+cyf(x,y)=ax^2+bx+c-y
0<x<a2b0<x<\cfrac{-a}{2b}時,利用y作爲增量
對任意一點d0(xp,yp)d_0(xp,yp),有
dp=F(M)=F(xp+1,yp+0.5)=a(xp+1)2+b(xp+1)+c(yp+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<0d_p<0則應取p1p_1作爲下一像素,而且再下一像素的判別式爲:
dp+1=F(xp+2,yp+0.5)=a(xp+2)2+b(xp+2)+c(yp+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)
此時,Δd=dp+1dp=a(2x+3)+b\Delta d=d_{p+1}-d_p=a(2x+3)+b
dp>0d_p>0則應取p2p_2作爲下一像素,而且再下一像素的判別式爲:
dp+1=F(xp+1,yp+1.5)=a(xp+1)2+b(xp+1)+c(yp+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)
此時,Δd=dp+1dp=a(2x+3)+b1\Delta d=d_{p+1}-d_p=a(2x+3)+b-1

在這裏插入圖片描述
同理,當x>a2bx>\cfrac{-a}{2b}時,利用x作爲增量
對任意一點d0(xp,yp)d_0(xp,yp),有
dp=F(M)=F(xp+0.5,yp+1)=a(xp+0.5)2+b(xp+0.5)+c(yp+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<0d_p<0則應取p1p_1作爲下一像素,而且再下一像素的判別式爲:
dp+1=F(xp+0.5,yp+2)=a(xp+0.5)2+b(xp+0.5)+c(yp+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)
此時,Δd=dp+1dp=a(2x+2)+b1\Delta d=d_{p+1}-d_p=a(2x+2)+b-1
dp>0d_p>0則應取p2p_2作爲下一像素,而且再下一像素的判別式爲:
dp+1=F(xp+1.5,yp+1)=a(xp+1.5)2+b(xp+1.5)+c(yp+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)
此時,Δd=dp+1dp=1\Delta d=d_{p+1}-d_p=-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如果是整型的話,這個圖像會很苗條,再使用中點畫圓算法之後恐怕會出現一條線。如果有大佬研究出整型的計算方案的話,請務必在評論區告訴我,感激不盡!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章