C#
animation
Unity
Animator
VRM

VRMのアバターの表情をアニメーションで管理

やりたかったこと

研究目的のために複数かつ大量のアバターに適用させるアニメーションを作る必要がありました。首から下はHumanoidBornで普通に操作できましたが、FBX(というか既存の人型アバター)だと表情の設定がバラバラすぎて困惑。
なのでせっかくなので最近できたVRMフォーマットで表情制御をしようと決意(Blenderとか使えれば簡単なんだろうけど挫折)

前提

Animation,Animatorは最低限使える

環境構築と設定変更

ドワンゴの公式ページを参考にしてUnityにVRMを読み込む環境構築
使うモデルはAliciaSolidUnityちゃん
ドラッグ&ドロップだけで普通に使えたので便利

首から下は既に作ってあった「座って腕を組んでる」アニメーションを適用。

Sceneに配置して、とりあえず再生ボタンを押すとなぜか顔が消えて軽くホラー
スクリーンショット (95).png

顔のパーツが顔に追従しない親子構造になってたが、構造壊すとどうなるかわからなかったので、とりあえずMeshRendererの位置を下げて対応する。
スクリーンショット (96).png

VRMBlendShapeProxyを触る

UniVRMのVRMBlendShapeProxyで表情アニメーション処理を共通化する
↑を読みつつ、VRMBlendShapeProxyを触ればいいのかと理解。
しかし、Editor上ではStartが始まらないとVRMBlendShapeProxyの値が変更できない=Animationで使えない
スクリーンショット (97).png

なので、この値をUpdate後にどうこうするスクリプトを書いて、その変数を変更するAnimationを設定することにした。

VRMShapesControle.cs
public class VRMShapesControle : MonoBehaviour
{

    private VRMBlendShapeProxy _proxy;

    [Range(0, 1)]
    public float A;

    [Range(0, 1)]
    public float I;

    [Range(0, 1)]
    public float U;

    [Range(0, 1)]
    public float E;

    [Range(0, 1)]
    public float O;

    [Range(0, 1)]
    public float Blink;

    [Range(0, 1)]
    public float Joy;

    [Range(0, 1)]
    public float Angry;

    [Range(0, 1)]
    public float Sorrow;

    [Range(0, 1)]
    public float Fun;

    [Range(0, 1)]
    public float LookUp;

    [Range(0, 1)]
    public float LookDown;

    [Range(0, 1)]
    public float LookLeft;

    [Range(0, 1)]
    public float LookRight;

    [Range(0, 1)]
    public float Blink_L;

    [Range(0, 1)]
    public float Blink_R;


    // Update is called once per frame
    void Update()
    {
        if (_proxy == null)
        {
            _proxy = GetComponent<VRMBlendShapeProxy>();
        }
        else
        {
            foreach (BlendShapePreset t in Enum.GetValues(typeof(BlendShapePreset)))
            {
                _proxy.SetValue(t, GetPropertyValue(p));
            }
        }
    }

    private float GetPropertyValue(BlendShapePreset t)
    {
        switch (t)
        {
            default:
                return 0;
            case BlendShapePreset.A:
                return A;
            case BlendShapePreset.Angry:
                return Angry;
            case BlendShapePreset.Blink:
                return Blink;
            case BlendShapePreset.Blink_L:
                return Blink_L;
            case BlendShapePreset.Blink_R:
                return Blink_R;
            case BlendShapePreset.E:
                return E;
            case BlendShapePreset.Fun:
                return Fun;
            case BlendShapePreset.I:
                return I;
            case BlendShapePreset.Joy:
                return Joy;
            case BlendShapePreset.LookDown:
                return LookDown;
            case BlendShapePreset.LookLeft:
                return LookLeft;
            case BlendShapePreset.LookRight:
                return LookRight;
            case BlendShapePreset.LookUp:
                return LookUp;
            case BlendShapePreset.O:
                return O;
            case BlendShapePreset.Sorrow:
                return Sorrow;
            case BlendShapePreset.U:
                return U;
        }
    }
}

本来はGetPropertyValueの中身を「変数名(string)から値を取得」とかしたかったけど、Enumからの変換とかが上手くいってないようなのでとりあえず原始的に実装。
このスクリプトをVRMBlendShapeProxyと同じGameObjectに追加。
BlendShapePresetは確か自分で変更できたはずなので、その度にここを変更しなきゃいけないのは難点。

とりあえず瞬きをさせたかったので、Blink(瞼を閉じる)の値を触る(0で開ける、1で閉じる)。10秒に1回0.5秒掛けて瞼を閉じる設定。
スクリーンショット (98).png

そして再生
ezgif-3-e5516dd3b0.gif
成功

同じことがUnityちゃんでもできたので目的達成
ezgif-3-e02aa66e51.gif

難点

項目が増えるごとにGetPropertyValueを変えないといけない。
ゲームの再生中じゃないとどう動くかがわからないので値の調整がやりにくいので、モーションを作っている間は常に再生中にしておく必要がある。

VRM

VRゲーム系の利用ということで、どうしてもアバターの種類がアニメよりに偏っているのが残念。RobotKyleみたいな学会発表中にニヤつかなくて済む感じのも欲しい…まあ自分で作れればいいんだろうけど…