高级性能优化
简介
良好的性能对很多游戏的成功至关重要。本章节讲述的是引擎底层一些高级的优化模块。
渲染模块优化
视锥体剔除(Frustum Culling)
只禁用相机视野外的对象渲染,不禁用视野中被遮挡的任何物体的渲染。
IdeaXR会自动执行视锥体剔除(Frustum Culling),以防止渲染视窗外的物体。这对于发生在小范围内的项目来说效果很好,然而在较大的场景关卡中,事情很快就会变得很麻烦。
遮挡剔除(Occlusion Culling)
比如走在一个小镇上,你可能只能看到你所在的街道上的几栋建筑,以及天空和几只飞过头顶的鸟。然而对渲染器而言,你仍然可以看到整个小镇。它不会只渲染你前面的建筑,它会渲染那后面的街道,与那条街上的人,那后面的建筑。你很快就会遇到这样的情况: 你试图渲染比可见的东西多10倍或100倍的东西。
我们有可能减少渲染量的一个方法是利用遮挡。例如,在我们的城市街道场景中,你可能会事先计算出,从街道A只能看到另外两条街道,B和C。而D到Z的街道是隐藏的。为了利用遮挡的优势,你所要做的就是计算出什么时候你的观察者在街道A中,然后你就可以隐藏其他街道。这是一个手动版本的所谓 "潜在可见集"。这是一种非常强大的技术,可以加快渲染速度。你也可以用它将物理或AI限制在局部区域,并加快这些以及渲染的速度。
层次细节(LOD)
在某些情况下,特别是在远处,用简单的版本代替复杂的几何图形可能是个好主意。最终用户可能看不出什么区别。考虑看看远处的大量树木。有几种策略可以替换不同距离的模型。你可以使用较低的多边形模型,或者使用透明度来模拟更复杂的几何体。或者不只渲染一棵树,而是将一些树作为一组来渲染。如果你能看到一个区域,但在项目中不能实际接近它,就可以使用这个方法。
你可以通过预先渲染对象的不同角度的视图来制作替代品。或者你甚至可以更进一步,周期性地将一个物体的视图重新渲染到一个纹理上,作为一个替代品使用。在远处,你需要将观察者移动相当长的距离,视角才会发生显著变化。这个工作可能会比较复杂,但可能也是值得的,这取决于你正在制作的项目类型以及复杂程度。
使用多网格(MultiMesh)
如果必须在同一地点或附近绘制多个相同的对象,请尝试使用多网格(MultiMesh)来代替。多网格(MultiMesh)允许以很小的性能代价来绘制成千上万的对象,这使得它非常适合用于绘制羊群,草地,粒子以及其他任何有成千上万相同对象的地方。
设置节点:
基本设置需要三个节点:MultiMeshInstance 节点和两个 MeshInstance 节点。
一个节点用作目标,即要在其上放置多个网格。在树示例中,这将是景观。
另一个节点用作源,即你要复制的网格。在树的情况下,这将是树本身。
选择 MultiMeshInstance 节点并查看工具栏,你应该会在视口的右上角看到一个名为MultiMesh的额外按钮。
单击它并在下拉菜单中选择填充表面。将弹出一个名为填充MultiMesh的新选项。
以下是选项的说明。
- 目标表面: 用作放置源网格副本的目标曲面的网格。
- 源网格: 要在目标曲面上复制的网格。
- 网格上方向轴: 用作源网格的向上轴的轴。
- 随机旋转: 围绕源网格的上轴随机旋转。
- 随机倾斜: 随机化源网格的整体旋转。
- 随机缩放: 随机化源网格的比例。
- 缩放: 将放置在目标表面上的源网格的比例。
- 数量: 放置在目标表面上的网格实例数量。
选择目标表面为“平面”节点。源网格为“网格实例”节点。根据你的喜好调整其他参数。按填充,源网格的多个副本将放置在目标网格上。如果你对结果满意,你可以删除用作源网格的网格实例。最总结果如下:
你可以看到,场景中多出了很多和源网格相同形状的节点。
烘焙照明
对物体进行照明是最昂贵的渲染操作之一。实时光照,阴影(尤其是很多灯光)和GI都特别昂贵。对于低功率的移动设备来说,它们可能根本无法处理。
考虑使用烘焙照明,尤其是移动端,但烘焙照明有一个缺点,那就是它不是动态的,这需要在效果和性能上做出权衡。一般来说,如果几个灯光需要影响一个场景,最好使用烘焙光照贴图。烘焙也可以通过增加间接光的反弹来提高场景的质量。
用户界面组件的优化
资源预加载
资源预加载是另一个性能优化技术,我们可以使用该技术来预先告知引擎某些资源可能在将来会被使用到。预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。
顾名思义,预加载就是在全部加载之前,对一些主要内容进行加载,以提供给用户更好的体验,减少等待的时间,在IdeaXR中可以使用preload()方法对资源,场景进行预加载。
物理引擎组件的优化
在某些情况下,物理学最终会成为一个瓶颈。尤其是在复杂的世界和大量物理对象的情况下,更是如此。
以下是一些加速物理的技巧:
尝试使用简化版本的渲染几何图形来处理碰撞形状。通常情况下,这对终端用户来说并不明显,但可以大大提高性能。
试着从物理学中移除物体,当它们不在视野中/在当前区域之外时,或者重新使用物理学对象(例如,也许你允许每个区域有8个怪物,并重新使用这些怪物)。
物理的另一个关键方面是物理刷新率(tick rate)。在一些项目工程中,你可以大大降低刷新率,比如说,你可以不用每秒更新物理60次,而只需每秒更新30次甚至20次。这样可以大大降低CPU的负载。
改变物理刷新率的缺点是,当物理更新速率与每秒渲染的帧数不匹配时,你可能会出现运动抖动。另外,降低物理刷新率会增加输入滞后。建议在大多数 以玩家实时移动为特色的项目中,坚持使用默认的物理刷新率(60 Hz)。
解决抖动的方法是使用固定时间步长插值,这涉及到平滑多个帧的渲染位置和旋转,以匹配物理。从性能上来说,插值是一个非常廉价的操作。它的速度快了好几个数量级,所以这在减少抖动的同时也带来了部分显著的性能提升。
内存管理
内存频繁申请及占用过高的规避
频繁地内存申请与释放在很大程度上影响着程序的运行效率,使程序极可能出错,同时给程序造成巨大的负担,因此需要尽量避免。
避免内存频繁申请与释放的方法:静态申请、对象池、减少字符串拼接。
内存泄漏的防止
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因,程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
它具有隐蔽性、积累性的特征,如果持续泄漏将因内存占用过大而导致程序崩溃。
内存泄漏也主要分为代码的泄漏和资源的泄漏。
防止内存泄漏的主要方法包括:
- 代码中的泄漏:对于没有使用的对象进行引用的清除,对于不再需要的对象及时将其删除。如果一个类继承自 Reference,那么实例将在不再使用时被释放。默认情况下,所有未定义继承的类都扩展Reference。其他情况可以手动调用instance.free()或者queue_free()进行释放。
- 资源中的泄漏:资源不用时没有及时卸载导致,尽量在加载环节来处理资源删除的操作。
使用工具脚本实现自动化重复操作
IdeaXR中可以实现在编辑器中运行的脚本,在脚本开头添加tool关键字即可,有效利用这一特性可以实现一些重复操作的自动化。比如说通过编写工具脚本实现自动生成材质,自动附加贴图等。
以自动生成pbr材质为例,实现思路大致为:
- 根据指定的贴图文件夹,遍历第一层子文件夹;
- 根据文件夹名称获取并存储相应参数值,进入子文件夹后遍历所有贴图文件;
- 以贴图去除后缀的名称新建材质,然后根据贴图名称的后缀,对相对应的材质赋贴图,并根据从文件夹名称处获取的参数值给材质相应参数赋值 ;
- 所有材质生成完后,再遍历材质文件夹,在场景中按照布局创建材质预览网格实例。