OpenGL是一个非常强大的图形引擎。传说当下最流行的图形引擎有两套,其中之一就是Windows平台上最常用的DirectX(而且只能在Microsoft的平台上使用,可以看下百度百科关于DirectX的介绍),而另外一套则是OpenGL了,可以用于非常多的平台(可以参看百度百科关于OpenGL的介绍),至少我是这么被告知的。说到OpenGL,就不得不提到NeHe(读音有点像“妮褐”,不过我平时都叫它“呵呵”),据我的浅薄认知来看,NeHe提供了大概48个使用OpenGL的例子,这些例子涉及了OpenGL编程非常多的方面,传说是掌握这些例子就可以无敌了,详细可以去NeHe官网看看,最右边有个“Legacy Tutorials”(嗯,对了,我确定是有48个例子了)就是所有的例子,所有的例子可以下载不同IDE(集成开发环境,比如像vs,vc,devc等)的源码。
关于OpenGL实现太阳系模型是因为选了三维动画的课,最后交的结课作业,为了不太浪费资源,所以写一篇文章来保留这些劳动成果,也为后来的人做个小小的参考,因为初涉OpenGL,模型设计实现不妥之处还望高手指教。以下是简要的设计描述:
为简便起见,简化模型:
太阳为光源星球,并且为太阳系行星的中心;
所有星球(除太阳以外)以圆形轨道绕行;
所有星球均为正球体。
对具体星球而言,具有以下属性:
颜色(Color);
半径(Radius);
自转速度(SelfSpeed);
公转速度(Speed);
距离太阳中心距离(Distance);
绕行星球(ParentBall);
当前自转角度(AlphaSelf);
当前公转角度(Alpha)。
设计描述星球的类及关键实现:
描述普通的能够自转并且绕某个点公转的球(class Ball);
描述具有材质属性的球(class MatBall);
描述具有发光属性的球(class LightBall);
每个星球类独立的处理自己的运动;
类中实现绘图方法(Draw)和更新方法(Update)用于绘制、更新星球;
Draw()方法中需要处理自己绕行点(ParentBall)的关系;
对于星球的属性数据需要案一定比例进行调整以符合观看需要。
程序流程如下:
使用Console模式开启程序;
初始化星球对象;
初始化OpenGL引擎,实现绘制函数(OnDraw)和更新函数(OnUpdate);
在绘制函数中调用每个星球对象的Draw()方法;
Draw()方法根据星球的属性进行变换并绘制;
在更新函数中调用每个星球对象的Update()方法;
Update()方法处理自转数据和公转数据;
实现按键监控,可以通过调整视角对太阳系模型进行观察。
下面是运行程序的截图(本来是彩色的,不过呢,因为word打印需要变成灰度图来看效果,又不想去再截图了,So….):
以下是完整的代码:
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
|
/***************************** BallDefinition.h ******************************/
#include <gl/glut.h>
#ifndef __BALLDEFINITION
#define __BALLDEFINITION
// 数组type
typedef
GLfloat (Float2)[2];
typedef
GLfloat (Float3)[3];
typedef
GLfloat Float;
typedef
GLfloat (Float4)[4];
// 对数组进行操作的宏
//#define Float(name, value) (name)=(value)
#define Float2(name, value0, value1) ((name)[0])=(value0), ((name)[1])=(value1)
#define Float3(name, value0, value1, value2) ((name)[0])=(value0), \
((name)[1])=(value1), ((name)[2])=(value2)
#define Float4(name, value0, value1, value2, value3) ((name)[0])=(value0), \
((name)[1])=(value1), ((name)[2])=(value2), ((name)[3])=(value3)
// 对数组进行操作的宏
//#define Float(name) (name)
#define RFloat2(name) ((name)[0]), ((name)[1])
#define RFloat3(name) ((name)[0]), ((name)[1]), ((name)[2])
#define RFloat4(name) ((name)[0]), ((name)[1]), ((name)[2]), ((name)[3])
class
Ball {
public
:
Float4 Color;
Float Radius;
Float SelfSpeed;
Float Speed;
// ParentBall是本球绕行的球
// Center是本球的中心点,当有ParentBall和Distance的时候可以不使用
// Distance是本球中心与ParentBall中心的距离
// Center暂时没有使用
//Float2 Center;
Float Distance;
Ball * ParentBall;
virtual
void
Draw() { DrawBall(); }
virtual
void
Update(
long
TimeSpan);
Ball(Float Radius, Float Distance, Float Speed, Float SelfSpeed, Ball * Parent);
// 对普通的球体进行移动和旋转
void
DrawBall();
protected
:
Float AlphaSelf, Alpha;
};
class
MatBall :
public
Ball {
public
:
virtual
void
Draw() { DrawMat(); DrawBall(); }
MatBall(Float Radius, Float Distance, Float Speed, Float SelfSpeed,
Ball * Parent, Float3 color);
// 对材质进行设置
void
DrawMat();
};
class
LightBall :
public
MatBall {
public
:
virtual
void
Draw() { DrawLight(); DrawMat(); DrawBall(); }
LightBall(Float Radius, Float Distance, Float Speed, Float SelfSpeed,
Ball * Parent, Float3 color);
// 对光源进行设置
void
DrawLight();
};
#endif
/**************************** BallDefinition.cpp *****************************/
#include "BallDefinition.h"
Ball::Ball(Float Radius, Float Distance, Float Speed, Float SelfSpeed, Ball * Parent) {
Float4(Color, 0.8f, 0.8f, 0.8f, 1.0f);
this
->Radius = Radius;
this
->SelfSpeed = SelfSpeed;
if
(Speed > 0)
this
->Speed = 360.0f / Speed;
AlphaSelf = Alpha= 0;
this
->Distance = Distance;
ParentBall = Parent;
}
#include <stdio.h>
#include <math.h>
#define PI 3.1415926535
// 对普通的球体进行移动和旋转
void
Ball::DrawBall() {
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
int
n = 1440;
glPushMatrix();
{
// 公转
if
(ParentBall != 0 && ParentBall->Distance > 0) {
glRotatef(ParentBall->Alpha, 0, 0, 1);
glTranslatef(ParentBall->Distance, 0.0, 0.0);
glBegin(GL_LINES);
for
(
int
i=0; i<n; ++i)
glVertex2f(Distance *
cos
(2 * PI * i / n),
Distance *
sin
(2 * PI * i / n));
glEnd();
}
else
{
glBegin(GL_LINES);
for
(
int
i=0; i<n; ++i)
glVertex2f(Distance *
cos
(2 * PI * i / n),
Distance *
sin
(2 * PI * i / n));
glEnd();
}
glRotatef(Alpha, 0, 0, 1);
glTranslatef(Distance, 0.0, 0.0);
// 自转
glRotatef(AlphaSelf, 0, 0, 1);
// 绘图
glColor3f(RFloat3(Color));
glutSolidSphere(Radius, 40, 32);
}
glPopMatrix();
}
void
Ball::Update(
long
TimeSpan) {
// TimeSpan 是天
Alpha += TimeSpan * Speed;
AlphaSelf += SelfSpeed;
}
MatBall::MatBall(Float Radius, Float Distance, Float Speed, Float SelfSpeed,
Ball * Parent, Float3 color) : Ball(Radius, Distance, Speed, SelfSpeed, Parent) {
Float4(Color, color[0], color[1], color[2], 1.0f);
}
// 对材质进行设置
void
MatBall::DrawMat() {
GLfloat mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
//下面两句替换可以出现彩色或者蓝色的太阳系模型
//GLfloat mat_emission[] = {RFloat4(Color)};
GLfloat mat_emission[] = {.0f, .0f, .1f, 1.0f};
GLfloat mat_shininess = 90.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
}
LightBall::LightBall(Float Radius, Float Distance, Float Speed, Float SelfSpeed,
Ball * Parent, Float3 color)
: MatBall(Radius, Distance, Speed, SelfSpeed, Parent, color) {}
// 对光源进行设置
void
LightBall::DrawLight() {
GLfloat light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
}
/**************************** Main.cpp *****************************/
#include <stdlib.h>
#include "BallDefinition.h"
#define WIDTH 700
#define HEIGHT 700
// 每次更新 看做过去了 1 天
#define TimePast 1
#include <math.h>
// 对太阳系星球的参数进行调整用的宏
#define KK .000001
#define sk (.07 * KK)
#define k (.5 * KK)
#define vk (1.5 * KK)
#define fk (.5 * KK)
#define hfk (.4 * KK)
#define ffk (.3 * KK)
#define dk (1.07 * KK)
#define edk (1.12 * KK)
#define lsk (.3 * KK)
#define mk (15000 * KK)
#define mrk (1.6 * KK)
#define tk .3
#define ttk .2
#define tttk .1
// 自转速度(都定义为定值)
#define SelfRotate 3
#define ARRAY_SIZE 10
enum
STARS {Sun, Mercury, Venus, Earth, Moon, Mars, Jupiter, Saturn, Uranus, Neptune};
Ball * Balls[ARRAY_SIZE];
void
init() {
Float3 Color;
// 定义星球,这些星球的数据是经过不同比例变化过的
// 太阳
Float3(Color, 1, 0, 0);
Balls[Sun] =
new
LightBall(sk * 696300000, 0, 0, SelfRotate, 0, Color);
// 水星
Float3(Color, .2, .2, .5);
Balls[Mercury] =
new
MatBall(
vk * 4880000, dk * 58000000, 87, SelfRotate, Balls[Sun], Color);
// 金星
Float3(Color, 1, .7, 0);
Balls[Venus] =
new
MatBall(
vk * 12103600, dk * 108000000, 225, SelfRotate, Balls[Sun], Color);
// 地球
Float3(Color, 0, 1, 0);
Balls[Earth] =
new
MatBall(
vk * 12756300, edk * 150000000, 365, SelfRotate, Balls[Sun], Color);
// 月亮
Float3(Color, 1, 1, 0);
Balls[Moon] =
new
MatBall(
mrk * 3844010.0f , mk * 1734.0f, 30, SelfRotate, Balls[Earth], Color);
// 火星
Float3(Color, 1, .5, .5);
Balls[Mars] =
new
MatBall(
vk * 6794000, KK * 228000000, 687, SelfRotate, Balls[Sun], Color);
// 木星
Float3(Color, 1, 1, .5);
Balls[Jupiter] =
new
MatBall(
lsk * 142984000, fk * 778000000, tk * 4328, SelfRotate, Balls[Sun], Color);
// 土星
Float3(Color, .5, 1, .5);
Balls[Saturn] =
new
MatBall(
lsk * 120536000, fk * 1427000000, ttk * 10752, SelfRotate, Balls[Sun], Color);
// 天王星
Float3(Color, .4, .4, .4);
Balls[Uranus] =
new
MatBall(k * 51118000,
hfk * 2870000000, tttk * 30664, SelfRotate, Balls[Sun], Color);
// 海王星
Float3(Color, .5, .5, 1);
Balls[Neptune] =
new
MatBall(k * 49532000,
ffk * 4497000000, tttk * 60148, SelfRotate, Balls[Sun], Color);
}
// 初始视角( 视点在(+z, -y)处 )
#define REST (700000000 * KK)
#define REST_Z (REST)
#define REST_Y (-REST)
// lookAt参数
GLdouble eyeX = 0, eyeY = REST_Y, eyeZ= REST_Z;
GLdouble centerX= 0, centerY= 0, centerZ= 0;
GLdouble upX= 0, upY= 0, upZ= 1;
void
OnDraw(
void
) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(.7, .7, .7, .1);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(75.0f, 1.0f, 1.0f, 40000000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eyeX, eyeY,eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
// 实际绘制
for
(
int
i=0; i<ARRAY_SIZE; i++)
Balls[i]->Draw();
glutSwapBuffers();
}
void
OnUpdate(
void
) {
// 实际更新
for
(
int
i=0; i<ARRAY_SIZE; i++)
Balls[i]->Update(TimePast);
OnDraw();
}
// 每次按键移动的距离
#define OFFSET (20000000 * KK)
// 按键操作变化视角
// w(+y方向) a(-x方向) d(+x方向) x(-y方向) s(+z 方向) S(-z 方向) r(reset)
void
keyboard (unsigned
char
key,
int
x,
int
y) {
switch
(key) {
case
'w'
: eyeY += OFFSET;
break
;
case
's'
: eyeZ += OFFSET;
break
;
case
'S'
: eyeZ -= OFFSET;
break
;
case
'a'
: eyeX -= OFFSET;
break
;
case
'd'
: eyeX += OFFSET;
break
;
case
'x'
: eyeY -= OFFSET;
break
;
case
'r'
:
eyeX = 0; eyeY = REST_Y; eyeZ= REST_Z;
centerX= 0; centerY= 0; centerZ= 0;
upX= 0; upY= 0; upZ= 1;
break
;
case
27:
exit
(0);
break
;
default
:
break
;
}
}
int
main(
int
argc,
char
* argv[]) {
init();
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(150, 50);
glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow(
"SolarSystem by Juwend"
);
glutDisplayFunc(&OnDraw);
glutIdleFunc(&OnUpdate);
glutKeyboardFunc(keyboard);
glutMainLoop();
return
0;
}
|
这个模型还有很多需要增加的地方,比如He老师(任课老师)提出的,轨道可以使用椭圆,包括星球也可以更现实一些,另外就是球面纹理了,需要把星球的皮给披上去,这样就更容易看出自转了,还有就是因为最长的公转链就是太阳、地球、月亮,所以在实现公转和自转的时候,还有点问题的,假如最长公转链里有更多,比如10个球,则以上代码就会出问题了,但是修改代码解决这个问题并不是很困难的问题。关于这些问题,如果有时间再改吧。
关于OpenGL环境配置的问题,我想我应该会再写一篇短文来介绍的,只是不知是何时了……………………………
在此我也要感谢JiangTao同学在这方面提供了大量的无私的帮助!
啊,太谢谢你了~~~~~~~~~~~
另外,这是我在计算机三维动画这门课交的最后的大作业,希望CV代码的朋友一定要注意这个问题,并且能够理解我的补充这么一句话的意思。
补注:
事隔多月,居然又需要用到OpenGL的东西,还好当时的学习使得些基础,不过居然还是有些东西忘记了,重新思考才回忆起来。
1
|
gluLookAt(eyeX, eyeY,eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
|
这个函数的8个参数分别是这个意思:
eyeX/Y/Z:分别是视点(也可以认为是眼睛)的位置
centerX/Y/Z:分别是参照坐标,其实是什么参照坐标,我也不是很清楚
upX/Y/Z:是指明朝上的向量,以转动整个GL坐标系
如果要做仰视/俯视效果,设置好eyeX/Y/Z之后,就需要调整centerX/Y/Z的值了。
假设up为(0,0,1),则是z轴朝上,
俯视的话就需要center为(0,0,z),z>0
仰视的话就需要center为(0,0,z),z<0
反正感觉情况就是这样的,到时候问过大神再补……
我X……换了win7 64位系统之后,这个程序运行起来使用了GLU.h内方法的地方各种crtexe的报错……不清楚肿么回事,研究研究再说……
OpenGL实现太阳系模型 —— Juwend
Juwend’s – http://www.juwends.com
笔者水平有限,若有错漏,欢迎指正,欢迎转载以及CV操作,但希注明出处,谢谢!