凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

uLipSync の新機能解説(実行時のセットアップ / タイムライン上の自動クリップ追加機能)

はじめに

uLipSync は Unity 上で音声データ / 音声入力をもとにリップシンクを実現するアセットです。

tips.hecomi.com

今回は要望を頂いていた以下の 2 つの機能追加・サンプル更新をしましたので解説します。

  • 実行時にセットアップしたい
  • Timeline でのリップシンククリップ配置を簡単にしたい

リリース

v2.6.0 をリリースしました。

github.com

実行時のセットアップ

例えば VRM をロードしてそこにコンポーネントをセットアップしたい場合、これまでは API が足りておらず自前で頑張る必要がありました。これを解決するために、いくつか API を生やし、それらを使うサンプルを 10. Runtime Setup に追加しました。あくまでサンプルなのでそのまま使える感じではありませんが...、また色々と要望を聞きつつアップデートしていこうと思います。

SkinnedMeshRenderer 経由で指定

方針としては、

  1. uLipSyncBlendShape をアタッチ
  2. そこに SkinneMeshRenderer を登録
  3. AddBlendShape(string phoneme, string blendShape)Profile に登録された音素名と、それに対応する SkinnedMeshRenderer の BlendShape を紐付け
  4. uLipSync をアタッチ
  5. Profile を登録
  6. uLipSyncBlendShape のコールバックを登録

となります。

using UnityEngine;
using System.Collections.Generic;

public class uLipSyncBlendShapeRuntimeSetupExample : MonoBehaviour
{
    // リップシンクを追加したいゲームオブジェクト(AudioSource 
    public GameObject target;

    // バインドしたいプロファイル
    public uLipSync.Profile profile;

    // ブレンドシェイプを含む SkinnedMeshRenderer
    public string skinnedMeshRendererName = "MTH_DEF";

    // とりあえず何かしら適当なデータがあるとする
    [System.Serializable]
    public class PhonemeBlendShapeInfo
    {
        public string phoneme;
        public string blendShape;
    }
    public List<PhonemeBlendShapeInfo> phonemeBlendShapeTable 
        = new List<PhonemeBlendShapeInfo>();
    
    uLipSync.uLipSync _lipsync;
    uLipSync.uLipSyncBlendShape _blendShape;

    void Start()
    {
        if (!target) return;

        SetupBlendShpae();
        SetupLipSync();
    }

    void SetupBlendShpae()
    {
        // 指定した名前の GameObject を検索
        var targetTform = uLipSync.Util.FindChildRecursively(
            target.transform, 
            skinnedMeshRendererName);

        if (!targetTform) 
        {
            Debug.LogWarning(
                $"There is no GameObject named \"{skinnedMeshRendererName}\"");
            return;
        }

        // SkinnedMeshRenderer を取得
        var smr = targetTform.GetComponent<SkinnedMeshRenderer>();
        if (!smr) 
        {
            Debug.LogWarning(
                $"\"{skinnedMeshRendererName}\" does not have SkinnedMeshRenderer.");
            return;
        }

        // uLipSyncBlendShape をつける
        _blendShape = target.AddComponent<uLipSync.uLipSyncBlendShape>();
        _blendShape.skinnedMeshRenderer = smr;

        // 何かしらのデータを使って Phoneme とブレンドシェイプ名をバインド
        foreach (var info in phonemeBlendShapeTable)
        {
            _blendShape.AddBlendShape(info.phoneme, info.blendShape);
        }
    }

    void SetupLipSync()
    {
        if (!_blendShape) return;

        // uLipSync をつける
        _lipsync = target.AddComponent<uLipSync.uLipSync>();

        // プロファイルを指定
        _lipsync.profile = profile;

        // uLipSyncBlendShape のコールバックを登録
        _lipsync.onLipSyncUpdate.AddListener(_blendShape.OnLipSyncUpdate);
    }
}

実際はユーザに任せる段では SkinnedMeshRendere や BlendShape のリストを提供する UI などを用意する必要があるかもしれません。その際、音素名のリストは Profile.GetPhonemeNames() から取得することが出来ます。

VRM の場合

VRM の場合は少し簡単で、VRM に登録されている情報を指定するだけで良いです。例えば VRM 0.x 向け uLipSyncBlendShapeVRM コンポーネントを使う場合は以下のようにします。

public class uLipSyncBlendShapeVRMRuntimeSetupExample : MonoBehaviour
{
    ...
    // VRM 用のスクリプト
    uLipSync.uLipSyncBlendShapeVRM _blendShape;
    ...
    void SetupBlendShpae()
    {
        _blendShape = target.AddComponent<uLipSync.uLipSyncBlendShapeVRM>();

        // 音素と VRM のブレンドシェイプ名をバインド
        foreach (var info in phonemeBlendShapeTable)
        {
            _blendShape.AddBlendShape(info.phoneme, info.blendShapeClip);
        }
    }
    ...
}

VRM 1.0 向けに uLipSyncExpressionVRM を使って Expression を利用する場合も、同様に AddBlendShape() すれば良いです(将来的に API 名は変更になるかもしれません…)。

所感

SkinnedMeshRenderer 直接利用の場合は、対応する BlendShape を検索・セットアップする手順が面倒そうですね。どれが口のどの形に対応するかはモデルごとにまちまちですし、特にルールが有るわけでもありません。名前自体も blendShape1.MTH_A のように長くて分かりづらいです。

VRM の方は簡単で、VRM 0.x の場合は VRM.BlendShapeAvatar で定義された VRM.BlendShapeClipAI といったシンプルな名前を持っているのでよりユーザフレンドリーです。VRM 1.0 では BlendShape が Expression という呼称に代わっており、aaih といった国際標準名で与える形になります。また、BlendShape でなくジョイントを動かしたり UV を動かしたりする場合でも対応しているのでより汎用的です。コードも短くなりますし良いですね。

タイムライン上への自動クリップ追加

Audio Track に対応する形で uLipSync Track を自動追加したい、という要望を頂きましたので作ってみました。

uLipSync

Windows > uLipSync > Timeline Setup Helper でウィンドウを開きます。

このようなウィンドウが表示されます。

Timeline となっているフィールドに Timeline アセットを指定すると、そこに含まれる Audio Track および uLipSync Track がそれぞれプルダウンリストから選択できるようになります。追加していない場合は適宜追加してください。

Profile には BakedData を自動生成する際に利用する Profile(どの声でキャリブレーションを行うかのデータ)を指定します。Use Exiting Asset をチェックしている場合は、対象の AudioClip を利用した BakedData が存在した場合、新規生成せずにそれを利用します。Output Directory には新規生成した BakedData を保存するディレクトリを指定してください。

これで次のように Timeline が自動セットアップされるようになります。

おわりに

ランタイムセットアップの方に関しては、あとは Profile のランタイムでの構築、保存、読込のサンプルを用意しようと思います。

今回のように生の利用者の声がいただけると、今後も色々と改善に繋げられると思いますので、ぜひお気軽に Twitter などでお声がけください。