0%

OpenGL绘图实例十之绘制3D机器人

综述

通过上一节说的绘制3D图形基础,我们应该对绘制3D图形有了基本的认识,接下来我们就进行一个实例,绘制一个3D机器人。 本节我们要完成的任务有:

1.绘制一个仿真3D机器人(样式自选,参考例图),至少包含头、躯干、四肢三个部分. 2.对机器人填充颜色。 3.增加点光源,使得机器人更加真实。 4.实现交互,使得能够控制机器人进行旋转、前进、后退等动作。 键盘”w”:前进 键盘”s”:后退 键盘”a”:顺时针旋转 键盘”d”:逆时针旋转

接下来我们就一步步实现机器人的绘制吧。

绘制球体

绘制球体我们有两种方法,一个叫glutSolidSphere,另一个叫glutWireSphere,这两个的区别在于,一个绘制的是实心的球体,另一个绘制的是现状描绘而成的球体,在绘制过程中,我们可以通过一个参数来对其进行控制。 glutSolidSphere是GLUT工具包中的一个函数,该函数用于渲染一个球体。球体球心位于原点。在OpenGL中默认的原点就是窗口客户区的中心。 函数原型

1
2
void glutSolidSphere(GLdouble radius , GLint slices , GLint stacks);
void glutWireSphere(GLdouble radius, GLint slices, GLint stacks );

radius,球体的半径 slices,以Z轴上线段为直径分布的圆周线的条数(将Z轴看成地球的地轴,类似于经线) stacks,围绕在Z轴周围的线的条数(类似于地球上纬线) 一般而言, 后两个参数赋予较大的值, 渲染花费的时间要长, 效果更逼真。 然而我们可以发现,这里并没有定义球中心的参数,所以,我们可以利用平移函数组合实现。即利用glTranslated函数来实现。最后,我们定义的画球体的方法如下

1
2
3
4
5
6
7
8
9
10
11
//画球
void drawBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glPopMatrix();
}

其中两个常量定义如下

1
2
#define SOLID 1
#define WIRE 2

在这里我们还用到了上一节所说的PushMatrix和PopMatrix方法。如果不熟悉,请查看上一节的内容。

绘制长方体

同样地,利用变换平移放缩的方法,再加上类库的绘制正方体的方法,我们也可以轻松地实现绘制长方体的方法。 正方体怎样变成长方体,很简单,拉伸一下就好了。所以,我们用到了glScaled方法。 绘制长方体的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
//画长方体
void drawSkewed(double l, double w, double h, double x, double y, double z, int MODE) {
glPushMatrix();
glScaled(l, w, h);
glTranslated(x, y, z);
if (MODE == SOLID) {
glutSolidCube(1);
} else if (MODE == WIRE) {
glutWireCube(1);
}
glPopMatrix();
}

这里仍然还是定义了绘图模式,是线条还是实体。

定义光照

在这里提供一篇博文,讲光照讲得比较细致 OPENGL光照 那么在这里我就直接贴上光照设置的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void init() {
//定义光源的颜色和位置
GLfloat ambient[] = { 0.5, 0.8, 0.1, 0.1 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { -80.0, 50.0, 25.0, 1.0 };
//选择光照模型
GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
GLfloat local_view[] = { 0.0 };
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
//设置环境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//设置漫射光
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//设置光源位置
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//启动光照
glEnable(GL_LIGHTING);
//启用光源
glEnable(GL_LIGHT0);
}

其中设置了光源颜色,位置等参数。

图形的移动

在这里我们实现了鼠标的监听旋转和键盘的监听旋转,在这里分别描述如下

1.鼠标监听

对于鼠标监听事件,在前面的文章中已经做了说明,如果大家不熟悉可以看下面这篇文章 鼠标监听 我们要实现的就是在拖动鼠标的时候实现图形的旋转功能。 在点击鼠标时,我们记录下来点击的位置,然后在鼠标移动的时候记录下当前坐标与上一个位置的坐标之差。通过定义一个角度的变量,每次鼠标移动的时候让整个图形旋转角度加上这个差值,然后重新绘制图形,就可以实现整个图形的 旋转了。 鼠标监听的两个方法如下,分别是鼠标点击和鼠标移动。 定义两个变量,旋转角度

1
2
int spinX = 0;
int spinY = 0;

然后定义两个鼠标事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 鼠标移动事件 
void mouseMove(int x, int y) {
int dx = x - moveX;
int dy = y - moveY;
printf("dx;%dx,dy:%dy\n",dx,dy);
spinX += dx;
spinY += dy;
glutPostRedisplay();
moveX = x;
moveY = y;
}
//鼠标点击事件
void mouseClick(int btn, int state, int x, int y) {
moveX = x;
moveY = y;
}

恩,通过定义上面的方法,然后在main中加入监听。

1
2
3
4
//鼠标点击事件,鼠标点击或者松开时调用
glutMouseFunc(mouseClick);
//鼠标移动事件,鼠标按下并移动时调用
glutMotionFunc(mouseMove);

display函数中,在绘图前调用该方法即可

1
2
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);

分别是绕y轴和x轴旋转一定的角度。这样就可以实现鼠标的监听了。

2.键盘监听

键盘事件也很简单,同样是监听按键的按下。其中w、s键是用来控制机器人的远近的。 定义一个变量叫 dis,代表远近 键盘事件函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//键盘事件
void keyPressed(unsigned char key, int x, int y) {
switch (key) {
case 'a':
spinX -= 2;
break;
case 'd':
spinX += 2;
break;
case 'w':
des += 2;
break;
case 's':
des -= 2;
break;
}
glutPostRedisplay();
}

在main函数中加入监听

1
2
//键盘事件
glutKeyboardFunc(keyPressed);

display函数中加入如下的变换

1
2
3
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);

这样通过上述方法,我们便可以实现鼠标和键盘的监听了。

裁切物体

在这里我们可能要画一个半圆,那么最方便的方法就是裁切了,利用下面的函数,我们可以方便地实现。

void glClipPlane(GLenum plane, const GLdouble *equation);

定义一个裁剪平面。equation参数指向平面方程Ax + By + Cz + D = 0的4个系数。 equation=(0,-1,0,0),前三个参数(0,-1,0)可以理解为法线向下,只有向下的,即Y<0的才能显示,最后一个参数0表示从z=0平面开始。这样就是裁剪掉上半平面。 equation=(0,1,0,0)表示裁剪掉下半平面, equation=(1,0,0,0)表示裁剪掉左半平面, equation=(-1,0,0,0)表示裁剪掉右半平面, equation=(0,0,-1,0)表示裁剪掉前半平面, equation=(0,0,1,0)表示裁剪掉后半平面 代码示例如下

1
2
3
4
5
GLdouble eqn[4]={0.0,0.0,-1.0,0.0};
glClipPlane(GL_CLIP_PLANE0,eqn);
glEnable(GL_CLIP_PLANE0);
glutSolidSphere(headR,slices,slices);
glDisable(GL_CLIP_PLANE0);

首先我们必须要定义一个GLdouble数组,然后利用glClipPlane方法来设置,然后开启裁切,最后关闭裁切。 利用类似的方法我们可以写出绘制半球的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//画半球
void drawHalfBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
GLdouble eqn[4]={0.0, 1.0, 0.0, 0.0};
glClipPlane(GL_CLIP_PLANE0,eqn);
glEnable(GL_CLIP_PLANE0);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glDisable(GL_CLIP_PLANE0);
glPopMatrix();
}

恩,通过上述方法,我们可以方便地绘制出一个半球体。

绘制机器人

有了上述的铺垫,我们绘制机器人简直易如反掌,同样还可以实现各式各样的监听。 主要就是位置的确定了。 display函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void display(void) {
//清除缓冲区颜色
glClear(GL_COLOR_BUFFER_BIT);
//定义白色
glColor3f(1.0, 1.0, 1.0);
//圆点放坐标中心
glLoadIdentity();
//从哪个地方看
gluLookAt(-2.0, -1.0, 20.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glPushMatrix();
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);
//头
drawBall(2, 0, 1, 0, SOLID);
//身体
drawSkewed(5, 4.4, 4, 0, -0.75, 0, SOLID);
//肩膀
drawHalfBall(1, 3.5, -2.1, 0, SOLID);
drawHalfBall(1, -3.5, -2.1, 0, SOLID);
//胳膊
drawSkewed(1, 3, 1, 3.5, -1.3, 0, SOLID);
drawSkewed(1, 3, 1, -3.5, -1.3, 0, SOLID);
//手
drawBall(1, 3.5, -6.4, 0, SOLID);
drawBall(1, -3.5, -6.4, 0, SOLID);
//腿
drawSkewed(1.2, 3, 2, 1, -2.4, 0, SOLID);
drawSkewed(1.2, 3, 2, -1, -2.4, 0, SOLID);
//脚
drawSkewed(1.5, 1, 3, 0.9, -9.2, 0, SOLID);
drawSkewed(1.5, 1, 3, -0.9, -9.2, 0, SOLID);
glPopMatrix();
glutSwapBuffers();
}

恩,通过调用这个函数我们便可以完成机器人的绘制了。

完整代码

在这里提供完整代码示例,仅供参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <glut.h>
#include <stdlib.h>
#include <stdio.h>

#define SOLID 1
#define WIRE 2

int moveX,moveY;
int spinX = 0;
int spinY = 0;
int des = 0;


void init() {
//定义光源的颜色和位置
GLfloat ambient[] = { 0.5, 0.8, 0.1, 0.1 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { -80.0, 50.0, 25.0, 1.0 };
//选择光照模型
GLfloat lmodel_ambient[] = { 0.4, 0.4, 0.4, 1.0 };
GLfloat local_view[] = { 0.0 };
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_SMOOTH);
//设置环境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//设置漫射光
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//设置光源位置
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//启动光照
glEnable(GL_LIGHTING);
//启用光源
glEnable(GL_LIGHT0);
}

//画球
void drawBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glPopMatrix();
}

//画半球
void drawHalfBall(double R, double x, double y,double z, int MODE) {
glPushMatrix();
glTranslated(x,y,z);
GLdouble eqn[4]={0.0, 1.0, 0.0, 0.0};
glClipPlane(GL_CLIP_PLANE0,eqn);
glEnable(GL_CLIP_PLANE0);
if (MODE == SOLID) {
glutSolidSphere(R,20,20);
} else if (MODE ==WIRE) {
glutWireSphere(R,20,20);
}
glDisable(GL_CLIP_PLANE0);
glPopMatrix();
}

//画长方体
void drawSkewed(double l, double w, double h, double x, double y, double z, int MODE) {
glPushMatrix();
glScaled(l, w, h);
glTranslated(x, y, z);
if (MODE == SOLID) {
glutSolidCube(1);
} else if (MODE ==WIRE) {
glutWireCube(1);
}
glPopMatrix();
}

void display(void) {
//清除缓冲区颜色
glClear(GL_COLOR_BUFFER_BIT);
//定义白色
glColor3f(1.0, 1.0, 1.0);
//圆点放坐标中心
glLoadIdentity();
//从哪个地方看
gluLookAt(-2.0, -1.0, 20.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glPushMatrix();
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);
//头
drawBall(2, 0, 1, 0, SOLID);
//身体
drawSkewed(5, 4.4, 4, 0, -0.75, 0, SOLID);
//肩膀
drawHalfBall(1, 3.5, -2.1, 0, SOLID);
drawHalfBall(1, -3.5, -2.1, 0, SOLID);
//胳膊
drawSkewed(1, 3, 1, 3.5, -1.3, 0, SOLID);
drawSkewed(1, 3, 1, -3.5, -1.3, 0, SOLID);
//手
drawBall(1, 3.5, -6.4, 0, SOLID);
drawBall(1, -3.5, -6.4, 0, SOLID);
//腿
drawSkewed(1.2, 3, 2, 1, -2.4, 0, SOLID);
drawSkewed(1.2, 3, 2, -1, -2.4, 0, SOLID);
//脚
drawSkewed(1.5, 1, 3, 0.9, -9.2, 0, SOLID);
drawSkewed(1.5, 1, 3, -0.9, -9.2, 0, SOLID);
glPopMatrix();
glutSwapBuffers();
}
//鼠标点击事件
void mouseClick(int btn, int state, int x, int y) {
moveX = x;
moveY = y;
GLfloat ambient[] = { (float)rand() / RAND_MAX, (float)rand() / RAND_MAX, (float)rand() / RAND_MAX, 0.1 };
//设置环境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//启用光源
glEnable(GL_LIGHT0);
}

//键盘事件
void keyPressed(unsigned char key, int x, int y) {
switch (key) {
case 'a':
spinX -= 2;
break;
case 'd':
spinX += 2;
break;
case 'w':
des += 2;
break;
case 's':
des -= 2;
break;
}
glutPostRedisplay();
}
// 鼠标移动事件
void mouseMove(int x, int y) {
int dx = x - moveX;
int dy = y - moveY;
printf("dx;%dx,dy:%dy\n",dx,dy);
spinX += dx;
spinY += dy;
glutPostRedisplay();
moveX = x;
moveY = y;
}

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);
}

int main(int argc, char** argv) {
//初始化
glutInit(&argc, argv);
//设置显示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
//初始化窗口大小
glutInitWindowSize(500, 500);
//定义左上角窗口位置
glutInitWindowPosition(100, 100);
//创建窗口
glutCreateWindow(argv[0]);
//初始化
init();
//显示函数
glutDisplayFunc(display);
//窗口大小改变时的响应
glutReshapeFunc(reshape);
//鼠标点击事件,鼠标点击或者松开时调用
glutMouseFunc(mouseClick);
//鼠标移动事件,鼠标按下并移动时调用
glutMotionFunc(mouseMove);
//键盘事件
glutKeyboardFunc(keyPressed);
//循环
glutMainLoop();
return 0;
}

仅供参考,如有问题,敬请指正。 运行结果如下 QQ截图20150607013139 恩,大体就是这样。

总结

本次实验做的比较匆忙,只研究了一个晚上的时间,所以有些地方还是不太完善,希望发出来对小伙伴们有所启发,有所帮助。如果有问题,欢迎同我交流。谢大家!