动画器详解
我们现在以Jerry的Template来作为示范的例子,因为从0开始创造有一定难度,所以我们不妨先来看看成品是怎么样的。
Jerry的Template地址在这里:VRCFT-Template
在早期版本,这个模板是直接在Asset目录下的,但在5.0版本之后,则需要在Package中寻找VRCFT - Jerry's Templates
值得一提的是,Jerry的模板是使用VRC Fury来安装的,最方便的办法其实是用VRCFury的预制件直接丢入模型当中,但我们只是为了分析动画器,所以就直接使用资产中的Animator啦
Jerry的模板每一个版本都有可能发生一些排版、参数命名上的变化,所以本版本(6.2.1)仅供参考,但万变不离其宗,我们需要学的是里面的内容而不是冷冰冰的模仿出一个一模一样的面捕控制器
在目录"VRCFT - Jerry's Templates\Animators\"中有各种标准的动画器,我们之前说过我们只使用UE标准,我们点击进入UE对应的Animator,FX - Face Tracking就是我们的对象啦。
图层Layers
正如我说的,不同版本之间差异很大,左图为6.2.1版本的Template,右图为6.3.3版本,他将左图中下面所有的图层整合进同一个图层了,方便分开学习,我们还是先使用左图来进行讲解
各个图层Layers的含义如下:
Tracking_State:用于调整Avatar的追踪状态,主要使用AnimatorTrackingControl组件,组件文档详见State Behaviors | VRChat Creation
Face_Tracking_Blendshape_Driver:用于直接驱动模型的形态键,下称Driver图层
Face_Tracking_OSCmooth_Binary_Gen:用于对参数进行二进制压缩,详见基本原理 | VRCD 文档库,下称Binary图层
Face_Tracking_Frame_Time_Counter:用于同步帧时间,缓解极高和极低帧率下的特殊情况
Face_Tracking_OSCmooth_Smoothing_Gen:用于对参数进行平滑处理,详见基本原理 | VRCD 文档库,下称Smooth图层
参数Parameters
各个参数Parameters的含义如下:
参数名 | 动画器/本地 | 参数表/远程同步 | 描述 |
EyeTrackingActive | Float | Bool | 眼球追踪开启 |
LipTrackingActive | Float | Bool | 面部追踪开启 |
EyeDilationEnable | Float | Bool | 瞳孔追踪开启(瞳孔放大缩小,仅Tobii系可用) |
FacialExpressionsDisabled | Bool | Bool | 关闭手势表情 |
VisemesEnable | Bool | Bool | 口型同步开启 |
FT/v2/xxxxxx | Float | - | 原始输入的浮点值 |
FT/v2/xxxxxx1;FT/v2/xxxxxx2;FT/v2/xxxxxx4 | Bool | Bool | 原始输入的布尔值,之后会经由Binary转换为BinaryOut/FT/v2/xxxxxx |
BinaryOut/FT/v2/xxxxxx | Float | - | 经过Binary后的值,之后会经由Smooth转换为OSCm/Proxy/FT/v2/xxxxxx |
OSCm/Proxy/FT/v2/xxxxxx | Float | - | 经过Smooth后的值,之后会在Driver图层直接驱动形态键 |
OSCm/BlendSet | Float | - | 混合树同时进行的关键参数,请保持为1 |
OSCm/Local/FloatSmoothing | Float | - | 本地参数的通用平滑程度 |
OSCm/Local/PupilDilationSmoothing | Float | - | 本地参数的瞳孔平滑程度 |
OSCm/Remote/EyeLidSmoothing | Float | - | 远程参数的眨眼平滑程度 |
OSCm/Remote/FloatSmoothing | Float | - | 远程参数的通用平滑程度 |
OSCm/Remote/PupilDilationSmoothing | Float | - | 远程参数的瞳孔平滑程度 |
1. Driver图层内容详解
小Tips: 在动画器页面单击F,可以显示全部的状态
双击Face_Tracking_BlendTree可以进入混合树BlendTree页面
在6.3.0版本之后则是在FT Blendshape Driver中,同样是双击点开,这里是最后一次做关于更高版本的Template的详细解释,之后的请参考混合树节点名称来自行判断图层。
各种混合树简易介绍
Direct类型,则代表一旦满足Parameter中填入的参数,则会直接进入下一步,如果同时满足很多各Motion,则同时进入,例如我已经打开了LipTracking,也就是嘴部追踪,所有与嘴相关的,例如JawOpenBlend会运行。
接下来我们把目标聚集在Jaw Open Blend和Mouth Closed Blend,这是最简单的1D类型混合树
我们先从简单的看起,MouthClosedBlend中右上角Parameter,填入了OSCm/Proxy/FT/v2/MouthClosed,说明由这个参数决定以下的Motion。
而Threshold(阈值)它代表了参数值对应的各个动画的激活范围,也可以理解为当这个参数到达多少的时候进入这个Motion,当控制参数的值在两个阈值之间时,Unity 会自动在两个动画之间进行平滑插值。参数更靠近哪一边,哪一边的权重就更高。
当OSCm/Proxy/FT/v2/MouthClosed=0时,执行动画Mouth_Closed 0*1.0 +Mouth_Closed * 0
当OSCm/Proxy/FT/v2/MouthClosed=0.3时,执行动画Mouth_Closed 0 * 0.7 + Mouth_Closed * 0.3
当OSCm/Proxy/FT/v2/MouthClosed=1时,执行动画Mouth_Closed 0 * 0 + Mouth_Closed*1.0
而这两个动画也非常好理解,Mouth_Closed 0让Body的MouthClose形态键为0,Mouth_Closed让让Body的MouthClose形态键为100,当参数=0.5的时候,也就是在这两个状态的中点,那么形态键就会是50。
根据对阈值的简单了解,我们可以知道,参数_A从0变到1的时候,形态键数据是从0变到100,那么如果想要修改一个动作,让动作更加灵敏,则可以通过降低阈值,来让形态键更快的达到100。
举个例子,如果我把MouthClosed的阈值从1改成了0.7,那么在参数=0.7的时候,形态键=100;参数=0.35的时候,形态键=50,这样能够让形态键更快的到最大值,当然这样也会导致更加的灵敏,缺点就是,参数只要大过0.7,形态键都是在100,不会变。
而JawOpenBlend右上角是使用的OSCm/Proxy/FT/v2/JawOpen,那么,当参数=0时,则进入JawOpenHelper这个Motion,参数=1时,进入Jaw_Open这个动画,而JawOpenHelper这个Motion,是另一层嵌套上去的Blendtree。
而这个Helper也是同样的1D混合树,控制Jaw_Open相关动画。所以我们可以知道,为了实现形态键JawOpen=1,有几个路线:
- OSCm/Proxy/FT/v2/JawOpen=1
- OSCm/Proxy/FT/v2/JawOpen=0 但是 OSCm/Proxy/FT/v2/TongueOut=4
这个的思路就是:当我的设备检测到我的JawOpen打开了,那就是真的打开了,直接让模型的JawOpen也为1;但是有的时候我伸舌头TongueOut=1了,如果这时候设备检测到我的JawOpen其实为0,如果我直接伸出舌头会穿模,所以我让他用舌头的参数控制一点点的JawOpen,让嘴巴微微打开,让舌头伸出来不穿模。
还有一些Blendtree使用的参数是有负数值的,例如MouthX一个参数即可表示左和右
接下来是2D类型的混合树,图上所示的是右眼球的移动轨迹,参数为OSCm/Proxy/FT/v2/EyeRightX和OSCm/Proxy/FT/v2/EyeRightY,这说明有两个参数同时控制了这个混合树,而PosX则是对应左边那个参数的设置,PosY对应的则是右边,我们不难理解每一个位置对应的动作。
值得一提的是,这种类型的混合树录制动画的时候请让其他相冲的形态键为0
关于其他2D混合树,这里贴出Unity官方的文档进行解释:
__2D Simple Directional__:最好在运动表示不同方向(例如“向前走”、“向后退”、“向左走”和“向右走”或者“向上瞄准”、“向下瞄准”、“向左瞄准”和“向右瞄准”)时使用。根据需要可以包括位置 (0, 0) 处的单个运动,例如“空闲”或“瞄准”。在 Simple Directional 类型中,在同一方向上_不_应该有多个运动,例如“向前走”和“向前跑”。
__2D Freeform Directional__:运动表示不同方向时,也使用此混合类型,但是您可以在同一方向上有多个运动,例如“向前走”和“向前跑”。在 Freeform Directional 类型中,运动集应始终包括位置 (0, 0) 处的单个运动,例如“空闲”。
__2D Freeform Cartesian__:最好在运动不表示不同方向时使用。凭借 Freeform Cartesian,X 参数和 Y 参数可以表示不同概念,例如角速度和线速度。一个示例是诸如“向前走不转弯”、“向前跑不转弯”、“向前走右转”、“向前跑右转”之类的运动。
2. Binary图层内容详解
图上就是一个Binary的最终实现的效果,通过输入TongueOut1、TongueOut2、TongueOut4,输出不同的BinaryOut/FT/v2/TongueOut值,最后经过平滑转为OSCm/Proxy/FT/v2/TongueOut
那接下来我们一样是双击进入混合树,开始我们的Binary部分
根部分与Driver几乎一样,都使用了Direct类型的混合树,同时驱动多个混合树。我们以TongueOut为例,可以看到同样使用了Direct类型,所使用的参数为默认为1的参数,则同时进行三个Motion的混合。
而这三个嵌套的Blentree使用参数分别为FT/v2/TongueOut1、FT/v2/TongueOut2、FT/v2/TongueOut4,对应的动画命名均为
参数名+False/True+Postive/Negative+GUID
这是一个特殊的关键帧,这是直接驱动动画器中对应的参数的关键帧,含义是将BinaryOut/FT/v2/TongueOut设置成0和0.28571,同时还有类似0.57143、0.14286,具体的取值不详细解释了,在上一页基本原理中有讲到,是二进制压缩的内容。
一般途径无法直接录制这个关键帧,通常在.anim文件中直接修改,或者使用代码生成,当这类关键帧启动的时候,动画器内被驱动的数值则会无法输入
Positive/Negative是区分类似MouthX这种有负值的参数使用的
当三个动画同时被驱动的时候,就能够将各自驱动的数值相加混合起来,也就是最开始这张图。
3. Smooth图层内容详解
本版本是直接整合到同一个State中,本地和远端共用同一个Blendtree,视版本不同可能会存在差异。
同样是双击进入混合树中,这里因为混合树节点数量太多,无法截全图,根节点的效果就是使用IsLocal来判断进入的是远端还是本地的平滑。
所谓远端,其实就是其他玩家看见你的模型的时候,也会同步加载你模型的所有FX等动画器,这时候动画器中的IsLocal就会为False/0,这时候就进入对应的远端平滑流程。而本地加载这个动画器的时候,就会是IsLocal为True/1,对应进入本地平滑流程。
现在以OSCm_Remote为例,OSCm_Local也是同理,可以参考。OSCm_Remote使用了Direct类型进行混合,我们选择TongueOut Root进行解释。
参数是OSCm/Remote/FloatSmoothing,我们本页说过,这是远端使用的平滑参数,决定了平滑程度。
假设我们这个OSCm/Remote/FloatSmoothing=0.3,那么对应在上图就是,0.7*Driver+0.3*Input
根据Jerry模版中这个Blendtree,当OSCm/Remote/FloatSmoothing越靠近1则越靠近OSCm_Input,越靠近0则越靠近OSCm_Driver,最终导致的情况是相反的(OSCmooth自动生成的是0是Input,1是Driver),所以我们后续制作的时候需要注意。
那接下来看OSCm_Driver和OSCm_Input,他们所使用的动画都是一样的,命名规则为:
参数名+-1/1+Smoother+(Remote)+GUID
Driver使用的参数为:OSCm/Proxy/FT/v2/TongueOut
Input使用的参数为:BinaryOut/FT/v2/TongueOut(在Local版本中,使用的是FT/v2/TongueOut)
动画文件则是之前提过的,驱动OSCm/Proxy/FT/v2/TongueOut,而这么做呢,能够让这个最后被驱动的参数得到一个平滑的过渡。
我们举个例子,假设OSCm/Remote/FloatSmoothing=0.3,那此刻,混合树进行的混合运算为Input*0.7 + Driver*0.3
然后我们现在将BinaryOut/FT/v2/TongueOut更改为1,而这个参数则会影响Input,驱动OSCm/Proxy/FT/v2/TongueOut=1,我们上面说的混合树计算,现在就变成了OSCm/Proxy/FT/v2/TongueOut=1*0.7,实际上这个参数只变成了0.7
同时Driver中初始值为0的OSCm/Proxy/FT/v2/TongueOut自然也就不会变化,那就是OSCm/Proxy/FT/v2/TongueOut=0*0.3
最终就是OSCm/Proxy/FT/v2/TongueOut=1*0.7 + OSCm/Proxy/FT/v2/TongueOut=0*0.3 = OSCm/Proxy/FT/v2/TongueOut=0.7
这个平滑是递进的,那这时候再次进行的时候,OSCm/Proxy/FT/v2/TongueOut初始值为0.7,这时候就是
OSCm/Proxy/FT/v2/TongueOut=1*0.7 + OSCm/Proxy/FT/v2/TongueOut=0.7*0.3 = OSCm/Proxy/FT/v2/TongueOut=0.91
一直递进到最后OSCm/Proxy/FT/v2/TongueOut=1时停止,这样就能实现一个平滑过程了,这种方法在远程同步中,通过逐步的参数递进,可以有效减少跳跃,达到平滑过渡效果。
选修:帧速率与Smooth的关系
那了解了Smooth层的平滑原理,接下来有一个选修课,Jerry模版中使用帧速率来控制平滑值,能够更好的解决卡顿情况下的丢失动作问题。
在单独的一个Face_Tracking_Frame_Time_Counter图层中,可以找到FrameTimeCounter,新版本在统一的混合树中,这里只有一个非常多帧数的动画,用于每秒让TimeSinceLoad参数+1,
在Jerry6.0版本及以上中,有一套在Smooth图层中的几个动画,使用一些特殊参数来进行驱动Smoothing平滑参数
因为使用的是Direct类型,所以会一起执行,而第一个参数就是使用我们前面提到的TimeSinceLoad,用来驱动LastTimeSinceLoad,因为Animator的特性,LastTimeSinceLoad一定会晚一帧与TimeSinceLoad的值同步,而当帧数低的时候,这个同步的时间可能会更久。
特性是:动画是由时间决定播放进度的,不会随着帧率的变化加快或减慢播放速度。同时Animator每一帧只会改变一次状态。
第二个是使用TimeSinceLoad来驱动FrameTime=1,第三个是使用LastTimeSinceLoad来驱动FrameTime=-1
第四第五和第六都是使用FrameTime来动态调整Smoothing的相关参数。
流程是这样子的:
首先TimeSinceLoad与LastTimeSinceLoad之间的差值FrameTime,会因为上面Animator的特性,随帧率的变化会变大变小,基本表现为帧率越高的时候差值越小,而帧数越低的时候差值越大。
所以我们可以有这样一个思路:为了避免丢失关键动作,要尽可能降低平滑,追求精准输出。而通过控制这个差值来控制我们的平滑参数,就能够实现上述想法。
而为了储存下来这个差值,我们让TimeSinceLoad驱动正数,而LastTimeSinceLoad驱动负数,这样就能把差值存入FrameTime中。
通过这个FrameTime的变化,来动态调整Smoothing的参数。
一般来说差值都在0.01-0.05,所以制作驱动Smoothing参数的关键帧的时候要放大关键帧的数据,这样才能让乘算后的参数在正常值。
这里的帧速率与平滑度的数值应为计算过的,请考虑数值的严谨性或者直接使用Jerry的模版数值。
No Comments