はじめに
Oculus 社が Unity 向けのリップシンクライブラリ、OVRLipSync をリリースしました。
- https://developer.oculus.com/downloads/audio/1.0.0-beta/Oculus_OVRLipSync_for_Unity_5/
- Oculus Launch Avatar Lip Sync Plugin for Unity - Road to VR
- Oculus、音声からVR内のアバターの口の動きを表現するプラグインを公開 | MoguraVR
オーディオストリーム(録音音源、マイク入力)から口形素(Visemes、ビジーム)と呼ばれる特定の発音に対応する唇や顔の形状を求め、それを予めモデルに仕込まれたモーフのブレンド率に反映させるプラグインです。利用している口形素は 15 種類の「sil(無音), PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, ih, oh, ou」で、例えば「PP」は「ポップコーン」と発音するときの「ポ」の最初の形状、「FF」は「フィッシュ」と発音するときの「フィ」の最初の形状になります。これらの記号は MPEG-4 の国際標準規格で定められているようですが、このあたりの詳細は勉強してもアレなので、取り敢えずはこれを一通り抑えればそれっぽい口の動きをする発音-唇形状の便利な関係、と覚えておけば良いと思います。
プラグインの形式としては、コア部分がネイティブで作られていて、それをラップしたスクリプト群が .unitypackage に収められています。対応プラットフォームとしては、Win 32/64bit、Mac OS X、Android(Gear VR)で、ライセンスは Audio SDK と同様のものです。
サードパーティ製ライブラリおよびライセンスについては同梱の THIRD_PARTY_NOTICES.txt
をご参照下さい。
ダウンロード
以下よりダウンロードできます。2016/02/15 現在バージョンは 1.0.1 になっています。
解凍するとライセンスやドキュメントの PDF、.unitypackage が含まれています。.unitypackage をインポートすればサンプルシーンを含む一連のアセットがインポートされます。
サンプルシーン
v.1.0.0 時では 3 種類のサンプルシーンがありましたが、v.1.0.1 のタイミングでアセットの整理がなされ、サンプルシーンは LipSync_Demo
の 1 つのみとなりました。
LipSync_Demo
モーフが仕込まれた唇を動かすデモです。
- キーマッピング
- 1: 唇 - マイク
- 2: ロボット - マイク
- 3: 唇 - 録音データ
- 4: ロボット - 録音データ
- L: マイク音声のループバック
- ←: ゲイン小
- →: ゲイン大
- D: 認識した口形素のデバッグ表示
MorphTest_MorphTarget_Girl (v.1.0.0 のみ)
Twitter でよく見かけたこの子(head_girl
ちゃん)は v.1.0.1 でいなくなってしまいました、残念。
MorphTest_TextureFlip (v.1.0.0 のみ)
テクスチャベースで作成されたロボットです。こちらは v.1.0.1 でも引き続き LipSync_Demo
シーンで見ることが出来ます。
処理のざっくりとした流れ
AudioSource
で再生しているオーディオを OnAudioFilterRead()
で引っ張ってきて DLL 側の認識のアルゴリズムに渡し、得られた結果を 3D モデルなら skinnedMeshRenderer
の BlendShape に突っ込み、2D テクスチャならテクスチャを変更する、という感じです。
スクリプト個別概要
同時にリップシンクするモデルが複数体いても良いように、コンテキストという単位で ID を割り振って認識を分割しています。呼ばれるヘルパや操作用のスクリプトもあるので思ったより沢山ファイルがありますが、重要なのは以下の 5 つです。
OVRLipSync
- DLL のラッパースクリプト
Awake()
を使ったシングルトン形式でシーンに 1 つだけ配置- static なメンバを通じてコンテキストの生成・破棄やデータを渡して認識を行う
OVRLipSyncContext
- 内部ではコンテキストの生成・破棄、そして
OnAudioFilterRead()
を使って再生中のオーディオのデータを取得して DLL へ投げたりするコア部分- コンテキストは C# 側へは
uint
の ID で返ってきて、これを通じて DLL 側で操作を行う
- コンテキストは C# 側へは
- モーフまたはテクスチャでリップシンクを行う対象の GameObject 1 つにつき 1 つずつアタッチ
GetCurrentPhonemeFrame()
で口形素の情報などを含むovrLipSyncFrame
を取ってこれる
OVRLipSyncContextMorphTarget
GetCurrentPhonemeFrame()
で取ってきた口形素をskinnedMeshRenderer.SetBlendShapeWeight()
に与えるOVRLipSyncContext
と同じ GameObject へアタッチ
OVRLipSyncContextTextureFlip
- 最も近い(値の大きい)口形素のテクスチャにマテリアルを変更
OVRLipSyncContext
と同じ GameObject へアタッチ
OVRLipSyncMicInput
- AudioSource にファイルを与える代わりに
Microphone
クラスを用いて得られたマイク入力を利用する OVRLipSyncContext
と同じ GameObject へアタッチ
ざっくりとこんな感じです。
ユニティちゃんで利用してみる
実際にモデルに適用して使い方を見ていきましょう。本来ならば全ての口形素のモーフを仕込んだモデルを用意するのが望ましいですが、未だそういったモデルはないと思うので、取り敢えず母音(あいうえお)のみ対応したモデルということでユニティちゃんを例に見ていこうと思います。
ユニティちゃんアセットと OVRLipSync アセットを同じプロジェクトにインポートしておきます。ユニティちゃんは Skinned Mesh Renderer が分割されているため、階層の下の方にある MTH_DEF
をセットする必要があります。また、標準のアニメーションにモーフが含まれているので、これを排除するためにちょっとスクリプトをいじります。
デモ
© UTJ/UCL
セットアップ
- 空の GameObject を作成して
OVRLipSync
コンポーネントをアタッチ unitychan
Prefab をシーンに追加unitychan
GameObject に以下のコンポーネントをアタッチOVRLipSyncContext
OVRLipSyncMorphTarget
OVRLipSyncMorphTarget
のSkinned Mesh Renderer
フィールドに Reference > Hips > Spine > Spine1 > Spine2 > Neck > Head 下にあるMTH_DEF
をセットOVRLipSyncMorphTarget
のViseme To Blend Targets
の母音部分を対象のモーフのインデックス番号を指定(スクショ参照)OVRLipSyncMorphTarget
のUpdate()
をLateUpdate()
に変更(下記コード参照)AudioSource
のAudioClip
に適当な音声を指定OVRLipSyncContext
のAudio Mute
のチェックを外す
コンポーネント配置
OVRLipSyncMorphTarget
のパラメタ
口形素 aa, ih, ou, E, oh を、あいうえおのモーフのインデックスにします。子音に対応するモーフはないので取り敢えず適当なインデックスを割り当てています(ここでは SMILE1)。aa(10), E(11), ih(12), oh(13), ou(14) となっています。
ちなみにユニティちゃんの口(MTH_DEF
)のメッシュの BlendShape は以下のようになっています(0 以外の数値は笑みを消すために適当に私の方で調整した値を入れています)。
あいうえおが 6 ~ 10 のモーフに割り当てられているので、その値を代入しているわけです。この値が直接 SkinnedMeshRenderer.SetBlendShapeWeight()
に与えるインデックス番号になります。
スクリプトの変更
アニメーション後に OVRLipSync の結果を反映させるために LateUpdate()
タイミングに変更します。
// 変更前(95 行目) void Update () { if((lipsyncContext != null) && (skinnedMeshRenderer != null)) ... // 変更後 void LateUpdate () { if((lipsyncContext != null) && (skinnedMeshRenderer != null)) ...
マイク入力
OVRLipSyncMicInput
コンポーネントをアタッチするだけで動作します。Mic Control
は Constant Speak
にしておくとずっと認識し続けるので分かりやすいと思います。口が余り動かない場合は Gain
パラメタを大きくしてみてください。また、ハウリングが起こる場合は OVRLipSyncContext
の Audio Mute
をチェックして自分の声が出力されないようにすると良いです。
上記例では利用例の紹介のために unitychan
に AudioSource
を割り当てていましたが、口の位置から音が聞こえるように本来ならば口もしくは口の下の階層に空の GameObject を作成し、そこに諸々のコンポーネントを割り当てた方が良いと思います。
MMD4Mecanim で利用してみる
MMD4Mecanim を利用したモデルでも勿論出来ます。表情豊かなTda式Appendミクさんをお借りしてやってみます。
デモ
セットアップ
基本的には先程のユニティちゃんと同じです。モーフが多いのが素晴らしいですね。モーフは MMD4Mecanim の Morph タブから確認し、SkinnedMeshRenderer コンポーネントの BlendShapes
からインデックス番号を確認、そして OVRLipSyncMorphTarget
の Viseme To Blend Targets
にそのインデックス番号を書き込みます。順番は「sil(無音), PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, ih, oh, ou」です。子音の口形素は「ん」などのモーフに適当に割り当てています。
その他気になる点
ライブラリと提供しているにも関わらず、コアのコンポーネント群にデバッグ用の機能(キー押下やクリックで音声のループバックの切り替え等)が含まれているのは結構面倒で一番気になります。
後、Editor スクリプトを使った UI でなくプレーンなスクリプトから作られた UI のため、結構面倒な所が多いのでこの辺りも今後改善されることに期待したいです。
おわりに
私も Unity 向けのリップシンクライブラリを作っていてコア部分以外は大体同じことをしていたのですが、OVRLipSync の方がキャリブも不要で認識精度も高く、また子音に対応する形状にも対応していることから、はるかに自然なリップシンクになります。。
VR のコミュニケーション手段としてだけではなく、幅広く応用できそうで素晴らしいアセットを公開して下さった Oculus 社に感謝です。