OpenGL和面向对象程序结构

OpenGL and OOP program structure

本文关键字:结构 程序 面向对象 OpenGL      更新时间:2023-10-16

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

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

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

这些设计中的一个明显比另一个好吗?我可以用什么方法来改进它们,以保持我的代码组织良好且高效?

首先,现在甚至不要为平台独立性而烦恼。等到你对你的体系结构有了更好的想法。

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

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

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

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

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

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

很明显,有很多小细节被遗漏了,但它可能会变得非常复杂,这取决于你想要的泛化和表现力,以及你想要的视觉复杂性。

你的draw(renderable)renderable.draw()的问题或多或少是无关紧要的,直到你确定了所有部分是如何结合在一起的。

[更新] 在这个空间工作了一段时间后,添加了一些见解

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

一个实用的建议是使用天真的/";纯";真正熟悉领域空间的方法。然后在第二遍(或者可能重写)中,重点关注硬件:比如内存表示、缓存位置、管道状态、带宽、批处理和并行性。一旦你真的开始考虑这些事情,你就会意识到你最初的设计大多都已经过时了。很有趣。

我认为OpenSceneGraph就是一个答案。看看它及其实现。它应该为您提供一些关于如何使用OpenGL、C++和OOP的有趣见解。

以下是我为物理模拟实现的内容,以及运行良好且处于良好抽象级别的内容。首先,我会将功能划分为类,例如:

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

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