2022N.GAME网易游戏开发者峰会于「4月18日-4月21日」举办,在技术驱动场,网易互娱引擎部技术专家许飞分享了服务器动画性能优化的相关干货。
以下为许飞的分享内容,为方便阅读,有部分删减与调整:
01 服务器动画的意义与现状
首先进入第一部分。很多有经验的开发者可能会有疑问:服务器需要跑动画吗?这是一个好问题,因为现在大多数游戏的服务器都没有跑动画。在传统观点中,动画和渲染特效一样属于表现层次,只要客户端看就可以了。
有限的几种需要动画参与的逻辑,比如打击部位的判定,服务器不跑但客户端还是有动画。我们让客户端判断,然后把结果发送给服务端,也能实现一样的效果,这是以前常见的做法。
需要提出的是,这一方法需要建立在网络必须可信任的前提下,但实际的网络环境并非如此——Peter Steiner在1993年发布于纽约客上的一副图画,被认为揭示了互联网环境的复杂性,它给游戏开发者造成了很大的挑战。
例如在竞技游戏中,外挂可能会对游戏的公平性造成毁灭性打击。反外挂的一个有效方法叫做服务器权威,简单介绍下思路:外挂会通过劫持游戏客户端来实现一些非法操作,但服务器所在的机房经过严密保护,一般很难被劫持。
如果我们把关键逻辑放在服务器上,仅仅把客户端作为指令输入者,就可以防止大部分外挂的操作。那为什么现在大多数游戏没有选择这种做法?这涉及到很多现实问题,例如游戏各种不同系统的开销,包括数据量的大小、更新频率的高低等。
我们可以看到,很早期的服务器其实只会保存等级之类的信息。发展一段时间之后,服务器就可以保存装备、技能等信息。而在动画方面,它需要的数据量和更新频率都很庞大,这对算力的要求十分高。
简单来说,如果我们在没有优化的情况下把动画从客户端挪到服务器,会导致服务器直接跑不起来。基于这样尴尬的现实,很多游戏都没有在服务器开启动画。
那随着技术的发展,是否存在让服务器搭载动画变成可能呢?我们收集了一段时间来服务器每条线程的成本,其中2007年、2016年和2020年的比较典型,同时这三年也诞生出三款典型的射击游戏。
从2007年到2016年,我们服务器单线程的成本大概降到了原来的1/3,2016年到2020年的成本更是降到了原来的1/2。2007年《穿越火线》发布,它的服务器几乎没有跑任何动画相关的东西,2016年的《守望先锋》跑了一部分。而2020年发布的《瓦罗兰特》,它的服务器是完全跑动画的。
什么意思呢?它会完全计算角色在服务器上的状态。此前,该款游戏的主程在分享中也明确说到,他们这样做就是为了反外挂。因为服务器只有有了非常全面的动画信息,判定受击时才不至于被客户端的外挂所欺骗。
我们也相信,随着技术发展,服务器动画的逻辑执行程度会越来越高。
02 服务器动画常见优化方向与方案
既然要在服务器跑动画,就需要优化它的动画开销。其组成部分包括条件更新、状态更新和姿态更新。一个动画系统其实可以理解为:这个系统接收外界输入、更新内部状态,最后计算出模型姿态。
首先是输入部分,一般为角色速度或者角色状态——当前是释放技能还是做其他动作。动画状态,比如角色的速度变化,可能从一个静止状态变成一个跑动状态,或者说从跑到跳的状态变化。最后再由这些状态计算出角色姿态,姿态就是美术K帧计算出角色最终的样子。
根据数据量和更新频率两方面计算,这三部分中开销最大的是姿态部分。因为每个角色的骨骼都有朝向、旋转和位置等众多属性,并且几乎每帧都在变化。
目前,业界常见的优化方式也正是针对这一块进行的。其中最简单最直接了当的即LOD,它主要减少了动画的数据量。例如去除对服务器判断受击没有任何作用的骨骼,一般可以减少20%-30%的开销。
除了减少数据量之外,我们还可以减少数据的更新频率。基于事件的姿态更新就是在减少姿态计算频率,例如《瓦罗兰特》就采用了这一技术。
简单来说,只有在角色被击中的瞬间,才会计算模型姿态,这种优化极大地降低了姿态更新频率,可以把开销从84%直接降到9%。做到这一步,你就可以在服务器上来跑动画了。可能还会有一定开销,但不至于完全跑不起来。
03 如何优化复杂的动画状态机
把姿态更新的开销降下来后,亟需解决的则是动画状态的更新。如果一个角色有很多复杂的动画逻辑,状态机情况就会非常复杂,其开销甚至会超过11%。接下来,我们将着重讲述如何优化复杂的状态机。
我们先看一下状态机是什么样子。以UE为例,其中有走、跑、跳等等状态。例如从起跳到落地的状态过程,大概可分成三部分:第一部分,Find_Transitions,角色从当前状态来找一个可能的跳转条件;第二部分,如果这个条件为真,执行跳转;第三部分,两种状态之间可能会有的一些过渡。
我们再看看如何优化。首先是优化状态过渡,以上述Locomotion为例,至少有两种过渡模式:
一种是角色上一个状态权重逐渐降低,下一个状态权重逐渐升高,其权重就会出现一个交叉,我们称之为Cross Fade。在此过渡过程中,它的两个状态权重都不为0,所以必须更新这两种状态。另外,如果两个状态中间又嵌套了别的状态机,也一定都要更新;
第二种方式,有的引擎称它为Immediate模式或者Inertialization模式。简单解释下:假如角色还在空中跳,下一刻要落地,该怎么办?我把空中跳的状态拍个快照,直接不再更新它,接下来下一个状态的权重逐渐从0升到1。通过这种方式,我们只需要更新下一个状态。
基于这些优化,状态更新的开销大概能降低10%左右。那在寻找一个可能跳转的条件情况下,我们又该怎样优化?还是以从空中到落地这段时间为例,“如何决定下一步是否需要着陆”这个条件有两种写法:
第一种是直接接受了一个Bool值;第二种通过大于小于表达式判断角色速度是否发生变化,再来决定是否跳转。根据UE官方提示,这两种方法的效率大概会相差10倍。原因在于,前者直接使用Bool值来判断,会编译成本地代码;后者编译的是虚拟机代码,经过蓝图虚拟机来执行才能判断结果。
需要提出的是,相比之下,Python、lua这样的非本地代码,性能本来就低。而过多使用非本地化语言写条件,也会给状态机更新造成很大的性能开销。当然,我们也可以通过人工将这部分判断转换成本地化代码。
而在UE中,开发者可以使用Nativization等工具自动将蓝图代码转换成本地化代码。对于动画状态机而言,这其实会带来10%左右的性能提升。那还有没有更有效的优化方法呢?这就要在状态跳转环节下功夫了。
通过进一步分析,我们发现跳转条件也分为两类:一类是依赖玩家输入;另一类是依赖动画播放进度。我们可以优化前者的更新频率,因为玩家输入频率不高,可以直接省掉更新。具体可以通过在UE蓝图中进行人工标注标明可优化的跳转信息。
在蓝图编译时,利用这些标注会更容易插入优化代码,这样最后生成的代码就是优化后的代码。经过测试,大概会有70%的提升。
04 服务器动画展望
总而言之,虽然现在真正应用服务器动画的游戏并不多,但按照当前的发展趋势,这会是一个比较有前景的领域。它可以提供更公平的联机环境、实现更细致的交互。现在行业所提出的云游戏、元宇宙等,很难想象会没有动画,这也是服务器性能优化当下及未来的阵地。
网易游戏2022N.GAME峰会已结束,点击下方链接即可查看回放。