Unity
VRM

VRMモデルをSALSAでリップシンクさせる

はじめに

VRMモデルのリップシンクを実装してみました。ARKitを使ったアプリにも対応できるように、iOS端末で動作するリップシンクが必要だったので、SALSAというアセットを使いました。
LipSync.gif
サンプルプロジェクトこちら ⇒ VRMLipSyncSample
VRMモデルをランタイムロードする場合のサンプルとかOVRLipSyncを使ったサンプルも一緒に入ってます。

オーディオファイルの再生に合わせたリップシンク

SALSAのインポート

アセットストアからSALSAをインポートする
ImportSalsa.png

シーン作成

新しいシーンを作成してVRMモデルを配置する
SceneSetup.png

リップシンクの設定

空のゲームオブジェクトを作成して「Salsa3D」をアタッチする。
ゲームオブジェクトの名前は「SampleVoice」などに変更しておく。
AttachSalsa.png

Salsa3DのAudioSourceの値を確認しておく
AttachSalsa2.png

Salsa3DのSpeech Propertiesの値を調整する(※この画像に示されている値が適切とは限らない)
AttachSalsa3.png

AudioSourceのAudioClipにサンプルボイスのオーディオファイルをセットする
AttachSalsa4.png

VRMモデルのゲームオブジェクトに以下のようなスクリプトをアタッチする

VRMLipSyncSalsaTarget.cs
using UnityEngine;
using VRM;
using CrazyMinnow.SALSA;

public class VRMLipSyncSalsaTarget : MonoBehaviour
{
    public BlendShapePreset SaySmallBlendShapeKey = BlendShapePreset.I;
    public BlendShapePreset SayMediumBlendShapeKey = BlendShapePreset.E;
    public BlendShapePreset SayLargeBlendShapeKey = BlendShapePreset.O;

    public VRMBlendShapeProxy blendShapeProxy;
    public Salsa3D salsa3d;

    void Start()
    {
        if(salsa3d == null)
        {
            salsa3d = GetComponent<Salsa3D>();
        }
        if(blendShapeProxy == null)
        {
            blendShapeProxy = GetComponent<VRMBlendShapeProxy>();
        }
    }

    void Update()
    {
        if((salsa3d != null) && (blendShapeProxy != null))
        {
            SalsaBlendAmounts blendAmounts = salsa3d.sayAmount;

            // Change the value range before set the BlendShape values.
            float saySmall = blendAmounts.saySmall / 100.0f;
            float sayMedium = blendAmounts.sayMedium / 100.0f;
            float sayLarge = blendAmounts.sayLarge / 100.0f;

            blendShapeProxy.SetValue(SaySmallBlendShapeKey, saySmall);
            blendShapeProxy.SetValue(SayMediumBlendShapeKey, sayMedium);
            blendShapeProxy.SetValue(SayLargeBlendShapeKey, sayLarge);
        }
    }
}

AttachSalsaTarget2.png

スクリプトをアタッチした後、BlendShapeProxyとSalsa3Dをセットする
AttachSalsaTarget3.png

シーンを再生すると、サンプルボイスに合わせたリップシンクが実現できているはずです。
LipSync.gif

マイク入力に合わせたリップシンク

上記で作成したシーンの「SampleVoice」というオブジェクトに以下のようなスクリプトをアタッチします。
シーンを再生するとマイク入力に合わせたリップシンクが実現できているはずです。ただし、マイク入力の音がミュートされずに再生されて聞こえてしまいます。

MicrophoneInputSource.cs
using UnityEngine;
using UnityEngine.Audio;

[RequireComponent(typeof(AudioSource))]
public class MicrophoneInputSource : MonoBehaviour
{
    private AudioSource audioSource;

    void Start()
    {
        if(Microphone.devices.Length > 0)
        {
            audioSource = GetComponent<AudioSource>();
            audioSource.clip = Microphone.Start(null, true, 3, 44100);
            audioSource.loop = true;

            // Use the Attenuation of AudioMixer to mute microphone input.
            audioSource.mute = false;

            while((Microphone.GetPosition("") <= 0)){}
            audioSource.Play();
        }
    }

    void Update()
    {
        if(audioSource != null)
        {
            float volume = GetAveragedVolume();
            Debug.Log("Mic input volume: " + volume);
        }
    }

    float GetAveragedVolume()
    { 
        float[] data = new float[256];
        float a = 0;
        audioSource.GetOutputData(data,0);
        foreach(float s in data)
        {
            a += Mathf.Abs(s);
        }
        return a / 256.0f;
    }
}

AttachMicInput.png

マイク入力に合わせたリップシンクの改良

AudioMixerを新規作成してMasterのAttenuationを-80.00dBに設定する
AudioMixer.png

上記で作成・設定したAudioMixerのMasterをAudioClipのOutputにセットする
AudioMixer2.png

これで、マイク入力の音が聞こえない状態で、マイク入力に合わせたリップシンクが実現できているはずです。

最初は「audioSource.mute = true」で実現できるかと思っていましたが、その方法だとマイク入力の音が取得されず、リップシンクできませんでした。
UnityでAudioSource.Mute=trueにした時にMicrophone(マイク)から音量が取得できない時の対処法」という記事を参考にしたら上手くいきました。

おわりに

SALSAというアセットを使ってVRMモデルをリップシンクさせてみました。
サンプルプロジェクトに含まれているSALSAを使ったシーンは、iPadで実行してリップシンクが動くことを確認しました。
この記事では説明していませんが、VRMモデルをランタイムロードする場合は手順の一部(「VRMLipSyncSalsaTargetをアタッチする」など)を実行時に処理します。

参考情報