有多少 VAO 和 VBO

How many VAOs and VBOs

据我理解:一个VAO代表了一个状态。如果我绑定一个 VAO,为索引和东西添加一些 VBO 和元素缓冲区,我可以保存我想要绘制和激活的对象的特定状态,并在以后渲染东西时轻松绘制它们。对吗?

所以 VBO 保存实际数据,而 VAO 只是一个 "wrapper-object" 保存指向我为它定义的所有缓冲区的指针?

更改 VAO 的成本很高(就像更改 VBO 一样?!)。目前我加载网格并将它们组合到模型中。每个模型都使用它自己的 VAO,并有一个 VBO(带有顶点)和一个带有索引的元素缓冲区。

现在据我了解,这是胡说八道,因为我的世界中的每个对象(具有模型)都使用它自己的 VAO。对于我的世界中的 ~30 个对象来说这不是问题,但我想正确地做到这一点,将来可能会有成百上千个对象,然后性能会大大下降。

因此,许多对象的模型都是 "the same"。我的意思是,例如,如果你在世界上有某种树类型,你可能会多次使用相同的模型,只是在不同的位置。

现在我该怎么做呢?我是否应该像这样手动跟踪我的 VAO:(伪代码如下!)

treesVAOId = 1;
rabbitsVAOId = 2;

然后如果我加载一个模型,只需检查 ID 是否已经绑定(如何?)并在那里添加另一组 VBO(或者甚至添加到正确的 VBO?如果是,如何?)

我正在考虑这里的大型游戏。假设游戏中有数千个角色。当然不是所有这些都是同时渲染的,但我不能为它们中的每一个创建一个 VAO 和 VBO,我可以吗?

我觉得缺少了更多的部分...比如为了不同的目的再次实例化和使用(或多或少)相同的数据。

这是我缺少的一步,如何在现实世界中真正优化 VAO 和 VBO 的使用。

您在此处描述的称为 Resource manager 或至少是资源管理器的一部分。在外部文件中描述资源是一种很好的做法,因此您需要一个资源文件,其中以某种方式描述了所有网格(考虑使用 XML 或 JSON)。

Class 层级

这是 class 层次结构的一种可能方法:

每个 VAO 代表一个网格,定义它的顶点坐标、纹理坐标、法线、顶点颜色等等。我认为没有理由在多个 VAO 中使用相同的 VBO,除非你有一个非常特殊的可视化案例。因此,假设您只使用每组数据一次,即使用 VAO 的 classes 不应该了解底层 VBO 的任何信息,并且没有必要为 VBO 编写 class 包装器。

一组网格(可能只包含一个网格)表示一个模型。最小模型 class 应该包括 VAO 的句柄和几何变换信息(旋转、平移,任何你想要的)。为什么不严格每个模型一个网格?有时您可能需要将一个变换应用于一组网格,而其中的哪些网格又具有自己的模型局部变换。例如,这种合成可以用于一种骨骼动画,或者只用于渲染一个角色,使用从可能的武器库中获取的任意武器。此外,您可以将这些模型组合在一起以获得具有相同界面的更复杂的模型,因此您将获得模型 class.

scene graph. Anyway it's a good idea to use composite 模式的相似之处

场景应包括模型、光源、力场等集合。

资源管理器

但是场景(或类似的游戏对象)将从哪里获得它的模型?资源管理器应该回答这个问题。 使每个模型都由某种唯一标识符定义。在最简单的情况下,真实或虚拟文件系统中的路径可以被视为标识符,但它不是很灵活。在我看来,最好使用富有表现力的人类可读名称来定义资源文件中的所有网格,并将每个名称绑定到一组数据(所有类型的坐标、颜色等)和属性。 你所有的代码都不应该直接使用模型,而应该使用资源管理器给你的句柄。显然,资源管理器必须在程序执行期间以及来自不同地方的调用之间保持状态。它旨在跟踪哪些网格已存储在内存中并保留所有存储网格的 VAO 标识符。考虑为资源管理器使用 singleton 模式。

示例:

ModelHandle footman = resMan->getModel("footman.model");
//.....
footman->setLocation(x,y,z);
footman->draw();

此处对 getModel("footman.model") 的调用开始构建模型,从而引发类似

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

获取所有网格。而 getMesh 完成了所有这些解释的工作。它检查之前是否加载过此类网格,如果是,它只是 returns 处理包含此网格的 VAO。否则它会创建新的 VAO 对象,将请求的数据加载到其中并 returns 处理新创建的对象。此对象的所有后续请求都不会导致新的 VAO 分配。

当然,所描述的场景图组织只是对其应有的样子的粗略近似。例如,它不区分模型和抽象场景图节点,但是为您的引擎开发和微调这种层次结构取决于您。

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

  • 你会使用单例还是你决定使用全局变量 原因?
  • 如果你决定使用单例,也许你希望有一些其他的 一些有限集合的私有非单例资源管理器 资源?然后考虑将单例设计为包装模板 class,使这样的代码成为可能:
    ResourceHandle h1 = Singleton<ResourceMan>::instance->getResource("foo");
    ResourceMan myPrivateManager;
    ResourceHandle h2 = myPrivateManager.getResource("bar");
  • 你会用一个全面的经理来管理所有类型的资源吗 或为每种资源类型使用特殊管理器 class?第二种方法更好。开发第二种方法的想法,让你的编译器负责为你编写代码!将模板资源管理器 class 与专门方法的一小部分一起使用。只需为每种资源类型指定一种资源创建方法,并保持所有其他资源管理代码不变!
  • 考虑资源生命周期。什么时候应该销毁特定的 VAO?考虑实现引用计数器 and\or 借用引用。
  • 缓存?将数据加载到设备(视频卡)后立即从主机内存中删除数据还是保留一段时间?多长时间?
  • streaming呢?它不应该是资源管理器的域,但流式支持会影响它。
  • glIsVertexArray 函数及其类似物可能会有用。

正在排序

VAO 并不是您在渲染场景时需要更改的唯一资源。您还需要更改纹理、着色器甚至帧缓冲区 减少状态变化次数的常用方法是按 属性.

对可显示对象进行排序

例如,您很可能只使用一个着色器渲染给定的网格。这就是为什么首先你可以按着色器对所有网格进行排序,这样你就可以最小化每个着色器的着色器数量 changes.Then(即在给定着色器的网格列表中)你可以按 VAO 对网格进行排序以减少 VAO 更改的数量可行的最小值。按质地分类?如果您需要按纹理对对象进行排序以及在何处执行此操作,则取决于您的应用程序。

结论

总而言之,如果您正在编写游戏引擎,您无论如何都需要一个资源管理器。如果您为 VAO 编写一个快速而肮脏的部分解决方案,那么您将面临完全相同的问题和处理纹理、额外的帧缓冲区和许多其他对象的问题,因此最好一次实现一个好的资源管理器。

开始的有用文章:

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/

更改 VAO 并不 昂贵。确切的数字显然高度依赖于硬件和平台。但只是为了给您一个大概的概念,几年前我在笔记本电脑上测得的 VAO switches/second 的数字只有几百万。假设您的机器可以切换 600 万次 VAO times/second。如果您想以该速率达到 60 fps,您可以每帧切换 VAO 100,000 次。

现在,您当然不想用所有 CPU 时间来切换 VAO。您的应用程序将有许多其他状态需要更改,您必须处理自己的应用程序逻辑,理想情况下您不希望将整体 CPU 负载保持得尽可能低。所以我不想接近上面的数字。尽管如此,每帧切换 VAO 1000 次在合理的高性能下应该不是问题 computer/device。

它确实可以与您通常在绘制调用之间进行的其他状态更改相媲美。您总是希望最小化它们(以及绘制调用本身的数量)。但就状态变化而言,绑定不同的 VAO 通常是相对便宜的。

如果您有共享相同几何形状的对象,例如示例中的树木,您当然不应该拥有相同数据的多个副本。那只是常识,甚至与图形没有太大关系。浪费内存当然是不可取的。即使您没有陷入内存紧缩的困境,拥有相同数据的多个副本仍然会损害性能,因为它可能会降低您的缓存命中率。

如何设计您的游戏以使其发挥最佳效果对于这种格式来说是一个比较宽泛的问题。好吧,我从来没有写过一个严肃的游戏,所以无论如何我都没有资格给你建议。我的第一直觉是拥有一组定义不同形状的 classes。例如,您可以有一个 TreeShape 拥有树的几何图形(VAO 和 VBO)。每个 kind 角色都一样。然后,如果你的场景包含一堆树,你有 Tree class 描述特定的树,其中实例可能只包含有关树的 position/size 的信息,但它们都共享对相同 TreeShape 的引用。因此所有树通过使用相同的 TreeShape 共享相同的 VAO/VBO,并且每个特定的 Tree 只包含每个树实例实际不同的信息。