面向对象的OpenGL


12

我使用OpenGL已经有一段时间了,并且阅读了大量的教程。除了它们中的许多仍在使用固定管道这一事实外,它们通常会将所有的初始化,状态更改和绘制都放在一个源文件中。对于教程的有限范围来说,这很好,但是我很难确定如何将其扩展到完整的游戏。

如何将OpenGL的使用跨文件分割?从概念上讲,我可以看到拥有一个仅将内容渲染到屏幕上的渲染类的好处,但是诸如着色器和灯光之类的东西将如何工作?我应该为灯光和着色器之类的课程设置单独的类吗?


1
那里有开源游戏以及包含更多代码的大型教程。检查其中的一些,看看代码是如何组织的。
MichaelHouse

您是渲染引擎的初学者吗?
Samaursa 2012年

如果不缩小范围,就不能合理地回答这个问题。您到底想建造什么?什么样的游戏,什么样的图形等等?
Nicol Bolas 2012年

1
@沙利文:“我认为这类概念几乎适用于任何游戏。” 他们没有。如果您要我做俄罗斯方块,我就不用费心在OpenGL上做一个大包装层或其他东西了。只需直接使用即可。如果您要求我制作诸如质量效应之类的东西,那么我们将需要在渲染系统周围进行多层抽象。在俄罗斯方块中投掷虚幻引擎正在使用a弹枪猎杀苍蝇。这比直接手工编码要困难得多。您应该只建立大而复杂的系统中,你需要的,并没有更大。
Nicol Bolas 2012年

1
@Sullivan:您的问题所隐含的唯一复杂性是您谈论拆分多个文件的位置。即使在《俄罗斯方块》中,我也会这样做。俄罗斯方块和虚幻引擎之间存在许多复杂程度。再说一遍:您要问的是哪一个?
Nicol Bolas 2012年

Answers:


6

我认为OO OpenGL并不是必须的。当谈论着色器,模型等类时,情况有所不同。

基本上,您将首先进行游戏/引擎初始化(以及其他操作)。然后,您可以将纹理,模型和着色器加载到RAM(如果需要)和“缓冲区对象”中,然后上载/编译着色器。之后,您在着色器,模型的数据结构或类中,具有着色器,模型和纹理缓冲区对象的int ID。

我认为大多数引擎都具有引擎组件,并且每个人都有特定的接口。我研究过的所有引擎都有一些组件,例如Renderer或SceneManager或两者都有(取决于游戏/引擎的复杂性)。比起拥有Renderer接口的OpenGLRenderer类和/或DXRenderer,您更是如此。然后,如果您有SceneManagerRenderer,则可以执行以下一些操作:

  • 遍历SceneManager并将每个对象发送到Renderer进行渲染
  • 在某些SceneManager方法中封装大写
  • SceneManager发送到Renderer,以便对其进行处理

渲染器可能会调用对象的绘制函数,该函数将调用组成的每个网格的绘制函数,并且网格将绑定纹理对象,绑定着色器,调用OpenGL绘制函数,然后不使用着色器,纹理和对象数据缓冲区对象。

注意:这只是示例,您应该更详细地研究SceneManager并分析您的用例,以了解最佳实施方案

您当然会拥有引擎的其他组件,例如MemoryManagerResourceLoader等,它们会同时处理视频和RAM内存的使用,因此它们可以根据需要加载/卸载某些模型/着色器/纹理。为此的概念包括内存缓存,内存映射等。每个组件都有很多细节和概念。

看一下其他游戏引擎的更详细描述,有很多它们,并且它们的文档非常多。

但是,是的,上课使生活更轻松。您应该完全使用它们,并记住有关封装,继承,接口以及其他更酷的东西。


这就是我一直在寻找的答案。但是,当您说“ ...将纹理,模型和着色器加载到RAM(如果需要)和缓冲区对象,以及上载/编译着色器...”时,我又回到了最初的问题;我应该使用对象来封装诸如模型和着色器之类的东西吗?
沙利文2012年

对象是类的实例,“您应该完全使用它们”。
edin-m 2012年

抱歉,我要问“我应该如何使用对象”。我的错。
沙利文

好吧,这取决于您的班级设计。一些基本的例子很直观,例如:object3d(类)具有网格(类);每个网格都有材质(类);每种材质都有纹理(可以是类,也可以只是id)和着色器(类)。但是,当您阅读每个组件时,您会发现有不同但相等的方法来制作SceneManager,Renderer,MemoryManager(所有这些可以是单个类,也可以是多个,继承等)。关于该主题(游戏引擎体系结构)的书籍很多,而要详细回答问题,可以写一本书。
edin-m 2012年

我认为这是我要获得的最佳答案。谢谢您的帮助:)
Sullivan

2

OpenGL中已经包含一些“对象”概念。

例如,任何带有id的东西都可以作为对象(也有专门命名为“ Objects”的东西)。缓冲区,纹理,顶点缓冲区对象,顶点数组对象,帧缓冲区对象等。只需一点工作,您就可以将类包装起来。如果您的上下文不支持扩展,它还为您提供了一种简单的方法来退回旧的已弃用的OpenGL函数。例如,一个VertexBufferObject可能会退回到使用glBegin(),glVertex3f()等。

您可能需要几种方法来摆脱传统的OpenGL概念,例如,您可能想将有关缓冲区的元数据存储在缓冲区对象中。例如,如果缓冲区存储顶点。顶点的格式是什么(即位置,法线,texcoords等)。它使用什么原语(GL_TRIANGLES,GL_TRIANGLESTRIP等),大小信息(存储了多少个浮点数,它们代表了多少个三角形等)。只是为了使它们易于插入到draw arrays命令中。

我建议您看一下OGLplus。它是OpenGL的C ++绑定。

同样glxx,虽然仅用于扩展加载。

除了包装OpenGL API外,您还应该考虑在其之上构建一个稍高的一级构建。

例如,负责所有着色器,加载和使用它们的材质管理器类。同样,它将负责将属性转让给他们。这样,您就可以调用:materials.usePhong(); material.setTexture(sometexture); material.setColor()。这可以提供更大的灵活性,因为您可以使用较新的东西(例如共享的统一缓冲区对象)来拥有1个大缓冲区,其中包含您的着色器在1个块中使用的所有属性,但是如果不支持,则您可以退回上载到每个着色器程序。如果支持,您可以拥有1个大型整体着色器,并使用统一的例程在不同的着色器模型之间进行交换,或者您可以退回使用一堆不同的小型着色器。

您还可以查看GLSL规范中用于编写着色器代码的内容。例如,#include将非常有用,并且在着色器加载代码中非常容易实现(它也有ARB扩展名)。您还可以基于所支持的扩展来即时生成代码,例如使用共享的统一对象或回退到使用常规统一。

最后,您将需要一个更高级别的渲染管道API,该API可以执行诸如场景图,特殊效果(模糊,发光)之类的事情,需要多次渲染过程的事物(如阴影,光照等)。然后,最重要的是一个游戏API,它与图形API无关,只处理一个世界中的对象。


2
“我建议您看一下OGLplus。它是OpenGL的C ++绑定。” 我建议反对。我喜欢RAII,但对于大多数OpenGL对象来说,这是完全不合适的。OpenGL对象与全局构造相关联:OpenGL上下文。因此,只有在具有上下文之前,您才能创建这些C ++对象;如果上下文已被破坏,则将无法删除这些对象。同样,它给人一种幻想,即您可以在不更改全局状态的情况下修改对象。这是一个谎言(除非您使用的是EXT_DSA)。
Nicol Bolas 2012年

@NicolBolas:我不能检查是否有上下文,然后才初始化吗?还是只允许管理者创建需要OpenGL上下文的对象?---顺便说一句,很棒的教程集(您的个人资料中的链接)!在搜索OpenGL /图形时,以某种方式我从未遇到过它们。
Samaursa

@Samaursa:目的何在?如果您有一个OpenGL对象管理器,那么...您实际上并不需要让对象自己照顾;经理可以为他们做。而且,如果仅在存在上下文时初始化它们,如果尝试使用未正确初始化的对象会发生什么?它只会在您的代码库中造成脆弱。我所说的关于在不影响全局状态的情况下修改这些对象的能力也不会改变。
Nicol Bolas 2012年

@NicolBolas:“除非拥有上下文,否则您将无法创建这些C ++对象” ...与使用裸OpenGL构造有什么不同吗?在我看来,oglplus::Context该类使这种依赖性非常明显-这会成为问题吗?我相信它将帮助新的OpenGL用户避免很多问题。
xtofl 2012年

1

在现代OpenGL中,您可以使用不同的vaos和着色器程序将渲染对象几乎完全分开。甚至一个对象的实现也可以分为许多抽象层。

例如,如果要实现地形,则可以定义一个TerrainMesh,其构造函数为该地形创建顶点和索引,并将其设置为数组缓冲区,并且-如果为其提供属性位置-则对数据进行着色处理。它也应该知道如何渲染它,并且应该注意还原它为设置渲染所做的所有上下文更改。此类本身不应该知道将要渲染它的着色器程序,也不应该知道有关场景中任何其他对象的任何信息。在该类的上方,您可以定义一个Terrain,它知道着色器代码,其工作是在着色器和TerrainMesh之间创建连接。这应该意味着获得属性和统一的位置并加载纹理,以及类似的事情。此类不应该了解有关如何实现地形,使用哪种LoD算法的任何知识,它仅负责为地形着色。在此之上,您可以定义非OpenGL功能,例如行为和碰撞检测等。

说到重点,即使OpenGL被设计为低级使用,您仍然可以构建独立的抽象层,从而可以将其扩展到具有虚幻游戏大小的应用程序。但是,您想要/需要的层数实际上取决于您想要的应用程序的大小。

但是不要对这个大小自欺欺人,不要尝试在10k行应用程序中模仿Unity的对象模型,结果将是一场彻底的灾难。逐步构建各层,仅在需要时才增加抽象层的数量。


0

ioDoom3可能是一个很好的起点,因为您可以依靠Carmack遵循出色的编码实践。我也相信他在Doom3中没有使用超大纹理,因此作为渲染管道相对来说比较简单。


6
“您可以依靠Carmack遵循出色的编码实践”,哦,老兄,那是一个很好的选择。Carmack遵循出色的C ++编码实践。那是一场骚乱!哦,你不是在开玩笑。嗯...您真的看过他的代码吗?他是C程序员,这就是他的想法。这对于C来说很好,但是问题是关于C ++的
Nicol Bolas 2012年

我来自C语言,所以他的代码对我而言很有意义:P是的,花了一些时间从事基于地震的游戏。
克里斯瓦伦茨(Chrisvarnz)2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.