我使用OpenGL已经有一段时间了,并且阅读了大量的教程。除了它们中的许多仍在使用固定管道这一事实外,它们通常会将所有的初始化,状态更改和绘制都放在一个源文件中。对于教程的有限范围来说,这很好,但是我很难确定如何将其扩展到完整的游戏。
如何将OpenGL的使用跨文件分割?从概念上讲,我可以看到拥有一个仅将内容渲染到屏幕上的渲染类的好处,但是诸如着色器和灯光之类的东西将如何工作?我应该为灯光和着色器之类的课程设置单独的类吗?
我使用OpenGL已经有一段时间了,并且阅读了大量的教程。除了它们中的许多仍在使用固定管道这一事实外,它们通常会将所有的初始化,状态更改和绘制都放在一个源文件中。对于教程的有限范围来说,这很好,但是我很难确定如何将其扩展到完整的游戏。
如何将OpenGL的使用跨文件分割?从概念上讲,我可以看到拥有一个仅将内容渲染到屏幕上的渲染类的好处,但是诸如着色器和灯光之类的东西将如何工作?我应该为灯光和着色器之类的课程设置单独的类吗?
Answers:
我认为OO OpenGL并不是必须的。当谈论着色器,模型等类时,情况有所不同。
基本上,您将首先进行游戏/引擎初始化(以及其他操作)。然后,您可以将纹理,模型和着色器加载到RAM(如果需要)和“缓冲区对象”中,然后上载/编译着色器。之后,您在着色器,模型的数据结构或类中,具有着色器,模型和纹理缓冲区对象的int ID。
我认为大多数引擎都具有引擎组件,并且每个人都有特定的接口。我研究过的所有引擎都有一些组件,例如Renderer或SceneManager或两者都有(取决于游戏/引擎的复杂性)。比起拥有Renderer接口的OpenGLRenderer类和/或DXRenderer,您更是如此。然后,如果您有SceneManager和Renderer,则可以执行以下一些操作:
渲染器可能会调用对象的绘制函数,该函数将调用组成的每个网格的绘制函数,并且网格将绑定纹理对象,绑定着色器,调用OpenGL绘制函数,然后不使用着色器,纹理和对象数据缓冲区对象。
注意:这只是示例,您应该更详细地研究SceneManager并分析您的用例,以了解最佳实施方案
您当然会拥有引擎的其他组件,例如MemoryManager,ResourceLoader等,它们会同时处理视频和RAM内存的使用,因此它们可以根据需要加载/卸载某些模型/着色器/纹理。为此的概念包括内存缓存,内存映射等。每个组件都有很多细节和概念。
看一下其他游戏引擎的更详细描述,有很多它们,并且它们的文档非常多。
但是,是的,上课使生活更轻松。您应该完全使用它们,并记住有关封装,继承,接口以及其他更酷的东西。
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无关,只处理一个世界中的对象。
oglplus::Context
该类使这种依赖性非常明显-这会成为问题吗?我相信它将帮助新的OpenGL用户避免很多问题。
在现代OpenGL中,您可以使用不同的vaos和着色器程序将渲染对象几乎完全分开。甚至一个对象的实现也可以分为许多抽象层。
例如,如果要实现地形,则可以定义一个TerrainMesh,其构造函数为该地形创建顶点和索引,并将其设置为数组缓冲区,并且-如果为其提供属性位置-则对数据进行着色处理。它也应该知道如何渲染它,并且应该注意还原它为设置渲染所做的所有上下文更改。此类本身不应该知道将要渲染它的着色器程序,也不应该知道有关场景中任何其他对象的任何信息。在该类的上方,您可以定义一个Terrain,它知道着色器代码,其工作是在着色器和TerrainMesh之间创建连接。这应该意味着获得属性和统一的位置并加载纹理,以及类似的事情。此类不应该了解有关如何实现地形,使用哪种LoD算法的任何知识,它仅负责为地形着色。在此之上,您可以定义非OpenGL功能,例如行为和碰撞检测等。
说到重点,即使OpenGL被设计为低级使用,您仍然可以构建独立的抽象层,从而可以将其扩展到具有虚幻游戏大小的应用程序。但是,您想要/需要的层数实际上取决于您想要的应用程序的大小。
但是不要对这个大小自欺欺人,不要尝试在10k行应用程序中模仿Unity的对象模型,结果将是一场彻底的灾难。逐步构建各层,仅在需要时才增加抽象层的数量。
ioDoom3可能是一个很好的起点,因为您可以依靠Carmack遵循出色的编码实践。我也相信他在Doom3中没有使用超大纹理,因此作为渲染管道相对来说比较简单。