26
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-10

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

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

BlendShapeも共通化されていて、あらゆるアバターのBlendShapeは「VRMBlendShapeProxy」を使い、共通のスクリプトで操作できるようになっています。
clip1.png
しかし、このVRMBlensShapeProxy、悲しいことに(現時点では)AnimationClipで制御できないんですよ……。下図のとおり、AnimationClipに設定できるプロパティがありません。
clip2.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
BlendShape値を変更すると、連動してVRMBlendShapeProxyのBlendShapeも変更され、モデルにも反映されることを確認してください。
ちなみに、SkinnedMeshRendererのBlendShapeは100が最大値ですが、VRMBlendShapeProxyは1が最大値となるため、値を100分の1にしています。

#実行中にAnimationClipを編集する
こうなれば、BlendShapeの情報をAnimationClipに登録できますね!
実行中でなければ登録できないというのは少し難点ですが。
clip4.png
もちろん、Animationを再生するときも上記スクリプトが必須です。
スクリプトさえ組み込んでおけば、アバターを切り替えても同じAnimationClipが使えるようになります。
clip5.png

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

そして、OVRLipSyncContextMorphTargetを非アクティブにしておきます。
image.png
これは、OVRLipSyncContextMorphTargetがStart()で設定するようになっているため、開始時点でアクティブになっているとSkinnedMeshRendererの設定のタイミングを損なうためです。

なお、これはあくまでも副産物なので、VRMにOVRLipSyncを対応させる目的だけであれば、坪倉さんのVRMLipSyncContextMorphTargetで十分です。

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

26
24
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
26
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?