はじめに
VRMモデルのリップシンクを実装してみました。ARKitを使ったアプリにも対応できるように、iOS端末で動作するリップシンクが必要だったので、SALSAというアセットを使いました。
サンプルプロジェクトこちら ⇒ VRMLipSyncSample
VRMモデルをランタイムロードする場合のサンプルとかOVRLipSyncを使ったサンプルも一緒に入ってます。
オーディオファイルの再生に合わせたリップシンク
SALSAのインポート
アセットストアからSALSAをインポートする
シーン作成
リップシンクの設定
空のゲームオブジェクトを作成して「Salsa3D」をアタッチする。
ゲームオブジェクトの名前は「SampleVoice」などに変更しておく。
Salsa3DのSpeech Propertiesの値を調整する(※この画像に示されている値が適切とは限らない)
AudioSourceのAudioClipにサンプルボイスのオーディオファイルをセットする
VRMモデルのゲームオブジェクトに以下のようなスクリプトをアタッチする
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);
}
}
}
スクリプトをアタッチした後、BlendShapeProxyとSalsa3Dをセットする
シーンを再生すると、サンプルボイスに合わせたリップシンクが実現できているはずです。
マイク入力に合わせたリップシンク
上記で作成したシーンの「SampleVoice」というオブジェクトに以下のようなスクリプトをアタッチします。
シーンを再生するとマイク入力に合わせたリップシンクが実現できているはずです。ただし、マイク入力の音がミュートされずに再生されて聞こえてしまいます。
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;
}
}
マイク入力に合わせたリップシンクの改良
AudioMixerを新規作成してMasterのAttenuationを-80.00dBに設定する
上記で作成・設定したAudioMixerのMasterをAudioClipのOutputにセットする
これで、マイク入力の音が聞こえない状態で、マイク入力に合わせたリップシンクが実現できているはずです。
最初は「audioSource.mute = true」で実現できるかと思っていましたが、その方法だとマイク入力の音が取得されず、リップシンクできませんでした。
「UnityでAudioSource.Mute=trueにした時にMicrophone(マイク)から音量が取得できない時の対処法」という記事を参考にしたら上手くいきました。
おわりに
SALSAというアセットを使ってVRMモデルをリップシンクさせてみました。
サンプルプロジェクトに含まれているSALSAを使ったシーンは、iPadで実行してリップシンクが動くことを確認しました。
この記事では説明していませんが、VRMモデルをランタイムロードする場合は手順の一部(「VRMLipSyncSalsaTargetをアタッチする」など)を実行時に処理します。