一、提要
今天的内容是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.youkuaiyun.com/vagrxie/article/category/628716/3