Unity骨骼动画的总结

  欢迎参与讨论,转载请注明出处。

前言

  恰逢假期,在家继续推进Demo,骨骼动画相关的调研算是告一段落了,遂以本文记录相关要点。
  首先要明确一点,本文所说的骨骼动画皆是3D模型的骨骼动画,与2D精灵的骨骼动画无关,虽然原理大致相通。

网格、骨骼、绑定

  了解过3D相关知识的都知道,模型(Model)是由一个个三角形组成的,而这种三角形的学名则是网格(Mesh)。当然在DCC软件里为了方便创作,会用2个三角形组成四边形作为网格:
0
  然后便是骨骼(Skeleton)了,它是驱动模型运动的根本,如下图所示,这是一种彼此之间有父子关系连接在一起的长条状玩意:
1
  最后便是让模型跟着骨骼一起运动了,这个骨肉融合的过程称为绑定,具体要做的事便是将某节骨骼与相关的网格建立关系:
2
  如上图所示,模型上有着不同的颜色,这表示该节骨骼所影响到的网格权重值(蓝色为0,红色为1),所以绑定也俗称“刷权重”。权重值越高,该节骨骼对相应网格的影响便越大(存在多节骨骼对相同网格存在影响,此时便要通过权重值来决定优先级了)。
  随着时代的发展,现代DCC软件基本配备自动刷权重的功能了。做好模型部件的划分(每个部件拥有独立的骨骼,通过各骨骼之间建立关系来联系模型),减少每个模型的权重复杂度,如此通过自动刷权重基本可以应对一般情况了。

模型与动画

  生成给Unity使用的模型与动画我选择FBX格式,毕竟这算是最流行的3D格式了。对于动画,我选择一个动画一个FBX文件的形式(业界也有全部做到一个文件里,在Unity内分割的行为),动画FBX文件里只有骨骼与动画信息,不含模型。
  说到这里,便有一个绕不开的点:多个模型复用相同的动画,这里涉及到Unity里的两种骨骼动画模式:Generic与Humanoid
  Generic如其名般:一般的动画,在这种动画模式下实现复用的思想很朴素:只要模型的骨骼与动画的骨骼要素相同,那么复用便是水到渠成的事了。这也表示必须同类模型与动画的骨骼结构是一致的。这也表示难以使用外界的第三方资源,对于一些以拼凑、同人、大乱斗为特色的民间项目,或是想直接使用某游戏提取出来的动画,那便捉急了。
  Humanoid则不然,这是一种专为人形设计的动画模式。如下图所示,它定义了人体通用的若干个关节点,将模型对应的骨骼填进去即可:
avatar
  事实上Humanoid便是做了一层中间层转换,让各自的人形模型的骨骼信息统一抽象为上图这套体系,并且还做了关节运动幅度的可控,如此便可实现复用了。且Unity实现了自动识别填充,使用起来还算方便。
  但事实上我放弃了这种做法,选择了Generic模式。原因如下:

  • Humanoid模式为了兼容不同体型下的情况,禁用了具有缩放行为的骨骼动画。
  • Humanoid模式只是为人形考虑,但实际上需要动画复用不只是人形。
  • 由于Demo模型选用的是小泥人,并没多少合适的第三方动画选择(已尝试过)。
  • 哪怕是人形,事实上也会有一些Humanoid无法顾及到的部件(如头发)。
  • 既然不用第三方动画,那么骨骼结构的稳定性自然有保证。

  以上原因不是说用Humanoid模式就完全无法解决,But simple is good,Generic就完事了。

Animator: 切换、分层、混合树

  时值2020年,Animator自然是动画组件的不二之选了。如下图所示般,构建动画状态机,实现一个有机的动画播放环境:
animator
  首先要注意的是,不推荐将动画状态机当成单位业务的状态机使用,虽然动画状态机有提供挂载脚本的形式,但事实上单位的状态并不是与某个动画绑死的,应该由状态去播放动画,而非是动画下绑定专门的业务。让动画的归动画,状态的归状态吧!
  在动画切换控制方面,我使用了Animator自带的变量机制(Parameters),结合条件切换、动画状态脚本、代码控制等方式,实现动画的高可控切换。
  如前文提到的动画复用问题,实际上哪怕骨骼主体相同,但仍会有各自的特殊部件,这时候便要用上Animator的分层机制(Layers)了。通过定义多个不同的层次,在层次中使用Avatar Mask确定影响的骨骼部位,在层次中定义各个动画状态下对应的部件动画。可以为层次定义独立的动画状态机体系,也可以在设置中选中Sync启用以主体层为准的体系。个人更推荐后者,除非与本体动画无关。
  最后是业界不少人士喜欢用的混合树(Blend Trees),分为多种类型(不同的维度乃至于机制),原理为定义若干个动画,确定每个动画在变量组合的特定值下权重最大(播放优先级最高),如此通过操纵变量即可灵活混合相关动画(每个动画的元素都有一定的权重,最终混合成独特的动画)。本人暂时还没用到,主要是追求更明确的动画,而非那种融合的感觉。

补间与帧动画

  骨骼动画的本质,便是在不同的时间点为某节骨骼定义了特定的位置、缩放、旋转。动画的运作便是根据两个时间点之间的骨骼数据做数值变化,这种行为称之为补间(Tweens),同理骨骼动画也就是一种补间动画。与补间动画相对应的概念是帧动画,帧动画只会在特定的时间点发生变化,时间点之间的运动途中是不变的,在许多经典的2D游戏动画便是这种做法。
  为何我会提及到这点呢?一个很明显的区别:较之帧动画,补间动画显得实在是太流畅了,毕竟理论上游戏运作的每一帧它都在改变。但是流畅不是很好么?这一直是电子游戏的追求才对啊!在大多数情况下也许没错,但有时太过流畅,反而会失去「力量感」,说的再通俗点,就是没2D游戏内味了。我想这也是不少一般3D动作游戏做的不好的一点。
  这种现象在日本动画业界运用3D时早有发现:由于2D手绘帧的标准是24帧每秒,而如果3D动画按照视频播放帧进行输出,就会显得两者仿佛根本不在一个世界般。故后来都选择了按照2D手绘帧的帧率进行抽帧,以此达到同步。当然哪怕如此,3D动画也是无法还原出2D手绘帧那股味的,一者在于手绘帧的每帧内容都是人为创作的,具有独特的节奏感。另者在于2D手绘帧的运动帧为了表达动感,往往会画成糊成一团的样子:
3
  这种效果在3D动画基本上是难以实现的,所以往往会盖一层特效解决:
4
  当然《塞尔达传说:荒野之息》对此的处理已经算是上乘了,特意选择了与武器本体颜色相近的特效,在形状上也与2D运动帧接近。个人认为可以在此基础上为武器加入短时间内夸张的形变,使之更有张力。这在《守望先锋》里也有相关运用,留待后日实践验证了。
  言归正传,鉴于补间动画过于流畅的特性,为此我也类似日本动画业界的做法一般,按照24帧每秒的形式对动画播放进行了抽帧。实现思想也很简单:平时将Animator暂停,使用一个定时器,在特定时间点让Animator一次性把暂停的时间差更新补上。如下图对照所示(图1抽帧,图2没有):
5
6
  当然这种方式并不完美:真正的2D帧动画每一帧的持续时间都是人为确定的,而这样只是粗暴的抽帧罢了。当然人为确定帧时间的方式势必带来更高的人力成本,具体如何仍需取舍。

后记

  关于「补间与帧动画」一节纯属个人看法,在正统3D派看来也许属于邪道也说不定(笑。骨骼动画涉及的相关种种实际远不止如此,如换装、部位组合动画等,限于篇幅,就不展开了。