Unreal Engine 4 动画系统介绍 - 纳金网
联系我们

给我们留言

联系我们

地址:福建省晋江市青阳街道洪山路国际工业设计园纳金网

邮箱:info@narkii.com

电话:0595-82682267

(周一到周五, 周六周日休息)

当前位置:主页 > 3D教程 > 图文教程

Unreal Engine 4 动画系统介绍

来源: 纳金网 | 责任编辑:传说的落叶 | 发布时间: 2019-05-15 08:37 | 浏览量:

       Unreal Open Day 2017 活动上 Epic Games 资深开发者支持工程师王祢先生为到场的开发者介绍了在 Unreal Engine 4 中动画系统,以下是演讲实录。 

 

       大家好!鉴于引擎移动端功能以及 UI 优化都有同事做了介绍,今天我选择讲的主题是关于动画。动画是一个非常复杂的系统,我会主要介绍一些基本的概念,大家在了解了基本概念后,就可以在上面做出扩展。我并不会教大家怎么使用动画工具,关于一些动画节点的使用,我们的在线文档上都有比较详细的说明,也有比较多的资源。今天不会讲到的内容包括 Morph target,IK,Retargeting,Rootmotion,Additive,Skeletal Control 这些。

 

       首先,我们先来看看引擎中的动画系统是如何工作的。为什么我要先讲解这样一个问题,因为国内有很多用户在使用动画系统的时候,有很多疑问。这些疑问并不是因为他们没有查阅文档,而是因为没有理解系统的工作方式。本质上,动画系统工作原理是非常简单的,我这里还是重新介绍一下。

 

112520umwph25ail3ew5k1


我们先来看看在引擎中动画相关的资源主要分为哪几类。

 

       第一大类是最基本的数据资源。其中主要来自于外部 DCC 工具制作并导入的原始资源,我们称之为 Anim sequence。

 

       然后,有些资源可能为了制作和导入的方便是分散开来的,但是有些情况下会组合到一起使用。所以引擎中有一种资源叫 Anim Composite。他是使用多个 Anim sequence 或是自身(Anim Composite)所组合成的资源。在使用时,依然被看作是普通的 Anim Sequence。

 

       第三种数据资源类型叫 Blendspace。他可以是一维的也可以是二维的。二维的情况下,在两个轴上,通过变量控制对任意在二维平面上指定的动画序列(Anim sequence)作混合。对于任意的二维输入,总能找到这个输入值在二维图像附近最接近的四个动画序列按照权重来混合。严格来说,Blendspace 并不是单纯的基础数据,他也受其它输入参数的影响来混合 Pose 。但是,由于在动画混合蓝图中是作为 Pose 的输入结点,我们这里依然把它作为数据类资源。

 

       第四种数据资源叫 Montage。这一类资源一般是直接受逻辑控制的组合资源。

 

       在数据资源的基础上,我们还可以绑定一些额外的数据。

 

       第一类常用的数据类型叫 Notify。引擎包括一些内建的 Notify类型。譬如,在走路的时候希望脚步踩到地面的那一刻,触发踩地面的事件,用来向地面投射贴花,用于产生脚印,以及播放脚步音效或扬起尘土的特效之类。这里的 Notify 你还可以扩展成你自定义的事件类型,可以在蓝图以及代码中去处理事件对应的逻辑。举个例子:如果做一个动作或格斗类游戏,在出招的时候,判定并不是从这个动画开始播放的时刻就已经有了的,可能是从出招动画到某一时刻开始,才有打击判定。那么我们就可以通过 Notify 来用事件通知游戏逻辑在特定的时候去打开和关闭判定。

 

       第二类叫 Curve。Curve 就是伴随动画序列的时间轴所绑定的曲线数据,后面会有一些举例。再然后你也可以绑定一些你自定义的数据类型。

 

112520omtkujtcv3sydk3d

        讲完刚刚这些数据类型,接下来就是最重要的处理动画混合逻辑的资源,叫 Anim instance。Anim sequence 的设计是基于对于 3A 级游戏中复杂的动画需求所产生。这里有一个假设,那就是动画状态在复杂的情况下一定是需要对骨骼结构有认知的。所以引擎中的 Anim instance 和骨骼是强耦合的关系。譬如你需要知道腰部的骨骼位置来区别开上半身和下半身的动画,这样的设计可以完成相当复杂的动画混合,但是却也带来了一些限制。如果我的整个动画状态只需要简单的一个状态机在不同的状态中,譬如闲置、追逐、攻击、受击、死亡,在每中状态中,并不作复杂的混合,而只是播放一个简单的 Anim instance。在整个逻辑中完全不需要用到骨骼信息。那么照理来说,即使拥有不同骨骼结构的对象,如果只需要这个简单逻辑的话都可以共享这套逻辑。然而由于我刚刚所说的 Anim instance 和骨骼的强耦合设计导致在现在的引擎框架下,这样的功能暂时无法完成。我们在内部也在作一些讨论,以后可能会有支持纯逻辑的 Anim instance 功能,而目前来看,如果大家有这样的需求,我建议在可能的情况下把这些对象的骨骼层次结构尽可能保持一致,这并不是说多个对象的骷髅要完全一致,而只是骨骼树的层次结构一致就可以了。譬如你的基础骨骼是个人形,有些怪物会多出尾巴或翅膀,这些多出的骨骼并不破坏原先的树状结构,而只是多出来的分支。所以还是可以利用 Retargeting 来共享 Anim instance 的逻辑。

 

112520luobl969mf9mfmnf

        Anim instance 中,最明显的两块分别是 EventGraph 和 AnimGraph。其中 EventGraph 就类似于普通的蓝图,用来在 tick 的时候处理一些逻辑状态的更新以及播放 Montage。当然这些逻辑也可以在 C++里面做。AnimGraph 是用来混合和输出 Pose 的地方。说到混合,我们可以把每一帧中整个混合的过程看成是一棵树,从叶子结点输出的 Pose 经过枝干结点的混合计算输出到根结点的最终 Pose。我们刚刚说到的数据类的资源,就是这里所谓的叶子结点。这些结点本身不需要其它的 Pose 作为输入,而直接提供了 Pose 的输出。而枝干结点则是进行混合的结点,当然真的说混合也不是很准确,有些枝干结点只需要输入一个 Pose,在自己的结点逻辑中,对这个 Pose 作一些修正,并不进行混合。我们把这些枝干结点计算调整和混合 Pose 的行为称作评估(evaluate)。举个最简单的枝干结点的例子,那就是多结点混合。譬如,输入的有两个 Pose ,一个权重是 0.8,另一个是 0.2,相当于是把第一个 Pose 的 BoneMap 的 transform 乘以 0.8,第二个乘以 0.2,再相加输出。这里我列了一个树状图,来表示动画混合的过程。但是因为这是个非常简化了的例子,所以其中不包括直接对骨骼进行控制或者直接 Override 一个 Fullbody slot 来强制更新整个 BoneMap 之类的行为。并且一般来说,一个正常的 anim graph 的一帧的混合也不会像这张简化图这样是棵红黑树。首先,就像我刚刚说的,你并不能保证他是二叉的,譬如刚刚说的多混合结点完全可以由三个或以上结点来混合,以及我刚刚说的有些枝干结点,只有一个输入。再者,大部分情况下他也不会是平衡的。在混合状态复杂的情况下,我们一般会分层次来混合,这就导致了这棵混合树会往一个分支方向衍生出去。

112520slx8kdlyekvkswf2


       好了,那么刚刚看到的是单帧的 Pose 混合计算情况。当持续到多帧以后,情况又会稍微复杂一些。譬如说两个 Pose 混合起来,他们的长度很有可能不一样。举例,我有一个走路的动画,他可能长达 2 秒,同时我又有一个跑步的动画,他长达 1 秒。如果我直接混合,就会出现很怪异的情况,譬如走路还在迈左腿的时候,跑步已经迈右腿了,混合起来的姿势就会非常奇怪。基于这种情况,我们引入了 Sync Groups 的概念,当我们设置这两个动画序列在同一个 Sync Groups 下进行混合时,引擎会把当前混合时权重较高的作为领导,把剩下的序列缩放到和领导序列一样长的情况,再按比例去做混合。这样就能解决动画长度不一致的混合问题。

 

112520c4vjql5jjll9qgj8


       再来看多帧动画状态下,如果状态复杂,动画树上的某些分支在不同的帧内是完全不同的状态。为了简化树的逻辑,动画混合系统中可以使用状态机来隔离每一帧的状态。我这里的图例举了一个比较简单的 Locomotion 的状态机。

 

       关于动画混合的这棵树,在复杂的情况下,我们还会把他做分层。也就是把一棵混合完的树的根结点缓存下来,作为另一棵树的叶子结点。当然你也可以把整个复杂的树连到一起,分层只是为了便于维护和调整。这个图片是我们的 MOBA 游戏《虚幻争霸》中一个角色分层混合的模版示例。


112521qh5m9fyv9clifylf

讲完了动画的基础概念后,我们来看一些例子加深理解。

 

112521k82n352h5gldi8d2

 

       子树类用例。在引擎中有一类功能叫 Sub anim instance。这就类似于刚刚说到分层里面的一棵子树,这个子树可以拥有一个输入结点,并且输出一个 Pose 。典型的应用方式,是把在同一个逻辑下有多种可替换的子逻辑分离开,做到不同的 Sub anim instance 中。这样可以把剩余的逻辑用来共享。通过替换不同的 Sub anim instance 来组合出最终不同的效果。

 

112521ysc77hxsq7qhdxtp

 

       接下来讲一些叶子类的用例。通常的叶子类结点就是我们刚刚说的数据类结点,我这里举两个比较特殊的例子。在 4.17 版本中,我们会加入一个叫 live link 的结点。它通过引擎的消息总线从外部实时读入数据输出 Pose 。这里的输入源可以是各种 DCC 工具,也可以是动作捕捉或手势识别类设备。在我们放出的第一个版本中,会带有一个 maya 的实现,通过 maya 的插件把在 maya 中当前动画的 BoneMap 数据通过 live link 消息总线和引擎进行通信。引擎把接收到的数据转换成引擎内的数据输出当前的 Pose 。这样就可以做到在 maya 中一边播动画一边在引擎中看到效果了。

 

112521bjnnr0cp5vndqf88


        下一个叶子类结点的举例,叫 Pose Snapshot。Pose Snapshot 就是把任意指定帧的 BoneMap 记录下来,在接下来的任意时刻,用来作为数据源输入和其它 Pose 做混合。譬如在 Robo Recall 中,你打倒了机器人,机器人会进入物理状态而倒地。你可以把这个状态存下来,在之后再和站起来的动画作混合。

 

        刚刚举了两个叶子类结点的例子,我们再来看看动画混合中最大的一类——枝干类结点的例子。大部分情况都是多个 Pose 按权重进行混合,当然也可以是按照 bool、int、enum 值进行混合。我这里依然举一些特殊的例子。


112522coj17o01070gjkx4

 

       第一个例子是 RigidBody 结点。在讲这个结点前,我要先介绍一个伴随而来的概念,叫 immediate mode physics。引擎中以前的 Physics 是所有的 RigidBody 都加到同一个 PhysX scene,这种情况下如果每个角色身上都有多个需要计算物理的 RigidBody,场景中又有大量的这样的角色,计算量就相当的大。但是大部分时候角色互相之间的物理碰撞细节大家并不关心,所以这样的效率比较低。

 

112522g8zt4zmm7fmzgq72


       因此我们和 Nvidia 进行了合作,他们对 PhysX 的 Api 进行了调整。在新版本中放出了更底层的 Api 可以让我们在引擎中做更细致的控制。大家可以看到这个新的 immediate physics,一个角色身上所有的 RigidBody 都只注册在当前这个 skeletal mesh component 下,多个 SMC(skeletal mesh component 缩写)之间并不会有交互,这样很大程度上提高了运行的效率。

 

112522r2auwpos2svuwarg

 

       大家可以看到,这里的视频同屏有几百个小兵站在地上做闲置的动画,在受到物理冲击后转入到物理状态。这么大量的物理对象在我的笔记本上依然能稳定在 60 帧,而右边的图也显示了单个较为复杂的角色在模拟物理时候的开销,只使用了 0.24ms。大家可能觉得这是一个纯粹物理的功能,为什么我放到动画的枝干结点的例子里来讲呢? 因为事实上你可以在动画中把动画计算完的 Pose 输入进去,在这个结点中根据当前动画的 Pose 和前一帧计算完的结果计算出骨骼结点的变化,从而模拟出物理受力的变化,并根据输入的权重混合回你的 Pose 。有了这样的功能,做我之前说的 Robo Recall 中很自然的击倒机器人或者拳击类的游戏、以及用枪射击怪物时怪物比较自然的受击都变得相当简单。


112522xiua66eu6i6neau6

 

       好了,下面我们再来看另一个枝干结点的例子。我们称之为 Speed Warping。传统的游戏中如果你调整了移动速度,那么为了不产生滑步你也需要调整跑步的动画播放的速率。譬如你的速度翻了一倍,那么很多时候你就需要把动画也加快一倍播放,大家可以看到在这里的视频右边加快播放后的动画其实是很别扭的。真实情况下我们提高速度除了迈出的脚步速度会有一些变快以外,更多的情况下,其实是调快了步幅。同样的减慢速度也是这样。所以 Speed Warping 就是做了这么一个效果。那我们是怎么计算的呢?

 

112522rbvwp6hrisi1v3si

 

       简单来讲,原始的动画双脚的位置是这里的红球。我们计算他跟腰部垂线的水平距离并根据加减速的倍率横向扩展。譬如当是 2 倍的时候,调整到绿球的位置。但这个时候两只脚的距离被拉的太长了,因此我们适当的往下调整了屁股的位置,并且将两只脚以刚才绿球所在位置到屁股的连线上挪动一段距离使得脚步的长度保持不变,所以最终计算出来的就是蓝球的位置。

 

112523lr399u3ev1epo3te


       我再举一些其它的例子。比如引擎中当你对 AnimBP 进行继承的时候,所创建出来的内容叫 Child AnimBP ——它所做的事情是让你重载所有的叶子类结点。举个实用的例子:譬如我有一种敌人,他永远是从初始的出现状态到发现玩家到向玩家攻击这样转化,而这样的怪物在地图上不同的场景下有不同的出现动画,有可能是从地上爬出来的,有可能是从墙上跳出来的。对于这个怪物来说,他的动画切换状态都是一样的,所不同的只是初始状态所需要使用的资源,所以只需要替换初始的动画(某个叶子结点)就可以了。

 

112523z73ll555pbggk7m1

 

       再举一个例子,有不少人问过,在《虚幻争霸》中,是怎么做到让角色不滑步的。传统的主机游戏中,为了让脚不滑步很多时候我们都是使用 root motion 来做移动的动画。但是因为《虚幻争霸》是个 MOBA 游戏,策划会希望能够用数据来驱动移动的速度。譬如在有不同的 buff 或者装备的情况下,角色的速度也会发生变化,这用 root motion 就很不好处理。所以我们做了一个叫 Distance Curve 的功能,这也是我刚刚说到的 Curve 数据的一种运用方式。我们可以把 Distance Curve 的方式看成是反向的 root motion。它通过给所有的启动、旋转、站定动画都加入曲线数据,曲线上的数值表示当前这帧动画到达站定点的位置的距离,其中站定点(Marker)是很容易预测的。

 

 

112523k3ww6f3l11wnnjwp

 

相关文章
网友评论

您需要登录后才可以发帖 登录 | 立即注册

关闭

全部评论:0条

推荐
热门