3D太阳系模拟:从程序员的视角

在3D编程的世界里,太阳系是一个极好的学习对象。它包含了行星、太阳、卫星、宇宙背景以及背景中的星星等元素。作为一名3D程序员,需要考虑如何将这些实体转换为编程环境中的对象。例如,宇宙背景通常是黑色的,这可以通过设置OpenGL的背景颜色来实现。星星可以看作是亮点,可以通过OpenGL的点原语来绘制它们。如果不想手动放置每一颗星星,可以使用随机函数生成大量星星,只要确保它们不会落在太阳系内部即可。行星是带有纹理的球体,它们有自己的轨道和自转轴,因此需要使用变量来跟踪这些属性,并随着时间的推移更新它们。如果不想在3D Max中创建球体,可以使用OpenGL的四边形来定义一组基本的三角函数形状,并为它们定义纹理坐标。卫星与行星类似,唯一的区别是它们的旋转轴位于行星上而不是太阳上。

使用代码

项目引用包括对ShadowEngine和TAO.OpenGL的引用。值得注意的是,不是在像XNA、GLUT等独立的窗口中创建图形上下文。图形上下文是在常见的.NET WinForm中创建的。这样做非常方便,因为可以在任何窗口中绘制3D内容,并将其与2D组件混合。稍后将看到,可以在几乎所有的2D组件中绘制3D内容。OpenGL初始化函数只需要一个有效的组件句柄就可以开始绘制3D。

Camera.cs - 这是一个经典的FPS(第一人称射击)摄像机。FPS的工作原理超出了本文的范围。它们是这样工作的:

  • 鼠标在屏幕中间居中。
  • 当用户移动鼠标时,从起始点计算出Delta X和Delta Y。
  • 这些Delta X和Delta Y被转换为角度,以及摄像机的旋转方式。
  • 当想要向前或向后移动时,摄像机会朝着角度指向的方向移动。

可以通过查看camera类中的public void Update(int pressedButton)方法来更好地理解。

MainForm.cs - 这个类名是自解释的,它是项目的主要且唯一的表单。它包含了纹理加载、3D上下文初始化、3D内容绘制等调用。它还处理用户按键和鼠标输入。由于3D内容至少需要每秒30帧来绘制,使用了一个计时器,并将所有绘制代码放在其中。一个有趣的点是,在面板上启动了一个3D上下文,所以可以将面板设置在表单内的任何位置。以下是项目中3D初始化的代码:

hdc = (uint)pnlViewPort.Handle; string error = ""; OpenGLControl.OpenGLInit(ref hdc, pnlViewPort.Width, pnlViewPort.Height, ref error);

以下是将纹理加载到OpenGL内存中的代码:

ContentManager.SetTextureList("texturas\\"); ContentManager.LoadTextures();

小型引擎负责加载位于该文件夹中的所有纹理,接受的纹理格式是TGA、JPG和BMP。纹理可能不是NPOT(非2的幂次),但仍然可以正确加载。以下是绘制所有场景的代码:

private void tmrPaint_Tick(object sender, EventArgs e) { // clears OpenGL Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT); // updates the camera solarSystem.Camara.Update(moving); // draws the scene solarSystem.DrawsScene(); // swaps buffers Winapi.SwapBuffers(hdc); // finish drawing operations Gl.glFlush(); }

Planet.cs - 一个行星包含以下变量:

  • 位置
  • 纹理
  • 轨道(当前距离太阳的距离)
  • 当前旋转角度
  • 当前轨道旋转角度
  • 当前轨道速度

使用OpenGL四边形来绘制行星球体。四边形是OpenGL预定义的形状,用于帮助进行小型绘图任务。四边形例如带有纹理坐标,所以不必使用3D编辑器像3D Max来正确地将纹理应用到每个行星上。在每一帧中,行星根据其轨道速度沿轨道移动。还有一个名为hasMoon的bool变量,用来指定是否要为该行星绘制月亮。只有月亮,但如果想绘制火星的卫星Phobos和Deimos,可以使用那段代码。行星类中包含的另一个有趣函数是用于绘制其轨道的函数。首先,使用sin函数生成点,然后使用GL_LINE_STRIP将它们连接起来。以下是代码:

public void DrawOrbit() { Gl.glBegin(Gl.GL_LINE_STRIP); for (int i = 0; i < 361; i++) { Gl.glVertex3f(p.x * (float)Math.Sin(i * Math.PI / 180), 0, p.x * (float)Math.Cos(i * Math.PI / 180)); } Gl.glEnd(); }

请注意,行星几乎总是有椭圆形轨道。这是一个圆形轨道。行星类中持有的两个角度变量用于保持行星围绕其轴的旋转和围绕太阳的旋转。

Satellite.cs - 一个卫星包含行星所做的一切。唯一的区别是它的旋转点不是太阳,而是包含它的行星。所以每次绘制时,它必须接收其包含行星的位置。将在其draw函数上注意到这一点。

SolarSystem.cs - 这个类包含行星、星星和卫星的列表。它只创建和绘制它们。行星保存在列表中,当从主表单调用DrawScene()时,它会进行foreach循环,调用行星的Draw方法。

Star.cs - 这个类绘制星星。星星是单个GL_POINTS,它们在随机位置生成。以下是生成它们的函数:

public void CreateStars(int amount) { Random r = new Random(); int count = 0; while (count != amount) { Position p = default(Position); p.x = (r.Next(110)) * (float)Math.Pow(-1, r.Next()); p.z = (r.Next(110)) * (float)Math.Pow(-1, r.Next()); p.y = (r.Next(110)) * (float)Math.Pow(-1, r.Next()); if (Math.Pow(Math.Pow(p.x, 2) + Math.Pow(p.y, 2) + Math.Pow(p.z, 2), 1 / 3f) > 15) { stars.Add(p); count++; } } }

这段代码的作用是生成一个随机点,并计算它与太阳的距离,如果距离小于预定义的值,则丢弃该点。在这种情况下,预定义的值是太阳系半径的两倍。这个操作将重复进行,直到达到所需的星星数量。

Sun.cs - sun类是最简单的。它就像行星类,只是没有轨道。它只有围绕其轴的旋转。太阳在OpenGL 3D坐标(0,0,0)处绘制。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485