物理动画深入分析

前言

本文打算对物理动画这个自从出现以后一直处于实验性的功能进行深入分析, 并尝试用一些常见的动画进行测试

官方文档

官方视频教程

星球大战物理动画演示

何为物理动画

物理动画不是Ragdoll(布娃娃), 这个概念很容易被误解

image-20211229095759908

录制_2021_12_29_17_04_10_262

简单说, 物理动画就是为了让角色播放动画的时候有物理效果(不仅限于碰撞)而存在的一个特殊功能

配置方式

配置方式不难(详见官方文档), 我们简述一下流程

  1. 开启模型的物理碰撞效果(角色的模型默认是NoCollision), 设置成类似PhysicsActor之类的接受Block的类型
  2. 将Mesh的物理刷新类型改成Kinematic即动力学驱动
  3. 添加PhysicalAnimationComponent
  4. 将Mesh作为参数调用PhysicalAnimationComponent::SetSkeletalMesh()
  5. 将Mesh开启物理效果, 一般会通过调用SetAllBodiesBelowSimulatePhysics 基于某个骨骼开启
  6. 调用物理动画组件的ApplyPhysicalAnimationProfileBelow 提供物理动画配置

没几个API, 核心问题还是要配置好合适的参数, 以及理解每个参数的作用

流程详解

SetSkeletalMeshComponent()

image-20211229105025648

使用物理动画的必要API, 需要将Mesh作为参数传递给组件, 当然也允许传空指针

设置新的Mesh会将之前保存的Drive参数和物理引擎数据都清空或重新设置, 然后将新的Mesh对应的物理实体对象(FBodyInstance)唤醒

Drive参数

image-20211229105424341

这个参数也许是影响物理动画最关键的参数, 也是刚开始最不容易理解的参数

其实我们在物理资产编辑的时候就可以通过预览界面预览其中的效果

录制_2021_12_22_11_38_39_726

简单说, 这些参数就是动画对骨骼的牵引力效果, 即

  • OrientationStrength : 旋转牵引力, 一般1000左右就可以保持比较快速的返回动画状态
  • AngularVelocityStrength: 角速度的牵引力, 这个值对人物影响不大, 一般设置成100即可
  • PositionStrength: 位置的牵引力, 类似旋转牵引力一般设置成1000
  • VelocityStrength: 速度牵引力,一般设置成100, 较低的值会导致类似弹簧一样的效果让位置返回的过程中反复

上面的值是一般而言,针对不同的效果, 比如死亡/受伤/攀爬以及不同的身体部位是需要使用不同的参数的

SetAllBodiesBelowSimulatePhysics()

image-20211229134307280

其实即就是对所有BodyInstance遍历操作一遍, 设置了每个BodyInstance对象的物理权重PhysicsBlendWeight, 剩下的就交给PhyX完成了

所以这个API后面就不需要跟一个SetAllBodiesBelowPhysicsBlendWeight()用来设置权重为1了

这里有一个特殊设定, 看下图

image-20211229151110112

即如果骨骼名称为None, 同时满足下面2个bool值(默认)的情况下, 就相当于对所有骨骼进行操作

SetStrengthMultiplyer()

image-20211229134758749

image-20211229134903307

这个缩放系数最终作用的就是上面的Drive参数(同样也可以看到对Chaos也有处理, 说明物理动画现在或者至少未来肯定支持Chaos)

所以我们也可以通过动态的设置这个参数来控制牵引力

有个地方需要注意, 在刚开始设置基本profile等参数的时候需要在delay一点时间以后(或者一直Tick)调用一次SetStrengthMultiplyer(), 不然人物会没有牵引力而躺下就像使用了纯粹的布娃娃效果. 原因是整个物理动画初始化过程有一些异步过程

动画测试

phyAnim1

上面演示了开启全身物理动画的效果, 虽然是可以明显的看到动画与物理的共存, 但是实际用于生成还是需要经过非常细致的处理的

再来看一个死亡动画

phyAnim2

已经看到, 如果角色模型在动画过程中与场景其他物体有严重的穿插, 那么必然就会得到奇怪的结果, 我们可以试想一下, 动画希望模型往前走, 但是物理不允许

那么如果是平地上的效果呢

phyAnim3

虽然没有大问题, 但是最后的姿势并不算理想, 因为多数情况我们希望在躺下以后开启正真的物理效果, 一来可以让躺下的姿势更自然(需要较好的物理约束和碰撞配置), 最主要的是能让角色满足各种地形情况

如何做到?

我们可以简单的设置 一个TimeLine来快速实现这个功能

image-20211229155857050

因为我们已经知道, Strength控制是整个Drive数据, 那么这里就用1秒时间让这个Strength从1到0, 即动画牵引力从有到无

看效果

phyAnim4

这样已经可以比较好的躺倒在各种地形上了, 到是还有一个问题, 看下图

phyAnim5

实际上在动画牵引力完全淡出之前如果碰到其他物体, 还是会有很奇怪的现象出现, 这种情况下可以用提前的射线检测等方式来让物理提前开启(动画提前淡出)来实现

下图展示了提前进行碰撞预测和悬空检测的效果, 蓝色虚影为动画原本表现效果

phyAnim6

phyAnim6 (2)

性能测试

在简单场景中创建108个角色, 统一使用单一跑步动画, 做几个维度的对比, 通过UE抓帧工具对比性能开销

  • 正常开启全身物理动画模拟

image-20211229175230754

image-20211229174750536

  • 不开启物理动画

image-20211229175313293

  • 去掉物理资产

image-20211229175406429

image-20211229175456964

  • 不开启物理模拟

    image-20211229191241851

image-20211229175717148

  • 开启物理动画但是物理权重为0

image-20211229191317905

image-20211229190816430

小结

只看游戏线程, 找到跟物理和动画有关系的Tick调用开销, 粗略判断

  • 默认:18.3ms
  • 关闭: 10.5ms
  • 去掉物理资产: 13.1ms
  • 关闭物理模拟: 12.6ms
  • 权重为0: 18.0ms

总结

物理动画在追求品质的游戏当中还是非常有必要的, 但是就现在的效果而言还是有很多地方值得深入研究, 当然也有可能是目前效果确实有待改善(为啥一直处于实验性?), 在有些时候很难确定物理和动画的边界容易造成奇怪的现象, 同时不同的行为使用的各种参数也会不同, 需要长时间的尝试才能有较好的用于生产的方案

补充

BodyInstance

个人理解BodyInstance是UE物理世界里的基本单元, 同样也是与PhyX产生对应关系的一个具体对象

对于一个静态网格物体StaticMesh,他的物理一般在建模软件里面就应该创建好, 本质上都是在编辑器里给UStaticMeshComponent构建一个UBodySetup,在开始游戏的时候在创建运行时的基本物理数据UBodyInstance。

这里我们重点讨论骨骼模型

对于骨骼网格物体SkeletalMesh,由于数据比较多,他的物理数据存储在PhysicsAsset里面。在游戏运行的时候,SkeletalMeshComponent会读取物理资产里面的数据UBodySetup随后再通过UBodySetup给角色创建对应的基本物理数据UBodyInstance。再进一步深入就是NVIDA的PhysX物理引擎了
我们看一下BodyInstance对象大概有那些东西

image-20211229113748264

image-20211229113651832