我正在开发基于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
传递给每个实例,以在一次绘制调用中用动画渲染蒙皮网格。
我还没有构建示例代码,因为我不知道该概念是否可以在WebGL上运行(我也不熟悉GLSL),但是我认为使用three.js来实现它的方法可以完成以下操作。
M_f_i
。THREE.InstancedBufferGeometry
和THREE.RawShaderMaterial
。
uniforms
中传递初始几何,M_f_i
和纹理。vertexShader
流程PI = (M_f_1 * PLT) + (M_f_2 * PLT) + (...)
中。fragmentShader
中,处理纹理(我不知道该怎么做)。f
传递THREE.InstancedBufferAttribute
和其他实例属性。M_f
在哪里,以及如何在第1步中由THREE.AnimationClip
获取它?PLT
(几何中的顶点)?Object3D
(Object3D.children
同时具有THREE.Mesh
和THREE.SkinnedMesh
)?我需要有人告诉我这个想法是否适用于three.js,以及如何解决上述问题。
答案 0 :(得分:0)
我记得过去有几何或网格 Merge 功能确实对我有帮助。我建议您朝那个方向搜索。
它的用法有很多对应物,例如失去使用的每个3d对象的个性,但在可能的情况下,应将其用于诸如环境对象之类的静态元素,在其他情况下,如果您的单个对象/塔是基于许多单个3d对象的方式,使它们变成一个...
根据我的经验(视每台计算机和3d视口的大小而定,可能会有很大不同),最后您在可见的摄像头区域前不应拥有超过50个(简单的)3d对象,并可以重复使用所有材质,几何形状,网格...否则,一旦您在游戏中发生有趣的事情,您最终的性能就会很差。
希望有帮助!