一、使用平台
windows 8.1 visual studio 2012
二、使用方法
鼠标左键控制旋转,鼠标右键控制平移,点击鼠标中键出现菜单可选多面体 或线框体绘制。
键盘↑↓←→控制平移,F1 和 F2 键分别为多面体和线框体绘制的切换。
三、实现简述
主函数
在 main 函数中与这次实验相关的主要有以下几个函数:
- glutReshapeFunc(myReshape); //窗口改变时重新设置透视投影的
参数,第一次打开窗口也会调用此函数。 - glutDisplayFunc(myDisplay); //显示图形,使用 gluLookAt 反向改变
视点位置从而实现物体平移。使用 glRotatef 根据记录的角度 angle
和旋转轴 axis 来旋转。 - glutIdleFunc(myIdle);//只要 cpu 空闲,就会不断调用,通过在回调
函数 myIdle 中添加 glutPostRedisplay 函数,保证在多边体面和线框
体中切换的时候立刻生效。 - glutMouseFunc(mouseButton);// 处 理 鼠 标 事 件 , 回 调 函 数
mouseButton 中判断左或者右键的按下,然后执行相关函数对小球状
态改变进行记录。 - glutMotionFunc(mouseMotion);//处理鼠标划动,回调函数首先判
断鼠标是在进行平移还是旋转,然后改变视点位置或者旋转轴和旋转角。 - createGLUTMenus();//创建菜单,并与鼠标中间关联,可以选择多面
体或者线框体的绘制。
额外功能
键盘控制平移,多面体和线框体的切换
glutSpecialFunc(processSpecialKeys);//处理特殊按键。使用键盘上
↑↓←→四个特殊按键也可以执行平移操作,按键 F1 为多面体绘制,F2 为线框体绘制。
四、知识点总结
单缓冲
一个缓冲区,当缓冲区填满后,再发送到驱动程序处理。
- 使用方法:
- 1. glutInitDisplayMode(GLUT_SINGLE);//设定使用单缓冲模式
- 2. glFlush();//强制刷新,使缓冲区中的指令送到驱动程序。
双缓冲
一个缓冲区用来显示,一个用来绘制。避免出现绘图不完整导致的闪烁, 效率高。 - 使用方法:
- 1. glutInitDisplayMode(GLUT_DOUBLE);//设定使用双缓冲模 式
- 2. glutSwapBuffers();//从绘制缓冲区到显示缓冲区的复制
深度测试
使用深度缓冲区存放每个像素的深度信息,以分辨物体前后关系。
使用方法:
1. glutInitDisplayMode(GLUT_DEPTH);//先声明使用深度缓存区
2. glEnable(GL_DEPTH_TEST);//打开深度测试函数
glDisable(GL_DEPTH_TEST);//关闭深度测试函数
3. glDepthFunc(GLenum func);//指定用于深度缓冲比较值。Func 是目
标像素与当前像素在 z 方向上值大小比较的函数,只有符合关系的目标像素才绘
制。
如 glDepthFunc(GL_EQUAL)表示当目标与当前像素深度相同时,绘制目标
像素。
又如 glDepthFunc(GL_LESS)表示当目标像素深度比当前像素深度小的时候,
绘制目标像素。
4. glClearDepth(GLclampf depth);//指定深度缓冲区的清除值,初始值
为 1,范围[0, 1]。
5. glClear(GL_DEPTH_BUFFER_BIT);//清除深度缓冲
五、问题和解决
问题一:物体无法旋转
通过鼠标的划动距离和方向算出旋转角度 angle 和旋转轴 axis 如下:
angle = 100.0f * sqrt(dx * dx + dy * dy + dz * dz);
axis[0] = lastPos[1] * curPos[2] - lastPos[2] * curPos[1];
axis[1] = lastPos[2] * curPos[0] - lastPos[0] * curPos[2];
axis[2] = lastPos[0] * curPos[1] - lastPos[1] * curPos[0];
并使用 glRotatef 函数进行旋转操作: glRotatef(angle, axis[0], axis[1], axis[2]); 结果:只有轻微的摆动,无法旋转。
观察 myDisplay 函数中:
glLoadIdentity();
该句在每次绘制前都会重新加载单位矩阵。
因此旋转的角度和旋转轴只有初始的轻微旋转,然后就无法继续了。
而每次旋转的角度和旋转轴都会发生变化,所以需要保存累积上一次的旋转角和旋转轴。
于是以上函数改为累加即可:
angle += 100.0f * sqrt(dx * dx + dy * dy + dz * dz);
axis[0] += lastPos[1] * curPos[2] - lastPos[2] * curPos[1];
axis[1] += lastPos[2] * curPos[0] - lastPos[0] * curPos[2];
axis[2] += lastPos[0] * curPos[1] - lastPos[1] * curPos[0];
问题二:多面体和线框体之间的切换不能立刻生效
设置这两种模式的语句如下:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//多面体
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//线框体
当按钮选择某一个之后,画面不会立即改变。因为此时没有旋转或移动物体,也
就没有调用 myDisplay 函数进行重新绘制。
为了实现点击后立刻生效,使用 glutIdleFunc 函数。
在后台不断调用此函数,并通过在回调函数 myIdle 中添加 glutPostRedisplay
函数重新绘制,保证在多边体面和线框体中切换的时候立刻生效。
六、心得体会
这次实验是一个综合性实验。其中涉及到计算机图形学里面的诸多概念。对
OpenGL 几何变换的渲染流水线顺序以及各种函数调用细节都有了更深的认识
和理解。
OpenGL 显示三维物体的基本步骤如下:
1. 视点变换 Viewing Transformation,即让相机对准三维物体。
2. 模型变换 Modeling Transformation,即将三维物体放在适当位置。
3. 投影变换 Projection Transformation,即调整相机焦距,使三维物体
投影到二维平面。
4. 视口变换 Viewport Transformation,即调整二维相片大小。
对于 OpenGL 中各种函数也有了更深的认识。
对应与以上步骤,OpenGL 中所以用的函数为:
1. 设定观察坐标系:gluLookAt
2. 平移:glTranslatef 旋转:glRotatef
以上两个变换都是在一个矩阵中进行控制的,即使用 glMatrixMode(GL_MODELVIEW);
3. 透视投影:gluPerspective 正交投影:glOrtho
4. 对视口大小调整:glViewport
在理解了各种基本概念之后再去做就会很顺手,因为基本就是调用 API 的事
情了。
代码如下:
/*
* 正十二面体跟踪球,鼠标左键控制旋转,鼠标右键控制平移,鼠标中键打开菜单选择多面体或者线框体。
* 键盘wsad或者↑↓←→控制平移,F1和F2键选择多面体或者线框体。
**/
#include <math.h>
#include <GL/glut.h>
#ifndef M_PI
#define M_PI 3.1415926535898
#endif
//绘制多面体或者线框图的标识符
#define POLYHEDRAL 0
#define WIREFRAME 1
int winWidth, winHeight;//窗口的宽高
GLfloat angle = 0.0;//旋转角度累积值
GLfloat axis[] = {0.0, 0.0, 0.0};// 记录累计旋转后的坐标轴向量
bool trackingMouse = false;//跟踪鼠标是否有点击并移动
bool startLocate = true;//每次点击鼠标时开始定位
bool isRotate = false;//标识是否在进行旋转
bool isTranslate = false;//标识是否在进行平移
GLdouble viewer[] = {0.0, 0.0, 6.0};//用于设置视点位置gluLookAt(),参数要求为GLdouble
int curx, cury;// 记录当前鼠标坐标
GLfloat lastPos[3] = {0.0, 0.0, 0.0};// 记录鼠标上一次位置向量
// 正十二面体点的坐标
GLfloat vertices[][3] = {
{1.214124, 0.000000, 1.589309},
{0.375185, 1.154701, 1.589309},
{-0.982247, 0.713644, 1.589309},
{-0.982247, -0.713644, 1.589309},
{0.375185, -1.154701, 1.589309},
{1.964494, 0.000000, 0.375185},
{0.607062, 1.868345, 0.375185},
{-1.589309, 1.154701, 0.375185},
{-1.589309, -1.154701, 0.375185},
{0.607062, -1.868345, 0.375185},
{1.589309, 1.154701, -0.375185},
{-0.607062, 1.868345, -0.375185},
{-1.964494, 0.000000, -0.375185},
{-0.607062, -1.868345, -0.375185},
{1.589309, -1.154701, -0.375185},
{0.982247, 0.713644, -1.589309},
{-0.375185, 1.154701, -1.589309},
{-1.214124, 0.000000, -1.589309},
{-0.375185, -1.154701, -1.589309},
{0.982247, -0.713644, -1.589309}
};
// 正十二面体的颜色
GLfloat colors[][3] = {
{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {1.0, 1.0, 0.0},
{1.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, {1.0, 1.0, 1.0}, {0.6, 0.6, 0.6},
{0.6, 0.0, 0.0}, {0.0, 0.6, 0.0}, {0.0, 0.0, 0.6}, {0.6, 0.2, 0.2}
};
//根据顶点和颜色参数画出面
void polygon(int a, int b, int c, int d, int e, int f)
{
glBegin(GL_POLYGON);
glColor3fv(colors[a]);
glVertex3fv(vertices[b]);
glVertex3fv(vertices[c]);
glVertex3fv(vertices[d]);
glVertex3fv(vertices[e]);
glVertex3fv(vertices[f]);
glEnd();
}
//根据顶点索引参数画正十二面体,分十二个面画
void colorcube(void) {
polygon(0, 0, 1, 2, 3, 4);
polygon(1, 0, 5, 10, 6, 1);
polygon(2, 1, 6, 11, 7, 2);
polygon(3, 2, 7, 12, 8, 3);
polygon(4, 3, 8, 13, 9, 4);
polygon(5, 4, 9, 14, 5, 0);
polygon(6, 15, 10, 5, 14, 19);
polygon(7, 16, 11, 6, 10, 15);
polygon(8, 17, 12, 7, 11, 16);
polygon(9, 18, 13, 8, 12, 17);
polygon(10, 19, 14, 9, 13, 18);
polygon(11, 19, 18, 17, 16, 15);
}
void trackball_ptov(int x, int y, int width, int height, GLfloat v[3]) {
GLfloat d, a;
// 将x, y投影到在width,height内的半球中心
v[0] = (2.0F * x - width) / width;
v[1] = (height - 2.0F * y) / height;
d = (GLfloat)sqrt(v[0] * v[0] + v[1] * v[1]);
v[2] = (GLfloat)cos((M_PI / 2.0F) * ((d < 1.0F) ? d : 1.0F));
a = 1.0F / (GLfloat)sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
v[0] *= a;
v[1] *= a;
v[2] *= a;
}
//记录旋转参数
void rotate(GLfloat curPos[3]) {
GLfloat dx, dy, dz;
dx = curPos[0] - lastPos[0];
dy = curPos[1] - lastPos[1];
dz = curPos[2] - lastPos[2];
angle += 100.0f * sqrt(dx * dx + dy * dy + dz * dz);
axis[0] += lastPos[1] * curPos[2] - lastPos[2] * curPos[1];
axis[1] += lastPos[2] * curPos[0] - lastPos[0] * curPos[2];
axis[2] += lastPos[0] * curPos[1] - lastPos[1] * curPos[0];
lastPos[0] = curPos[0];
lastPos[1] = curPos[1];
lastPos[2] = curPos[2];
}
//记录平移位置
void translate(GLfloat x, GLfloat y) {
GLfloat dx = 10.0 * (x - curx) / winWidth;
GLfloat dy = 10.0 * (cury - y) / winHeight;
//反向移动视点位置来实现物体平移
viewer[0] -= dx;
viewer[1] -= dy;
curx = x;
cury = y;
}
// 鼠标划动时回调函数
void mouseMotion(int x, int y) {
GLfloat curPos[3];
trackball_ptov(x, y, winWidth, winHeight, curPos);
//若第一次点击,则重新定位初始坐标
if (startLocate) {
lastPos[0] = curPos[0];
lastPos[1] = curPos[1];
lastPos[2] = curPos[2];
curx = x;
cury = y;
startLocate = false;
}
//跟踪鼠标
if (trackingMouse) {
if (isTranslate) {//跟踪平移
translate(x, y);
} else if (isRotate) {//跟踪旋转
rotate(curPos);
}
}
glutPostRedisplay();
}
// 开始跟踪旋转
void startRotation(int x, int y) {
trackingMouse = true;
trackball_ptov(x, y, winWidth, winHeight, lastPos);
isRotate = true;
startLocate = true;
}
// 停止跟踪旋转
void stopRotation(int x, int y) {
trackingMouse = false;
}
// 开始跟踪平移
void startTranslation(int x, int y) {
trackingMouse = true;
trackball_ptov(x, y, winWidth, winHeight, lastPos);
isTranslate = true;
startLocate = true;
}
// 停止跟踪平移
void stopTranslation(int x, int y) {
trackingMouse = false;
isTranslate = false;
}
//处理左右鼠标按钮事件
void mouseButton(int btn, int state, int x, int y) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (btn == GLUT_LEFT_BUTTON) {// 左键则跟踪旋转
switch (state) {
case GLUT_DOWN:
y = winHeight - y;
startRotation(x, y);
break;
case GLUT_UP:
stopRotation(x, y);
break;
}
} else if (btn == GLUT_RIGHT_BUTTON) {//右键则跟踪平移
switch (state) {
case GLUT_DOWN:
y = winHeight - y;
startTranslation(x, y);
break;
case GLUT_UP:
stopTranslation(x, y);
break;
}
}
}
void myDisplay(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 重新加载原矩阵
glLoadIdentity();
// 设定视点位置、焦点,达到平移的效果
gluLookAt(viewer[0], viewer[1], viewer[2],
viewer[0], viewer[1], viewer[2] - 5,
0.0, 1.0, 0.0);
// 旋转
if (isRotate) {
glRotatef(angle, axis[0], axis[1], axis[2]);
}
colorcube();
glFlush();
glutSwapBuffers();
}
void myReshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//透视投影
gluPerspective(85.0, w/h, 4.0, 15.0);
winWidth = w;
winHeight = h;
glMatrixMode(GL_MODELVIEW);
}
void myIdle() {
glutPostRedisplay();
}
//处理按键函数wsad四个方向
void processKeys(unsigned char key, int x, int y) {
switch (key) {
case 'w':
case 'W':
viewer[1] -= 0.3;
break;
case 's':
case 'S':
viewer[1] += 0.3;
break;
case 'a':
case 'A':
viewer[0] += 0.3;
break;
case 'd':
case 'D':
viewer[0] -= 0.3;
break;
}
myDisplay();
}
//处理特殊按键上下左右四个方向
void processSpecialKeys(int key, int x, int y) {
switch (key) {
case GLUT_KEY_UP:
viewer[1] -= 0.3;
break;
case GLUT_KEY_DOWN:
viewer[1] += 0.3;
break;
case GLUT_KEY_LEFT:
viewer[0] += 0.3;
break;
case GLUT_KEY_RIGHT:
viewer[0] -= 0.3;
break;
case GLUT_KEY_F1:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
case GLUT_KEY_F2:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
}
myDisplay();
}
//处理菜单条目事件
void processMenuEvents(int option) {
switch (option) {
case POLYHEDRAL:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//设置为多面体绘制
break;
case WIREFRAME:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//设置为线框体绘制
break;
}
}
//创建菜单,供选择两种绘制模式
void createGLUTMenus() {
glutCreateMenu(processMenuEvents);
glutAddMenuEntry("Polyhedral", POLYHEDRAL);
glutAddMenuEntry("Wireframe", WIREFRAME);
glutAttachMenu(GLUT_MIDDLE_BUTTON);//与鼠标中间的按钮连接
}
int main(int argc, char **argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(650, 650);
glutCreateWindow("Dodecahedron");
glutReshapeFunc(myReshape);
glutDisplayFunc(myDisplay);
glutIdleFunc(myIdle);
glutMouseFunc(mouseButton);
glutMotionFunc(mouseMotion);
glutKeyboardFunc(processKeys);
glutSpecialFunc(processSpecialKeys);
glEnable(GL_DEPTH_TEST);
createGLUTMenus();
glutMainLoop();
return 0;
}