2012-03-26 49 views
23

我已经使用OpenGL和C++进行了各种演示项目,但他们都涉及简单地渲染一个有一些有趣效果的单个立方体(或类似的简单网格)。对于这样的简单场景,立方体的顶点数据可以存储在不雅观的全局数组中。我现在正在研究渲染更复杂的场景,以及不同类型的多个对象。OpenGL和OOP程序结构

我觉得很有道理,为不同类型的对象(RockTreeCharacter等)的不同的类,但我不知道如何干净地打破了数据和场景中的对象渲染功能。每个类将存储它自己的顶点位置,纹理坐标,法线等数组。然而,我不确定在哪里放置OpenGL调用。我想我会有一个循环(在WorldScene类中)遍历场景中的所有对象并呈现它们。

应该渲染它们涉及调用每个对象中的渲染方法(Rock::render(), Tree::render(),...)或将对象作为参数的单个渲染方法(render(Rock), render(Tree),...)?后者看起来更清晰,因为我不会在每个类中都有重复的代码(尽管可以通过继承一个RenderableObject类来缓​​解这个问题),并且如果我想稍后将端口转换为的DirectX。另一方面,我不确定是否可以将它们分开,因为无论如何,我可能都需要存储在对象中的OpenGL特定类型(例如,顶点缓冲区)。另外,将渲染功能与对象分开似乎有点麻烦,因为它必须调用大量的Get()方法才能从对象中获取数据。最后,我不确定这个系统如何处理必须以不同方式绘制的对象(不同的着色器,不同的变量传递给着色器等)。

这些设计之一明显比其他设计更好吗?我可以通过哪些方式改进他们,以使我的代码组织良好并且高效?

回答

22

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

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

你会想要一些原始的地理类,但延迟任何复杂的一些类型的网格类,它处理索引tris。对于高性能应用程序,您需要在着色管道中为相似的输入类型批量调用(以及可能的数据),以尽量减少不必要的状态更改和管道刷新。

着色器参数和纹理通常通过与可呈现相关联的某个材质类进行控制。

场景中的每个可渲染对象通常是层次场景图中节点的一个组件,其中每个节点通常通过某种机制继承其祖先的变换。您可能需要一个使用空间分区方案的场景调色板来快速进行可视性确定,并避免出现视图调用开销。

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

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

显然有一小堆细节遗漏了,但它可能会变得非常复杂,这取决于您想要的泛化和性能以及您瞄准的视觉复杂程度。

您的问题draw(renderable),或renderable.draw()或多或少是无关紧要的,直到您确定所有部件如何配合在一起为止。

[更新]在这个空间多一点的工作之后,一些额外的洞察力

话虽如此,在商业引擎,其通常更喜欢draw(renderBatch)其中每个渲染批次对象的集合由于在异构对象(在纯粹的“OOP场景图形通过多态性)上迭代并且一个接一个地调用obj.draw()具有可怕的缓存局部性并且通常是GPU资源的低效使用,所以它们以某种有意义的方式与GPU一致。采用面向数据的方法来设计引擎如何以尽可能最有效的方式与底层图形API进行对话,尽可能多地分配事务而不会对代码结构/可读性产生负面影响是非常有用的。

一个实际的建议是编写第一个引擎使用天真/“纯”的方法来真正熟悉域空间。然后在第二遍(或者可能重写)时,专注于硬件:诸如内存表示,缓存局部性,流水线状态,带宽,批处理和并行性等。一旦你真正开始考虑这些事情,你会意识到,你的初步设计大部分都在窗外。好开心。

+1

谢谢,这给了我几个想法。我不太了解的一件事是可提供的类和你提到的网格类之间的区别。我不希望网格类是可以绘制自己的可渲染的吗?在更高层次上,我认为设计将比我预期的更复杂。你知道任何提供设计渲染系统的好介绍的在线资源吗?我找到的大多数OpenGL教程都介绍了绘制,纹理和点亮几个三角形的过程,而没有对大图片架构进行太多讨论。 – Jeff 2012-03-26 20:00:56

+0

@Jeff:没有。网格只是顶点数据的集合。单独的网格*是不够的。为了渲染某些东西,你需要一个网格,你想要渲染的着色器,以及其他各种状态(纹理等)。 – 2012-03-27 00:37:52

+0

@Jeff尼科尔说的是正确的。并非所有绘制的东西都是网格物体(例如,您可能还想绘制线条或其他像四边形网格物体可能会过度杀伤的其他原始物体)。一个网格类应该描述几何体(有时候每三层都有材料索引,分组/层次结构),但是没有其他的东西。看看有关游戏引擎开发的书籍(即使你没有制作游戏)。一个好的是“游戏引擎架构”,也是“游戏编程宝石”和“游戏引擎宝石”有很多很好的信息。您可能还想潜伏gamedev.net论坛 – 2012-03-27 01:26:04

3

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

+0

感谢您的建议;我会检查一下,看看我能从中得到什么。然而,我认为我真正需要的是规模稍小一点的东西,这是典型的OpenGL教程(例如渲染光照,纹理,旋转立方体)与完整图形库之间的垫脚石。 – Jeff 2012-03-26 20:07:11

0

这是我为物理仿真所实现的功能,以及工作得很好,并且处于良好的抽象层次。首先,我想分开功能分为类,如:

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

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