一、提要
今天的内容是OpenGL的编程实践—太阳系的模拟!
红宝书上有相应的教程,但这里我们要实现得更全面一些。iPad上有一个很棒的应用,名字叫Solar System,我们尽量去达到它的效果。
先来看一下最终效果:
、
思路:
建立9个球体,分别赋予不同的材质,再通过动画不断变换它们的位置,就可以实现模拟了。
二、有关太阳系的知识
太阳系有一颗恒星:太阳,8颗行星:水,金,地,火,木,土,天王,海王。9颗星球的位置如下:
然后去搜集了解一下各个星球的自转和公转周期,脑袋里面有个概念。
接着可以去找一些星球贴图的素材了,我在网上找到了一些资源,太阳的贴图是自己用ps绘制的,然后把它们通通添加到资源文件中,就像这样:
三、程序结构
相比与之前的框架,这里添加了一个星球类,文件结构如下:
程序还是比较清晰,直接贴代码了:
- /*
- -----------------------------------------------------------------------------
- Filename: star.h
- -----------------------------------------------------------------------------
- //星球类
- -----------------------------------------------------------------------------
- */
- #ifndef STAR_H
- #define STAR_H
- #include <QtOpenGL>
- #include <GL/glut.h>
- class Star
- {
- public:
- Star();
- Star(int tex,GLfloat r,GLfloat x,GLfloat y,GLfloat revS,GLfloat rotS,GLfloat a[], GLfloat d[], GLfloat p[],GLfloat s);
- ~Star();
- //公转
- void revolute();
- //自转
- void rotate();
- //星球半径
- float radious;
- //星球位置
- GLfloat disX;
- GLfloat disY;
- //纹理id
- int texId;
- //环境反射光
- GLfloat *ambient;
- //漫反射
- GLfloat *diffuse;
- //镜面反射
- GLfloat *specular;
- //镜面反射强度
- GLfloat shinniness;
- //公转速度
- GLfloat revSpeed;
- //自转速度
- GLfloat rotSpeed;
- //公转角度
- float revAngle;
- //自转角度
- float rotAngle;
- /* //公转周期
- float revPeriod;
- //自转周期
- float rotPeriod;
- //纹理属性
- GLfloat WRAP_S;
- GLfloat WRAP_T;
- GLfloat MAG_FILTER;
- GLfloat MIN_FILTER;
- GLfloat ENV_MODE;
- */
- };
- #endif // STAR_H
- /*
- -----------------------------------------------------------------------------
- Filename: star.cpp
- -----------------------------------------------------------------------------
- //星球类
- -----------------------------------------------------------------------------
- */
- #include "star.h"
- Star::Star()
- {
- }
- Star::Star(int tex, GLfloat r, GLfloat x, GLfloat y, GLfloat revS, GLfloat rotS, GLfloat a[], GLfloat d[], GLfloat p[], GLfloat s)
- {
- this->texId=tex;
- this->radious=r;
- this->disX=x;
- this->disY=y;
- this->ambient=a;
- this->diffuse=d;
- this->specular=p;
- this->shinniness=s;
- this->revSpeed=revS;
- this->rotSpeed=rotS;
- this->revAngle=0.0;
- this->rotAngle=0.0;
- }
- Star::~Star()
- {}
- void Star::revolute()
- {
- this->revAngle= this->revAngle + this->revSpeed< 360 ? this->revAngle + this->revSpeed: 0;
- }
- void Star::rotate()
- {
- this->rotAngle= this->rotAngle + this->rotSpeed< 360 ? this->rotAngle + this->rotSpeed: 0;
- }
- /*
- -----------------------------------------------------------------------------
- Filename: nehewidget.h
- -----------------------------------------------------------------------------
- //opengl渲染窗口类
- -----------------------------------------------------------------------------
- */
- #ifndef NEHEWIDGET_H
- #define NEHEWIDGET_H
- #include <QGLWidget>
- #include <QtGui>
- #include <QtOpenGL>
- #include <QtCore>
- #include <GL/glut.h>
- #include<iostream>
- #include "star.h"
- #define PI 3.14159265
- class NeHeWidget : public QGLWidget
- {
- Q_OBJECT
- public:
- explicit NeHeWidget(QWidget *parent = 0);
- ~NeHeWidget();
- void zoomOut();
- void zoomIn();
- void enableBlend();
- void disableBLend();
- void calFrequency();
- void speedUp();
- void speedDown();
- void eyeXup();
- void eyeXdown();
- void eyeZup();
- void eyeZdown();
- protected:
- //设置渲染环境
- void initializeGL();
- //绘制窗口
- void paintGL();
- //响应窗口的大小变化
- void resizeGL( int width, int height );
- //加载纹理
- void loadGLTextures(QString filename,int id);
- //绘制星球
- void drawStar(Star *s);
- //材质设置
- void setMaterial(Star *s);
- //正方体在三个方向上的旋转
- QFont fpsFont;
- GLfloat xRot, yRot, zRot;
- //纹理存储数组
- GLuint texture[13];
- //场景深入屏幕的距离
- GLfloat zoom;
- //立方体在X轴和Y轴上旋转的速度
- GLfloat xSpeed, ySpeed;
- //计时器,实现动画
- QTimer *timer;
- //帧刷新时间
- int fpsSpan;
- GLfloat colorSpan;
- GLUquadricObj *mySphere;
- Star *sky;
- Star *sun;
- Star *mercury;
- Star *venus;
- Star *earth;
- Star *mars;
- Star *jupiter;
- Star *saturn;
- GLfloat eyeX;
- GLfloat eyeY;
- GLfloat eyeZ;
- };
- #endif // NEHEWIDGET_H
- /*
- -----------------------------------------------------------------------------
- Filename: nehewidget.cpp
- -----------------------------------------------------------------------------
- //opengl渲染窗口类
- -----------------------------------------------------------------------------
- */
- #include "nehewidget.h"
- NeHeWidget::NeHeWidget(QWidget *parent) :
- QGLWidget(parent)
- {
- xRot = yRot = zRot = 0.0;
- zoom = -5.0;
- xSpeed = ySpeed = 0.0;
- fpsFont=QFont("Times", 20);
- colorSpan=0;
- fpsSpan=50;
- timer = new QTimer(this);
- timer->start(fpsSpan);
- connect(timer,SIGNAL(timeout()),this,SLOT(updateGL()));
- mySphere=gluNewQuadric();
- eyeX=0.0;
- eyeY=0.0;
- eyeZ=190.0;
- //夜空参数设置
- GLfloat sky_ambient[]={0.0,0.0,0.0,1.0};
- GLfloat sky_diffuse[]={0.0,0.0,0.0,1.0};
- GLfloat sky_specular[]={0.0,0.0,0.0,1.0};
- GLfloat sky_shininess=0.0;
- GLfloat sky_radious=290.0;
- // GLfloat sky_rotSpeed= (GLfloat)360/58/100;
- sky=new Star(0,sky_radious,0,0,0,0,sky_ambient,sky_diffuse,sky_specular,sky_shininess);
- //太阳参数设置
- GLfloat sun_ambient[]={0.0,0.0,0.0,1.0};
- GLfloat sun_diffuse[]={0.0,0.0,0.0,1.0};
- GLfloat sun_specular[]={0.0,0.0,0.0,1.0};
- GLfloat sun_shininess=20.0;
- GLfloat sun_radious=10.0;
- GLfloat sun_rotSpeed= (GLfloat)360/58/100;
- sun=new Star(1,sun_radious,0,0,0,sun_rotSpeed,sun_ambient,sun_diffuse,sun_specular,sun_shininess);
- //水星
- GLfloat mercury_ambient[]={0.0,0.0,0.0,1.0};
- GLfloat mercury_diffuse[]={0.5,0.5,0.5,1.0};
- GLfloat mercury_specular[]={0.0,0.0,0.0,1.0};
- GLfloat mercury_shininess=20.0;
- GLfloat mercury_radious=0.7;
- GLfloat mecury_revSpeed=(GLfloat)360/88;
- GLfloat mecury_rotSpeed= (GLfloat)360/58/100;
- mercury=new Star(2,mercury_radious,15.2,0,mecury_revSpeed,mecury_rotSpeed,mercury_ambient,mercury_diffuse,mercury_specular,mercury_shininess);
- //金星
- GLfloat venus_ambient[]={0.0,0.0,0.0,1.0};
- GLfloat venus_diffuse[]={0.8,0.8,0.8,1.0};
- GLfloat venus_specular[]={0.0,0.0,0.0,1.0};
- GLfloat venus_shininess=20.0;
- GLfloat venus_radious=1.24;
- GLfloat venus_revSpeed=(GLfloat)360/224;
- GLfloat venus_rotSpeed= (GLfloat)360/243/100;
- venus=new Star(3,venus_radious,19.2,0,venus_revSpeed,venus_rotSpeed,venus_ambient,venus_diffuse,venus_specular,venus_shininess);
- //地球
- GLfloat earth_ambient[]={0.1,0.1,0.1,1.0};
- GLfloat earth_diffuse[]={0.4,0.4,0.8,1.0};
- GLfloat earth_specular[]={0.0,0.0,0.0,1.0};
- GLfloat earth_shininess=20.0;
- GLfloat earth_radious=1.24;
- GLfloat earth_revSpeed=(GLfloat)360/365;
- GLfloat earth_rotSpeed= (GLfloat)360/1/100;
- earth=new Star(4,earth_radious,26,0,earth_revSpeed,earth_rotSpeed,earth_ambient,earth_diffuse,earth_specular,earth_shininess);
- //火星
- GLfloat mars_ambient[]={0.1,0.1,0.1,1.0};
- GLfloat mars_diffuse[]={0.6, 0.6, 0.6, 1.0};
- GLfloat mars_specular[]={0.0,0.0,0.0,1.0};
- GLfloat mars_shininess=20.0;
- GLfloat mars_radious=1.0;
- GLfloat mars_revSpeed=(GLfloat)360/687;
- GLfloat mars_rotSpeed= (GLfloat)360/1/100;
- mars=new Star(5,mars_radious,31,0,mars_revSpeed,mars_rotSpeed,mars_ambient,mars_diffuse,mars_specular,mars_shininess);
- //木星
- GLfloat jupiter_ambient[]={0.0, 0.0, 0.0,1.0};
- GLfloat jupiter_diffuse[]={0.6, 0.6, 0.6, 1.0};
- GLfloat jupiter_specular[]={0.0,0.0,0.0,1.0};
- GLfloat jupiter_shininess=20.0;
- GLfloat jupiter_radious=4.0;
- GLfloat jupiter_revSpeed=(GLfloat)360/4329;
- GLfloat jupiter_rotSpeed= (GLfloat)360/0.3/100;
- jupiter=new Star(6,jupiter_radious,43,0,jupiter_revSpeed,jupiter_rotSpeed,jupiter_ambient,jupiter_diffuse,jupiter_specular,jupiter_shininess);
- //土星
- GLfloat saturn_ambient[]={0.0, 0.0, 0.0,1.0};
- GLfloat saturn_diffuse[]={0.6, 0.6, 0.6, 1.0};
- GLfloat saturn_specular[]={0.0,0.0,0.0,1.0};
- GLfloat saturn_shininess=20.0;
- GLfloat saturn_radious=3.5;
- GLfloat saturn_revSpeed=(GLfloat)360/10768;
- GLfloat saturn_rotSpeed= (GLfloat)360/1.4/100;
- saturn=new Star(7,saturn_radious,56.5,0,saturn_revSpeed,saturn_rotSpeed,saturn_ambient,saturn_diffuse,saturn_specular,saturn_shininess);
- }
- NeHeWidget::~NeHeWidget()
- {}
- void NeHeWidget::loadGLTextures(QString filename, int id)
- {
- QImage tex, buf;
- if ( !buf.load(filename ) )
- {
- //如果载入不成功,自动生成一个128*128的32位色的绿色图片。
- qWarning("Could not read image file!");
- QImage dummy( 128, 128,QImage::Format_RGB32 );
- dummy.fill( Qt::green );
- buf = dummy;
- }
- //转换成纹理类型
- tex = QGLWidget::convertToGLFormat( buf );
- //创建纹理
- glGenTextures( 1, &texture[id] );
- //使用来自位图数据生成的典型纹理,将纹理名字texture[0]绑定到纹理目标上
- glBindTexture( GL_TEXTURE_2D, texture[id] );
- glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
- GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
- }
- void NeHeWidget::drawStar(Star *s)
- {
- glPushMatrix();
- //公转
- glRotatef(s->revAngle,0.0,0.0,1.0);
- glTranslatef(s->disX, s->disY, 0.0);
- //自转
- glRotatef(s->rotAngle,0.0,0.0,1.0);
- gluSphere(mySphere, s->radious, 32, 16);
- //设置材质属性
- glMaterialfv(GL_BACK, GL_AMBIENT, s->ambient);
- glMaterialfv(GL_BACK, GL_DIFFUSE, s->diffuse);
- glMaterialfv(GL_BACK, GL_SPECULAR, s->specular);
- glMaterialf(GL_BACK, GL_SHININESS, s->shinniness);
- //
- glPopMatrix();
- }
- void NeHeWidget::setMaterial(Star *s)
- {
- }
- void NeHeWidget::initializeGL()
- {
- //载入纹理
- loadGLTextures( ":/data/sun.jpg",sun->texId);
- loadGLTextures( ":/data/mercury.bmp",mercury->texId);
- loadGLTextures( ":/data/venus.jpg",venus->texId);
- loadGLTextures( ":/data/earth2.jpg",earth->texId);
- loadGLTextures( ":/data/mars.bmp",mars->texId);
- loadGLTextures( ":/data/saturn.jpg",saturn->texId);
- loadGLTextures( ":/data/jupiter.bmp",jupiter->texId);
- loadGLTextures( ":/data/sky.jpg",sky->texId);
- //loadGLTextures( ":/data/neptune.bmp",neptune->texId);
- // 启用阴影平滑
- glShadeModel( GL_SMOOTH );
- // 黑色背景
- glClearColor( 0.0, 0.0, 0.0, 0.0 );
- // 设置深度缓存
- glClearDepth( 1.0 );
- // 启用深度测试
- glEnable( GL_DEPTH_TEST );
- //启用纹理
- glEnable( GL_TEXTURE_2D );
- // 所作深度测试的类型
- glDepthFunc( GL_LEQUAL );
- // 告诉系统对透视进行修正
- glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
- // 开启剔除操作效果
- //glEnable(GL_CULL_FACE);
- // 使用平滑法线
- gluQuadricNormals(mySphere, GL_SMOOTH);
- // 使用纹理
- gluQuadricTexture(mySphere, GL_TRUE);
- // 设置球纹理映射
- }
- void NeHeWidget::paintGL()
- {
- // 清除屏幕和深度缓存
- glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
- //glRotatef( yRot, 0.0, 0.0, 1.0 );
- glColor3f(1.0,1.0,1.0);
- glBindTexture(GL_TEXTURE_2D, texture[sky->texId]);
- drawStar(sky);
- glBindTexture(GL_TEXTURE_2D, texture[sun->texId]);
- drawStar(sun);
- glBindTexture(GL_TEXTURE_2D, texture[mercury->texId]);
- drawStar(mercury);
- glBindTexture(GL_TEXTURE_2D, texture[venus->texId]);
- drawStar(venus);
- glBindTexture(GL_TEXTURE_2D, texture[earth->texId]);
- drawStar(earth);
- glBindTexture(GL_TEXTURE_2D, texture[mars->texId]);
- drawStar(mars);
- glBindTexture(GL_TEXTURE_2D, texture[jupiter->texId]);
- drawStar(jupiter);
- glBindTexture(GL_TEXTURE_2D, texture[saturn->texId]);
- drawStar(saturn);
- //旋转速度
- yRot += 0.4;
- sun->rotate();
- mercury->revolute();
- mercury->rotate();
- venus->revolute();
- venus->rotate();
- earth->revolute();
- earth->rotate();
- mars->revolute();
- mars->rotate();
- jupiter->revolute();
- jupiter->rotate();
- saturn->revolute();
- saturn->rotate();
- glLoadIdentity();
- gluLookAt (eyeX, eyeY, eyeZ, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);
- // gluLookAt (80.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);
- glFlush();
- //fps的字体颜色
- glColor3f(0.0f,0.0f,1.0f);
- //计算FPS
- calFrequency();
- }
- // 重置OpenGL窗口大小
- void NeHeWidget::resizeGL(int width, int height)
- {
- // 防止窗口大小变为0
- if ( height == 0 )
- {
- height = 1;
- }
- // 重置当前的视口
- glViewport( 0, 0, (GLint)width, (GLint)height );
- // 选择投影矩阵
- glMatrixMode( GL_PROJECTION );
- // 重置投影矩阵
- glLoadIdentity();
- // 设置视口的大小
- gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 600.0 );
- // 选择模型观察矩阵
- glMatrixMode( GL_MODELVIEW );
- glLoadIdentity();
- }
- void NeHeWidget::speedUp()
- {
- fpsSpan+=1;
- qDebug()<<fpsSpan;
- timer->setInterval(fpsSpan);
- //timer->start(fpsSpan);
- updateGL();
- }
- void NeHeWidget::speedDown()
- {
- if(fpsSpan>1) fpsSpan-=1;
- else fpsSpan=1;
- qDebug()<<fpsSpan;
- timer->setInterval(fpsSpan);
- updateGL();
- }
- void NeHeWidget::eyeXup()
- {
- eyeX+=1;
- }
- void NeHeWidget::eyeXdown()
- {
- // if(eyeX>10) eyeX-=1;
- //else eyeX=10;
- eyeX-=1;
- }
- void NeHeWidget::eyeZup()
- {
- eyeZ+=1;
- }
- void NeHeWidget::eyeZdown()
- {
- // if(eyeX>10) eyeX-=1;
- //else eyeX=10;
- eyeZ-=1;
- }
- void NeHeWidget::zoomOut()
- {
- zoom+= 0.2;
- updateGL();
- }
- void NeHeWidget::zoomIn()
- {
- zoom -= 0.2;
- updateGL();
- }
- void NeHeWidget::calFrequency()
- {
- static QString tmp="";
- static float framesPerSecond=0.0f;//fps的数值
- static float frames = 0.0f; // 用于存储渲染的帧数
- static float lastTime = 0.0f; // 前一秒的时刻
- float currentTime = glutGet(GLUT_ELAPSED_TIME)* 0.001f;//程序运行的时间
- ++frames;
- if( currentTime - lastTime > 1.0f )//,每秒刷新一次
- {
- framesPerSecond=frames;
- tmp.setNum(framesPerSecond);
- lastTime = currentTime;
- frames= 0;
- }
- renderText(100,100,"FPS: "+tmp,fpsFont);//最终结果在窗口中渲染
- }
- /*
- -----------------------------------------------------------------------------
- Filename: mainwindow.h
- -----------------------------------------------------------------------------
- //主窗口类
- -----------------------------------------------------------------------------
- */
- #ifndef MAINWINDOW_H
- #define MAINWINDOW_H
- #include <QtGui/QMainWindow>
- #include <QKeyEvent>
- #include "nehewidget.h"
- class MainWindow : public QMainWindow
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = 0);
- ~MainWindow();
- protected:
- bool fullscreen;
- //处理键盘事件
- void keyPressEvent( QKeyEvent *e );
- private:
- NeHeWidget *neheWidget ;
- };
- #endif // MAINWINDOW_H
- /*
- -----------------------------------------------------------------------------
- Filename: mainwindow.cpp
- -----------------------------------------------------------------------------
- //主窗口类
- -----------------------------------------------------------------------------
- */
- #include "mainwindow.h"
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- {
- neheWidget = new NeHeWidget();
- fullscreen = true;
- setGeometry(100,100,1000,768);
- setWindowTitle(tr("NeHe's OpenGL Framework"));
- setCentralWidget(neheWidget);
- }
- MainWindow::~MainWindow()
- {
- }
- void MainWindow::keyPressEvent(QKeyEvent *e)
- {
- switch ( e->key() )
- {
- case Qt::Key_F2:
- fullscreen = !fullscreen;
- if ( fullscreen )
- {
- showFullScreen();
- }
- else
- {
- showNormal();
- }
- neheWidget->updateGL();
- break;
- case Qt::Key_Escape:
- close();
- break;
- case Qt::Key_PageUp:
- neheWidget->zoomOut();
- break;
- case Qt::Key_PageDown:
- neheWidget->zoomIn();
- break;
- case Qt::Key_Down:
- neheWidget->speedUp();
- break;
- case Qt::Key_Up:
- neheWidget->speedDown();
- break;
- case Qt::Key_W:
- neheWidget->eyeXup();
- break;
- case Qt::Key_S:
- neheWidget->eyeXdown();
- break;
- case Qt::Key_E:
- neheWidget->eyeZup();
- break;
- case Qt::Key_D:
- neheWidget->eyeZdown();
- break;
- }
- }
- //main函数
- #include <QtGui/QApplication>
- #include "mainwindow.h"
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- MainWindow w;
- glutInit(&argc, argv);
- w.show();
- return a.exec();
- }
四。程序中未完善的地方
这个程序应该只能算是一个大致的框架,还是有很多地方可以改进,比如:添加天王星,海王星,冥王星,添加每个星球的倾角,添加月球....
有兴趣的同学可以继续完善,我们可以继续讨论。
参考资料
1. 《 OpenGL Reference Manual 》, OpenGL 参考手册
2. 《 OpenGL 编程指南》(《 OpenGL Programming Guide 》), Dave Shreiner , Mason Woo , Jackie Neider , Tom Davis 著,徐波译,机械工业出版社
3. 《win32 OpenGL编程 》 一个大牛的博客 http://blog.csdn.net/vagrxie/article/category/628716/3