OpenGL绘制三维彩色立方体并实现自动旋转

OpenGL绘制三维彩色立方体并实现自动旋转


北京航空航天大学计算机学院 2020春季计算机图形学课程第二次作业,使用OpenGL绘制三维彩色立方体并实现自动旋转,目标结果如下图:

OpenGL绘制三维彩色立方体并实现自动旋转_第1张图片


本次任务重点主要有两个,一是绘制立方体,二是使之自动旋转。绘制立方体涉及到三维物体的呈现,包括了坐标的设定,观察位置和角度的设定。立方体的自动旋转涉及到动画,使用双缓冲,需要设置定时器回调函数。


1. 绘制立方体

有关绘制立方体,本次代码实现了两种绘制彩色立方体的方式。两种方法均将立方体放置在三维空间的第一卦限中,八个顶点分别位于(0, 0, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1), (1, 0, 0), (1, 1, 0), (1, 1, 1), (1, 0, 1)。由于立方体放置在三维空间的第一卦限,平面上每一个像素点的颜色都恰好是该像素点三维坐标的RGB值。因此整个立方体,包括其内部的所有点,代表了整个RGB色彩空间。但本次代码仅仅绘制立方体的外表六个平面,内部的色彩是没有呈现出来的。

1.1 逐个像素点绘制小正方形

第一种方式是逐个绘制“像素”,实际是绘制很多个小的空间正方形,每个像素使用不同的颜色。这种方法的优点在于可以灵活控制像素点的尺寸,来绘制出不同效果的彩色立方体。但这种方法最明显的缺点在于其消耗的算力很大,在配合旋转的动画效果后,“分辨率”过高的彩色立方体会因为占据过多算力而卡住不动。在这种方法中,函数get_pix()来获取单个像素点(小正方形)四个顶点的坐标值,其输入是像素点所在平面,以及像素点在平面上的相对位置。输出的像素点正方形的四个顶点中,第一个顶点三维坐标值直接作为该像素点的颜色进行绘制,相关代码如下:

// 绘制一个色彩像素点(实际是小正方形)
// 返回值二维数组pix[4][3],分别存储正方形四个顶点坐标,同时第一个顶点坐标就是颜色值
// 参数direction表示像素点所在平面是正面还是背面,0表示背面,1表示正面
// 参数main_color表示像素点所在平面的基调颜色,0表示红色,1表示绿色,2表示蓝色
//		同时main_color也决定了像素点所在的平面法向,红色平面与x轴垂直,绿色y轴,蓝色z轴
// 参数relative_x和relative_y表示像素点在立方体平面上的相对位置
//		同时相对位置也和基调颜色共同决定了像素点的颜色RGB值
GLfloat** get_pix(unsigned int front_back, unsigned int direction, 
	GLfloat relative_x, GLfloat relative_y)
{
	// 申请存储像素正方形四个顶点的二维数组pix[4][3]
	GLfloat** pix = (GLfloat**)malloc(sizeof(GLfloat*) * 4);
	if (pix == NULL) return NULL;
	for (unsigned int i = 0; i < 4; i++) {
		pix[i] = (GLfloat*)malloc(sizeof(GLfloat) * 3);
		if (pix[i] == NULL) return NULL;
	}	

	if (front_back == 0) {
	// 像素点位于朝向背面的三个面,正方形的顶点顺时针顺序排列
		if (direction == 0) {
		// 像素点位于YOZ平面(右后侧面)
			pix[0][0] = 0.0f; pix[0][1] = relative_x;		pix[0][2] = relative_y;
			pix[1][0] = 0.0f; pix[1][1] = relative_x;		pix[1][2] = relative_y + PIX;
			pix[2][0] = 0.0f; pix[2][1] = relative_x + PIX; pix[2][2] = relative_y + PIX;
			pix[3][0] = 0.0f; pix[3][1] = relative_x + PIX; pix[3][2] = relative_y;
		}
		else if (direction == 1) {
		// 像素点位于ZOX平面(左后侧面)
			pix[0][0] = relative_y;		  pix[0][1] = 0.0f;	pix[0][2] = relative_x;
			pix[1][0] = relative_y + PIX; pix[1][1] = 0.0f;	pix[1][2] = relative_x;
			pix[2][0] = relative_y + PIX; pix[2][1] = 0.0f; pix[2][2] = relative_x + PIX;
			pix[3][0] = relative_y;		  pix[3][1] = 0.0f; pix[3][2] = relative_x + PIX;
		}
		else if (direction == 2) {
		// 像素点位于XOY平面(底面)
			pix[0][0] = relative_x;		  pix[0][1] = relative_y;		pix[0][2] = 0.0f;
			pix[1][0] = relative_x;		  pix[1][1] = relative_y + PIX;	pix[1][2] = 0.0f;
			pix[2][0] = relative_x + PIX; pix[2][1] = relative_y + PIX; pix[2][2] = 0.0f;
			pix[3][0] = relative_x + PIX; pix[3][1] = relative_y;		pix[3][2] = 0.0f;
		}
	}
	else if (front_back == 1) {
	// 像素点位于朝向正面的三个面,正方形的顶点逆时针顺序排列
		if (direction == 0) {
			// 像素点位于垂直于X轴向前的平面(左前侧面)
			pix[0][0] = 1.0f; pix[0][1] = relative_x;		pix[0][2] = relative_y;
			pix[1][0] = 1.0f; pix[1][1] = relative_x + PIX;	pix[1][2] = relative_y;
			pix[2][0] = 1.0f; pix[2][1] = relative_x + PIX; pix[2][2] = relative_y + PIX;
			pix[3][0] = 1.0f; pix[3][1] = relative_x;		pix[3][2] = relative_y + PIX;
		}
		else if (direction == 1) {
			// 像素点位于垂直于Y轴向前的平面(右前侧面)
			pix[0][0] = relative_y;		  pix[0][1] = 1.0f;	pix[0][2] = relative_x;
			pix[1][0] = relative_y;		  pix[1][1] = 1.0f;	pix[1][2] = relative_x + PIX;
			pix[2][0] = relative_y + PIX; pix[2][1] = 1.0f; pix[2][2] = relative_x + PIX;
			pix[3][0] = relative_y + PIX; pix[3][1] = 1.0f; pix[3][2] = relative_x;
		}
		else if (direction == 2) {
			// 像素点位于垂直于Z轴向上的平面(顶面)
			pix[0][0] = relative_x;		  pix[0][1] = relative_y;		pix[0][2] = 1.0f;
			pix[1][0] = relative_x + PIX; pix[1][1] = relative_y;		pix[1][2] = 1.0f;
			pix[2][0] = relative_x + PIX; pix[2][1] = relative_y + PIX; pix[2][2] = 1.0f;
			pix[3][0] = relative_x;		  pix[3][1] = relative_y + PIX;	pix[3][2] = 1.0f;
		}
	}
	return pix;
}

在display函数中,依次绘制六个平面,每个平面依次绘制每个像素点,相关代码如下:

// 逐个像素点绘制小正方形
	for (unsigned int front_back = 0; front_back < 2; front_back++) {
		for (unsigned int direction = 0; direction < 3; direction++) {
			for (unsigned int i = 0; i < COLOR; i++) {
				for (unsigned int j = 0; j < COLOR; j++) {
					GLfloat** pix = get_pix(front_back, direction,
						(GLfloat)(i / COLOR), (GLfloat)(j / COLOR));
					glColor3fv(pix[0]);
					glBegin(GL_QUADS);
					for (unsigned int v = 0; v < 4; v++)
						glVertex3fv(pix[v]);
					glEnd();
				}
			}
		}
	}

1.2 直接绘制渐变平面

第二种方式是直接绘制立方体的六个平面,通过在glBegin()与glEnd()之间,在设定平面的四个顶点之间直接变换glColor3fv(),来绘制颜色渐变效果的立方体。这种方法的优点在于简单直观,并且节省算力,缺点在于不能灵活控制像素色彩“分辨率”。

OpenGL绘制三维彩色立方体并实现自动旋转_第2张图片

如图,立方体的八个顶点依次编号,首先将八个顶点的三维坐标存储好,这样在绘制的时候省去了手动设定坐标的麻烦。其次,即使是每次调用一个点的坐标使用glVertex3fv(),也需要手动调用24次之多,可以进一步将六个平面顶点调用的顺序也存储下来,通过循环直接调用即可。同样的,顶点的坐标就是顶点位置颜色的RGB值,在glBegin()与glEnd()之间,在设定平面的四个顶点之间直接变换glColor3fv()即可得到渐变效果的正方形平面。相关代码如下:

// 设置立方体的八个顶点坐标
	static const GLfloat vertex[][3] = {
		0.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		1.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
		1.0f, 0.0f, 1.0f,
		0.0f, 1.0f, 1.0f,
		1.0f, 1.0f, 1.0f
	};
	// 设置绘制六个面时顶点的顺序
	static const GLint index[][4] = {
		0, 2, 3, 1,
		0, 4, 6, 2,
		0, 1, 5, 4, 
		4, 5, 7, 6, 
		1, 3, 7, 5, 
		2, 6, 7, 3
	};
	// 绘制六个面
	glBegin(GL_QUADS);
	for (unsigned int i = 0; i < 6; i++)
		for (unsigned int j = 0; j < 4; j++) {
			// 每个顶点的RGB颜色值和其顶点位置坐标一致
			glColor3fv(vertex[index[i][j]]);
			glVertex3fv(vertex[index[i][j]]);
		}
	glEnd();

1.3 设置平面方向及只绘制正面

经过实践可以发现,三维平面的绘制过程中,后绘制的平面会覆盖在先绘制的平面之上,比如如果先绘制三个正对着相机的面,后绘制三个背对着相机的面,则会出现背面覆盖正面的现象。如果绘制静止的立方体,则调整平面的绘制顺序即可,但考虑到立方体的旋转,这一问题就必须通过区分平面方向来解决。

OpenGL设定了平面方向机制可供选择,我们可以根据右手系,设定正方形四个顶点逆时针顺序排布时平面是正面(右手四指按四个顶点排列顺序方向弯曲,拇指指向为平面方向),然后在绘制开始时设定不绘制背朝观察点的面。这样一来,即使是旋转过程中,从任意角度都可以看到正常的立方体平面。相关代码如下:

// 设置逆时针排列的点围成的平面为正面
glFrontFace(GL_CCW);
// 设置不绘制背面,节省算力同时不会出现背面覆盖正面的情况
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

2. 使立方体旋转

绘制好静止的立方体后,需要考虑的就是如何观察立方体,以及如何使立方体旋转。OpenGL提供了可以设置观察者位置和方向的函数,同时也提供了可以使绘制对象变换的矩阵操作函数,因此使立方体旋转总体上有两种思路:让观察者旋转或让绘制对象旋转。本次代码直接采用了固定观察者,让绘制的立方体旋转的思路。

首先gluLookAt()函数提供了设定观察者位置的接口,该函数的九个参数,每三个一组分别设置了观察者的位置,观察者朝向的点以及观察者头顶指向的方向(因为你可以歪着头看)。该函数在display函数中只调用一次,固定观察者。其次glTranslatef()和glRotatef()配合实现绘制对象绕任意轴旋转。glTranslatef()有三个参数,分别是将坐标系从原点平移到的目标点;glRotatef()有四个参数,分别是旋转的角度,以及旋转轴的向量。单纯的glRotatef()只能实现旋转轴过原点的旋转,若要实现任意旋转轴有以下思路:给定旋转轴上的两点A和B,首先将坐标系从原点平移到A点,然后根据A到B的方向旋转,最后再返还平移到原点。相关代码如下:

// 旋转初始的角度
GLfloat angle = 0.0f;
// 设置旋转轴:两个三维的点确定的旋转轴
GLfloat axis[][3] = {
	0.0f, 0.5f, 0.5f,
	1.0f, 0.5f, 0.5f
};

// 加载单位阵
glLoadIdentity();
// 设置相机的位置和视角
// 有关gluLookAt:https://blog.csdn.net/Augusdi/article/details/20470813
gluLookAt(2, 2, 2, 0.0, 0.0, 0.0, -1, -1, 1);
// 设置绕给定的轴旋转
glTranslatef(axis[0][0], axis[0][1], axis[0][2]);
glRotatef(angle, axis[1][0] - axis[0][0], axis[1][1] - axis[0][1], axis[1][2] - axis[0][2]);
glTranslatef(-axis[0][0], -axis[0][1], -axis[0][2]);

若要产生连续的动画效果,需要设置定时器回调函数,来实现间隔一定的时间刷新一次。当间隔时间足够小时,在人眼视觉感官上就呈现了连续的动画效果。有关定时器回调函数和其在main函数中的调用代码如下:

// 动画所需的定时器回调函数
// 有关定时器回调函数:https://blog.csdn.net/shimazhuge/article/details/17894883
void timer_function(GLint value)
{
	// 旋转角度增加
	angle += STEP;
	// 若角度大于360转完一圈则清零
	if (angle > 360.0) angle -= 360.0;
	glutPostRedisplay();
	glutTimerFunc(50, timer_function, value);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	// 设置双缓冲和RGB颜色模式
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	// 设置窗口大小、位置和名称
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("color_cube");
	// 设置绘制函数、窗口大小自适应函数和定时器回调函数
	glutDisplayFunc(display_1);
	glutReshapeFunc(reshape);
	glutTimerFunc(500, timer_function, 1);
	// 进入主循环
	glutMainLoop();
	return 0;
}

3. 运行效果

使用连续的渐变平面,以垂直于XOZ平面的旋转轴旋转的效果:

使用逐个像素点,像素点尺寸为1/8单位,以垂直于YOZ平面的旋转轴旋转的效果:


附录:完整代码

#include
#include
#include
#define COLOR 8.0
#define PIX ((GLfloat)(1.0 / COLOR))
#define STEP 1.0f
using namespace std;

// 旋转初始的角度
GLfloat angle = 0.0f;
// 设置旋转轴:两个三维的点确定的旋转轴
GLfloat axis[][3] = {
	0.0f, 0.5f, 0.5f,
	1.0f, 0.5f, 0.5f
};

// 绘制一个色彩像素点(实际是小正方形)
// 返回值二维数组pix[4][3],分别存储正方形四个顶点坐标,同时第一个顶点坐标就是颜色值
// 参数direction表示像素点所在平面是正面还是背面,0表示背面,1表示正面
// 参数main_color表示像素点所在平面的基调颜色,0表示红色,1表示绿色,2表示蓝色
//		同时main_color也决定了像素点所在的平面法向,红色平面与x轴垂直,绿色y轴,蓝色z轴
// 参数relative_x和relative_y表示像素点在立方体平面上的相对位置
//		同时相对位置也和基调颜色共同决定了像素点的颜色RGB值
GLfloat** get_pix(unsigned int front_back, unsigned int direction, 
	GLfloat relative_x, GLfloat relative_y)
{
	// 申请存储像素正方形四个顶点的二维数组pix[4][3]
	GLfloat** pix = (GLfloat**)malloc(sizeof(GLfloat*) * 4);
	if (pix == NULL) return NULL;
	for (unsigned int i = 0; i < 4; i++) {
		pix[i] = (GLfloat*)malloc(sizeof(GLfloat) * 3);
		if (pix[i] == NULL) return NULL;
	}	

	if (front_back == 0) {
	// 像素点位于朝向背面的三个面,正方形的顶点顺时针顺序排列
		if (direction == 0) {
		// 像素点位于YOZ平面(右后侧面)
			pix[0][0] = 0.0f; pix[0][1] = relative_x;		pix[0][2] = relative_y;
			pix[1][0] = 0.0f; pix[1][1] = relative_x;		pix[1][2] = relative_y + PIX;
			pix[2][0] = 0.0f; pix[2][1] = relative_x + PIX; pix[2][2] = relative_y + PIX;
			pix[3][0] = 0.0f; pix[3][1] = relative_x + PIX; pix[3][2] = relative_y;
		}
		else if (direction == 1) {
		// 像素点位于ZOX平面(左后侧面)
			pix[0][0] = relative_y;		  pix[0][1] = 0.0f;	pix[0][2] = relative_x;
			pix[1][0] = relative_y + PIX; pix[1][1] = 0.0f;	pix[1][2] = relative_x;
			pix[2][0] = relative_y + PIX; pix[2][1] = 0.0f; pix[2][2] = relative_x + PIX;
			pix[3][0] = relative_y;		  pix[3][1] = 0.0f; pix[3][2] = relative_x + PIX;
		}
		else if (direction == 2) {
		// 像素点位于XOY平面(底面)
			pix[0][0] = relative_x;		  pix[0][1] = relative_y;		pix[0][2] = 0.0f;
			pix[1][0] = relative_x;		  pix[1][1] = relative_y + PIX;	pix[1][2] = 0.0f;
			pix[2][0] = relative_x + PIX; pix[2][1] = relative_y + PIX; pix[2][2] = 0.0f;
			pix[3][0] = relative_x + PIX; pix[3][1] = relative_y;		pix[3][2] = 0.0f;
		}
	}
	else if (front_back == 1) {
	// 像素点位于朝向正面的三个面,正方形的顶点逆时针顺序排列
		if (direction == 0) {
			// 像素点位于垂直于X轴向前的平面(左前侧面)
			pix[0][0] = 1.0f; pix[0][1] = relative_x;		pix[0][2] = relative_y;
			pix[1][0] = 1.0f; pix[1][1] = relative_x + PIX;	pix[1][2] = relative_y;
			pix[2][0] = 1.0f; pix[2][1] = relative_x + PIX; pix[2][2] = relative_y + PIX;
			pix[3][0] = 1.0f; pix[3][1] = relative_x;		pix[3][2] = relative_y + PIX;
		}
		else if (direction == 1) {
			// 像素点位于垂直于Y轴向前的平面(右前侧面)
			pix[0][0] = relative_y;		  pix[0][1] = 1.0f;	pix[0][2] = relative_x;
			pix[1][0] = relative_y;		  pix[1][1] = 1.0f;	pix[1][2] = relative_x + PIX;
			pix[2][0] = relative_y + PIX; pix[2][1] = 1.0f; pix[2][2] = relative_x + PIX;
			pix[3][0] = relative_y + PIX; pix[3][1] = 1.0f; pix[3][2] = relative_x;
		}
		else if (direction == 2) {
			// 像素点位于垂直于Z轴向上的平面(顶面)
			pix[0][0] = relative_x;		  pix[0][1] = relative_y;		pix[0][2] = 1.0f;
			pix[1][0] = relative_x + PIX; pix[1][1] = relative_y;		pix[1][2] = 1.0f;
			pix[2][0] = relative_x + PIX; pix[2][1] = relative_y + PIX; pix[2][2] = 1.0f;
			pix[3][0] = relative_x;		  pix[3][1] = relative_y + PIX;	pix[3][2] = 1.0f;
		}
	}

	return pix;
}

void display_1(void)
{
	// 设置逆时针排列的点围成的平面为正面
	glFrontFace(GL_CCW);
	// 设置不绘制背面,节省算力同时不会出现背面覆盖正面的情况
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);
	// 设置背景为白色
	glClearColor(1.0, 1.0, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
	// 加载单位阵
	glLoadIdentity();
	// 设置相机的位置和视角
	// 有关gluLookAt:https://blog.csdn.net/Augusdi/article/details/20470813
	gluLookAt(2, 2, 2, 0.0, 0.0, 0.0, -1, -1, 1);
	// 设置绕给定的轴旋转
	glTranslatef(axis[0][0], axis[0][1], axis[0][2]);
	glRotatef(angle, axis[1][0] - axis[0][0], axis[1][1] - axis[0][1], axis[1][2] - axis[0][2]);
	glTranslatef(-axis[0][0], -axis[0][1], -axis[0][2]);
	// 逐个像素点绘制小正方形
	for (unsigned int front_back = 0; front_back < 2; front_back++) {
		for (unsigned int direction = 0; direction < 3; direction++) {
			for (unsigned int i = 0; i < COLOR; i++) {
				for (unsigned int j = 0; j < COLOR; j++) {
					GLfloat** pix = get_pix(front_back, direction,
						(GLfloat)(i / COLOR), (GLfloat)(j / COLOR));
					glColor3fv(pix[0]);
					glBegin(GL_QUADS);
					for (unsigned int v = 0; v < 4; v++)
						glVertex3fv(pix[v]);
					glEnd();
				}
			}
		}
	}
	// 双缓冲下的刷新帧缓存
	glutSwapBuffers();
}

void display_2()
{
	// 设置逆时针排列的点围成的平面为正面
	glFrontFace(GL_CCW);
	// 设置不绘制背面,节省算力同时不会出现背面覆盖正面的情况
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);
	// 设置背景为白色
	glClearColor(1.0, 1.0, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT);
	// 加载单位阵
	glLoadIdentity();
	// 设置相机的位置和视角
	// 有关gluLookAt:https://blog.csdn.net/Augusdi/article/details/20470813
	gluLookAt(2, 2, 2, 0.0, 0.0, 0.0, -1, -1, 1);
	// 设置绕给定的轴旋转
	glTranslatef(axis[0][0], axis[0][1], axis[0][2]);
	glRotatef(angle, axis[1][0] - axis[0][0], axis[1][1] - axis[0][1], axis[1][2] - axis[0][2]);
	glTranslatef(-axis[0][0], -axis[0][1], -axis[0][2]);
	// 设置立方体的八个顶点坐标
	static const GLfloat vertex[][3] = {
		0.0f, 0.0f, 0.0f,
		1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		1.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
		1.0f, 0.0f, 1.0f,
		0.0f, 1.0f, 1.0f,
		1.0f, 1.0f, 1.0f
	};
	// 设置绘制六个面时顶点的顺序
	static const GLint index[][4] = {
		0, 2, 3, 1,
		0, 4, 6, 2,
		0, 1, 5, 4, 
		4, 5, 7, 6, 
		1, 3, 7, 5, 
		2, 6, 7, 3
	};
	// 绘制六个面
	glBegin(GL_QUADS);
	for (unsigned int i = 0; i < 6; i++)
		for (unsigned int j = 0; j < 4; j++) {
			// 每个顶点的RGB颜色值和其顶点位置坐标一致
			glColor3fv(vertex[index[i][j]]);
			glVertex3fv(vertex[index[i][j]]);
		}
	glEnd();
	// 双缓冲下的刷新帧缓存
	glutSwapBuffers();
}

// 动画所需的定时器回调函数
// 有关定时器回调函数:https://blog.csdn.net/shimazhuge/article/details/17894883
void timer_function(GLint value)
{
	// 旋转角度增加
	angle += STEP;
	// 若角度大于360转完一圈则清零
	if (angle > 360.0) angle -= 360.0;
	glutPostRedisplay();
	glutTimerFunc(50, timer_function, value);
}

// 窗口大小自适应函数,使得窗口大小改变时仍保持图形的比例不变
// 有关窗口自适应函数:http://blog.sina.com.cn/s/blog_5497dc110102w8qh.html
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(2, 2, 2, 0.0, 0.0, 0.0, -1, -1, 1);
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	// 设置双缓冲和RGB颜色模式
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
	// 设置窗口大小、位置和名称
	glutInitWindowSize(500, 500);
	glutInitWindowPosition(100, 100);
	glutCreateWindow("color_cube");
	// 设置绘制函数、窗口大小自适应函数和定时器回调函数
	glutDisplayFunc(display_1);
	glutReshapeFunc(reshape);
	glutTimerFunc(500, timer_function, 1);
	// 进入主循环
	glutMainLoop();
	return 0;
}

你可能感兴趣的:(OpenGL学习笔记,图形学,opengl)