有多少VAO和VBO

时间:2015-04-08 13:01:30

标签: opengl language-agnostic vbo vao

据我所知:VAO代表某种状态。如果我绑定一个VAO,添加一些VBO和元素缓冲区用于指示和东西我可以保存我想要绘制和激活的对象的某个状态,并在我想要渲染内容时稍后在路上绘制它们。正确?

因此,VBO保存实际数据,而VAO只是一个“包装器对象”,它保存指向我为其定义的所有缓冲区的指针?

更改VAO的成本很高(改变VBO也是如此?!)。目前我加载Meshes并将它们组合到模型中。每个模型使用它自己的VAO并且有一个VBO(带顶点)和一个带有索引的元素缓冲区。

现在我理解这一点,这是废话,因为我的世界中的每个对象(有一个模型)都使用它自己的VAO。对于我这个世界中的~30个对象来说不是问题,但我想要做到这一点,未来可能会有数百或数千个对象,然后性能会大幅下降。

因此,就其模型而言,许多物体“相同”。我的意思是,例如,如果你在世界上有某种树型,你可能会多次使用相同的模型,只是在不同的位置。

现在我该如何解决这个问题?我应该手动跟踪我的VAO,如:(伪代码跟随!)

treesVAOId = 1;
rabbitsVAOId = 2;

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

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

我觉得有更多的部分缺失...就像实例化和再次使用(或多或少)相同的数据为不同的目的有效。

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

2 个答案:

答案 0 :(得分:8)

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

班级等级

以下是类层次结构的可能方法:

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

一组网格(可能只包含一个网格)代表一个模型。最小模型类应包括VAO(s)的句柄和几何变换信息(旋转,平移,无论你想要什么)。为什么不是每个模型严格一个网格?有时您可能需要将一个变换应用于一组网格,其中的一个网格具有它自己的模型 - 局部变换。例如,这样的构图可以用于一种骨架动画,或者仅用于从可能的武器库中获取具有任意武器的角色。此外,您可以将这些模型组合在一起,使用相同的界面获得更复杂的模型,这样您就可以获得scene graph的相似性。无论如何,将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这项工作引起了所有这些解释。它检查之前是否加载了这样的网格,如果是,它只返回包含该网格的VAO的句柄。否则,它会创建新的VAO对象,将请求的数据加载到其中,并将句柄返回给新创建的对象。此对象的所有后续请求都不会导致新的VAO分配。

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

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

  • 您是否会使用单例或者您决定使用全局变量 原因是什么?
  • 如果您决定使用单身,也许您希望有其他一些 一些有限的私人非单身资源管理者 资源?然后考虑将单例设计为包装模板 class,使这样的代码成为可能:
   
    ResourceHandle h1 = Singleton<ResourceMan>::instance->getResource("foo");
    ResourceMan myPrivateManager;
    ResourceHandle h2 = myPrivateManager.getResource("bar");
  • 您是否可以通过一名综合经理管理所有类型的资源 或者为每种资源类型使用特殊的经理类?第二种方法更好。开发第二种方法的想法,为您编写代码,为您编写代码!使用模板资源管理器类和专用方法的小子集。只需为每种资源类型专门设计一种资源创建方法,并且不要触及所有其他资源管理代码!
  • 考虑资源生命周期。当一个特定的VAO应该被销毁?考虑实施参考计数器和\或借用参考。
  • 缓存?将主机内存加载到设备(显卡)后立即删除数据或保留一段时间?多久了?
  • streaming怎么样?它不应该是资源管理器的域,但流媒体支持可能会影响它。
  • glIsVertexArray功能及其类似物可能很有用。

<强>分拣

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

例如,您很可能只使用一个着色器渲染给定的网格物体。这就是为什么首先你可以通过着色器对所有网格进行排序,以便最小化着色器更改的数量。然后对于每个着色器(即在给定着色器的网格列表中),您可以通过VAO对网格进行排序以减少VAO的数量改变到可行的最小值。按纹理排序?如果您需要按纹理对对象进行排序以及在何处进行排序,这取决于您的应用程序。

<强>结论

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

有用的文章开头:

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 :(得分:2)

更改VAO 非常昂贵。确切的数字显然高度依赖于硬件和平台。但是为了给你一个粗略的想法,几年前我在笔记本电脑上测量了数百万的VAO开关/秒。让我们假设您的机器可以将VAO切换到600万次/秒。如果你想以这个速率达到60 fps,你可以每帧切换VAO 100,000次。

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

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

如果您拥有共享相同几何体的对象(例如示例中的树),则您当然不应拥有相同数据的多个副本。这只是常识,甚至与图形无关。浪费记忆当然是不可取的。即使你没有处于绝望的内存紧缩状态,拥有相同数据的多个副本仍然会影响性能,因为它可能会降低缓存命中率。

如何设计您的游戏以使其最佳工作对于此格式来说是一个广泛的问题。好吧,我从来没有写过一个严肃的游戏,所以无论如何我都没有理由给你建议。我的第一直觉是拥有一组定义不同形状的类。例如,您可以拥有一个拥有树的几何(VAO和VBO)的TreeShape。每个种类的角色都是一样的。然后,如果您的场景包含一堆树,则您具有描述特定树的Tree类,其中实例可能只包含有关树的位置/大小的信息,但它们都共享对同一树的引用{ {1}}。因此,所有树都使用相同的TreeShape共享相同的VAO / VBO,并且每个特定的TreeShape仅包含每个树实例实际不同的信息。