您的位置:首页 > 运维架构

【转】OpenGL实现太阳系模型

2013-02-22 11:42 239 查看
转自:http://www.juwends.com/tech/opengl/opengl-solar-system.html

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….):

View Code

/***************************** 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实现太阳系模型 —— Juwend
Juwend’s – http://www.juwends.com 笔者水平有限,若有错漏,欢迎指正,欢迎转载以及CV操作,但希注明出处,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: