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

网友投稿 1557 2022-05-28

综述

通过上一节说的绘制 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

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

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

#include

#include

#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;

}

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

总结

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

OpenGL 机器人

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【云小课】应用平台第7课 Kafka Manager,您的Kafka界面管理专家
下一篇:600 条最强 Linux 命令总结
相关文章