0%

通过一个初步的太阳系程序来理解OpenGL中相关的函数

完成一个初步的太阳系程序:三个球体,分别为太阳、地球和月亮。实现地球不停绕太阳旋转,月亮绕地球旋转。

扩展功能:实现两个月亮绕地球旋转,使轨道倾斜。


思路

  1. 如何实现自转和公转

    在模型变换中使用glTranslatef(),glRotaef()来操作,使用画球体函数gluWireSphere()来绘制。

    如果先旋转在绘制,则是表现为自转;而先绘制在旋转,再移动一定距离,则表现为公转。

  2. 变换的操作实际是对矩阵的操作

    由于在实现坐标的变换是通过操作矩阵来实现的,即当前矩阵乘上对应操作的矩阵。当操作完一个变换函数后,矩阵也随之被改变。OpenGL中有glPushMatrix(),glPopMatrix()两个函数,当变换后使用glPushMatrix()来把变换后的位置和角度保存起来,当再做第二次变换时,使用glPopMatrix()来把刚刚保存好的位置和角度等恢复。

  3. 实现球体不停运动

    通过使用一个定时器函数gluTimerFunc(),在单位时间内触发事件,调用glutPostRedisplay()来进行重绘。

    还可以使用glutIdleFunc()函数,当循环队列处于空闲时则触发该事件。

下面来一些重要函数作出详细的说明。


一些重要函数的说明

glutWireSphere()

功能

用于渲染出球体(由经纬线构成)。球体球心位于原点,即是在窗口客户区的中心。

函数原型

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

参数

  • radius球的半径

  • slices:以Z轴上线段为直径分布的圆周线的条数(将Z轴看成地球的地轴,类似于经线)

  • stacks:围绕在Z轴周围的线的条数(类似于地球上纬线)

类似的函数为:glutSolidSphere


glRotatef()

功能

以点(0,0,0)到点(x,y,z)为轴,旋转angle角度。

函数原型

1
void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z)

参数

  • angle:为旋转的角度,单位为度。
  • x,y,z:为对应xyz轴的布尔值变量。0.0表示假,而非零参数则表示真。

做(0,0,0)到(x,y,z)的向量,用右手握住这条向量,大拇指指向向量的正方向,四指环绕的方向就是旋转的方向

如果设置的旋转值(x,y,z的值)为正数,那么旋转的方向是逆时针的,如果旋转值是负数,那么旋转的方向是顺时针的


glTranslatef()

功能

将你绘点坐标的原点在当前原点的基础上平移一个(x,y,z)向量。

函数原型

1
void glTranslatef(GLfloat x,GLfloat y,GLfloat z);

glPushMatrix() && glPopMatrix()

glPushMatrix()glPopMatrix()这两个函数是搭配使用的,中间放置几何变换。glPushMatrix的作用是把矩阵压入栈中保存起来,留着以后再用。消除了上一次变换对这一次的影响,使本次变换基于世界坐标系的原点为参考点。


gluTimerFunc()

功能

定时器函数,为时球体能不停运动。

函数原型

1
glutTimerFunc(unsigned int millis, void (*func)(int value), int value);

参数

glutTimerFunc(毫秒数, 回调函数指针, 区别值);

用法

  • 写自己的回调函数 void OnTimer(int value),用value来区分是哪个定时器
  • 在函数里添加和位置有关的变量,然后调用glutPostRedisplay()来重绘
  • 最后再次调用glutTimerFunc,因为glut的定时器是调用一次才产生一次定时,所以如果要持续产生定时的话,需要在内部再次调用该方法。

实现两个月亮的绘制

红宝书中有对绘制多个行星的解析

如果打算绘制几颗卫星绕着同一行星旋转,需要在移动每颗卫星的位置之前保存坐标系统,并且在绘制每颗卫星之后恢复坐标系统。

所以要对两个月亮的变换都增加glPushMatrix()glPopMatrix()来实现矩阵的保存和恢复。

注意的地方是改变移动的距离方向,不然就和之前绘制的月亮重合了。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//the Moon1
glPushMatrix();
glRotatef(G_fAngle_moon, 0.0f, 1.0f, 0.0f);
glTranslatef(0.0, 0.0, -G_fEarthToMoon); //G_fEarthToMoon为月球绕地球旋转的轨道半径
glColor3f(0.8f, 0.8f, 0.0f);
glutSolidSphere(0.1, 20, 16);
glPopMatrix();

//the Moon2
glPushMatrix();
glRotatef(G_fAngle_moon, 0.0f, 1.0f, 0.0f);
glTranslatef(0.0, 0.0, G_fEarthToMoon);
glColor3f(0.0f, 0.8f, 0.0f);
glutSolidSphere(0.1, 20, 16);
glPopMatrix();

实现轨道倾斜

实现轨道倾斜就是把行星的轴倾斜。在球体的变化操作完之后,再调用glRotatef()函数让轨道倾斜即可。

举例对月球的轨道倾斜:

1
2
3
4
5
6
7
glPushMatrix();
glRotatef(-30, 0.0f, 0.0f, 1.0f); //月球轨道倾斜-30度
glRotatef(G_fAngle_moon, 0.0f, 1.0f, 0.0f);
glTranslatef(0.0, 0.0, -G_fEarthToMoon);
glColor3f(0.8f, 0.8f, 0.0f);
glutSolidSphere(0.1, 20, 16);
glPopMatrix();

程序完整代码

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
#include <windows.h>
#include <GL/freeglut.h>

//定义输出窗口的大小
#define WINDOW_HEIGHT 300
#define WINDOW_WIDTH 500

//摄像机离物体的距离
float G_fDistance = 6.0f;
float G_fAngle_horizon = 0.0f;
float G_fAngle_vertical = 0.0f;

float G_fSunToEarth = 3.0f;
float G_fEarthToMoon = 0.5f;

//物体的旋转角度
float G_fAngle_earth = 90.0f;
float G_fAngle_moon = 90.0f;

GLboolean G_bPause = false;

float G_vLit1Position[4] = { 0.0f, 0.0f, 0.0f, 1.0f };

void myinit(void);
void myReshape(GLsizei w, GLsizei h);
void display(void);

//响应键盘输入, 从而设定物体移近移远以及旋转的回调函数
void processSpecialKeys(int key, int x, int y);
void processNormalKeys(unsigned char key, int x, int y);

void planetMove(int value);

//主函数
int main(int argc, char* argv[])
{
glutInit(&argc, argv);

//初始化OPENGL显示方式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

//设定OPENGL窗口位置和大小
glutInitWindowSize(1000, 500);
glutInitWindowPosition(100, 100);

//打开窗口
glutCreateWindow("作业2");

//调用初始化函数
myinit();

//设定窗口大小变化的回调函数
glutReshapeFunc(myReshape);

//设定键盘控制的回调函数
glutSpecialFunc(processSpecialKeys);
glutKeyboardFunc(processNormalKeys);
glutTimerFunc(10, planetMove, 1);


//开始OPENGL的循环
glutDisplayFunc(display);
// glutIdleFunc(planetMove);

glutMainLoop();

return 0;
}

//用户初始化函数
void myinit(void)
{
//your initialization code
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
}

//窗口大小变化时的回调函数
void myReshape(GLsizei w, GLsizei h)
{
//设定视区
glViewport(0, 0, w, h);

//设定透视方式
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, 1.0 * (GLfloat)w / (GLfloat)h, 1.0, 30.0);
}

//每桢OpenGL都会调用这个函数,用户应该把显示代码放在这个函数中
void display(void)
{
//设置清除屏幕的颜色,并清除屏幕和深度缓冲
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

//绘图
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); //使栈顶矩阵为单位矩阵
glTranslatef(0.0, 0.0, -G_fDistance);
glRotatef(G_fAngle_horizon, 0.0, 1.0, 0.0);
glRotatef(G_fAngle_vertical, 1.0, 0.0, 0.0);

//the Sun
glColor3f(0.8f, 0.2f, 0.0f);
glutSolidSphere(0.6, 20, 16);

//the Earth
glRotatef(-10, 0.0f, 0.0f, 1.0f); //地球轨道倾斜-10度
glRotatef(G_fAngle_earth, 0.0f, 1.0f, 0.0f);
glTranslatef(0.0, 0.0, -G_fSunToEarth);
glColor3f(0.0f, 0.2f, 0.8f);
glutSolidSphere(0.3, 20, 16);

//the Moon1
glPushMatrix();
glRotatef(-30, 0.0f, 0.0f, 1.0f); //月球轨道倾斜-30度
glRotatef(G_fAngle_moon, 0.0f, 1.0f, 0.0f);
glTranslatef(0.0, 0.0, -G_fEarthToMoon);
glColor3f(0.8f, 0.8f, 0.0f);
glutSolidSphere(0.1, 20, 16);
glPopMatrix();

//the Moon2
glPushMatrix();
glRotatef(G_fAngle_moon, 0.0f, 1.0f, 0.0f);
glTranslatef(0.0, 0.0, G_fEarthToMoon);
glColor3f(0.0f, 0.8f, 0.0f);
glutSolidSphere(0.1, 20, 16);
glPopMatrix();

//交换前后缓冲区
glutSwapBuffers();
}


void processSpecialKeys(int key, int x, int y)
{
switch (key) {
case GLUT_KEY_LEFT:
G_fAngle_horizon -= 5.0f;
break;
case GLUT_KEY_RIGHT:
G_fAngle_horizon += 5.0f;
break;
case GLUT_KEY_UP:
G_fAngle_vertical -= 5.0f;
break;
case GLUT_KEY_DOWN:
G_fAngle_vertical += 5.0f;
break;
}
glutPostRedisplay();
}

void processNormalKeys(unsigned char key, int x, int y)
{
switch (key) {
case 97: //"a"
G_fDistance -= 0.3f;
break;
case 65: //"A"
G_fDistance += 0.3f;
break;
case 32: //space
G_bPause = !G_bPause;
break;
case 27: //"esc"
exit(0);
}
glutPostRedisplay();
}

void planetMove(int value)
{
if (!G_bPause)
{
G_fAngle_earth += 0.2f;
G_fAngle_moon += 1.5f;

glutPostRedisplay();
}
glutTimerFunc(10, planetMove, 1);
}

运行效果


参考

OpenGL的glRotatef旋转变换函数详解

OpenGL编程指南4:双缓冲实现运行

OpenGL学习笔记(6)第一个动画

OpenGL太阳星系的实现openglcDoBetterCSDN博客