3. 模型层面

基本原理

简易流程图

下面是一个从现实动作到模型动作的简单流程图,仅供参考,实际情况可能会有所变化。

image.png

简单来说,头显发送原始数据到VRCFT,然后通过VRCFT转化UE参数到模型当中,模型通过动画器控制动画驱动模型形态键实现动作。

参数的平滑和参数二进制压缩

根据VRChat的文档和我们自己游玩的经验,我们可以知道VRC的参数同步是有延迟的,一般会隔0.2s左右会向远端(也就是其他玩家)同步一次同步参数。例如我的GestureRightWeight这个float的参数,我在一秒内将他从0变到1,在本地会看到这个过程很平滑,而远端其他玩家就会出现段落感,如图,下面的滑条为这个参数本地变化,上面为远端看到的这个参数的变化情况

ezgif-2-63d50de718.gif即时是本地,也有可能出现数据跳跃,为了避免这个情况,所以我们现在需要将这个参数更好的流畅的同步到远端,并且本地也做一定的平滑处理,常用的平滑处理就是使用内插运算,简单举个例子,就是一个参数从0,在变成0.2的时候,取中值0.1,然后变化到0.3的时候,取得中值0.2,无时无刻进行这个运算,就会平滑的处理参数变化。这整个流程一般称作Smooth。

这时候就需要OSCmooth的作用,我们留在后文讲。

同时,因为面捕涉及的形态键众多,如果每一个形态键都由一个对应的float值来操控的话,如果是ARKit,那就需要52*8=416个参数,UE只会更多,众所周知VRC SDK的话只允许有256个同步参数,这时候我们就需要对参数进行一些压缩,针对一些不需要那么精细的动作,进行二进制压缩。这个流程一般称作Binary。

二进制压缩的基本原理可以理解为,如果我们之前使用一个int参数来表达0、1、2、3、4、5、6、7,总共8个值,占用了8个参数空间,而我们现在使用3个bool参数来实现相同的表达,以下用1和0来表达一个bool的状态,可以得出这个对照表:

int 0 1 2 3 4 5 6 7
bool 000 001 010 011 100 101 110 111

这样我们就可以只用3个bool,也就是3个参数空间做到了原本需要8个参数空间的表达,相同的,0-1的float占用8参数空间,我们也可以用3个bool表达一个float,对应的,需要把0-1分成7份,如下表:

float 0 0.143 0.286 0.429 0.571 0.714 0.857 1
bool 000 001 010 011 100 101 110 111

如果增加Bool的数量,能够表达的int值就会更多,float值就会更加细致,但过度增加Bool不仅会徒增参数消耗,同时还收益不高

了解了这些,我们就可以准备接下来的内容了

动画器详解

我们现在以Jerry的Template来作为示范的例子,因为从0开始创造有一定难度,所以我们不妨先来看看成品是怎么样的。

Jerry的Template地址在这里:VRCFT-Template

在早期版本,这个模板是直接在Asset目录下的,但在5.0版本之后,则需要在Package中寻找VRCFT - Jerry's Templates

值得一提的是,Jerry的模板是使用VRC Fury来安装的,最方便的办法其实是用VRCFury的预制件直接丢入模型当中,但我们只是为了分析动画器,所以就直接使用资产中的Animator啦

Jerry的模板每一个版本都有可能发生一些排版、参数命名上的变化,所以本版本(6.2.1)仅供参考,但万变不离其宗,我们需要学的是里面的内容而不是冷冰冰的模仿出一个一模一样的面捕控制器

image.png

image.png

在目录"VRCFT - Jerry's Templates\Animators\"中有各种标准的动画器,我们之前说过我们只使用UE标准,我们点击进入UE对应的Animator,FX - Face Tracking就是我们的对象啦。

图层Layers

image.png

image.png

正如我说的,不同版本之间差异很大,左图为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

image.pngimage.png

各个参数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 - 本地参数的通用平滑程度,越接近1平滑程度越高
OSCm/Local/PupilDilationSmoothing Float - 本地参数的瞳孔平滑程度,越接近1平滑程度越高
OSCm/Remote/EyeLidSmoothing Float - 远程参数的眨眼平滑程度,越接近1平滑程度越高
OSCm/Remote/FloatSmoothing Float - 远程参数的通用平滑程度,越接近1平滑程度越高
OSCm/Remote/PupilDilationSmoothing Float - 远程参数的瞳孔平滑程度,越接近1平滑程度越高

1. Driver图层内容详解

image.png

小Tips: 在动画器页面单击F,可以显示全部的状态

双击Face_Tracking_BlendTree可以进入混合树BlendTree页面

image.png

在6.3.0版本之后则是在FT Blendshape Driver中,同样是双击点开,这里是最后一次做关于更高版本的Template的解释,之后的请参考混合树节点名称来自行判断图层。

image.png

各种混合树简易介绍

image.pngimage.png

Direct类型,则代表一旦满足Parameter中填入的参数,则会直接进入下一步,如果同时满足很多各Motion,则同时进入,例如我已经打开了LipTracking,也就是嘴部追踪,所有与嘴相关的,例如JawOpenBlend会运行。

接下来我们把目标聚集在Jaw Open Blend和Mouth Closed Blend,这是最简单的1D类型混合树

image.png

我们先从简单的看起,MouthClosedBlend中右上角Parameter,填入了OSCm/Proxy/FT/v2/MouthClosed,说明由这个参数决定以下的Motion,而Threshold(阈值)也就是当这个参数到达多少的时候进入这个Motion,当OSCm/Proxy/FT/v2/MouthClosed=1时,执行动画Mouth_Closed,参数=0时,执行MouthClosed 0。

image.png

而这两个动画也非常好理解,Mouth_Closed 0让Body的MouthClose形态键为0,Mouth_Closed让让Body的MouthClose形态键为1,我们需要知道,Blendtree是能够提供中间过渡态的,也就是说,当参数=0.5的时候,也就是在这两个状态的中点,那么形态键就会是50。

image.pngimage.png

根据对阈值的简单了解,我们可以知道,参数_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

image.png

image.png

而这个Helper也是同样的1D混合树,控制Jaw_Open相关动画。所以我们可以知道,为了实现形态键JawOpen=1,有几个路线:

  1. OSCm/Proxy/FT/v2/JawOpen=1
  2. OSCm/Proxy/FT/v2/JawOpen=0 但是 OSCm/Proxy/FT/v2/TongueOut=4

这个的思路就是:当我的设备检测到我的JawOpen打开了,那就是真的打开了,直接让模型的JawOpen也为1;但是有的时候我伸舌头TongueOut=1了,如果这时候设备检测到我的JawOpen其实为0,如果我直接伸出舌头会穿模,所以我让他用舌头的参数控制一点点的JawOpen,让嘴巴微微打开,让舌头伸出来不穿模。

还有一些Blendtree使用的参数是有负数值的,例如MouthX一个参数即可表示左和右

image.png

接下来是2D类型的混合树,图上所示的是右眼球的移动轨迹,参数为OSCm/Proxy/FT/v2/EyeRightX和OSCm/Proxy/FT/v2/EyeRightY,这说明有两个参数同时控制了这个混合树,而PosX则是对应左边那个参数的设置,PosY对应的则是右边,我们不难理解每一个位置对应的动作。

image.png

值得一提的是,这种类型的混合树录制动画的时候请让其他相冲的形态键为0

image.png

image.png

关于其他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图层内容详解

2024-09-19 23-29-06_1.gif

图上就是一个Binary的最终实现的效果,通过输入TongueOut1、TongueOut2、TongueOut4,输出不同的BinaryOut/FT/v2/TongueOut值,最后经过平滑转为OSCm/Proxy/FT/v2/TongueOut

那接下来我们一样是双击进入混合树,开始我们的Binary部分

image.png

image.png

根部分与Driver几乎一样,都使用了Direct类型的混合树,同时驱动多个混合树。我们以TongueOut为例,可以看到同样使用了Direct类型,所使用的参数为默认为1的参数,则同时进行三个Motion的混合。

image.png

而这三个嵌套的Blentree使用参数分别为FT/v2/TongueOut1、FT/v2/TongueOut2、FT/v2/TongueOut4,对应的动画命名均为

参数名+False/True+Postive/Negative+GUID

image.pngimage.pngimage.png

image.png


image.pngimage.png

这是一个特殊的关键帧,这是直接驱动动画器中对应的参数的关键帧,含义是将BinaryOut/FT/v2/TongueOut设置成0和0.28571,同时还有类似0.57143、0.14286,具体的取值不详细解释了,在上一页有讲到,是二进制压缩的原理内容。

一般途径无法直接录制这个关键帧,通常在.anim文件中直接修改,或者使用代码生成,当这类关键帧启动的时候,动画器内被驱动的数值则会无法输入

Positive/Negative是区分类似MouthX这种有负值的参数使用的

当三个动画同时被驱动的时候,就能够将各自驱动的数值相加混合起来,也就是最开始这张图。

2024-09-19 23-29-06_1.gif


3. Smooth图层内容详解

TBD