OpenGL实现太阳系模型

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

34

以下是完整的代码:

/***************************** 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的东西,还好当时的学习使得些基础,不过居然还是有些东西忘记了,重新思考才回忆起来。

gluLookAt(eyeX, eyeY,eyeZ, centerX, centerY, centerZ, upX, upY, upZ);

这个函数的8个参数分别是这个意思:
eyeX/Y/Z:分别是视点(也可以认为是眼睛)的位置
centerX/Y/Z:分别是参照坐标,其实是什么参照坐标,我也不是很清楚
upX/Y/Z:是指明朝上的向量,以转动整个GL坐标系

如果要做仰视/俯视效果,设置好eyeX/Y/Z之后,就需要调整centerX/Y/Z的值了。
假设up为(0,0,1),则是z轴朝上,
俯视的话就需要center为(0,0,z),z>0
仰视的话就需要center为(0,0,z),z<0
反正感觉情况就是这样的,到时候问过大神再补……

我X……换了win7 64位系统之后,这个程序运行起来使用了GLU.h内方法的地方各种crtexe的报错……不清楚肿么回事,研究研究再说……

本文《OpenGL实现太阳系模型》来自 www.juwends.com ,欢迎转载或CV操作,但请注明出处,谢谢!

OpenGL实现太阳系模型》上有8条评论