c++ - OpenGL和OOP程序结构

标签 c++ oop opengl architecture game-engine

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

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

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

其中一种设计明显优于另一种吗?我可以通过哪些方式改进它们以保持我的代码组织良好且高效?

最佳答案

首先,现在甚至不要为平台独立性而烦恼。等到您对自己的架构有了更好的了解。

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

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

着色器参数和纹理通常通过与可渲染对象关联的某个 Material 类进行控制。

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

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

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

显然有很多小细节被遗漏了,但它可能会变得非常复杂,具体取决于您想要的通用性和性能以及您的目标视觉复杂性。

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

[更新] 在这个领域工作了一段时间后,增加了一些见解:

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

一个实用的建议是使用幼稚/“纯粹”的方法编写第一个引擎,以真正熟悉域空间。然后在第二遍(或可能重写)时,专注于硬件:内存表示、缓存位置、管道状态、带宽、批处理和并行性。一旦你真正开始考虑这些事情,你就会意识到你的大部分初始设计都被忽略了。很好玩。

关于c++ - OpenGL和OOP程序结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9875953/

相关文章:

c++ - 不能为对象的 shared_ptr 的 vector 调用没有对象的成员函数

php - 这是域对象类吗?

java - 从静态内部类中引用非静态变量

c - OpenGL,物体移动时闪烁

c++ - OpenGL Performer 最简单的移植路径?

c++ - 游戏实体更新方法

c++ - 如何使用 C++ 在数组中存储不同的值类型?

c++ - 如何使用 C++ 从 Windows 成像组件 (WIC) 获取错误消息?

c++ - Main 函数与状态之间的通信

math - 在 OpenGL 中,如何确定给定深度的 View 边界