继续上两篇的博客
前两篇博客分别介绍了unity中物理引擎的大部分物理特性和部分优化技术用于提升游戏的物理性能,接下来继续玩完善用于提升游戏物理性能的优化技术;
最小化涉嫌发射和边界体积检查
所有的射线投射方法都很有用,但是他们相对于其他地方来说消耗太大,特别是CapsileCast 和SphereCast方法,应该避免在Update中定期的调用这些方法,并且只在脚本代码的关键部分使用它们;
如果在场景中使用持续的线、涉嫌或者区域效果碰撞区域,(类似于上篇博客中说的安全激光、持续燃烧的火焰、光束武器等),并且对象保持相对静止,那么使用简单的触发体积就可能会更好的模拟他们。
如果不能进行此类的替换,且确实需要使用这些方法进行持久的投射检查,那么应该使用层遮罩老最小化每个涉嫌投射的处理两,如果使用Physics.RayCastAll()方法,这一点尤其重要,如下是一种射线优化不佳的使用方法;
void PerformRaycast()
{
RaycastHit[] hits;
hits = Physics.RaycastAll(transform.position, transform.forward, 100.0f);
for(int i = 0;i <hits.Length; i++)
{
RaycastHit hit = hits[i];
EnemyComponent e = hit.transform.GetComponent<EnemyComponent>();
if (e.GetType() == EnemyType.orc)
{
e.DealDamage(10);
}
}
}
可以看出来,示例对射线路径上的每个对象进行射线碰撞数据收集,但是仅对持有固定EnemyComponent组件的对象处理该效果,因此物理引擎所处理的计算量超过实际所需要的工作;
一种更好的方法是使用RaycastAll的另一个重载版本,它接受LayerMask值作为参数,该参数为射线过滤碰撞,其方式与碰撞矩阵一样,仅对给定层对象进行测试,如下:
void PerformRaycast()
{
RaycastHit[] hits;
hits = Physics.RaycastAll(transform.position, transform.forward, 100.0f, _layerMask);
for(int i = 0;i <hits.Length; i++)
{
RaycastHit hit = hits[i];
EnemyComponent e = hit.transform.GetComponent<EnemyComponent>();
if (e.GetType() == EnemyType.orc)
{
e.DealDamage(10);
}
}
}
但是这样的优化对于Physics.RayCastHit,因为这个版本的函数,只为射线提供与之碰撞的第一个对象提供光线碰撞信息;
避免使用复杂的网格碰撞器
使用更简单的基本体
为了提高碰撞检测效率,对于以下碰撞器,球体、胶囊体、立方体、凸网格碰撞器、凹面网格碰撞器,由于碰撞总是涉及到成对的对象,解决碰撞所需要的工作量取决于两个对象的复杂性,如果监测两个基本物体之间的碰撞可以简化为一组相对简单的数学公式,但是如果监测一对凸网格碰撞器进行碰撞监测,是一个更复杂的方程,这与基本物体之间的碰撞监测想比较多了一个数量级,而凹面碰撞器则更加复杂,没有办法简化为一个简单的公式,需要在两个网格上的每对三角形之间解决碰撞检查,当处理不同形状组之间的碰撞时,所涉及到的工作量也是类似的,例如基本体碰撞器与凹面网格碰撞器 之间的碰撞要比两个基本体碰撞器之间的碰撞慢,但是要比两个凹面网格碰撞器之间快;
一般来说一个复杂物体的碰撞器是可以使用多个简单碰撞器来形成的,这通常也会比使用单一的复杂网格消耗更低;
使用更简单的网格碰撞器
上面的是简化之后的网格碰撞器
而这时原始的网格碰撞器,也就是说,分配给网格碰撞器的网格不一定需要匹配相同对象的图形表示,也可以使用更简单的网格,这种方式将渲染的网格简化为具有较低多边形的凸多边形,将大大减少确定边界体积与其他碰撞器重叠所需要的开销;
使物理对象休眠
物理引擎的休眠特性会给游戏带来一些问题,首先,并不是所有的开发人员都能意识到,许多刚体在应用程序的大部分生命周期中都是以休眠状态存在,这往往会使开发人员错误的认为,可以在游戏中增加一倍的刚体胡亮,而总成本只会增加一倍,这种假设其实使很不现实的,碰撞频率和活动物体的总累计时间更有可能以指数形式增加,每次在模拟中引入新的物理对象,都会导致意外的成本,所以说如果决定要增加场景的物理复杂度的时候,要时刻记住这一点;
其次,在运行时修改Rigibody组件的任何属性,例如mass、drag以及use gravity会重新唤醒对象,如果经常改变这些值,他们将比正常情况更加活跃;
还有一个需要注意的是,休眠的物理对象有产生岛屿效应的危险,当大量的刚体互相接触,并且随着系统动能的降低,会逐渐休眠变成岛屿,然而由于他们依然相互接触,因此,一旦这些对象会唤醒,就会产生链式反应,唤醒附近的所有刚体,由于唤醒的时候,需要这些物体重新进入物理模拟,因此会产生链式反应,产生较大的CPU峰值,甚至由于对象太近,在对象再次进入休眠之前,会有大量潜在的碰撞需要处理;
因此,最好降低场景的复杂性,以避免该情况,如果无法做到这一点,就需要修找方法来监测岛屿的形成,然后战略性的销毁掉一些,以防止形成大量的岛屿,但是,在所有刚体之间进行定期的距离比较并不是以想很低消耗的任务,物理引擎本身在广泛剔除阶段执行了这样的检查,但是Unity没有通过物理引擎API公开这些数据,实际上任何解决这个问题的方法都取决于游戏的设计方式,例如,一个游戏要求玩家将大量物理物体移动到某个区域(如,将羊感到围栏)将可以选择在玩家将羊的碰撞器移动到位置之后,立即移除,将物体锁定到其最终目的地,减轻物理引擎的工作量,并防止形成群岛问题;
最后说一下,休眠对象有好有坏,它们可以节省大量的处理能力,但是如果有太多的休眠对象同时被唤醒,或者模拟过于繁忙,无法让足够多的对象进入休眠状态,那么可能会在游戏中导致一些不幸的性能成本,应该尽可能的避免这些情况,使对象尽可能的进入休眠状态,避免将他们分成更大的集群;
修改处理器迭代次数
在物理引擎中,使用关节、弹簧和其他方法,将刚体连接到一起是相当复杂的模拟,由于将对象连接在一起而产生相互以来的交互作用(内部表示为运动约束),系统必须经常尝试求解必要的数学方程,当物体链的任何的一部分发生变化的时候,需要使用这种多迭代方法来计算精确的结果;
因此,必须在限制处理器解决特定情况的最大尝试次数和限制所得结果的准确性之间找到平衡,也就是说,处理器不应该在一次碰撞上花费太多时间,因为引擎,需要在一次迭代中完成很多其他任务;
处理器允许尝试的最大迭代次数:Edit->projectsettings->Physics->default solver itertions
大多数情况,这个迭代次数的值是完全可以接受的,但是如果场景中有大量复杂的关节系统,就可能希望增加这个值;
需要知道的是,这个值只对新生成的刚体起作用,如果在运行是通过Physics.DefaultSolverIterations属性修改这个值,不会对已经存在的刚体产生影响,如果有必要,可以在刚体构造之后通过Rigidbody.solverIterations属性修改他们的Slover Iteration Count;
如果在游戏中使用复杂的关节对象,经常会遇到不稳定、违反物理规则的情况,那么应该考虑增加Slover Iteration Count,如果关节物体吸收了太多的能量,处理器在要求放弃之前无法迭代得到合理的结果,这个时候需要增大这个值
增大这个值将使处理器有更多的机会在基于关节对象碰撞期间计算合理的速度;
修改此值的方式:运行时通过Physics.defaultSolverVelocityIterations、Rigidbody.solverVelocityIterations
确定何时使用物理
实际上,提高性能的最明显的方式就是尽量避免使用它,对于游戏中的所有可移动的物体,应该花点时间去思考一下,是否真的有必要去使用物理引擎,如果没有,应该去修找机会用更简单、消耗更低的东西来取代它;
假设用物理方法去监测玩家是否掉入了一个杀戮区,这个区域比正常的区域要低很多,那么就可以简单的判断玩家的Y值是否低于某个值来进行判定;
例如实现一个库存系统,当玩家点击拾取对象时,这些对象中的每一个都可以与玩家的位置进行比较,以确定那个对象最接近,那么就可以考虑一下用Physics.OverLapSphere()调用替换所有脚本,在按下键时获取附近的对象,然后从结果中找出最近的拾取对象;
。。。
确保场景中删除不必要的物理工作,或者使用物理替换通过脚本代码执行时代价昂贵的行为,将能够很好的提高性能;
个人学习记录,借鉴unity性能优化