LoginSignup
2
2

More than 1 year has passed since last update.

VRChatでOSCを使ってパーフェクトシンクできるようにした話

Last updated at Posted at 2022-03-02

はじめに

先日、VRC PerfectSyncerというツールをリリースしました。
これは、VRChatでパーフェクトシンクに対応したアバターを使えるようにするツールです。

仕組みとしては、VRChat 2022.1.1で実装されたOSCを使って表情等を制御しています。
しかし、VRChatのOSCで使えるデータは128ビットしかなく、そのままではパーフェクトシンクを扱うことはできません
(パーフェクトシンクでは52個のBlendShapeを使い、それぞれ0~100の値を取るため)。

この記事では、この制限の中でどうやってパーフェクトシンクを実現したのかについて説明します。
自分が試行錯誤した知見の共有も兼ねて、VRChatでOSCを利用するユーザーの一助になればと思います。

VRC PerfectSyncerの仕組み

まず、VRC PerfectSyncerの仕組みについて説明します。
VRC PerfectSyncerは、専用のセットアップをしたアバターをOSCで制御することによってパーフェクトシンクを実現しています。

具体的に書くと、

  • Clientアプリで
    • iFacialMocapからパーフェクトシンク用のBlendShapeの値 x 52 を受け取り
    • Expression Parameter x 12 にエンコードし
  • アバターに仕込んだAnimatorで
    • Expression Parameter x 12 をデコードし
    • 対応するAnimationClip x 52 を再生することで
    • BlendShape x 52 を適用

しています。
また、アバターにAnimatorを仕込む作業はSetUpperで行います。

この仕組みを図にすると、以下のようになります。
VRC PerfectSyncerの仕組み (5)C.png
それでは、具体的にどうやってデータをやり取りしているのかについて、次章から説明していきます。

データをやり取りする方法

52個のBlendShapeの値を、128ビット以内でどのようにやり取りしているのかについて説明します。
結論から言うと、値の精度を落としてやり取りしています。

OSCで使えるビット数は128です。
BlendShapeの個数は52です。

1つのBlendShapeで使えるビット数を計算すると
128 ÷ 52 ≒ 2
となります。

つまり、1つのBlendShapeにつき2ビット使えるという計算になります。

さて、ここで2ビットだとどれぐらいの精度でBlendShapeが扱えるのかについて考えてみます。
2ビットというのは、2桁の2進数と考えられるので、

bits
00
01
10
11

の4つの値を取ることができます。

つまり、4段階でBlendShapeの値を表すことができます。
具体的に書くと、以下のようにBlendShapeの値を表すことができます。

bits BlendShapeの値
00 0
01 33
10 66
11 99

こうすることで、値は飛び飛び(33刻み)になるものの、4段階で0~99までの値を表せます(本当は0~100にするべきですが現状はこうなっています)。

ところで、元々0~100の値をたったの4段階にしてしまって大丈夫なのか、と思われるかと思います。
ですが、(最初の動画を見れば分かるかと思いますが)意外と大丈夫でした。
このあたりは「補間」の章で説明します。

さて、ではこれらの値を実際にどのようにやり取りしているのかについて、次章で説明します。

エンコードとデコード

エンコード

前章で、1つのBlendShapeにつき2ビットで表すと説明しました。
一方で、OSCで扱える値の型には2ビットのものはありません。

OSCで扱うためには、BlendShapeの値を何らかの型に変換してあげる必要があります。
この変換のことを、ここではエンコードと呼びます。
エンコードの方法としては、ざっと以下の選択肢が思いつきます。

  1. Int値(8ビット) x 1 にエンコードする → 2ビット x 4を1つのInt値にする
  2. Bool値(1ビット) x 2 にエンコードする → 2ビットを1ビットずつに分割する

現在の実装では1の方法を取っています。
(実は2の方法の方が色々と都合が良いことに後から気づいたのですが、現状は1の実装になっています)

さて、いきなりエンコードと言われても想像がつかないと思うので、具体的な例を挙げます。

VRC PerfectSyncerでは、eyeLookOutLefteyeLookInLefteyeLookDownLefteyeBlinkLeftの値をPerfectSyncValue0というInt値にエンコードしています。
例えば、これらのBlendShapeの値がそれぞれ9966330 の場合、以下のようにエンコードされます。
VRC PerfectSyncerの仕組み (2)C.png
ここで、2進数の11100100を符号無しで解釈すると、10進数では228となります。
つまり、この場合はPerfectSyncValue0228という値が入ることになります。

以上がエンコードについての説明になりますが、これで分かりましたでしょうか?
正直、2進数は普段は触れないと思うのでパッと分からないかもですが、ひとまずこういうことをやっています。

さて、値をエンコードするということは、使う際にはデコードしてあげる必要があるということです。

デコード

エンコードは、BlendShapeの値 x 4をInt値に変換することでした。
デコードは、その逆を行います。
これを実現するのがAnimator (闇) です。

OSCで送った値は、AnimatorのParameterに反映されます。
先ほどの例だと、PerfectSyncValue0228になるわけですね。

これをエンコード前の値(eyeLookOutLeft: 11, eyeLookInLeft: 10, eyeLookDownLeft: 01, eyeBlinkLeft: 00)に復元することを、ここではデコードと呼びます。

では、具体的にどのようにAnimatorでデコードを行うのかについて説明します。

AnimatorにはStateという状態を表す物があり、これらをTransitionで繋ぐことができます。
このとき、Transitionに条件を設定すると、その条件を満たした時のみ状態が遷移するようになります。
つまり、「Parameter Xの値がYのとき、Aに遷移する」という処理が書けます。

これを利用してデコードを行い、対応するBlendShapeを持つAnimationClipを再生します。
具体的な例を挙げると、以下のようにAnimationClipを再生するAnimatorを作ります。

  • PerfectSyncValue00の場合(つまり2進数で00000000の場合)
    • eyeLookOutLeft: 0, eyeLookInLeft: 0, eyeLookDownLeft: 0, eyeBlinkLeft: 0
  • PerfectSyncValue01の場合(つまり2進数で00000001の場合)
    • eyeLookOutLeft: 0, eyeLookInLeft: 0, eyeLookDownLeft: 0, eyeBlinkLeft: 1
  • ...
  • PerfectSyncValue0255の場合(つまり2進数で11111111の場合)
    • eyeLookOutLeft: 11, eyeLookInLeft: 11, eyeLookDownLeft: 11, eyeBlinkLeft: 11

ちなみに、BlendShape1つにつき、1つのAnimatorLayerを使用します。
パーフェクトシンクを実現するためには全てのBlendShapeを足し合わせる必要があるため、各BlendShapeを並列して処理する必要があるためです。

実際のAnimatorを示したのが以下の図です。
Layer.png
図はeyeDownLeftに関するAnimatorLayerです。
見た目はシンプルですが、条件付きのTransitionが沢山繋がれています
(表示は省略されてますが、各Stateに入るTransitionは64本ずつあります)。

BlendShapeは52個あるため、この様なAnimatorLayerが計52層あります(量が多いためコードを書いて生成しています)。

さて、これでエンコードとデコードについて説明し終えました。
これで52個のBlendShapeの値をOSCでやりとりする仕組みが分かったかと思います。

ただし、このままだと値の精度を4段階に落としている都合上、動作がカクカクします。
VRC PerfectSyncerではこれをスムーズにするため、補間を行ってます。

補間

精度を落とした値をそのまま使うと、当然ですが動作がカクカクします。

これをスムーズにするため、補間を行います。

補間の行い方は簡単で、ExitStateへのTransitionTransition Durationを適当な値にするだけです。
VRC PerfectSyncerでは0.1秒に設定しています。

これにより、旧Stateから新Stateへ0.1秒かけて補間されます。
これで動作がだいぶスムーズになります。

補間ナシと比較して、補間アリの場合は動作がスムーズになりました。

最後に

以上で、VRC PerfectSyncerのパーフェクトシンク部分についての説明を終わります。
VRC PerfectSyncerでは他にも頭と視線の制御を行っており、これらはパーフェクトシンク部分と違った処理をしているのですが、
そこまで書くと記事が長くなるため、知見だけオマケとして共有しておきます。
ここまで読んでいただきありがとうございました!

(もし良ければ、最初のツイートをRTいいねしてもらえると嬉しいです!)

オマケ

Stateをすぐに遷移させる方法

結論から言うと

  • 常にtrueになるparameterをAnimatorに追加しておく(例として名前をTrueとする)
  • TransitionConditionTrue=trueを追加

これでStateがすぐに次のStateに遷移する。

普通に考えると、StateのHas Exit Timeにチェックを入れてTransition Duration0を設定すれば良さそうだが、何故か0に設定すると遷移しなくなるという罠(仕様?)がある(推測だが、内部的に無効な設定とみなされてそう)。

このため、Stateをすぐに遷移させるためには上記のようなハックをする必要がある。
今回はAnimatorでデコーダを実装する必要があったので(それ自体が異常だが)、このテクは必須だった。

Animatorで何かしらのロジックを組む人は覚えておいた方が良さそうと思ったので共有しておきます。
(他に良い方法があれば教えてもらえると助かります🙏)

Avatar Parameter Driver について

  • AnimatorのStateに追加して使うコンポーネント
  • これが追加されたStateに遷移したとき、好きなパラメータを好きな値にできる
  • これの何が嬉しいかと言うと、複数のAnimatorLayerにまたがるような処理を書けるようになる

実際に使った例

頭の回転値のデコード

  • 回転を表すには3つのパラメータが必要
  • 当然、パラメータは同時にデコードしたい
  • しかしAnimatorの仕様上、同時に処理を行うためにはレイヤーを分ける必要がある
  • そこでAvatar Parameter Driverを使い、別々のレイヤーでデコードした値を別のパラメータにセットしている

Animator Tracking Control

https://docs.vrchat.com/docs/state-behaviors#animator-tracking-control
アニメーションによる頭の回転を有効にするのに使った。
具体的には、HeadAnimationにした。

これをしないと、頭が回転しない代わりに肩から下が少し動くという状態になった
(おそらくIKによる頭の回転が優先されるため)。

また、使い方に少しクセがあり、Animatorが動き始めてからすぐに実行しても無視されるので注意。
これは推測になるが、おそらくアバターの初期化処理中に実行されてしまい、設定が上書きされている。

これを避けるため、VRC PerfectSyncerではWait Stateを作って2秒間待機している。
image.png

Avatar Descriptor の Lower Body の設定

VRC PerfectSyncerではUse Auto-Footsteps ...の設定をオフにしている。
image.png
これをしないと、何故かアバターが勝手に前に進む現象が発生したため。
移動操作してないのにアバターが勝手に足踏みし始めて、勝手に歩き始める…(カメラは元の位置に残る)。
最初見た気はマジで怖かった。

同じ現象で悩んでいる人が居るかもしれないので共有です。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2