OpenGL 繪圖實(shí)例十之繪制 3D 機(jī)器人
綜述
通過上一節(jié)說的繪制 3D 圖形基礎(chǔ),我們應(yīng)該對(duì)繪制 3D 圖形有了基本的認(rèn)識(shí),接下來我們就進(jìn)行一個(gè)實(shí)例,繪制一個(gè) 3D 機(jī)器人。 本節(jié)我們要完成的任務(wù)有:
1. 繪制一個(gè)仿真 3D 機(jī)器人 (樣式自選,參考例圖),至少包含頭、軀干、四肢三個(gè)部分. 2. 對(duì)機(jī)器人填充顏色。 3. 增加點(diǎn)光源,使得機(jī)器人更加真實(shí)。 4. 實(shí)現(xiàn)交互,使得能夠控制機(jī)器人進(jìn)行旋轉(zhuǎn)、前進(jìn)、后退等動(dòng)作。 鍵盤”w”: 前進(jìn) 鍵盤”s”: 后退 鍵盤”a”: 順時(shí)針旋轉(zhuǎn) 鍵盤”d”: 逆時(shí)針旋轉(zhuǎn)
接下來我們就一步步實(shí)現(xiàn)機(jī)器人的繪制吧。
繪制球體
繪制球體我們有兩種方法,一個(gè)叫 glutSolidSphere,另一個(gè)叫 glutWireSphere,這兩個(gè)的區(qū)別在于,一個(gè)繪制的是實(shí)心的球體,另一個(gè)繪制的是現(xiàn)狀描繪而成的球體,在繪制過程中,我們可以通過一個(gè)參數(shù)來對(duì)其進(jìn)行控制。 glutSolidSphere 是 GLUT 工具包中的一個(gè)函數(shù),該函數(shù)用于渲染一個(gè)球體。球體球心位于原點(diǎn)。在 OpenGL 中默認(rèn)的原點(diǎn)就是窗口客戶區(qū)的中心。 函數(shù)原型
1
2
void glutSolidSphere(GLdouble radius , GLint slices , GLint stacks);
void glutWireSphere(GLdouble radius, GLint slices, GLint stacks );
radius,球體的半徑 slices,以 Z 軸上線段為直徑分布的圓周線的條數(shù)(將 Z 軸看成地球的地軸,類似于經(jīng)線) stacks,圍繞在 Z 軸周圍的線的條數(shù)(類似于地球上緯線) 一般而言, 后兩個(gè)參數(shù)賦予較大的值, 渲染花費(fèi)的時(shí)間要長, 效果更逼真。 然而我們可以發(fā)現(xiàn),這里并沒有定義球中心的參數(shù),所以,我們可以利用平移函數(shù)組合實(shí)現(xiàn)。即利用 glTranslated 函數(shù)來實(shí)現(xiàn)。最后,我們定義的畫球體的方法如下
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();
}
其中兩個(gè)常量定義如下
1
2
#define SOLID 1
#define WIRE 2
在這里我們還用到了上一節(jié)所說的 PushMatrix 和 PopMatrix 方法。如果不熟悉,請(qǐng)查看上一節(jié)的內(nèi)容。
繪制長方體
同樣地,利用變換平移放縮的方法,再加上類庫的繪制正方體的方法,我們也可以輕松地實(shí)現(xiàn)繪制長方體的方法。 正方體怎樣變成長方體,很簡單,拉伸一下就好了。所以,我們用到了 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();
}
這里仍然還是定義了繪圖模式,是線條還是實(shí)體。
定義光照
在這里提供一篇博文,講光照講得比較細(xì)致 OPENGL 光照 那么在這里我就直接貼上光照設(shè)置的實(shí)現(xiàn)
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);
//設(shè)置環(huán)境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//設(shè)置漫射光
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//設(shè)置光源位置
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//啟動(dòng)光照
glEnable(GL_LIGHTING);
//啟用光源
glEnable(GL_LIGHT0);
}
其中設(shè)置了光源顏色,位置等參數(shù)。
圖形的移動(dòng)
在這里我們實(shí)現(xiàn)了鼠標(biāo)的監(jiān)聽旋轉(zhuǎn)和鍵盤的監(jiān)聽旋轉(zhuǎn),在這里分別描述如下
1. 鼠標(biāo)監(jiān)聽
對(duì)于鼠標(biāo)監(jiān)聽事件,在前面的文章中已經(jīng)做了說明,如果大家不熟悉可以看下面這篇文章 鼠標(biāo)監(jiān)聽 我們要實(shí)現(xiàn)的就是在拖動(dòng)鼠標(biāo)的時(shí)候?qū)崿F(xiàn)圖形的旋轉(zhuǎn)功能。 在點(diǎn)擊鼠標(biāo)時(shí),我們記錄下來點(diǎn)擊的位置,然后在鼠標(biāo)移動(dòng)的時(shí)候記錄下當(dāng)前坐標(biāo)與上一個(gè)位置的坐標(biāo)之差。通過定義一個(gè)角度的變量,每次鼠標(biāo)移動(dòng)的時(shí)候讓整個(gè)圖形旋轉(zhuǎn)角度加上這個(gè)差值,然后重新繪制圖形,就可以實(shí)現(xiàn)整個(gè)圖形的 旋轉(zhuǎn)了。 鼠標(biāo)監(jiān)聽的兩個(gè)方法如下,分別是鼠標(biāo)點(diǎn)擊和鼠標(biāo)移動(dòng)。 定義兩個(gè)變量,旋轉(zhuǎn)角度
1
2
int spinX = 0;
int spinY = 0;
然后定義兩個(gè)鼠標(biāo)事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 鼠標(biāo)移動(dòng)事件
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;
}
//鼠標(biāo)點(diǎn)擊事件
void mouseClick(int btn, int state, int x, int y) {
moveX = x;
moveY = y;
}
恩,通過定義上面的方法,然后在 main 中加入監(jiān)聽。
1
2
3
4
//鼠標(biāo)點(diǎn)擊事件,鼠標(biāo)點(diǎn)擊或者松開時(shí)調(diào)用
glutMouseFunc(mouseClick);
//鼠標(biāo)移動(dòng)事件,鼠標(biāo)按下并移動(dòng)時(shí)調(diào)用
glutMotionFunc(mouseMove);
display 函數(shù)中,在繪圖前調(diào)用該方法即可
1
2
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
分別是繞 y 軸和 x 軸旋轉(zhuǎn)一定的角度。這樣就可以實(shí)現(xiàn)鼠標(biāo)的監(jiān)聽了。
2. 鍵盤監(jiān)聽
鍵盤事件也很簡單,同樣是監(jiān)聽按鍵的按下。其中 w、s 鍵是用來控制機(jī)器人的遠(yuǎn)近的。 定義一個(gè)變量叫 dis,代表遠(yuǎn)近 鍵盤事件函數(shù)如下:
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 函數(shù)中加入監(jiān)聽
1
2
//鍵盤事件
glutKeyboardFunc(keyPressed);
display 函數(shù)中加入如下的變換
1
2
3
glRotated(spinX, 0, 1, 0);
glRotated(spinY, 1, 0, 0);
glTranslated(0, 0, des);
這樣通過上述方法,我們便可以實(shí)現(xiàn)鼠標(biāo)和鍵盤的監(jiān)聽了。
裁切物體
在這里我們可能要畫一個(gè)半圓,那么最方便的方法就是裁切了,利用下面的函數(shù),我們可以方便地實(shí)現(xiàn)。
void glClipPlane(GLenum plane, const GLdouble *equation);
定義一個(gè)裁剪平面。equation 參數(shù)指向平面方程 Ax + By + Cz + D = 0 的 4 個(gè)系數(shù)。 equation=(0,-1,0,0),前三個(gè)參數(shù)(0,-1,0)可以理解為法線向下,只有向下的,即 Y<0 的才能顯示,最后一個(gè)參數(shù) 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);
首先我們必須要定義一個(gè) GLdouble 數(shù)組,然后利用 glClipPlane 方法來設(shè)置,然后開啟裁切,最后關(guān)閉裁切。 利用類似的方法我們可以寫出繪制半球的方法如下
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();
}
恩,通過上述方法,我們可以方便地繪制出一個(gè)半球體。
繪制機(jī)器人
有了上述的鋪墊,我們繪制機(jī)器人簡直易如反掌,同樣還可以實(shí)現(xiàn)各式各樣的監(jiān)聽。 主要就是位置的確定了。 display 函數(shù)如下
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) {
//清除緩沖區(qū)顏色
glClear(GL_COLOR_BUFFER_BIT);
//定義白色
glColor3f(1.0, 1.0, 1.0);
//圓點(diǎn)放坐標(biāo)中心
glLoadIdentity();
//從哪個(gè)地方看
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();
}
恩,通過調(diào)用這個(gè)函數(shù)我們便可以完成機(jī)器人的繪制了。
完整代碼
在這里提供完整代碼示例,僅供參考
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
#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);
//設(shè)置環(huán)境光
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
//設(shè)置漫射光
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
//設(shè)置光源位置
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//啟動(dòng)光照
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) {
//清除緩沖區(qū)顏色
glClear(GL_COLOR_BUFFER_BIT);
//定義白色
glColor3f(1.0, 1.0, 1.0);
//圓點(diǎn)放坐標(biāo)中心
glLoadIdentity();
//從哪個(gè)地方看
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();
}
//鼠標(biāo)點(diǎn)擊事件
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 };
//設(shè)置環(huán)境光
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();
}
// 鼠標(biāo)移動(dòng)事件
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);
//坐標(biāo)原點(diǎn)在屏幕中心
glLoadIdentity();
//操作模型視景
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
}
int main(int argc, char** argv) {
//初始化
glutInit(&argc, argv);
//設(shè)置顯示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
//初始化窗口大小
glutInitWindowSize(500, 500);
//定義左上角窗口位置
glutInitWindowPosition(100, 100);
//創(chuàng)建窗口
glutCreateWindow(argv[0]);
//初始化
init();
//顯示函數(shù)
glutDisplayFunc(display);
//窗口大小改變時(shí)的響應(yīng)
glutReshapeFunc(reshape);
//鼠標(biāo)點(diǎn)擊事件,鼠標(biāo)點(diǎn)擊或者松開時(shí)調(diào)用
glutMouseFunc(mouseClick);
//鼠標(biāo)移動(dòng)事件,鼠標(biāo)按下并移動(dòng)時(shí)調(diào)用
glutMotionFunc(mouseMove);
//鍵盤事件
glutKeyboardFunc(keyPressed);
//循環(huán)
glutMainLoop();
return 0;
}
僅供參考,如有問題,敬請(qǐng)指正。 運(yùn)行結(jié)果如下 恩,大體就是這樣。
總結(jié)
本次實(shí)驗(yàn)做的比較匆忙,只研究了一個(gè)晚上的時(shí)間,所以有些地方還是不太完善,希望發(fā)出來對(duì)小伙伴們有所啟發(fā),有所幫助。如果有問題,歡迎同我交流。謝大家!
OpenGL 機(jī)器人
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。