VRMのBlendShapeをAnimationClipに記録し、どのアバターでも使える表情モーションを作成する

#概要
・通常ではAnimationClipに記録できないVRMのBlendShapeを記録、再生できるようにする。
・ついでに、VRMでOVRLipSyncをそのまま利用できるようにする。

#VRMのBlendShapeは、AnimationClipで制御できない
VRMは、アバターの情報を共通化されたフォーマットにまとめてくれているナイスな規格です。
そのため、これまではアバターの差し替えによって色々と調整する必要があったところが、およそ無調整で対応できるようになりました。

BlendShapeも共通化されていて、あらゆるアバターのBlendShapeは「VRMBlendShapeProxy」を使い、共通のスクリプトで操作できるようになっています。
![clip1.png](https://qiita-image-store.s3.amazonaws.com/0/275693/22770029-3623-377b-779e-6184960b22de.png)
しかし、このVRMBlensShapeProxy、悲しいことに（現時点では）AnimationClipで制御できないんですよ……。下図のとおり、AnimationClipに設定できるプロパティがありません。
![clip2.png](https://qiita-image-store.s3.amazonaws.com/0/275693/dc017c36-ec10-7493-ab61-193a4c890cb1.png)
そのため、どのアバターでも共通で使えるモーションをAnimationClipで作りたくても、BlendShapeを組み込むことができないという悩みがあります。

#空のSkinnedMeshRendererを組み込み、VRMBlendShapeProxyと連動させる
解決策は、AnimationClipで設定できるプロパティを作成して、その値をVRMBlendShapeProxyと連動させることです。
単なるスクリプトの変数でもいいと思いますが、SkinnedMeshRendererであればOVRLipSyncなどの表情操作ツールにも適用できるようになるので、一石二鳥です。
新たにSkinnedMeshRendererを作成し、VRMと同構成のBlendShapeを組み込む方法を採ります。

そのスクリプトが以下のとおり。これを、VRMアバターのルートオブジェクト（＝VRMBlendShapeProxyが存在するオブジェクト）に追加します。
VRMアバターのオブジェクトに手を加えたくなければ、別のスクリプトから動的に組み込んでください。（私はそうしました）

(2019/1/11 AnimatorControllerを貼りなおす処理を追加しました)

```BlendShapeAnimationController.cs
using System.Collections.Generic;
using UnityEngine;
using VRM;

public class BlendShapeAnimationController : MonoBehaviour {

    public bool isActive = true;
    public bool lipSyncTarget = false;

    VRMBlendShapeProxy proxy;
    SkinnedMeshRenderer smr;
    List<string> shapes = new List<string>();

    void Start ()
    {
        proxy = GetComponent<VRMBlendShapeProxy>();

        //VRMのBlendShape情報を取得
        BlendShapeAvatar bsa = GetComponent<VRMBlendShapeProxy>().BlendShapeAvatar;

        //VRMのBlendShapeClipリストからBlendShape名を取り出し、新規Meshに登録する
        Mesh smr_mesh = new Mesh();
        foreach (BlendShapeClip clip in bsa.Clips)
        {
            string sn = clip.BlendShapeName;
            smr_mesh.AddBlendShapeFrame(sn, 1, new Vector3[0], new Vector3[0], new Vector3[0]);
            shapes.Add(sn);
        }

        //新たなSkinnedMeshRendererをVRMBlendShapeProxyのあるGameObjectに追加
        smr = transform.gameObject.AddComponent<SkinnedMeshRenderer>();

        //SkinnedMeshRendererにMeshを登録
        smr.sharedMesh = smr_mesh;

        //LipSyncを適用させる
        if (lipSyncTarget)
        {
            SetLipSyncTarget();
        }

        //AnimatorControllerがあれば貼りなおす
        Animator anim = GetComponent<Animator>();
        if (anim)
        {
            RuntimeAnimatorController rac = anim.runtimeAnimatorController;
            if (rac)
            {
                anim.runtimeAnimatorController = null;
                anim.runtimeAnimatorController = rac;
            }
        }
    }
	
	void Update ()
    {
        if (isActive)
        {
            for (int i = 0; i < shapes.Count; ++i)
            {
                //Shape値を取得
                float weight = smr.GetBlendShapeWeight(i) / 100;

                //VRMBlendShapeProxyに反映
                proxy.ImmediatelySetValue(shapes[i], weight);
            }
        }
    }

    public void SetLipSyncTarget()
    {
        OVRLipSyncContextMorphTarget target = FindObjectOfType<OVRLipSyncContextMorphTarget>();
        target.skinnedMeshRenderer = smr;
        target.enabled = true;
    }
}
```

実行すると、VRMと同じBlendShapeキーを持ったSkinnedMeshRendererコンポーネントが姿を現します。
![clip3.png](https://qiita-image-store.s3.amazonaws.com/0/275693/02503d6b-7d7f-01e0-3a6a-17e57a67819c.png)
BlendShape値を変更すると、連動してVRMBlendShapeProxyのBlendShapeも変更され、モデルにも反映されることを確認してください。
ちなみに、SkinnedMeshRendererのBlendShapeは100が最大値ですが、VRMBlendShapeProxyは1が最大値となるため、値を100分の1にしています。

#実行中にAnimationClipを編集する
こうなれば、BlendShapeの情報をAnimationClipに登録できますね！
実行中でなければ登録できないというのは少し難点ですが。
![clip4.png](https://qiita-image-store.s3.amazonaws.com/0/275693/8b373c94-9994-1c30-6a81-aeb065fae8e9.png)
もちろん、Animationを再生するときも上記スクリプトが必須です。
スクリプトさえ組み込んでおけば、アバターを切り替えても同じAnimationClipが使えるようになります。
![clip5.png](https://qiita-image-store.s3.amazonaws.com/0/275693/902d9456-6445-2ff5-4925-7899a22f5635.png)

#オマケ：OVRLipSyncを使えるようにする
先に少し触れましたが、このスクリプトを使うことでOVRLipSyncをそのまま使用できるようにもなります。
方法は、このスクリプトを静的に組み込んでいる場合、InspectorでlipSyncTargetをtrueにしてください。
動的に組み込んでいる場合、組み込んだ直後にlipSyncTargetをtrueにしてください。

そして、OVRLipSyncContextMorphTargetを非アクティブにしておきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/275693/10d7ffdd-e77b-cd40-5fd7-6ac43fbc6e15.png)
これは、OVRLipSyncContextMorphTargetがStart()で設定するようになっているため、開始時点でアクティブになっているとSkinnedMeshRendererの設定のタイミングを損なうためです。

なお、これはあくまでも副産物なので、VRMにOVRLipSyncを対応させる目的だけであれば、坪倉さんの[VRMLipSyncContextMorphTarget](https://github.com/TsubokuLab/VRMLipSyncContextMorphTarget)で十分です。

#注意点
VRMBlendShapeProxyを常時連動させているため、これが生きている間は逆に他のスクリプトからVRMBlendShapeProxyを制御できなくなる可能性があります。
その場合、AnimationClipを再生しないときは連動処理を停止させる(isActive=false)などしてください。