有多少个VAO和VBO?

7
据我理解:VAO代表了一种特定的状态。如果我绑定一个VAO,添加一些VBO和元素缓冲区用于索引等,我就能保存我想要绘制的对象的某种状态,并在以后需要渲染时轻松地激活和绘制它们。对吗?
因此,VBO保存实际数据,而VAO只是一个“包装器对象”,它保存了我为其定义的所有缓冲区的指针?
更改VAO很费时间(更改VBO也是如此?)。目前,我加载网格并将它们组合成模型。每个模型都使用自己的VAO,并具有带有顶点的VBO和一个包含它们索引的元素缓冲区。
现在据我所知,这是胡说八道的,因为我的世界中的每个对象(具有模型)都使用自己的VAO。 ~对于我的世界中的约30个对象来说没有问题,但是我想做到正确,并且在未来可能会有数百或数千个对象,那么性能将大大降低。~
因此,许多对象在其模型方面“相同”。我的意思是,如果在世界中有特定的树类型,则可能会多次使用相同的模型,只是位置不同。
现在我该怎么办?我应该手动跟踪我的VAO,如下所示:(以下是伪代码!)
treesVAOId = 1;
rabbitsVAOId = 2;

然后,如果我加载了一个模型,只需检查ID是否已经绑定(如何?),并在那里添加另一组VBOs(甚至加入正确的VBO也可以吗?如果可以,如何实现?)

我在考虑一个大型游戏。假设游戏中有成千上万个角色。当然,并不是所有这些角色都在同一时间内渲染,但我不能为每个角色创建一个VAO和VBO,对吧?

我感觉还缺少一个更重要的部分...像实例化和高效地重复使用(或多次使用)相同的数据以实现不同的目的。

这是我所缺少的一步,如何在实际应用中真正优化VAOs和VBOs的使用。


听起来你想要使用ARB_vertex_attrib_binding - ratchet freak
@ratchetfreak 因为我必须支持OpenGL 3.3,所以不行。 :( - user4063815
2个回答

10
你所描述的被称为资源管理器,或者至少是资源管理器的一部分。在外部文件中描述资源是一个好习惯,因此您需要一个资源文件,在其中以某种方式描述所有网格(考虑使用XML或JSON)。 类层次结构 以下是可能的类层次结构方法:
每个VAO代表一个网格,定义其顶点坐标、纹理坐标、法线、顶点颜色等。我认为除非您有一个非常特殊的可视化情况,否则没有理由在几个VAO中使用相同的VBO。因此,假设您仅使用每组数据一次,即使用VAO的类不应了解任何关于底层VBO的信息,并且不需要编写VBO的类包装器。
一组网格(可能只包含一个网格)表示一个模型。最小模型类应包括对VAO的句柄和几何变换信息(旋转、平移等)。为什么不是每个模型严格一个网格?有时您可能需要对一组网格应用一个变换,而其中每个网格都有自己的模型本地变换。例如,这种组合可以用于某种骨骼动画或仅用于渲染从可能武器库中取出的任意武器的角色。此外,您可以将这些模型分组在一起,获得具有相同接口的更复杂的模型,因此您将获得场景图的相似之处。无论如何,将组合模式用于模型类是一个好主意。
场景应包括模型集合、光源、力场等。 资源管理器 但是场景(或类似的游戏对象)从哪里获取它的模型呢?资源管理器应该回答这个问题。为每个模型定义一种独特的标识符。在最简单的情况下,真实或虚拟文件系统中的路径可以被视为标识符,但它并不是非常灵活。在我看来,最好使用具有表现力的人类可读名称在资源文件中定义所有网格,并将每个名称绑定到一组数据(所有类型的坐标、颜色等)和属性。
所有你的代码都不应直接使用模型,而应该使用资源管理器给你的句柄。显然,资源管理器必须在程序执行期间和不同地方的调用之间保持状态。它旨在跟踪哪些网格已经存储在内存中,并保留所有存储网格的VAO标识符。考虑使用singleton模式作为资源管理器。
例子:
ModelHandle footman = resMan->getModel("footman.model");
//.....
footman->setLocation(x,y,z);
footman->draw();

这里调用getModel("footman.model")开始构建模型,导致像下面这样的调用:

MeshHandle resMan->getMesh("footman1.mesh");

获取所有网格。 getMesh 完成了这个解释的工作。它检查是否之前已经加载了这样的网格,如果是,则只返回包含此网格的VAO的句柄。否则,它将创建新的VAO对象,将请求的数据加载到其中,并返回对新创建对象的句柄。此后对该对象的所有后续请求都不会导致新的VAO分配。

当然,所描述的场景图组织只是大概近似于它应该看起来像什么。例如,它没有区分模型和抽象场景图节点,但为您的引擎开发和微调这种层次结构是由您决定的。

资源管理器类的最终接口是另一个需要讨论和设计的主题。一些问题和想法:

  • 您将使用单例还是因为某些原因决定使用全局变量?
  • 如果您决定使用单例,也许您希望拥有一些其他私有非单例资源管理器来处理一些有限的资源集合?那么请考虑将单例设计为包装模板类,以使得这样的代码成为可能:
    ResourceHandle h1 = Singleton<ResourceMan>::instance->getResource("foo");
    ResourceMan myPrivateManager;
    ResourceHandle h2 = myPrivateManager.getResource("bar");
  • 你会使用一个全面的管理器来管理所有类型的资源,还是为每种资源类型使用特殊的管理器类?第二种方法更好。发展第二种方法的想法,让编译器为您编写代码!使用带有少量专门方法的模板资源管理器类。只需为每种资源类型专门化一个资源创建方法,即可使所有其他资源管理代码不受影响!
  • 考虑资源的生命周期。何时应该销毁特定的VAO?考虑实现引用计数和/或借用引用。
  • 缓存?在将数据加载到设备(显卡)后立即从主机内存中删除它,还是保留一段时间?保存多长时间?
  • 关于流媒体怎么样?这不应该是资源管理器的领域,但流媒体支持可能会影响它。
  • glIsVertexArray函数及其类似函数可能会很有用。

排序

渲染场景时,VAO并不是您需要更改的唯一资源。您还需要更改纹理、着色器甚至是帧缓冲区等。减少状态更改数量的常见方法是按某些属性对可显示对象进行排序。

例如,很可能你只使用一个着色器来渲染给定的网格。因此,你可以首先通过着色器对所有网格进行排序,以最小化着色器更改的数量。然后,对于每个着色器(即在给定着色器的网格列表中),你可以按VAO对网格进行排序,以将VAO更改的数量降至最低。按纹理排序?这取决于你的应用程序是否需要按纹理对对象进行排序以及在哪里进行排序。
结论:
总之,如果你正在编写游戏引擎,那么你肯定会需要资源管理器。如果你为VAOs编写了一个快速且不完整的解决方案,那么你将面临处理纹理、附加帧缓冲区和许多其他对象的同样问题和疑问,因此最好实现一个良好的资源管理器。
有用的起步文章:

http://www.gamedev.net/page/resources/_/technical/game-programming/a-resource-manager-for-game-assets-r3807

http://www.gamedev.net/page/resources/_/technical/game-programming/a-simple-fast-resource-manager-using-c-and-stl-r2503

非常有用的书:

http://www.gameenginebook.com/


1
谢谢。实际上,你的回答有点跑题了,因为我仍然不确定应该有多少个VAO和VBO是可以的。如果每个模型都有自己的VAO,那么我就停留在了目前的状态。(我的项目中确实有一个Model和Mesh类。只是因为每个实体都有一个模型,所以它有自己的VAO)我面临的问题是,我希望我的游戏在处理数百个对象时表现良好,而不仅仅是几十个,并且我想知道这种方法是否过于资源密集,因为切换VAO和VBO相当昂贵。 - user4063815
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Sergey
当然,你是正确的,我并没有假设有一个全局答案来回答“多少个vaos/vbos是可以的”,而是关注如何构建应用程序以便以高效的方式重复使用相同模型的vaos/vbos,同时能够将它们设置在不同的位置并操作单个模型而不是所有模型。像这样的东西。如果我碰到一个树模型,我希望只有这个树模型响应,而不是每一个,尽管它们引用了相同的vao/vbo。 - user4063815

2
更改VAO并不是很昂贵。具体的数字显然高度依赖于硬件和平台。但是只是为了给你一个大致的想法,我在几年前的笔记本电脑上测量了每秒VAO切换次数,结果为低百万级别。假设你的机器可以每秒切换VAO 6百万次。如果你想以这个速率达到60 fps,则每帧可以切换VAO 100,000次。

当然,你肯定不希望用所有CPU时间来切换VAO。你的应用程序将有大量其他状态需要改变,你必须处理自己的应用逻辑,并且理想情况下,你不希望保持整体CPU负载尽可能低。所以我不想接近上面的数字。不过,在相对高性能的计算机/设备上,每帧切换VAO 1000次应该不成问题。

这与你通常在绘制调用之间进行的其他状态更改非常相似。你总是希望尽量减少它们(以及绘制调用本身的数量)。但就状态更改而言,绑定不同的VAO通常是一个相对便宜的操作。

如果您有共享相同几何形状的对象,比如您示例中的树木,那么您肯定不应该有多个相同数据的副本。这只是常识,并且与图形甚至没有太多关系。浪费内存当然是不可取的。即使您并没有急需内存,拥有多个相同数据的副本仍可能会影响性能,因为它很可能会降低缓存命中率。
如何设计您的游戏以使其最有效地工作,在这种格式下有些广泛的问题。好吧,我从未写过正式的游戏,所以我也不是理想的人选来给您建议。我的第一反应是拥有一组定义不同形状的类。例如,您可以拥有一个名为TreeShape的类,该类拥有树的几何形状(VAO和VBO)。每种角色都是同样的情况。然后,如果您的场景包含一堆树,则有一个描述特定树木的Tree类,其中实例可能只包含有关树木位置/大小的信息,但它们都共享对同一个TreeShape的引用。因此,所有树都使用相同的TreeShape共享相同的VAO/VBO,而每个特定的Tree仅包含每个树实例实际不同的信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接