OpenGL/C++实战——C++实现太阳系行星系统

注:本教程版权归实验楼所有,有兴趣的同学也可点进官网蓝桥网课:C++实现太阳系行星系统学习(免费课程)

框架设计
认识 OpenGL 和 GLUT

OpenGL 包含了很多渲染函数,但是他们的设计目的是独立于任何窗口系统或操作系统的。因此,它自身并没有包含创建打开窗口或者从键盘或鼠标读取时间的函数,甚至连最基本的显示窗口的功能都没有,所以单纯只使用 OpenGL 是完全不可能创建一个完整的图形程序的。并且绝大多数程序都需要与用户进行交互(响应键盘鼠标等操作)。GLUT 则提供了这一便利。

GLUT 其实是 OpenGL Utility Toolkit 的缩写,它是一个处理 OpenGL 程序的工具库,主要负责处理与底层操作系统的调用及 I/O 操作。使用 GLUT 可以屏蔽掉底层操作系统 GUI 实现上的一些细节,仅使用 GLUT 的 API 即可跨平台的创建应用程序窗口、处理鼠标键盘事件等等。

我们先在实验楼环境中安装 GLUT:

sudo apt-get update && sudo apt-get install freeglut3 freeglut3-dev

一个标准的 GLUT 程序结构如下代码所示:

// 使用 GLUT 的基本头文件
#include <GL/glut.h>

// 创建图形窗口的基本宏
#define WINDOW_X_POS 50
#define WINDOW_Y_POS 50
#define WIDTH 700
#define HEIGHT 700

// 用于注册 GLUT 的回调
void onDisplay(void);
void onUpdate(void);
void onKeyboard(unsigned char key, int x, int y);

int main(int argc, char*  argv[]) {

    // 对 GLUT 进行初始化,并处理所有的命令行参数
    glutInit(&argc, argv);
    // 这个函数指定了使用 RGBA 模式还是颜色索引模式。另外还可以
    // 指定是使用单缓冲还是双缓冲窗口。这里我们使用 RGBA 和 双缓冲窗口
    glutInitDisplayMode(GLUT_RGBA |  GLUT_DOUBLE);
    // 设置窗口被创建时左上角位于屏幕上的位置
    glutInitWindowPosition(WINDOW_X_POS, WINDOW_Y_POS);
    // 设置窗口被创建时的宽高, 为了简便起见
    glutInitWindowSize(WIDTH, HEIGHT);
    // 创建一个窗口,输入的字符串为窗口的标题
    glutCreateWindow("SolarSystem at Shiyanlou");

    // glutDisplayFunc 的函数原型为 glutDisplayFunc(void (*func)(void))
    // 这是一个回调函数,每当 GLUT 确定一个窗口的内容需要更新显示的时候,
    // glutDisplayFunc 注册的回调函数就会被执行.
    //
    // glutIdleFunc(void (*func)(void)) 将指定一个函数,用于处理当事件循环
    // 处于空闲的时候,就执行这个函数。这个回调函数接受一个函数指针作为它的唯一参数
    //
    // glutKeyboardFunc(void (*func)(unsigned char key, int x, int y))
    // 会将键盘上的键与一个函数关联,当这个键被按下或者释放时,函数就会调用
    //
    // 因此下面的三行实际上是在向 GLUT 注册关键的三个回调函数
    glutDisplayFunc(onDisplay);
    glutIdleFunc(onUpdate);
    glutKeyboardFunc(onKeyboard);

    glutMainLoop();
    return 0;

}

/home/shiyanlou/ 目录下新建 main.cpp 文件,并向其中写入如下代码:

//
//  main.cpp
//  solarsystem
//
#include <GL/glut.h>
#include "solarsystem.hpp"

#define WINDOW_X_POS 50
#define WINDOW_Y_POS 50
#define WIDTH 700
#define HEIGHT 700

SolarSystem solarsystem;

void onDisplay(void) {
    solarsystem.onDisplay();
}
void onUpdate(void) {
    solarsystem.onUpdate();
}
void onKeyboard(unsigned char key, int x, int y) {
    solarsystem.onKeyboard(key, x, y);
}

int main(int argc, char*  argv[]) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA |  GLUT_DOUBLE);
    glutInitWindowPosition(WINDOW_X_POS, WINDOW_Y_POS);
    glutCreateWindow("SolarSystem at Shiyanlou");
    glutDisplayFunc(onDisplay);
    glutIdleFunc(onUpdate);
    glutKeyboardFunc(onKeyboard);
    glutMainLoop();
    return 0;
}

提示

  • 单缓冲,是将所有的绘图指令在窗口上执行,就是直接在窗口上绘图,这样的绘图效率是比较慢的,如果使用单缓冲,而电脑处理性能不够,屏幕会出现闪烁状。
  • 双缓冲,会将绘图指令是在一个缓冲区完成,这里的绘图非常的快,在绘图指令完成之后,再通过交换指令把完成的图形立即显示在屏幕上,进而避免出现绘图的不完整,效率很高。

双缓冲则主要分为前台缓冲和后台缓冲,前台缓冲即我们说看到的屏幕,后台缓冲则维护内存中,对用户不可见。使用双缓冲时所有绘图操作都会在后台进行,当完成绘制后,才会将结果复制到屏幕上。这么做的好处是,如果我们让绘制操作实时与显卡进行操作,当绘制任务复杂时,IO 操作同样会变得复杂,造成性能较低;而双缓冲只会在交换缓冲区时将绘制完成后的结果直接发送给显卡进行渲染,IO 显著降低。

在 OpenGL 中,推荐使用 GLfloat 来表示浮点数。

类设计

OOP 编程中首先要梳理清楚我们要处理的对象是什么。显然整个天体系统中,他们都是一颗星球(Star),区别行星和恒星只需要通过它们是否具有父节点即可;其次,对于不同的星球而言,它们通常具有自身的材质,并且不同的材质会表现出自身是否发光,由此我们有了初步的对象模型。故我们将星球分为:普通能够自转并绕某个点公转的星球(Star), 具有特殊材质的星球(Planet), 能够发光的星球(LightPlanet)

此外,为了编程实现上的方便,我们要对现实世界的实际编程模型做一些前提假设:

  1. 星球的运行轨道为圆形;
  2. 自转速度保持相同;
  3. 每次刷新绘制的时候假设时间经过了一天。

首先,我们可以考虑按下面的思路整个实现逻辑:

  1. 初始化星球对象;
  2. 初始化 OpenGL 引擎, 实现 onDraw 和 onUpdate;
  3. 星球应该自己负责处理自己的属性、绕行关系、变换相关绘制,因此在设计星球的类时应该提供一个绘制 draw() 方法;
  4. 星球也应该自己处理自己自转公转等更新显示的绘制,因此在设计星球类时候也应该提供一个更新方法 update();
  5. 在 onDraw() 中应调 用星球的 draw() 方法;
  6. 在 onUpdate() 中调用星球的 update() 方法;
  7. 在 onKeyboard() 键盘调整整个太阳系的显示.

进一步,对于每个星球而言,都具有如下的属性:

  1. 颜色 color
  2. 公转半径 radius
  3. 自转速度 selfSpeed
  4. 公转速度 speed
  5. 距离太阳中心的距离 distance
  6. 绕行的星球 parentStar
  7. 当前的自转的角度 alphaSelf
  8. 当前的公转角度 alpha
stars.hpp

/home/shiyanlou/ 目录下新建 stars.hpp 文件,根据前面的分析,我们可以设计如下的类代码:

【以下节代码为准,此处未引入头文件定义宏】

class Star {
public:
    // 星球的运行半径
    GLfloat radius;
    // 星球的公转、自传速度
    GLfloat speed, selfSpeed;
    // 星球的中心与父节点星球中心的距离
    GLfloat distance;
    // 星球的颜色
    GLfloat rgbaColor[4];

    // 父节点星球
    Star* parentStar;

    // 构造函数,构造一颗星球时必须提供
    // 旋转半径、旋转速度、自转速度、绕行(父节点)星球
    Star(GLfloat radius, GLfloat distance,
         GLfloat speed,  GLfloat selfSpeed,
         Star* parentStar);
    // 对一般的星球的移动、旋转等活动进行绘制
    void drawStar();
    // 提供默认实现,负责调用 drawStar()
    virtual void draw() { drawStar(); }
    // 参数为每次刷新画面时的时间跨度
    virtual void update(long timeSpan);
protected:
    GLfloat alphaSelf, alpha;
};
class Planet : public Star {
public:
    // 构造函数
    Planet(GLfloat radius, GLfloat distance,
           GLfloat speed,  GLfloat selfSpeed,
           Star* parentStar, GLfloat rgbColor[3]);
    // 增加对具备自身材质的行星绘制材质
    void drawPlanet();
    // 继续向其子类开放重写功能
    virtual void draw() { drawPlanet(); drawStar(); }
};
class LightPlanet : public Planet {
public:
    LightPlanet(GLfloat Radius, GLfloat Distance,
                GLfloat Speed,  GLfloat SelfSpeed,
                Star* ParentStar, GLfloat rgbColor[]);
    // 增加对提供光源的恒星绘制光照
    void drawLight();
    virtual void draw() { drawLight(); drawPlanet(); drawStar(); }
};

solarsystem.hpp

此外,我们还需要考虑太阳系类的设计。在太阳系中,太阳系显然是由各个行星组成的;并且,对于太阳系而言,太阳系中行星运动后的视图刷新应该由太阳系来完成。据此太阳系成员变量应为包含行星的变量,成员函数应作为处理太阳系内的视图刷新及键盘响应等事件,所以,在 /home/shiyanlou/ 目录下新建 solarsystem.hpp 文件,在其中我们可以设计 SolarSystem 类:

【以下节代码为准】

class SolarSystem {

public:

    SolarSystem();
    ~SolarSystem();

    void onDisplay();
    void onUpdate();
    void onKeyboard(unsigned char key, int x, int y);

private:
    Star *stars[STARS_NUM];

    // 定义观察视角的参数
    GLdouble viewX, viewY, viewZ
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: c实现太阳系行星系统可以借助计算机编程语言和图形渲染技术来模拟太阳系中各个行星的运动和特征。下面是一种可能的实现方式: 首先,我们需要利用计算机编程语言(如C语言)创建一个基于二维空间坐标系统的模拟环境。可以使用图形(如OpenGL实现图形绘制和渲染功能。 接下来,我们需要设定太阳系各个行星的初始位置、质量、速度和轨道参数等。通过设置恰当的数值,可以实现真实的行星运动模拟。行星运动可以采用开普勒定律或牛顿运动定律进行计算,根据行星的质量和位置来推导出行星受到的重力和运动方程。 然后,我们需要根据计算得到的行星位置,使用图形渲染技术将行星的图像在模拟环境中呈现出来。可以使用贴图技术来使行星的表面具有纹理和细节,使其更加逼真。 为了使模拟更加完整,还可以加入太阳行星之间的光照和阴影效果。通过根据行星处于太阳光照射下的位置来计算光照的强度和方向,再根据行星的几何形状和表面特征生成阴影效果,使得太阳系行星仿真更加真实。 最后,我们可以通过用户交互来控制模拟的运行。例如,用户可以通过键盘或鼠标控制时间流逝的速度,或者选择特定的行星观察。 综上所述,通过合理的编程和图形渲染技术,我们可以用C语言实现一个太阳系行星系统的模拟。 ### 回答2: 要实现太阳系行星系统,我们需要一些基本的物理参数和模拟算法。 首先,我们需要收集太阳系行星的基本信息,如质量、半径、公转周期等。这些信息可以通过科研文献或天文学数据获得。同时,我们还需要知道太阳的质量以及每个行星相对于太阳的初始位置。 接下来,我们可以使用牛顿万有引力定律来模拟行星之间的相互作用。根据该定律,行星之间的引力与它们的质量和距离有关。我们可以根据行星之间的距离和质量,计算出它们之间的引力,并应用牛顿第二定律,计算每个行星的运动轨迹和速度变化。 为了模拟行星系统的运动,我们可以将时间分为离散的步长,并在每个时间步长内更新行星的位置和速度。通过迭代计算每个行星的引力和速度变化,我们可以模拟出行星太阳系中的轨迹。 此外,还可以考虑其他因素如行星之间的相互摄动、行星自转等。这些因素可以通过引入更复杂的模型和算法进行模拟。 最后,为了将模拟结果可视化,我们可以使用计算机图形学技术。将太阳行星的质量和位置用合适的比例和单位表示,并在每个时间步长中绘制出它们的位置,最终就可以得到太阳系行星系统的动态模拟。 总结起来,实现太阳系行星系统的模拟需要获取行星的基本信息,利用牛顿万有引力定律计算行星之间的相互引力,通过迭代更新行星的位置和速度,最后使用计算机图形技术可视化模拟结果。 ### 回答3: 为了实现太阳系行星系统,我们需要了解太阳系中的各个行星以及它们的特征和行为。太阳系行星系统太阳、水星、金星、地球、火星、木星、土星、天王星和海王星组成。 首先,我们可以使用计算机图形学技术来实现太阳。我们可以绘制一个球体,使用黄色来代表太阳的光芒。为实现真实感,我们可以通过渲染技术添加光照效果,使太阳看起来更加真实和明亮。 接下来是行星实现。我们可以使用不同大小和颜色的球体来代表行星行星的大小和颜色是根据实际情况进行设定的。例如,我们可以使用较大的球体来代表木星和土星,因为它们是太阳系中最大的行星。同时,我们也需要为每个行星设定正确的公转轨道。根据行星的公转周期和轨道半径,我们可以使用数学算法来模拟行星的运动。 行星上的特征也可以通过计算机图形学技术来实现。例如,我们可以使用纹理映射技术来添加行星表面的纹理和细节,使其看起来更加逼真。此外,我们还可以添加云层效果、大气层效果以及行星的自转效果,以增强行星的真实感。 最后,太阳系行星系统实现还可以加入一些特殊效果,如行星间的引力和碰撞效果,太阳系中的行星位置的实时更新等。这些效果可以通过物理引擎和实时计算技术来实现。 总而言之,要实现太阳系行星系统,我们需要使用计算机图形学技术来创建太阳和各个行星的模型,并使用数学和物理计算技术来模拟它们的运动和特征。通过合理运用这些技术,我们可以创造一个逼真而令人惊叹的太阳系行星系统

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值