OpenGL和OOP程序结构

Jef*_*eff 24 c++ architecture opengl oop game-engine

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

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

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

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

Pre*_*eti 22

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

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

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

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

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

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

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

很明显,除了你想要的普遍性和高效性以及你想要的视觉复杂性之外,还有很多细节,但它可能变得非常复杂.

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

[更新] 在这个空间工作了一点之后,一些额外的见解:

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

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