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

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


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

在这里插入图片描述


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


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(),来绘制颜色渐变效果的立方体。这种方法的优点在于简单直观,并且节省算力,缺点在于不能灵活控制像素色彩“分辨率”。

在这里插入图片描述

如图,立方体的八个顶点依次编号,首先将八个顶点的三维坐标存储好,这样在绘制的时候省去了手动设定坐标的麻烦。其次,即使是每次调用一个点的坐标使用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<GL/glut.h>
#include<math.h>
#include<iostream>
#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;
}

  • 28
    点赞
  • 155
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: OpenGL(Open Graphics Library)是一种跨平台的图形编程接口,可以用于开发2D和3D图形应用程序。下面是一些OpenGL项目实战教程: 1. 光照效果:学习如何使用OpenGL实现各种光照效果,例如平行光、点光源和聚光灯效果。通过调整光照参数和材质属性,可以创建逼真的光照场景。 2. 纹理映射:学习如何使用OpenGL将纹理映射到三维模型上。通过加载图像文件并将其应用于模型表面,可以实现逼真的贴图效果。 3. 阴影效果:学习如何使用OpenGL实现阴影效果,例如投影阴影和阴影贴图。阴影效果可以增强场景的逼真感和深度感。 4. 粒子系统:学习如何使用OpenGL创建粒子系统,例如火焰、烟雾和爆炸效果。通过调整粒子属性和行为,可以创建各种动态和生动的效果。 5. 物理模拟:学习如何使用OpenGL结合物理引擎实现物理模拟效果,例如碰撞检测、重力和运动模拟。通过模拟真实世界的物理规律,可以创建更真实的交互体验。 6. 游戏开发:学习如何使用OpenGL和其他游戏开发库(例如SDL或SFML)开发2D或3D游戏。从游戏引擎的搭建到游戏场景的渲染,可以实现自己的游戏创意。 这些项目实战教程可以帮助初学者学习并掌握OpenGL的基本概念和技术,同时也可以帮助有一定OpenGL经验的程序员进一步提升他们的图形编程能力。通过实际动手实现这些项目,可以更好地理解OpenGL的原理和使用方法,并能够应用于实际的图形应用程序开发中。 ### 回答2: OpenGL(Open Graphics Library)是一个用于三维图形渲染的跨平台开放式图形库。它提供了一系列的函数和工具,帮助开发人员创建高性能的图形应用程序。 关于OpenGL项目实战教程,我可以给出以下几个方面的建议: 首先,了解基础知识。在开始实战项目之前,需要掌握OpenGL的基本概念和原理,包括顶点缓冲对象、顶点数组对象、着色器、纹理等。可以通过查阅OpenGL的官方文档或相关教程来学习。 其次,选择一个合适的实战项目。可以根据自己的兴趣和实际需求选择一个合适的项目,比如创建一个简单的3D游戏、设计一个图形界面等。选择一个适合自己水平和时间的项目,逐步提升自己的技能。 然后,学习项目所需的技术和工具。根据项目的需求,可能需要学习一些额外的技术和工具,比如图形数学、碰撞检测、模型导入等。可以通过在线教程、书籍或论坛来学习这些知识,并逐步应用到自己的项目中。 接下来,编写代码并调试。根据项目需求,使用OpenGL提供的函数和工具编写代码,并对代码进行调试。可以通过输出调试信息、使用调试工具等方式来排查问题并解决。 最后,不断学习和优化。OpenGL是一个庞大而复杂的库,可能需要不断地学习和掌握新的技术和工具。在实战过程中,可以将学到的经验和技巧总结下来,并进行项目的优化,提高性能和用户体验。 总结起来,OpenGL项目实战教程需要学习基础知识、选择合适的项目、学习相关技术和工具、编写代码和调试,并不断学习和优化。通过实际的实践和项目经验,可以提升自己的OpenGL编程能力。 ### 回答3: OpenGL(Open Graphics Library)是一个用于渲染二维和三维图形的跨平台图形库。它提供了一系列函数用于操作图形、纹理、着色器等,能够实现复杂的图形渲染和动画效果。下面将简要介绍OpenGL项目实战教程。 OpenGL项目实战教程是一种通过实际项目来学习和实践OpenGL技术的教学方法。它通常基于具体的应用场景,通过逐步完成一个完整的项目,来引导学习者了解和掌握OpenGL的相关知识和技能。 在开始OpenGL项目实战教程之前,学习者需要具备一定的编程基础,如C++或Java等编程语言的基础知识。同时,对于图形学的基本概念和算法也有一定的了解。 在实战教程中,教学者通常会选择具有代表性的项目,例如创建一个简单的3D游戏场景或实现一个基本的图形编辑器等。通过这些项目,学习者可以逐步了解和掌握OpenGL的基本概念、渲染流程、坐标系统、纹理映射、着色器编程等核心内容。 教程通常会结合理论和实践,通过讲解相关概念和技术,例如图形渲染管线、顶点和片段着色器、缓冲区对象等,来引导学习者完成项目的不同阶段。学习者可以借助开源的OpenGL库或框架,例如OpenGL ES、GLEW、GLFW等,加快项目的开发进程。 通过完成OpenGL项目实战教程,学习者可以获得以下几方面的收益。首先,他们可以将理论知识应用于实际项目,更深入地理解和掌握相关技术。其次,他们可以通过项目实战来锻炼编程能力和解决问题的能力。最后,完成的项目还可以作为学习者的作品展示,增强他们的简历或作为个人项目的起点。 总之,OpenGL项目实战教程是一种有效的学习和实践OpenGL技术的教学方法,可以帮助学习者深入了解和掌握OpenGL的相关知识和技能,同时提升他们的编程和解决问题的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值