キャラクターを喋らせる!OVR Lip Syncの導入から問題発生時の解決法まで

王子様のささやき朗読VR

はじめに

イケメンテックラボで 「王子様のささやき朗読VR」の メインエンジニアを務めている、茨田と申します。

第3回目になる今回は、キャラクターを実際の人間のように音声と同期して口を動かせるようになる、Oculus Lipsync Unity(OVRLipSync)導入について、ご紹介させていただきたいと思います。

▼以前の記事はこちらから

王子様のささやき朗読VR」で、最初にプランナーから貰ったオーダーは、「2人きり」で、「雰囲気ある」場所で、「囁かれたい」というものでした。

キャラクターの音声が聞こえるのに、口が動いていないと、現実との差が大きくコンテンツに集中できません。そこで OVRLipSync を使用してどのように「囁かれる」体験を実現したのかをご紹介します。

今回説明で扱うキャラクター王子様ではなく、どなたでも比較的入手しやすいユニティちゃん(© UTJ/UCL)使って説明させていただきます。

そもそもリップシンクってなんだろう?

すごく簡単に言うと、シンクロするからリップシンクと言います。

音声にどんな成分(母音など)が含まれているかを解析して音に合わせて最初に設定してあった口の形状合成しながら切り替える技術で、今回使う、OVRLipSyncでは、全部で15形状設定ができます。

日本語の場合は「あかさたなはまやらわ」「あ」「いきしちに・・・」の列は「い」「うくすつぬ・・・」の列は「う」みたいな形で、アイウエオ母音分の形状5つ作って適応するとかなり喋っているような口の動きを再現できます。

試した環境とダウンロード方法

今回はUnity2018.2.12を使用して動作検証を行っています。
 ※「王子様のささやき朗読VR開発時には当時の最新のUnityのバージョンを追っていた為、Unity2017.4.0f1Unity2018.1.0f2にて動作検証を行っていました。

まず最初に必要なアセットなどをダウンロードしてきます。
今回はLipSyncを3Dのキャラクターに適応するためのunitypackageと、実際に喋らせるキャラクターのユニティちゃんをDLします。

Oculus Lipsync Unity(OVRLipSync)の準備

Oculus Lipsync Unityを以下からver1.30.0をダウンロードします。
 ※開発時は1.16.0を利用していました
https://developer.oculus.com/downloads/package/oculus-lipsync-unity/

ダウンロードしたファイルを解凍すると、UnityPluginの中にOVRLipSync.unitypackageが入っています。

このファイルをAsset > ImportPackage > CustomPackageからインポートしましょう。

Asset配下が以下の画像の様になれば成功です。

ユニティちゃんの準備

次にユニティちゃんを下記URLからダウンロードします。
http://unity-chan.com/contents/guideline/
ダウンロードするにはユニティちゃんライセンス同意必要なので、内容を確認した上で、データをダウンロードするボタンを押しましょう。

ユニティちゃん 3Dモデルデータ」から最新(検証時ver1.2.1)のデータをダウンロードします。

ダウンロードした、UnityChan_1_2_1.unitypackageUnityで、Asset > ImportPackage > CustomPackageからインポートすることで、ユニティちゃんを使えるようになります。

Asset配下が上記の画像の様になれば成功です。

ユニティちゃんにLipSyncを適応する適応手順

インポートが終わったユニティちゃんOVRLipSyncを適応して喋らせて見ましょう。

OVRLipSyncの配置

まず最初に、Hierarchy右クリックして、Create Emptyを選択し、空のGameObjectを作成します。

作成したGameObject選択してInspectorからAddComponentを押すと、ウインドウが出るので、OVRと検索をかけて、OVRLipSync選択して、コンポーネントアタッチします。

あとで見た時に分かりやすい様にGameObject名前OVRLipSyncに変更しておきます。

ユニティちゃんプレハブをシーンに追加する

ProjectAssets/UnityChan/Prefabs/の中にある、unitychan.prefabHierarchyドラックアンドドロップ(以降D&Dと呼びます)します。

これでシーン上の原点にユニティちゃんが表示されました。

ユニティちゃんへのコンポーネントの適応

ユニティちゃんの口のパーツは、
Character1_Reference > Character1_Hips > Character1_Spine > Character1_Spine1 > Character1_Spine2 > Character1_Neck > Character1_Head の下にあるMTH_DEFになります。

口のパーツあたりから音を鳴らしたいので、Character1_Headを右クリックしてCreateEmptyで空のGameObjectを作ります。

作ったGameObjectOVRLipSyncContextOVRLipSyncContextMorphTargetAddComponentします。
 ※OVRLipSyncContextを追加すると自動音源になるAudioSourceも追加されます
GameObjectの名前もLipSyncSoundに変更しておきます。

OVRLipSyncとモデルの口の動きを繋ぎ込む

OVRLipSyncContextMorphTargetSkinnedMeshRenderer動かす口のパーツを設定します。

先ほども言った通り、口のパーツは、
Character1_Reference > Character1_Hips > Character1_Spine > Character1_Spine1 > Character1_Spine2 > Character1_Neck > Character1_Head の下にある MTH_DEF を使います。
 ※ユニティちゃん以外を使う場合、顔(口)のSkinned Mesh Rendererを探して設定すること

HierarchyにあるMTH_DEFOVRLipSyncContextMorphTargetSkinnedMeshRendererD&Dします。

次にOVRLipSyncContextMorphTargetVisemeToBlendTargetsを設定していきます。
VisemeToBlendTargets口の形状解析した音声つなぎ合わせる部分になります。
日本語の母音は下の方にある[aa]=あ、[E]=え、[ih]=い、[oh]=お、[ou]=うと設定することで実現できます。
初期では下の画像のように順番に数字が入っているだけなので、ユニティちゃんのBlendShapesに合わせて設定する必要があります。

BlendShapes口の動き表情などの各形状に対して、0から100の数値が入力できるようになっています。
複数の形状合成することで、口の動き表情などの複雑な形状再現できるようになります。
各形状インデックス番号Skinned Mesh RendererBlendShapes一番上を0として下の画像の赤字の様順番に振られいます。

ユニティちゃんの場合、インデックス番号の6がa、7がi、8がu、9がe、10がoとなっているため、VisemeToBlendTargets[aa]=6、[E]=9、[ih]=7、[oh]=10、[ou]=8を設定します。他の数値は0に設定しておきます。

これでOVRLipSyncモデルの口の動き繋ぎこみは完了です。

再生したい音声の設定

再生したい音声を先ほど作ったLipSyncSoundに自動的に追加されたAudioSourceに設定します。

ユニティちゃんの場合、Assets/UnityChan/Voice/の中に音声ファイルが同梱されています。その中にある、unity-chan_voice_list.txtを参考に好きな音声を再生して見ましょう。

今回は口の動きがわかりやすいように、長めの音声、univ1341を設定して見ます。
AudioSourceAudioClipuniv1341D&Dします。

次に上記で設定したAudioSourceOVRLipSyncContext中にあるAudioSourceD&Dします。

これで音声設定できました。

再生するための問題対処

このまま再生すると以下のようなエラーが出てしまいます。

Array index (15) is out of bounds (size=11)
UnityEngine.SkinnedMeshRenderer:SetBlendShapeWeight(Int32, Single)
OVRLipSyncContextMorphTarget:SetLaughterToMorphTarget(Frame) (at Assets/Oculus/LipSync/Scripts/OVRLipSyncContextMorphTarget.cs:191)
OVRLipSyncContextMorphTarget:Update() (at Assets/Oculus/LipSync/Scripts/OVRLipSyncContextMorphTarget.cs:129)

これはOVRLipSyncContextMorphTargetLaughterBlendTargetに設定されている数値に対して、SkinnedMeshRendererの形状が少なかったため起きているエラーです。
ユニティちゃんの場合、LaughterBlendTarget10に設定すると起きなくなります。
 ※恐らくSkinned Mesh RendererBlendShapesが0から10のため、最大値より大きい数字を指定するとエラーが起きる

次にスクリプトの修正をします。
Assets/Oculus/LipSync/Scripts/にある、OVRLipSyncContextMorphTarget.cs119行前後(OVRLipSyncのバージョンによって変わります)にある、Update()をLateUpdate()に変更します。

    void Update ()
    {
        if((lipsyncContext != null) && (skinnedMeshRenderer != null))
        {
            // get the current viseme frame
            OVRLipSync.Frame frame = lipsyncContext.GetCurrentPhonemeFrame();
・・・
    void LateUpdate ()
    {
        if((lipsyncContext != null) && (skinnedMeshRenderer != null))
        {
            // get the current viseme frame
            OVRLipSync.Frame frame = lipsyncContext.GetCurrentPhonemeFrame();
・・・

LateUpdate関数Update関数後に実行される関数で、Update関数で必要なデータを作り、LateUpdate関数で、口の形状を変化させる流れが作れます。
表情のアニメーションUpdateで行なっている場合、リップシンクLateUpdateで行うことで、表情変化後口の動きを適応できるようになります。

次に、AudioSourcePlayOnAwake以外のチェックが入っていないかを確認しましょう。筆者の環境では、BypassEffectsにチェックが入っていると、音声は流れますが、口が動きませんでした。

これで音を解析して(音を鳴らさずに)口が動くようになりました。
口の動き同時に音を鳴らす場合は、OVRLipSyncContextAudioLoopbackチェックを入れましょう。

カメラの位置調整

最後にユニティちゃんの口元が見やすい位置カメラを移動しましょう。

HierarchyからMainCameraを選択して、InspectorTransformを変更します。
PositionをX:0 Y:1.3 Z:0.45に設定してカメラを移動させます。
RotateをX:0 Y:180 Z:0に設定してカメラを回転させます。

これでユニティちゃんの顔周辺にカメラが近づきました。

実際に再生している様子

開発時に起きた問題と解決方法

王子様のささやき朗読VR」を開発時(OVRLipSync1.16.0とUnity2017.4.0f1を使用)起きた問題解決方法を記載しておきます。
 ※最新版を使うと解決されているものが殆どです。理由があって昔のものを使う場合、参考にしてください

最初の問題と解決方法

問題:
再生時シーンに最初からユニティちゃんのプレハブが置いてあると、音声だけ再生されて、口パクが発生しない。

解決方法:
根本原因は不明ですが、以下を行うことで回避できるようになります。
空のGameObjectに一つスクリプトを挟んで、スクリプトのStart関数でInstantiateを行い、ユニティちゃんを生成すると口パクするようになりました。

根本原因を少し詳しく調べたメモ

OVRLipSync.Frame frame = lipsyncContext.GetCurrentPhonemeFrame();

上記のframe値が入っていない
本来はframeの値が毎フレーム更新されるはずで、値が全て0なのはおかしい
もしかすると、初期化の順番などのタイミングでうまく行っていないのかもしれない。

原因が判明した時メモ

原因はOVRPhonemeContext.Start ERROR: Could not create Phoneme context.というエラーが出ていたせいだった。
エラーなのになぜかワーニングで出力されており、なかなか気がつかなかったが、OVRLipSyncが初期化に失敗していたようで、一度RemoveComponentして、再度OVRLipSyncスクリプトをアタッチすることで口パクするようになった。
Unityを起動するたびに起きる可能性があるため、注意が必要。

起動時も回避する方法(最終的な解決方法)

根本原因OVRLipSyncOVRLipSyncContextスクリプトの実行順番だった。
プロジェクト設定からスクリプトの実行順番を設定することで回避することができるようになります。
Edit > ProjectSettings > ScriptExecutionOrderで設定を開いて、Inspector+ボタンからOVRLipSyncOVRLipSyncContextを追加します。
OVRLipSyncは-1OVRLipSyncContextは100に設定することで実行順番を制御できます。
 ※数字が小さいほうが先に実行されるので-1と1などでも大丈夫です
この設定により、最初からシーン上にモデルを置いて、LipSyncがつけてあっても、ちゃんとLipSyncが動作するようになりました。

おわりに

今回は、ユニティちゃん音声合わせて口を動かすために、OVRLipSyncを設定する方法と、開発時に実際に起きた問題や解決方法を説明させていただきました。

このように、3Dのキャラクター音声などを入手できれば、個人でもキャラクターを自由に喋らせることができます。

今回の記事では扱いませんでしたが、OVRLipSyncでは、マイクから入力した音声を使ったLipSyncや、2DキャラクターでもLipSyncさせることが可能です。

是非、自分好みのキャラクター自由に喋らせてみてください。

次回以降も、「王子様のささやき朗読VR」で使った技術Tipsなどを紹介させていただきますので、よろしくお願いいたします。

GCREST DEVELOPERS BLOGをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む