OVRLipSyncで音声ファイルからリップシンクアニメーションを作る

2025/08/14

Unity VRDanceViewer プログラム

t f B! P L
 OVRLipSyncで得られたリップシンクのデータを .anim に記録する方法です。

OVRLipSyncのセットアップ

以下の記事を参照して OVRLipSync を利用できるようにします。
ダウンロード と セットアップ の項目を行ってください。

Unity でリップシンクができる OVRLipSync を試してみた 
(モデルは下記のVRMモデルを使うので、適宜読み替えてください)

モデルについて

元記事ではユニティちゃんを利用していますが、今回は VRM を利用します。
(VRMモジュールがインストールされてない場合はVRMのパッケージをダウンロードしてインポートしてください)

VRoidStudioのサンプルモデルをダウンロードするか、VRoidStudio製のモデルを用意してインポートしてください。

上記サンプルモデルモデルの場合はスキンドメッシュレンダラーに Face をセットして Viseme To Blend Targets の値には以下のように指定します。

他のモデルを利用する場合は スキンドメッシュレンダラー に あいうえお のブレンドシェイプを含むメッシュを指定し、 Viseme To Blend Targets の 10 に "あ" , 11 に "え ", 12 に "い" , 13 に "お" , 14 に "う" のブレンドシェイプ番号を指定します(詳しくはこちらの記事を参照してください)。


ブレンドシェイプ番号は上から0,1,2... と数えます

音声を用意

リップシンク用の音声を用意します。伴奏などが含まれているとうまくいかないので、ボーカルのみのデータを用意します。最近だとRVCの伴奏ボーカル分離がいい感じです。

ボーカルのみのデータができたら OVRLipSyncContext をアタッチしたときに生成される Audio Source のオーディオクリップにセットしておきます。

録画スクリプト

以下のスクリプトを作成し、適当なGameobjectにアタッチします。
using UnityEngine;
using System.Collections.Generic;

public class BlendShapeRecorder : MonoBehaviour
{
    public SkinnedMeshRenderer skinnedMeshRenderer;
    public float recordDuration = 10f;

    private float timer = 0f;
    private bool isRecording = false;
    private Dictionary<int, AnimationCurve> curves = new Dictionary<int, AnimationCurve>();
    private Mesh mesh;

    void Start()
    {
        mesh = skinnedMeshRenderer.sharedMesh;
        for (int i = 0; i < mesh.blendShapeCount; i++)
        {
            curves[i] = new AnimationCurve();
        }
        isRecording = true;
    }

    void Update()
    {
        if (!isRecording) return;

        timer += Time.deltaTime;

        for (int i = 0; i < mesh.blendShapeCount; i++)
        {
            float weight = skinnedMeshRenderer.GetBlendShapeWeight(i);
            curves[i].AddKey(timer, weight);
        }

        if (timer >= recordDuration)
        {
            isRecording = false;
            SaveAnimationClip();
        }
    }

    void SaveAnimationClip()
    {
        AnimationClip clip = new AnimationClip();
        foreach (var kvp in curves)
        {
            string path = "blendShape." + mesh.GetBlendShapeName(kvp.Key);
            clip.SetCurve("", typeof(SkinnedMeshRenderer), path, kvp.Value);
        }

#if UNITY_EDITOR
        UnityEditor.AssetDatabase.CreateAsset(clip, "Assets/RecordedBlendShape.anim");
        UnityEditor.AssetDatabase.SaveAssets();
        Debug.Log("BlendShapeアニメーションを保存しました");
#endif
    }
}
  
スキンドメッシュレンダラーにブレンドシェイプを含むメッシュ(例ではFace)をセット、RecordDuration に録画秒数を入力します。 

Play モードにすると Asset フォルダに RecordedBlendShape.anim が生成されます。

sil に対応するブレンドシェイプはセットしてないので、(このモデルの場合は) Fcl_ALL_Nuetral に割当てられています。使ってない他のブレンドシェイプも含めて削除しておきましょう。

VR Dance Viewer の VRM Animation Brige データを作る

ついでに、VR Dance Viewer 用のリップシンクデータも作ってみます。
VrmAnimationBridge を操作する事前準備を済ませておいてください。

先ほどまでの OVRLipSync の設定までを終えたモデルに VrmAnimationBridge をアタッチします。

以下のコードを作成し、適当な Gameobject にアタッチします。

(コードはVRoidのモデル用です。他のモデルで使う場合は Fcl_MTH_x のブレンドシェイプ名を使うモデルのものに書き換えてください)
using UnityEngine;
using System.Collections.Generic;
using System.Reflection;
using VRM.AnimationBridgeSample;

public class LipSyncRuntimeRecorder : MonoBehaviour
{
    public SkinnedMeshRenderer skinnedMeshRenderer;
    public OVRLipSyncContext lipSyncContext;
    public AnimationBridge animationBridge;
    public float recordDuration = 10f;

    private Dictionary<string, AnimationCurve> blendShapeCurves = new Dictionary<string, AnimationCurve>();
    private Dictionary<string, AnimationCurve> bridgeCurves = new Dictionary<string, AnimationCurve>();
    private float timer = 0f;
    private bool isRecording = false;

    private readonly Dictionary<int, (string blendShapeName, string bridgeField)> visemeMap = new Dictionary<int, (string, string)> {
        { 0, ("Fcl_MTH_A", "Lip_A") },
        { 1, ("Fcl_MTH_I", "Lip_I") },
        { 2, ("Fcl_MTH_U", "Lip_U") },
        { 3, ("Fcl_MTH_E", "Lip_E") },
        { 4, ("Fcl_MTH_O", "Lip_O") }


    };

    void Start()
    {
        foreach (var kvp in visemeMap)
        {
            blendShapeCurves[kvp.Value.blendShapeName] = new AnimationCurve();
            bridgeCurves[kvp.Value.bridgeField] = new AnimationCurve();
        }

        timer = 0f;
        isRecording = true;

    }

    void Update()
    {
        if (!isRecording) return;

        timer += Time.deltaTime;
        var frame = lipSyncContext.GetCurrentPhonemeFrame();

        foreach (var kvp in visemeMap)
        {
            int visemeIndex = kvp.Key;
            string blendShapeName = kvp.Value.blendShapeName;
            string bridgeField = kvp.Value.bridgeField;

            float visemeValue = frame.Visemes[visemeIndex];
            float blendValue = visemeValue * 100f;

            int blendIndex = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(blendShapeName);
            if (blendIndex >= 0)
                skinnedMeshRenderer.SetBlendShapeWeight(blendIndex, blendValue);

            blendShapeCurves[blendShapeName].AddKey(timer, blendValue);

            FieldInfo field = animationBridge.GetType().GetField(bridgeField);
            if (field != null)
                field.SetValue(animationBridge, visemeValue);

            bridgeCurves[bridgeField].AddKey(timer, visemeValue);
        }

        if (timer >= recordDuration)
        {
            isRecording = false;
            SaveAnimationClip();
        }
    }

    void SaveAnimationClip()
    {
        AnimationClip clip = new AnimationClip();

        foreach (var kvp in blendShapeCurves)
        {
            string path = "blendShape." + kvp.Key;
            clip.SetCurve("", typeof(SkinnedMeshRenderer), path, kvp.Value);
        }

        foreach (var kvp in bridgeCurves)
        {
            clip.SetCurve("", typeof(AnimationBridge), kvp.Key, kvp.Value);
        }

#if UNITY_EDITOR
        UnityEditor.AssetDatabase.CreateAsset(clip, "Assets/RecordedBlendShape.anim");
        UnityEditor.AssetDatabase.SaveAssets();
        Debug.Log("完了:Assets/RecordedBlendShape.anim に保存されました");
#else
        Debug.LogWarning("AnimationClipの保存はEditorでのみ可能です");
#endif
    }
}
  
スキンドメッシュレンダラーにブレンドシェイプを含むメッシュ(例ではFace)をセット、LipSyncContent と AnimationBridgeに モデルのルートをセット、RecordDuration に録画秒数を入力します。

Play モードにすると Asset フォルダに RecordedBlendShape.anim が生成されます。

VR Dance Viewer 用のタイムラインを作る際、今回作ったトラックをモーションのオーバーライドとして追加すると、リップシンクも動くデータになります。

このブログを検索

Amazon.co.jp[PR]

このブログについて

QooQ