はじめに
2018/10/14のアンリアルフェスにて、UE4でVTuberをする方法について講演しました。
その中で「Oculus LipSyncはいいぞ!」ムーブをキメました(p94以降)が…具体的にどのように導入・使用するのかについて触れなかったので記事にまとめようと思います。
Oculus LipSyncについて
Oculus LipSync ( 以下、OVRLipSync ) はOculus社が提供しているリップシンク(口パク)ライブラリで、2018/10/5にリリースされた v1.30.0 でUE4に対応しました。そして、2018/11現在(v.1.30.0)ではUE4.20に対応しており、サンプルプロジェクトも用意されています(v.1.30.0、UE4.21でも一応動きました)。
このライブラリを使うことで、マイク音声または事前に収録した音声データ( SoundWavアセット )からリップシンク情報を取得することができます。つまり、セリフに合わせたキャラクタの口パクアニメーションを簡単に作ることができます!
( 表情用のMorph TargetをキャラのSkeletalMeshに用意する必要はありますが… )
サンプルでは、OVRLipSyncが検出した口形素( Viseme )をSkeletal MeshのMorph Targetに渡すBPが用意されています。Visemeは音声から唇の形状を分類したもので、全部で15パターン用意されています。( ただ一般的によく用いられるのは母音(a, i , u, e, o)なので、全てのパターンを使い分ける必要はないかと思います。)
Visemeに関するドキュメント:Viseme Reference
以降は、サンプルに含まれるBPについて簡単にですが解説します。
公式ドキュメント
https://developer.oculus.com/documentation/audiosdk/latest/concepts/book-audio-ovrlipsync-unreal/
https://developer.oculus.com/documentation/audiosdk/latest/concepts/audio-ovrlipsync-using-unreal/
https://developer.oculus.com/documentation/audiosdk/latest/concepts/audio-ovrlipsync-precomputed-unreal/
https://developer.oculus.com/documentation/audiosdk/latest/concepts/audio-ovrlipsync-sample-unreal/
OC5におけるOVRLipSyncに関する講演
サンプルのセットアップ・動作確認
まずはサンプルを動かしてみましょう。上述のダウンロードページからサンプルプロジェクトをダウンロードできます。
ダウンロード後、"LipSync\UnrealPlugin\OVRLipSyncDemo" にOVRLipSyncDemo.uproject を実行してみましょう!…以下のようなウィンドウが出てきます(;´∀`)
残念ながら、OVRLipSync (v1.30.0)を使うためにプラグインをビルドする必要があります。「はい」を選択しビルドを走らせましょう。また、もしVisual Studioが入っていない場合はビルドに必要なのでインストールしましょう。
ビルドが完了すると自動的にサンプルプロジェクトが立ち上がります。やったね!
サンプルには以下の2つのレベルが用意されています。初回起動時に開かれるのはLiveCaptureレベルです。
- “Content/Maps/LiveCapture”:マイク音声を使用するサンプル
- “Content/Maps/CannedPlayback” 音声データを使用するサンプル
具体的な解説に行く前にサンプルが正常に動作するか確認しましょう!Playボタンを実行して、マイクに向かって喋ってみましょう。声に合わせてモデルが口パクしたら正常に動作しています。
もし正常に動作していない場合はOutput Logを確認してみましょう。以下の文字列が出力されていた場合、PCにマイクがデバイスとして認識されていません。「windows マイク デバイス 設定」などで検索して設定して下さい。
LogVoiceCapture: Warning: Failed to create capture device 0x88780078
LogTemp: Error: Can't create voice capture
もし音声データ(SoundWav)を持ってる方は CannedPlaybackレベルも試してみましょう。持ってないけど試したい方はサンプルボイスがプロジェクトにあります
“Content/Audio/vox_lp_01”
まずはSoundWavアセットを右クリックし、Generate LipSyncSequenceを実行すると、同じフォルダに OVRLipSync用のアセットが生成されます。
そして、このアセットをCannedPlaybackレベルに配置されているPlaybackBlueprint の OVRLipSyncPlaybackコンポーネントの Sequenceプロパティに設定すればOKです。
この状態でPlayを押すと、指定した音声が再生されつつ口パクしてくれます。
サンプルが正常に動作したという前提で、各レベルで使われているBPについて解説します。といっても、結構シンプルな作りになってるのでご安心ください!
LiveCaptureBlueprint について
LiveCaptureレベルの場合は”Content/Blueprints/LiveCaptureBlueprint” が 口パクの検出・適用処理をしています。
OVRLipSyncコンポーネント
LiveCaptureBlueprintで使われているOVRLipSyncコンポーネントは、その名の通りOVRLipSyncの機能を使うためのコンポーネントです。用意されているプロパティは以下の通り。
Sample Rate:弄る際は慎重に。適切な値ではない場合は正しく処理されません。基本はデフォルト値でいい気がします。
Provider Kind:OVRLipSyncの検出処理の設定を変えているのですが…詳細な解説が今の所ない(はず)です。おそらく、v 1.30.0 の笑い声検出機能(β)を使いたいなら Enhanced with Laughter、使いたくないなら Enhancedでいい気がします
Enable Hardware Acceleration: …デフォルトの有効のままでいいのかなと(正直な所、どんな効果があるかわからない!たぶんサウンド屋さんが知ってるはず!)
BeginPlay
StartノードでOVRLip Syncによる検出機能を開始し、その後にWindowsの場合は voice.SilenceDetectionThreshold 0.0コマンドを使ってます。これはWindowsは マイクの無音判定用の閾値 である voice.SilenceDetectionThreshold に 0より上の値が設定されているため、OVRLipSyncによる検出の妨げになるからです。ちなみに、なぜ無音判定処理がデフォルトで入っているかというと、OVRLipSyncはUE4のボイスチャット用のマイク入力機能を使用しているからです。
On Visemes Ready
On Visemes Readyは「OVRLipSync内で一定間隔毎に行われる音声解析処理」の実行後に呼ばれるイベントです。そのため、On Visemes ReadyのタイミングでOvrLipSyncの最新の検出結果を取得・利用できます。ちなみに、デフォルトでは20ms間隔で呼ばれます。間隔変えたい方は、OVRLipSyncLiveActorComponent.cpp の UOVRLipSyncActorComponent::VoiceCaptureTimerRate を変更する必要があります(デフォルトは0.2f)。
On Visemes Readyでは、OVRLipSyncが推定した各Visemeの値を Skeletal Meshの各MorphTargetに反映する処理を行っています。
Get Visemes NamesノードはドキュメントにあるVisemeの名前の配列 を返します。そして、Get Visimesノードは各Visemeの検出値を返します。これらをForEachLoopを使って順にSkeletal MeshのMorphTargetに渡してます。あと、Get Laughter Scoreノードで笑い声の推定結果の値もSkeletal Meshに渡しています。
以上!
ね?シンプルでしょ?
PlaybackBlueprint について
CannedPlaybackレベルの場合は、”Content/Blueprints/PlaybackBlueprint” が口パクの検出・適用処理をしています。更にシンプルな作りです。
まずボイスチャットの機能を使わないので、コンソールコマンドを使っていません。加えて、AssignVisemesToMorphTargetsノードというものだけが使われてます。…実はこの関数の中身は LiveCaptureBlueprintにおける各visemeの設定処理と全く同じです(つまり、笑い声の判定処理はありません)。
void UOVRLipSyncActorComponentBase::AssignVisemesToMorphTargets(USkeletalMeshComponent *Mesh, const TArray<FString> &MorphTargetNames) { for (int cnt = 0; cnt < MorphTargetNames.Num(); cnt++) { Mesh->SetMorphTarget(FName(*MorphTargetNames[cnt]), Visemes[cnt]); } }
じゃあ、AssignVisemesToMorphTargetsノードだけでいいじゃん!と思うかもしれませんが…残念ながらそうはいきません。
サンプルの問題点
これはサンプルで使用しているメッシュのMorphTargetです。見ての通り、名前と順番がOVRLipSyncが持つVisemesと全く同じです。この状態にしないと、AssignVisemesToMorphTargetsノードは正常に動作しません
更に言うなら、LiveCaptureBlueprintにおける実装も動作しません!!!
つまり、サンプルにあるBPの実装をそのまま別モデルに流用することはできない ということです。と言っても、対応は簡単です!
長くなりましたので、一旦ここまで。
次回の記事では自分のプロジェクトでOVRLipSyncを導入する手順やサンプル以外のモデルに適用する際の手順について解説します
つづく