OpenGL和OOP程序结构

时间:2012-03-26 16:28:25

标签: c++ oop opengl architecture game-engine

我曾经使用OpenGL和C ++开展过各种各样的演示项目,但他们都只是简单地渲染一个具有一些有趣效果的立方体(或类似的简单网格)。对于像这样的简单场景,立方体的顶点数据可以存储在不优雅的全局数组中。我现在正在研究使用不同类型的多个对象渲染更复杂的场景。

我认为为不同类型的对象(RockTreeCharacter等)设置不同的类是有意义的,但我想知道如何干净地分解场景中对象的数据和渲染功能。每个类都将存储自己的顶点位置数组,纹理坐标,法线等。但是我不确定在哪里放置OpenGL调用。我想我会有一个循环(在WorldScene类中)迭代场景中的所有对象并渲染它们。

渲染它们是否涉及在每个对象(Rock::render(), Tree::render(),...)中调用一个render方法,或者是一个将对象作为参数(render(Rock), render(Tree),...)接受的渲染方法?后者看起来更干净,因为我不会在每个类中都有重复的代码(虽然可以通过从单个RenderableObject类继承来减轻),并且它允许在我想要的情况下轻松替换render()方法以后移植到DirectX。另一方面,我不确定我是否可以将它们分开,因为我可能还需要存储在对象中的OpenGL特定类型(例如顶点缓冲区)。此外,将渲染功能与对象分开似乎有点麻烦,因为它必须调用许多Get()方法来从对象获取数据。最后,我不确定这个系统如何处理必须以不同方式绘制的对象(不同的着色器,传递给着色器的不同变量等)。

这些设计中的一个明显优于其他设计吗?在哪些方面我可以改进它们以保持我的代码组织良好和高效?

3 个答案:

答案 0 :(得分:22)

首先,现在甚至不打扰平台独立性。等到你对你的架构有了更好的了解。

做很多绘制调用/状态更改很慢。在引擎中执行此操作的方式是,您通常希望拥有可以绘制自身的可渲染类。这种可渲染将与它需要的任何缓冲区(例如顶点缓冲区)和其他信息(如顶点格式,拓扑,索引缓冲区等)相关联。着色器输入布局可以与顶点格式相关联。

您将需要一些原始地理类,但是将某些复杂的东西推迟到处理索引tris的某种类型的网格类。对于高性能应用程序,您需要批量处理着色管道中类似输入类型的调用(以及可能的数据),以最大限度地减少不必要的状态更改和管道刷新。

着色器参数和纹理通常通过与可渲染相关联的一些材质类来控制。

场景中的每个可渲染本身通常是分层场景图中节点的一个组件,其中每个节点通常通过某种机制继承其祖先的变换。您可能希望使用空间分区方案的场景剔除器进行快速可见性确定,并避免对视图外的事物进行绘制调用开销。

大多数交互式3D应用程序的脚本/行为部分紧密连接或挂钩到其场景图节点框架和事件/消息系统中。

这一切都在高级循环中组合在一起,您可以根据时间更新每个子系统并在当前帧绘制场景。

显然,遗漏了很多细节但是它可能会变得非常复杂,这取决于你想要的普遍性和高效性,以及你的目标是什么样的视觉复杂性。

在确定所有部分如何组合在一起之前,您对draw(renderable),vs renderable.draw()的问题或多或少无关紧要。

[更新] 在这个领域工作了一些,一些增加了洞察力

话虽如此,在商业引擎中,它通常更像draw(renderBatch),其中每个渲染批处理都是以与GPU有些有意义的方式同质的对象聚合,因为迭代异构对象(在“纯粹的”中) “通过多态性的OOP场景图”和逐个调用obj.draw()具有可怕的缓存局部性,并且通常是GPU资源的低效使用。采用面向数据的方法来设计引擎如何以最有效的方式与其底层图形API进行对话,尽可能地对事物进行批处理而不会对代码结构/可读性产生负面影响,这非常有用。 p>

一个实际的建议是使用天真/“纯”方法编写第一个引擎,以便真正熟悉域空间。然后在第二遍(或可能是重写),专注于硬件:诸如内存表示,缓存局部性,管道状态,带宽,批处理和并行性之类的东西。一旦你真正开始考虑这些事情,你就会意识到你的大多数初始设计都会消失。很有趣。

答案 1 :(得分:3)

我认为OpenSceneGraph是一种答案。看看它及其implementation。它应该为您提供有关如何使用OpenGL,C ++和OOP的一些有趣见解。

答案 2 :(得分:1)

以下是我为物理模拟实现的内容以及效果非常好并且处于良好抽象级别的内容。首先,我将功能分为以下类:

  • 对象 - 包含所有必要对象信息的容器
  • AssetManager - 加载模型和纹理,拥有它们(unique_ptr),返回指向对象的资源的原始指针
  • 渲染器 - 处理所有OpenGL调用等,在GPU上分配缓冲区并将资源的渲染句柄返回给对象(当想要渲染器绘制对象时,我调用渲染器给它模型渲​​染句柄,纹理句柄和模型矩阵),渲染器应该汇总这些信息,以便能够分批绘制它们
  • 物理 - 使用对象及其资源的计算(特别是顶点)
  • 场景 - 连接以上所有内容,也可以保存一些场景图,取决于应用程序的性质(可以有多个图形,BVH用于碰撞,其他表示用于绘制优化等)。

问题在于GPU现在是GPGPU(通用gpu),因此OpenGL或Vulkan不仅仅是一个渲染框架。例如,正在GPU上执行物理计算。因此,渲染器现在可能会转换为 GPUManager 以及其上方的其他抽象。最佳绘制方式是一次调用。换句话说,整个场景的一个大缓冲区也可以通过计算着色器进行编辑,以防止过多的CPU< - > GPU通信。