如何在充满皮肤的场景中使用实例化来优化性能?

时间:2018-12-27 15:48:10

标签: javascript three.js

我正在开发基于Three.js的网络塔防游戏。现在,我一直坚持优化性能。

我的游戏加载了两个GLTF模型作为敌人和塔楼,它们都具有蒙皮网格。当玩家创建塔楼或游戏产生敌人时,我使用THREE.AnimationUtils.clone克隆加载的模型。然后,将这个克隆的模型添加到场景中。对于动画,我使用THREE.AnimationObjectGroup为所有敌人设置动画。

在性能测试中,结果是平均每帧370个抽奖呼叫,场景中有45个塔楼和70个敌人,这是游戏的噩梦。

我认为也许使用实例化可以优化性能,因为每个塔和敌人在每个帧中共享相同的模型和状态,但是只有旋转和位置不同。但是,在我研究了使用实例化的一些示例之后,再也没有使用带蒙皮网格的实例化的示例了。 (有一个讨论here,但此处的结果未提及任何实例化方法。)

是否有可能通过three.js或针对这种情况的其他解决方案来做到这一点?

更新

经过更多研究后,我发现一些概念可能可以帮助我实现带蒙皮网格的实例化。

概念

原始帖子here在Unity中通过实例化实现了皮肤网格。 (它是用中文写的,下面我翻译了主要概念。)

在加载蒙皮网格后,它具有所有顶点的初始状态(为清楚起见,以下每个初始顶点均表示为PLT)。在动画的任何帧中,PLT的最终位置(表示为PI)等于一系列矩阵乘法PI = (M_rootlocal * ... * M_2_3 * M_1_2 * M_bind_1 * PLT) + (M_rootlocal * ... * M_2_3 * M_1_2 * M_bind_2 * PLT) + (...)

  • M_bind_1是骨骼1的骨骼绑定矩阵。
  • M_m_n意味着在骨骼m的坐标系下,骨骼n相对于其初始状态的转换。

为简化起见,请使用M_f_i = M_rootlocal * ... * M_2_3 * M_1_2 * M_bind_i来表示转换。 M_f_i表示在帧i乘法后骨骼f的骨骼绑定矩阵,因此PI = (M_f_1 * PLT) + (M_f_2 * PLT) + (...)一旦知道M_f_i,我们就可以计算每个顶点的位置在f帧中。

可以通过传递M_f_i并将其包装为纹理,在GPU内部完成上述过程。 (在蒙皮网格需要动画十个动画且骨骼较少的前提下,所需内存约为0.75Mb。)最后,我们可以将不同的帧号f传递给每个实例,以在一次绘制调用中用动画渲染蒙皮网格。

使用three.js实现

我还没有构建示例代码,因为我不知道该概念是否可以在WebGL上运行(我也不熟悉GLSL),但是我认为使用three.js来实现它的方法可以完成以下操作。

  1. 按照here获得M_f_i
  2. 使用THREE.InstancedBufferGeometryTHREE.RawShaderMaterial
    • uniforms中传递初始几何,M_f_i和纹理。
    • vertexShader流程PI = (M_f_1 * PLT) + (M_f_2 * PLT) + (...)中。
    • fragmentShader中,处理纹理(我不知道该怎么做)。
  3. 使用f传递THREE.InstancedBufferAttribute和其他实例属性。

问题

  • M_f在哪里,以及如何在第1步中由THREE.AnimationClip获取它?
  • 如何索引每个PLT(几何中的顶点)?
  • 如何处理纹理?
  • 如何处理层次结构Object3DObject3D.children同时具有THREE.MeshTHREE.SkinnedMesh)?

我需要有人告诉我这个想法是否适用于three.js,以及如何解决上述问题。

Screenshot of the performance test

Analysis using Chrome dev tool

1 个答案:

答案 0 :(得分:0)

我记得过去有几何或网格 Merge 功能确实对我有帮助。我建议您朝那个方向搜索。

它的用法有很多对应物,例如失去使用的每个3d对象的个性,但在可能的情况下,应将其用于诸如环境对象之类的静态元素,在其他情况下,如果您的单个对象/塔是基于许多单个3d对象的方式,使它们变成一个...

根据我的经验(视每台计算机和3d视口的大小而定,可能会有很大不同),最后您在可见的摄像头区域前不应拥有超过50个(简单的)3d对象,并可以重复使用所有材质,几何形状,网格...否则,一旦您在游戏中发生有趣的事情,您最终的性能就会很差。

希望有帮助!