下图这是什么动画?

最近迷上的一首好歌,周深的《大鱼》,分享给大家!

今天要介绍的是骨骼动画的基本原理和一些常用优化手段,本文也不涉及任何骨骼动画API的使用, 只涉及底层原理机制。

在写这篇文章的时候,为了保证讲的内容并不是纸上谈兵,我专门花时间造了个轮子: spine-player

这次造的轮子还是跟以前一样,不使用任何渲染引擎(Unity,Cocos), 使用Typescript编写骨骼动画的核心代码, 渲染层通过自己封装webgl的接口进行渲染。

Spine骨骼动画是2D的骨骼动画软件,   本文里提到的骨骼动画原理, 不仅仅适用于像Spine这样的2D骨骼,也同样适用于3D骨骼, 主要的区别仅仅是顶点坐标多了个z维度, mesh顶点数量多出好多倍而已。

为什么我们需要骨骼动画

如果不使用骨骼动画,那么美术同学当然也可以通过mesh deform(网格变形)来做出这个动画, 但是一旦人的动作需要调整,所有的mesh都要调整一遍, 我估计美术同学会骂街。

而且这种不用骨骼做出来的动画, 在存储动画数据的时候,每个顶点都有关键帧的信息需要存储,导致存储量巨大

所以骨骼动画是应运而生, 它把这样的动画拆解成2部分:

我们先假设角色内部是由一个骨架构成的,头发,皮肤这些就是蒙在对应的骨骼上面, 当骨骼动起来的时候, 蒙在上面的皮也会对应动起来。

像图中这个妹子的骨骼可能就只有20多根, 美术调动作只需要调整这20多根骨骼的位置就可以了,存储的也是这20根骨骼的动画信息,需要存储的信息量大大减少

而对于某些局部的细节, 比如乳摇这样的效果, 可以通过多定义几根骨骼,用权重蒙皮来实现效果, 也可以用最原始的非骨骼方法,直接对这个局部mesh做deform动画。

所以, 骨骼动画和 非骨骼的mesh 变形动画都是美术需要的,2者其实是可以互相结合的。而Spine软件也支持这2种做法。

下面这个哥布林是Spine官方提供的demo, 图片中的效果是使用本文造的轮子代码播放出来的动画效果:

可以看到造的轮子实现了以下功能:

骨骼动画+权重蒙皮+mesh deform + 动态网格替换(眼睛部分)+ 多种插值方式

如果想对骨骼动画的实现原理有进一步理解,建议把仓库clone下来看代码,  因为只实现了spine的核心功能,更容易看懂。

我们先看一下像下图这样的动画是怎么拆解成骨骼动画的:

首先我们在非动画状态(也就是setup pos下)下进行骨架的创建和蒙皮的绑定

下图是我们创建好的骨架:

在骨架上蒙好皮之后是这样的:

接着我们让骨架子动起来,会看到对应的蒙皮也会一起动:

下面我们针对骨架动画 和 蒙皮实现分别进行深入阐述。

1.构成骨架的骨骼之间存在父子关系, 父骨骼动起来的时候会带动子骨骼运动

2.单根骨骼内部是使用关键帧进行插值的

下面看2个骨骼的关键帧动画例子

这个hip是骨盆骨骼,它的属性关键帧只包含了移动,也就是随着动画播放,它的xy会发生变化,变化的值使用关键帧之间插值得来。

这个torsor是躯干骨骼, 它的属性关键帧只包含了旋转(变形暂且忽略),但是因为它的父骨骼是hip骨骼,也就是随着动画播放,它不仅在做旋转,也在跟着父骨骼做上下移动。

3.不同的关键帧插值方式

关键帧插值主要包括线性插值和贝塞尔曲线插值

1)先来看最简单的插值线性插值

线性插值的公式很简单, 开始值是P1, 结束值是P2, 中间某帧的值P, 假设P1到P2变化使用了1秒时间,中间该帧处于t秒(0<=t<=1)

2)接着是三阶贝塞尔曲线插值

曲线插值是应用比较广的插值方式,它不像线性插值在开始结束的地方比较生硬, 三阶的贝塞尔曲线多了2个控制点,很方便美术去控制曲线的形状

这个求值函数应该怎么实现呢?在实际实现中常用的方法是用多根首尾相接的折线来模拟这根曲线, 我们先判断t落在哪根折线上, 在线段内部做线性插值即可。

如上图所示,我们把0到1分别均分成10个值,

然后根据三阶贝塞尔曲线公式:

然后判断t落在哪2个点坐标之间, 接着在这2红点之间线段内进行线性插值即可

5. 骨架动画的计算目标

我们计算骨架动画,最终的目的, 是要算出在某个时刻, 所有骨骼在根节点下的变换矩阵, 我们暂且称之为 世界变换矩阵

1)对所有的骨骼, 根据当前时间, 和动画关键帧, 插值得到属性(xy, rotation等)

3)从根骨骼开始, 从上到下计算所有骨骼在骨骼根节点空间上的变换矩阵(world transformation)

第1步里需要计算插值, 前面已经讲到了,不再冗述。

第2步需要计算所有骨骼的本地变换矩阵, 这个其实不难, 一般骨骼的属性有 xy, rotation, scale, shear(变形),

我们用一个4x4的矩阵来存储这个变换信息,

我们可以得到最终的本地变换矩阵:

第3步, 我们需要从根骨骼开始计算每根骨骼的最终世界变换矩阵

依次计算如下:(w表示世界矩阵, l表示本地矩阵)

计算顺序很重要,所以一开始需要对骨骼做好排序,然后依次计算即可。

到这里为止,我们已经得到了某根骨骼最终的世界变换矩阵, 接下来进入蒙皮计算。

mesh的顶点跟某根骨骼绑定, 可以想象成mesh的父节点就是这根骨骼

我们假设mesh中某个顶点相对这根骨骼的坐标是x1, y1, 那么这个顶点在骨骼空间里的最终坐标为:

我们先来考虑最简单的蒙皮, 也就是mesh只绑定在一根骨骼上的情况

如图所示, 我们有2根骨骼, 上面蒙了一层mesh, 每个顶点只绑定了一根骨骼,我们假设2根骨骼接缝处这个绿色的顶点绑定的是左边1号红色骨骼。

那么当骨骼转动如下的时候:

根据蒙皮的计算, 绿色这个顶点是跟着骨骼1走的, 所以会得到如下的效果:

这种情况下看上去就不太自然,关节处感觉凸出了一个角。

所以一般来说,在这种关节处,顶点一般会绑定多根骨骼,

像图中这种情况我们可以让绿色顶点绑定左右2根骨骼,并分别给与50%的权重W。

首先我们假设绿色顶点如果只绑定了骨骼1, 它的实际坐标会是P1,

然后我们假设绿色顶点如果只绑定了骨骼2, 它的实际坐标会是P2,

P就是最终的坐标, 如下图所示

橙色点坐标就是最终混合后的坐标。

为了让这个关节处更平滑,如果我们在这个关节处左右两边再增加一些顶点,靠左边的顶点,给与骨骼1的权重大一些,

靠右边的顶点,给与骨骼2的权重大一些,最后我们混合完所有顶点坐标后,就能得到如下比较平滑的效果:

可以看到这种情况下关节处会比较平滑不再那么生硬。

假设某个顶点绑定了 2根骨骼

下图是哥布林手上拿的木棍的mesh的蒙皮设置:

图中该点绑定了上下两根骨骼, 权重大约是一半一半, 我们把骨骼的动作幅度故意调大一点可以看到骨骼动起来后,该点坐标的变化情况:

可以看到该点由于权重混合的原因,位置始终介于2根骨骼的中间位置。

前面提到过,对于局部的mesh, 美术可以自由调整mesh的顶点位置,从而实现一些非骨骼动画,

这种mesh顶点通过本身的xy关键帧插值产生的动画叫 mesh deform

在上图中哥布林的头部的耳朵部分,主要使用了这种mesh动画来实现。

这种mesh deform公式也很简单,先对mesh顶点在时间轴根据关键帧做插值计算,  算出的值再代入蒙皮公式:

公式跟以前是一样的,只是x1, y1, x2, y2是根据动画时间做动态计算。

当骨骼正在播放某个动作到一半时,如果需要切换到另外一个动作, 如果硬切, 就会产生生硬的效果

通常我们可以使用动作融合的方式,让2个动作在某个短时间内做混合,最终过渡到第2个动作

经过上面的讲解,我们可以总结一下, 骨骼动画播放的全过程,其实就是:

这里面每一个步骤都可能有优化的空间,每种优化的手段都是根据性能瓶颈原因和优化目的来做的。

我们发现骨架动画这块, 有3步计算, 骨骼越多计算量越大, 对于那些循环播放的骨骼动画,有很多的计算总是重复在计算。

所以我们其实可以在骨骼动画初始化的时候,就把每一帧,每一个骨骼的世界变换矩阵先计算好存起来,在后面使用到的时候, 直接取相应矩阵就可以了。

这其实也是一种空间换时间的方法。

如果我们把这个计算再提前到程序运行之前,那就有点类似于离线烘焙动画了(baking animation)

计算完的世界矩阵,我们可以放到内存里,也可以序列化到磁盘上。

如果在下一步优化里需要使用到gpu skinning, 我们甚至可以把这个世界矩阵序列化到一个临时的纹理上, 然后传入着色器。

如果我们发现模型的mesh顶点数太多,做skinning消耗了太多CPU时, 我们可以考虑对这个模型做GPU Skinning

我们先把相关所有骨骼的动画数据先烘焙成一张纹理,当做uniform传入顶点着色器,

然后把每个顶点相关的骨骼索引,权重, 相对xy信息,当做顶点数据的属性传入, 这样就可以在顶点着色器里做skinning.

我们假设动画一共30帧,骨骼一共4根,每根骨骼在每一帧有一个世界矩阵要存储, 而这个矩阵,在2D里,最下面2行都是常数

我们把上面2行一共8个数值存储到上下2个相邻像素里即可(32位纹理)

30帧,4根骨骼, 可以输出宽度30, 高度8的纹理图片。

然后大家发现单通道是1个字节时,存储矩阵的浮点数可能存在精度丢失问题, 我们可以考虑把纹理改成 单通道4字节的纹理, 又或者是用4倍的像素数量来存储这个矩阵(但是这样会造成顶点着色器采样次数过多的情况)。

我们可以使用4x4的矩阵来存储这16个数值,当做顶点属性,直接传入顶点着色器就可以了。

顶点着色器VS拿到了上面baking出来的骨骼数据,又知道了蒙皮信息,那在VS里做蒙皮计算就只是几个乘法加法而已。

如果有这样的场景, 有大量的草丛(1000个)需要绘制, 单个草丛本身是一个如下的spine动画:

默认的渲染,会造成大量的drawcall和大量的带宽开销,

每个草丛之间仅仅只有transformation matrix不一样,我们可以把每个草丛的matrix拼接起来放到一个buffer里面作为instancing时的顶点属性集合。

在实际绘制的时候,传入GPU只有一个Spine mesh的顶点数据,draw call命令也只有一条, 额外需要做的仅仅是告诉GPU, 需要把这个mesh画1000次,每一次实例绘制,从之前的buffer里取出不同的变换矩阵即可。

不同的图形API都有相应的方法来实现instancing, 不过要注意目前已知是opengles 3.1才比较好的支持了这个特性。

instancing画出来的结果是这样的, 满帧60帧在跑,压力全在GPU这。

目前我们正在热招的技术岗位有:

}

你知道大脑是会欺骗自己的吗?由于特殊角度的的原因会让人产生错觉现象,下面就是一组视错觉图片,能让你看到一个“眼见不一定为实”的世界!

1、一张静止的图片,如果你的精神状态很差的话就会发现图片有滚动效果,这是很典型的视错觉图片。

2、下图是一张普通的照片,如果你仔细看的话就会发现这三个人好像是悬空坐着的,你能看出这到底是怎么回事吗?

3、这是前不久在网络上争议很久的真实照片,远处的船像是悬浮在空中,许多人认为是罕见的海市蜃楼奇观,其实这是由不同温度的空气产生的光学折射现象。

4、照片中女孩的腿看起来似乎与本人不太相符,你看出是什么原因了吗?

5、虽然是一幅静态图,如果你觉得图中的球在向上移动的话,说明你的眼睛已经很疲劳了,赶紧休息一下眼睛吧。

6、现实版的“不可能的立方体”,看了很久也没弄清楚到底是怎么做出来的,你觉得这个立方体是真实的吗?

7、下图中的蓝色线条看起来是倾斜的,但其实都是由笔直的平行线构成的,在线条的交汇处巧妙地使用了色差形成了视觉扭曲。

8、仔细盯着下图中人物脸上的红点看十秒钟,然后把视线转移到光线较暗的区域快速眨眼,就会发现眼前出现了一个美女,你看出是谁了吗?

9、下图中两个球的颜色会是一样的吗?明显左边的看起来是蓝色而右边的是绿色,但这两个球的颜色确实是相同的。

如果你不相信的话,通过动画还原出两个球的真实颜色,确实是一个很神奇的视错觉效果,但你知道这种错觉是怎么形成的吗?

10、下图中到底有多少只鸭子呢?虽然一眼看上去就是9只,但还有许多鸭子就隐藏在其中,看看你能找出多少只?

11、这是一个很不可思议的光学错觉实验,许多人在教室里无意中遇到过这种情况,只是不知道尺子为什么放到阳光下就会变成这样,你知道原因吗?

12、下图中两只猫咪睡觉的姿势有疑问,很难分辨不出两只猫咪的脑袋到底是属于哪边的?

你觉得答案应该是A,还是B呢?

上面这些的视错觉图片是不是已经把你看花眼了,你都看出来了吗?

}

4月18日至4月21日,由网易互娱学习发展举办的2022N.GAME游戏开发者峰会在线上举办。在19号的峰会上,来自网易互娱引擎部的技术专家许飞,以《构建公平的联机环境,服务器动画性能优化》为主题发表了演讲。

许飞提到,虽然现在真正使用服务器动画的游戏并不多,但这是一个比较有前景的领域,因为可以提供更公平的联机环境,而且可以实现更细致的交互。比如服务器一旦有了动画之后,服务器的角色就是活的,不再只是一堆数据,它有姿态,可以进行更复杂的交互。

我们现在提倡的云游戏也好,元宇宙也好,很难想象里面的东西是没有动画的。

以下是演讲全文(有删减):

大家好,我是来自网易互娱技术中心的许飞,很荣幸参加网易N.GAME游戏开发者峰会。本次分享的题目是《构建公平的联机环境,服务器动画性能优化》,分为四个部分:

第一部分:服务器动画的意义和现状

第二部分:服务器动画常见优化方向与方案

第三部分:如何优化复杂的动画状态机

第四部分:服务器动画展望

在座很多有经验的开发者可能会有疑问,服务器需要跑动画吗?因为现在大多数游戏服务器是没有跑动画的。传统观点认为,动画和渲染特效一样,属于表现的层次,只要客户端看就可以了。

有限的几种需要动画来参与的逻辑,比如打击部位的判定,服务器不跑,但客户端还是有动画的。我们让客户端来进行判断,然后把结果发送给服务端,也能实现一样的效果。这种想法是正确的,也是以前常见的做法,但有个前提——你网络必须是可信任的。

但我们实际的网络环境是什么样的?

下图是Peter Steiner在1993年发布于纽约客上的一副图画,被认为揭示了互联网环境的复杂性。互联网环境非常缤纷多彩,这种多彩一方面给网络游戏的发展提供了肥沃的土壤,另一方面,它的复杂性也给游戏开发者造成了很大的挑战。

比如,我们其实并不关心玩家究竟是什么背景,有什么特点,但我们害怕的是玩家用不用外挂。对于一个交手型的游戏来说,一旦使用外挂,对游戏公平性的破坏几乎是毁灭性的。

有没有一种办法能够有效地防止或反外挂呢?一个有效的方法叫做服务器权威。

它的思路其实很简单,外挂是通过劫持游戏客户端来实现一些非法操作,但相对于玩家的客户端来讲,我们的服务器是在机房里,机房经过非常严密的保护,一般的外挂开发者很难劫持我们的服务器。

但如果把关键的逻辑都放在服务器上面,客户端仅仅作为一个指令的输入者,我们就可以防止大部分外挂的操作。

这个方法只要把客户端逻辑搬到服务器就好,很简单,但为什么现在大多数游戏没有这么做呢?

看下图,这是游戏不同系统开销的展示,横坐标是游戏某个系统数据量的多少,纵坐标是属性更新频率。像等级、装备这些信息,数据量相对比较少,更新频率也相对比较低。像技能或状态之类,更新频率相对高,数据量也会相对大。

很早期的服务器,其实只会保存等级信息之类的,后来发展了一段时间之后,就开始保存装备信息、技能信息。

动画在红色的位置,具有非常庞大的数据量和非常高的更新频率,这两个轴相乘的结果才是某个功能对于算力的需求,可想而知动画对于算力的要求是非常高的。

如果我们简单地把动画从客户端挪到服务器,在没有优化的情况下,会导致服务器直接跑不起来。

这就是现实,也是为什么很多游戏没有在服务器开启动画。

还有人说,随着技术的发展,算力的价格其实在下降,CPU越来越强大,核数越来越多,技术的发展会不会让服务器的动画变得可能呢?

我收集了三个比较典型的年代:2007年、2016年和2020年,服务器每条线程成本的变化。这三年刚好也是三款典型射击游戏的发布年代。

从2007年到2016年,CPU单线程的成本大概降到了原来的1/3;从2016年到2020年,更是降到了原来的1/2。

2007年发布的《穿越火线》,服务器几乎没有跑任何动画相关的东西。2016年的《守望先锋》,它的服务器是知道动画状态的,但是只跑了一部分。什么意思呢?服务器知道这个角色,当前是跑还是跳还是释放技能。

到了2020年发布的《Volarant》,它的服务器是完全跑动画的,会完全计算角色在服务器上的状态。他们的主程在分享中也说道,这样做就是为了反外挂。因为服务器只有有了非常全面的动画信息,在判定受击的时候,才不至于被客户端的外挂所欺骗。

所以可以看到,随着技术发展,服务器动画的逻辑执行程度是越来越高的,最近一些游戏已经开始尝试在服务器上跑动画了。

业界有哪些常用的服务器动画优化方案?

既然要在服务器跑动画,那我们就要优化它的动画开销。业界常用的方法有哪些呢?

首先我们分析一下,动画系统的开销,大概分成三个部分:系统接收外界的输入,更新内部状态,最后计算出模型的姿态。

输入部分,一般就是角色速度或者说角色状态,比如在做什么。内部状态,比如角色的速度变化,可能从一个静止的状态变成跑的状态,或者从跑到跳这样的变化,最后再由这些状态计算出我的姿态——姿态就是从美术Key帧里面算出角色最终的样子。

这三部分的开销我觉得很明显:姿态更新部分的开销最大。

正如上文所说,我们评价一个功能开销,可以从它的数据量和频率两方面来计算,姿态更新为什么数据量非常大,因为每个人骨骼都有朝向、旋转和位置众多的属性,而且几乎每帧都要变化,所以姿态更新占比这么多其实并不意外,我们业界常见的优化方式也正是针对这一块进行的。

比如最简单的LOD,主要是减少了动画的数据量。服务器跑动画是为了来判断受击,但有些客户端用来表现的一些骨骼是不需要的,比如披风、头发,对于判断毫无作用,那么服务器就可以不跑,把这些剪掉就可以了。这就是服务器LOD的一种思路、一种做法。

像下图里面红色框里的骨骼,对于服务器判断受击是没有任何作用的,把它给去掉,一般可以省20-30%的开销。

除了减少数据量之外,还可以减少数据的更新频率。

基于事件的姿态更新,也就是减少姿态计算的频率。《Volarant》的技术就是这样子。一个角色只有当被击中的那一瞬间,才需要计算姿态是什么样子的,计算模型的姿态。这种优化极大地减少了姿态更新的频率。

这样做的效果非常惊人,可以把姿态更新的开销从84%直接降到9%。基本上做到这一步之后,就可以在服务器上跑动画了。可能会有一定的开销,但不至于完全跑不起来。这是业界常见的一些优化方式。

而当我们把姿态更新的开销降到9%,这时最高的开销就成了动画状态更新,占比11%,变得更凸显了。

如果一个角色很多动画逻辑非常复杂,导致状态机也非常复杂,那么开销可能还会超过11%的占比,这时候如何优化状态更新就成了当务之急。

复杂的状态机如何优化?

我们以UE为例,先来介绍状态机的样子。

首先里面有一些状态,比如说走跑跳,下图是一个最简单的Locomotion,移动的状态机,有在地面idle/walk/Run的状态,有起跳的JumpStart,在空中循环播放的JumpLoop,落地的JumpEnd的状态,它们之间有一些条件连接起来。

比如外面条件说角色下一刻要腾空了,那我就会改变一个条件,让他从idle状态跳转到JumpStart状态,起跳结束就循环播放一个空中的动作。大概就是这样的流程。

所以大概可分成三部分,第一部分叫做Find_Transitions,即从当前状态找一个可能的跳转条件。第二部分,如果这个条件为真的话,有一个跳转可能发生,那么就执行这个跳转。第三部分,执行完了之后,两个状态之间可能会有一些过渡,那还要更新这两个状态。

三个部分的开销占比是怎样的?

我们发现,寻找一个可能的跳转,和最后的状态更新,占据了绝大部分的开销。所以再来优化这部分的时候,也会选取这些开销更大的。

我们从哪些方面去优化状态机的更新?

第一,优化跳转之后两个状态的过渡。

第二,因为状态跳转每次都要计算一个条件,为真才跳转,为假就不跳转了,所以可以优化这个条件的计算。

第三,每次都要判断所有条件哪些为真,哪些为假,这个频率可以降低。还是上文所说,我们既可以降低数据量也可以降低频率。

以Locomotion为例。我选取的是在空中到落地这一段时间的状态跳转,比如原来的角色在空中,还在那里不停地播一个循环的动作,接下来他要着地了,这个时候因为两个动作之间是有一些变化的,我们不能让他瞬切,所以中间就会有一个过渡。

那这个过渡,至少有两种模式。

一种是Cross Fade,上一个状态权重逐渐降低,下一个状态权重逐渐升高,这样两个权重会出现交叉,过渡过程中两个状态权重都不为0 ,所以必须更新这两种状态。如果这两个状态中间又嵌套了别的状态机,也是一定要更新的。

假如还在空中跳,下一刻要落地了,会把空中跳的状态拍个快照,然后直接就不再更新它了。接下来,下一个状态的权重逐渐从0升到1。通过这种方式,只需要更新下一个状态就ok了。这样,当状态跳转的时候,开销降了一半,因为只需要更新一个状态。

做了这样的优化之后,在Update_States状态更新这一块,会降低大概10%的开销。这是一个很好的事情,因为其实对逻辑没有影响,是一个无损的优化。

还是以UE、从空中到落地这段时间为例。

怎么决定接下来需要着陆?一般来讲会写条件来判断,有两种写法。

下图上面的写法,直接接收了一个Bool值,Bool值如果是True就跳转。下面的写法,是写了一个表达式,通过判断速度是不是变化,来决定是否跳转。

这两种方式有什么区别呢?根据UE官方的提示,这两种方法的效率大概会相差10倍。

原因比较简单,第一种直接使用Bool值来判断的话会编译成本地代码。如果用第二种写法,编译的是虚拟机代码,经过蓝图虚拟机来执行才能判断其中的结果。

实际情况中,如果不用UE也会有一样的问题,因为这个条件也有可能不是C++写的,可能是Python来写的,那会面临一样的问题。脚本语言相比像Python、lua这样的非本地代码,性能本来就是低的。

如果我们过多地使用了不是本地化的语言写的条件,就会给状态机的更新造成很大的性能开销,那么我们可以把这部分的判断变成本地化代码,就获得了一定的性能提升。

对UE来讲,你可以用它的Nativization工具,自动化地把蓝图代码变成本地化代码,这是一个很好的工具,大家可以研究。

对于动画状态机来讲,也会带有10%左右的性能提升。

为什么优化了10倍,整体只能优化10%呢?因为那个10倍对UE来讲,仅仅是蓝图的执行和本地代码执行的开销,但是状态机是一个基于节点的结构,它的执行还是依赖节点跳转。

那就会导致一个后果,没法像优化一个语法树那样,直接把某些节点给裁剪掉。所以对于状态机这样的情况来讲,它有优化,但并没有我们想的那么高。

那还有没有更有效的优化方法?我们可以分析一下状态跳转的频率。

比如像下图左上角这个状态机,每一帧都会判断,当前状态有没有一个条件可以跳到别的状态。

像左上角这种,每一个状态只有一个指向外面的箭头,只要判断一次。对于右上角这个状态机,就比较复杂,它的idle状态的话有三条路径可以跳出去,每一帧对应的时候,就需要把三个条件全部判断一遍。

那状态机在什么时候才需要跳转呢?

有些时候,比如角色站在那儿不动,是不会跳转的,只有当玩家按下了跳跃键,或者按下了一个行走键的时候,才需要来把状态机发生跳转。而且,玩家的输入频率一般是非常低的,顶尖的电竞运动员的输入频率也就每秒钟大概7Hz,但是我们服务器的更新频率可能是每秒钟是30Hz以上。

于是我们发现,这里面有一个方法来降低跳转条件测试的频率。

其实跳转条件也是分成两类的,一类是依赖玩家的输入,这种条件我们可以把它优化掉,因为玩家的输入本来就没有那么高的频率,用一些方法来省掉它的更新就可以了。

另一种依赖于动画播放进度,我们可以优化前者的更新频率,因为玩家输入频率不高,可以直接省掉更新。具体地,可以通过在UE蓝图中进行人工标注标明可优化的跳转信息。

其实相对比较简单,写一个很快的方法来判断玩家的输入有没有发生变化,如果没变化根本不用测试,就像刚才idle突然有三条箭头冲向外面,但如果判断三个条件都没变化的话(判断Cache是不是可用的),如果Cache还是可用的话,那就直接跳过去,来省掉大部分的跳转测试开销。这个比较简单。

UE会比较复杂一点,因为UE的动画状态机是一个动画蓝图,这就意味着,相比较那些状态机纯粹是逻辑结构的情况(判断某个状态依赖的一些条件跳转相对容易一些),UE会编译成一些蓝图的字节码来执行。

当然我们可以通过编译原理的一些方法,分析蓝图的语法节点,然后来找到依赖条件。我这里还提供一个相对比较取巧的方法,这种依赖条件其实可以人工标注。

比如当前是Jump_Loop,还空中不停地循环,它依赖的跳转条件是下一个是不是已经着地了,我们把这个条件标注在这个状态上面,然后在蓝图编译的时候,利用这些标注就更容易把优化代码插进去,这样最后生成的代码就是优化后的代码。这就是一个比较简单可行的办法,也比较容易实现蓝图的优化。

但对于其他引擎来讲,如果它的状态机仅表示逻辑结构,没有像动画蓝图这么复杂,可以用更简单的办法来实现这一步跳转的优化。

这部分的优化还是挺可观的。动画状态过渡优化到了10%左右,跳转优化优化了70%。

这样就实现了比较好的效果,因为完全不影响动画的逻辑,只是让状态机跑的更快了,节省了一些不必要的计算。这就意味着服务器可以跑更多角色了,

服务器的算力没要那么高,也可以省钱。

未来:公平的环境,完备的逻辑,细致的交互

服务器动画按照现在的发展趋势,其实好处是非常多的,虽然现在真正使用服务器动画的游戏并不多,但这是一个比较有前景的领域,因为可以提供更公平的联机环境,而且可以实现更细致的交互。

比如服务器一旦有了动画之后,服务器的角色就是活的,就不只是一堆数据了,它有姿态,可以进行更复杂的交互。

我们现在提倡的云游戏也好,元宇宙也好,大概很难想象元宇宙里的东西是没有动画的。

对于服务器性能优化来讲,虽然做了这么多的工作,但其实并不彻底,因为按照我们的传统看法,动画还是基于每帧更新的,但服务器,我们觉得比较彻底的优化要完全实现动画系统的事件更新的驱动,这样,服务器只需要计算它所需要的数据就可以了。

同时,我们知道服务器虽然越来越强大,但它的性能其实还是有上限的,当服务器的性能不足以支撑这么多动画计算的时候,就需要一个动画的自动分级机制,让服务器不至于雪崩。

我觉得当这两个方向做的非常的完美了,服务器动画可能会迎来更好的发展。

本次的分享到此结束,谢谢大家。

}

我要回帖

更多关于 下图什么意思 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信