下面是上周的作业的要求:
基本功能:
完成一个初步的太阳系程序。
场景中有三个球体,一个表示太阳,一个表示地球,一个表示月亮;
地球不停地绕太阳旋转,月亮绕地球旋转。
进一步扩展功能(选作下面的1项或几项功能):
如果地球有两个月亮呢?
画上轨道线;
让轨道倾斜;
提示:
画球体的函数: glutWireSphere(1.0, 20, 16);
如何让物体不停运动呢?void glutTimerFunc( );glutPostRedisplay()或glutIdleFunc()
在
中我总结了上周学到所有的变换的原理以及对应的函数。下面我们就要通过这个作业来剖析OpenGL中这些变换函数的具体细节。
因为是刚开始学,第三周就直接上手很难受,感觉无从下手,所以我找了度娘的一个实现程序,通过老师课上讲的与这个程序中每处用到变换的细节对比就可以理解真谛所在。
在看这段程序之前,我总结了三点理解OpenGL中具体程序运行的个人观点:
(1)OpenGL中并无绝对单位,只有相对大小!!!
(2)OpenGL中所有的变化都是局部变化,也就是说OpenGL中所有物体的变换都以当前物体的中心为原点,只不过有时候他自己的中心恰好在默认的原点。
(3)OpenGL是先对物体进行操作(先罗列一堆变换等函数),再画这个物体
以上三点对于OpenGL程序的理解至关重要,一定要带着这三点去理解下面的程序和函数,其中一些观念与我们主观会不同,要慢慢接受。
#include <windows.h>
#include <GL/freeglut.h>
static int day=200;
void display()
{
glEnable(GL_DEPTH_TEST);//用户初始化
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除屏幕和深度缓冲
glMatrixMode(GL_PROJECTION);//设置矩阵模型(投影变换)
glLoadIdentity();//载入单位化矩阵
//透视变换角度75度,长宽比1:1,最近可视距离1,最远400000两倍地球公转半径
gluPerspective(75,1,1,400000);
glMatrixMode(GL_MODELVIEW);//设置矩阵模型(模型、视点变换)
glLoadIdentity();//载入单位化矩阵
//整体布局,视角位(这里是45度倾角),物体位置,z轴正向
gluLookAt(0,-200000,-200000,0,0,0,0,0,1);
//太阳
glColor3f(1.0f, 0.0f, 0.0f);
glutWireSphere(69600,20,20);
//地球
glColor3f(0.0f, 0.0f, 1.0f);
glRotatef(day,0,0,-1);
glTranslatef(150000,0,0);
glutWireSphere(15945,20,20);
//月亮
glColor3f(1.0f, 1.0f, 0.0f);
glRotatef(day/30*360-day,0,0,-1);
glTranslatef(38000,0,0);
glutWireSphere(4345,20,20);
//交换前后缓冲区
glutSwapBuffers();
}
void timer(int p)
{
day++;
if(day>360) day=0;
//注册按一定时间间隔触发的定时器回调函数
glutTimerFunc(50,timer,0);
//在图像绘制的所有操作之后,要加入 glutPostRedisplay() 来重绘图像,否则图像只有响应鼠标或键盘消息时才会更新图像。
//有点像窗口重绘函数一样。它要求当前的回调函数返回时执行显示回调函数glutMainLoop():使程序进入事件处理循环。
glutPostRedisplay();
}
void processNormalKeys(unsigned char key,int x,int y)
{
if(key==27){ //"esc"
exit(0);
}
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);//初始化freeglut
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA);//初始化OPENGL显示方式
//设定OPENGL窗口位置和大小
glutInitWindowSize (400, 400);
glutInitWindowPosition (100, 100);
//打开窗口
glutCreateWindow ("太阳地球月亮");
//按esc退出
glutKeyboardFunc(processNormalKeys);
//开始OPENGL的循环
glutDisplayFunc(display);
//glutIdleFunc(myIdle);可以用来代替下面的glutTimerFunc让物体一直动
//设置全局的回调函数,当没有窗口事件到达时,FREEGLUT程序功能可以执行后台处理任务或连续动画。
//如果启用,这个idle function会被不断调用,直到有窗口事件发生。回调函数没有参数。
//当前的窗口和菜单在执行idlefunc之前不会改变。当程序依赖多窗口或菜单时最好不要依赖于当前设定。
glutTimerFunc(50,timer,0);//按一定时间间隔触发的定时器回调函数
glutMainLoop();
return 0;
}
/*void myIdle(void)
{
++day;
if(day>=360)
day=0;
display();
}*/
上面这个程序的效果是月亮围着地球转,二者又同时围着太阳转,月亮、地球、太阳都没有自转。对于程序中OpenGL一定会用到的框架类型的函数,就不解释了,可以看https://blog.csdn.net/derbi123123/article/details/104619415这个文章。下面我们就来剖析这个程序中所有用到OpenGL变换相关的函数的细节,也就是display函数中的内容。
如果看不同上面一些变换函数的参数或者不知道它在干嘛,可以看OpenGL中各种变换的原理以及对应的OpenGL函数里面总结了所有变换相关的函数。
涉及到投影、模型、视点变换的函数:
glMatrixMode(GL_PROJECTION);//设置成模型矩阵模式(投影变换)
glLoadIdentity();//载入单位化矩阵
//透视变换角度75度,长宽比1:1,最近可视距离1,最远400000两倍地球公转半径
gluPerspective(75,1,1,400000);
glMatrixMode(GL_MODELVIEW);//设置成模型矩阵模式(模型、视点变换)
glLoadIdentity();//载入单位化矩阵
//整体布局,视角位(这里是45度倾角),物体位置,z轴正向
gluLookAt(0,-200000,-200000,0,0,0,0,0,1);
对象的变换都是通过矩阵来实现的,在进行矩阵操作前,需用glMatrixMode()
指定当前操作的矩阵对象,GL_PROJECTION
告诉电脑是要进行投影变换了,GL_MODELVIEW
告诉电脑是要进行模型、视点变换了。
glLoadIdentity()
用来载入单位化矩阵(重载矩阵),是为了想让之后进行的变换不受之前变换的影响。
gluPerspective()
就是投影变换,gluLookAt()
是视点变换,这里可能有些懵逼,这些乱七八糟的参数是怎么得到的?说白了还是要清楚一点OpenGL中并无绝对单位,只有相对大小!!! 比如投影变换的第四个参数是400000,也就是远平面到视点的距离是400000个单位,而视点变换的前三个参数是视点位置,程序中是(0,-200000,-200000),就是是我们的视点,而后三个参数是头朝上的方向,程序中是(0,0,1),也就是说我们头朝z轴,我们来看看效果:
一定要仔细看太阳(红色的),上面的顶点在我们看不见的那侧,你会发现我们在仰视这个太阳,也就是原点在屏幕中心,我们在(0,-200000,-200000)看它,是这样的效果,为了体现出参数设置细节,我们不妨把gluLookAt(0,-200000,-200000,0,0,0,0,0,1);
改成
gluLookAt(0,200000,200000,0,0,0,0,0,1);
,我们再来看效果:
我们可以看到上面的顶点在我们视野的下方了,下面的顶点反而看不见了,我们是在俯视这个太阳,这样我们就可以体会到视点变换函数前三个参数它具体的作用。
涉及到几何变换的函数:
一定要带着下面的思想取体会这段代码。
OpenGL中并无绝对单位,只有相对大小!!!
OpenGL中所有的变化都是局部变化,也就是说OpenGL中所有物体的变换都以当前物体的中心为原点,只不过有时候他自己的中心恰好在默认的原点。
OpenGL是先对物体进行操作(先罗列一堆变换等函数),再画这个物体
//太阳
glColor3f(1.0f, 0.0f, 0.0f);
glutWireSphere(69600,20,20);
//地球
glColor3f(0.0f, 0.0f, 1.0f);
glRotatef(day,0,0,-1);
glTranslatef(150000,0,0);
glutWireSphere(15945,20,20);
//月亮
glColor3f(1.0f, 1.0f, 0.0f);
glRotatef(day/30*360-day,0,0,-1);
glTranslatef(38000,0,0);
glutWireSphere(4345,20,20);
就是这段代码产生了地球围着太阳转,月亮既围着地球转又围着太阳转的静态效果,让物体不停运动的函数glutTimerFunc()不是这里的重点,可以百度了解。注意上述代码得到的就是一个静态图(也许和下面不一样,反正是静态的):
关键在于这段代码这样写的缘由:
(1)首先是理解OpenGL中所有的变化都是局部变化,也就是说OpenGL中所有物体的变换都以当前物体的中心为原点,我们可以看到画了太阳之后,对地球的变换都是相对于太阳做的,画了地球之后对月亮的变换都是相对于地球做的。由此我们要体会画图的顺序是怎样的。
(2)其次要理解OpenGL是先对物体进行操作(先罗列一堆变换等函数),再画这个物体,这个很容易,我们看程序也能看出来。
(3)最后理解OpenGL中并无绝对单位,只有相对大小!!!
先看一下这个画球体的函数:
glutWireSphere(半径,球体纬线的条数,球体经线的条数)
其作用是画一个可透视的球体。
我们可以看到太阳的半径是69600,地球是15945,月亮是4345,他就是一个相对大小,只要你愿意把它设成1,2,3都行(但要确保前后其他函数的比例也与1,2,3一致),这就是相对的含义,还有地球glTranslatef(150000,0,0);是相对于太阳沿x轴z方向平移150000个单位,前面也看到了,投影函数设置的远平面是400000个单位,所以说这都是相对的,一定要理解这个。
至于旋转变换函数的参数取值我只能说地球绕太阳一圈要一年我们为了方便计算用的360天,月亮绕地球一圈要27天我们为了方便计算用的30天。
最后,再加上可以让物体一直运动的函数后实现的效果是月亮围着地球转,二者又同时围着太阳转,但月亮、地球、太阳都没有自转,所以大家去思考尝试实现月亮、地球、太阳的自转可以发现更多的奥秘!!!