【レポート】クライアントエンジニアが語る、ゲーム開発技術勉強会! - KLab TECH Meetup #2 -
リアルタイムリップシンク実現 高品質x省エネの実装手法紹介
3人目は陶鋭さんの登壇です。
陶鋭(タオ・ルイ)/KLab株式会社 クライアントエンジニア。早稲田大学卒。2013年に新卒でKLabへ入社。
陶さんの発表は音声と同期してアニメーション口を動かす「リップシンク」に取り組んだ事例について。まず、リップシンクの手法にはどのようなものがあるのか、下記の4点を紹介しました。
- ボイスの内容に関係なく、口を開けて閉じる単純なアニメーションを再生する
- 音声再生時の音量により口のサイズを動的に制御する
(音量が大きければ口を大きくし、音量が小さければ口を小さくする) - 事前にアニメーションを数パターン用意し、再生時に適切なパターンへ切り替える
- ボイスに完璧に合わせたアニメーションを事前に用意し、ボイス再生と別軸でそのアニメーションを再生する
手法1から4になるほどリアル度が高まりますが、陶さんは「手法2」と「手法3」をベースに実装することを決めます。その理由は次の通りです。
「口の動きを音声にできるだけ合わせて高品質なアニメーションにしたいと考えました。でも、『アニメ風』の動きがよかったので、リアルすぎないようにしたかったんです。
また、すでに『CRI』というサウンドミドルウェアを使っていて、ボイスファイルは数千個もある状態でした。ですから、制作の作業や関連データを少なめにする必要があったのです。
そこで、『手法1』と『手法4』は選択肢から外しました」(陶さん)
陶さんは以下を実装の基本方針に定めました。
- 時間軸の波形をベースにする
- ボイス再生時には常に口を開ける状態にする
- 音節を切り替えるタイミングで閉じる演出を1回入れる
(音節切替時には、音量が一瞬下がるため) - 口を開けるサイズは音量の大きさで決める
「『CRI』には周波数軸で音声を表現できるスペクトラムアナライザという機能があります。ここからある時点の全周波数域の振幅が取得できますが、全時点でのデータをまとめると時間軸の波形を構築できるわけです。また、低周波だったり、高周波だったりとボイスの特徴も読み取れます。さらに、処理負荷が少ないというメリットもあります。
実際には、まずあるボイスの平均振幅値を257回取得して時間軸の波形を作成しました。この波形から振幅の変化が激しいことがわかりました。
実装にあたっては、口の状態を『閉じている』『半分開いている』『全部開いている』の3パターンを用意しました。そして波形の『谷』となっている部分は音節が切り替わるタイミングなので『閉じている』アニメーションを挟みます。また、波形が『峰』となっている箇所は『全部開いている』アニメーションに遷移させます。この切替の制御には、ソースコード側ではステートマシーンを使用しました」(陶さん)
しかし、「この方法では3つの問題点があった」と陶さんは振り返りました。その問題点は次の通りです。
- 振幅の幅が小さな「峰」と「谷」で口の切り替えが速すぎてしまう
- 実際には何もしゃべっていない部分でも雑音を拾ってしまう関係で口が開いてしまう
- 『半分開いている』『全部開いている』の明確な境界がない
この問題を解決するために陶さんは次の3つの閾値を導入しました。
振幅変化閾値
その閾値内であれば変化がなかったことにする最大値無言閾値
その閾値以下であればしゃべっていないとみなす最小値口全開閾値
その閾値以上であれば『全部開いている』状態に変化させる最小値
「キャラクターが同じであれば、声の特徴はそれほど変わりません。ですから、ボイスファイルごとに閾値を設定するのではなく、だいたいの場合は共通の閾値でOKです」(陶さん)
最後に陶さんは、実用に当たっての下記Tipsを共有して、講演を終了しました。
- 無口なキャラなど通常の口の動きと異なる場合には閾値にレシオをかける
- 「ふむー」「ん?」など口を閉じたまま音を出したい場合は、リップシンクを無効にして個別にアニメーションを用意する
- だいたいの場合は共通の閾値の設定でOKだが、声優さんの状態やボイス収録の条件によってはうまく機能しないので、ボイスファイルごとに閾値設定ができるようにしておく
アセットバンドルに関する試行錯誤の話
4人目は真子拓馬さんの登壇です。
真子拓馬(まなこ・たくま)/KLab株式会社 インフラマネジメント部 エンジニア。2011年にKLabへ入社。
アセットバンドルに関連して真子さんが行なう業務は主に2つの領域に分かれています。ひとつは、アセットを交通整理し、ビルドしてデプロイしたり、どのタイミングでステージングしてリリースするのかを管理すること。もうひとつが様々な問題を調査することです。
真子さんはまずこの問題調査にあたって、全体となるアセットバンドル最適化の基本ポリシーを紹介します。
- ダウンロードサイズはできるだけ小さく
- ファイル数はなるべく増えすぎないように
- ロード処理は最小限に
- メモリ使用量はなるべく抑える
次に真子さんは、実際の問題調査の事例を6つ列挙します。
事例1:iOSで特定のアセットバンドルの「Prefab」をロードした場合のみ、クラッシュする
「アプリバイナリで使用していない『Unity』のコンポーネントをアセットバンドルで使用しているとクラッシュするという問題がありました。このときは実機でデバッグすることですぐに原因が究明しました。
ただ、このように実機でしか発生しない問題もあるので、実機デバッグの手法も知らなければいけません」(真子さん)
事例2:一部エフェクトの「Prefab」のサイズが大きい
「フォントのデータが中に入っているためにサイズが大きくなってしまっていました。これは元々フォントがアセットバンドルの対象ではなかったため、個々の『Prefab』にデータが入ってしまったため発生した問題です。
そのためフォントをアセットバンドルの対象として依存関係を持たせました」(真子さん)
事例3:多言語対応時に、誤った言語のリソースを参照してしまう
「以前は『国内版だけで使うアセット』『全言語の海外版で使うアセット』『特定の言語の海外版のみで使うアセット』の3パターンがありました。まずそれぞれのアセットを分類し、『特定の言語の海外版のみで使うアセット』同士の参照はバリデータで不正な参照として弾くようにしました。
バリデータとは、アセットのレギュレーション違反や不正な状態をチェックするプログラムのことですね。ビルド中は動作に支障があるクリティカルなものだけチェックして、軽度の違反はスルーしています。そして、ステージングの際には厳密なチェックを行います。
チェック項目は『命名規則』『不正なリソース参照』『不正なインポート設定』などがありますね」(真子さん)
事例4:意図しないアセットバンドルの差分が毎回発生する
「差分が発生するのはビルド中に『Prefab』へのコンポーネントアタッチ、テクスチャのインポート設定を行なっていたことが原因でした。これらを全て事前に行い、ビルド中はアセットに変更を入れないようにすることで解決します。以前はワークアラウンドとして『Library』を毎回削除していましたが、それが不要になりました」(真子さん)
事例5:ビルド時間が肥大化
「現在進行形の問題です。ジョブ一本化の弊害で、全アセットを毎回インポートしている部分がネックとなってしまっており、ビルドに50分ほどの時間が掛かっていました。
しかし、事例4の対策で『Library』の毎回削除が不要になったため、インポート時間は30分くらいまで減っています」(真子さん)
事例6:ロードしたリソースがメモリに残り続ける
「アセットバンドルからロードしたリソースの参照を紐付けたオブジェクトが、『Destroy』されているのにも関わらず解放されない問題がありました。
詳細な原因は不明ですが、『Destroy』の際に明示的に参照に『null』を代入して対策を行いました」(真子さん)
この6つの事例からアセットバンドル管理には、下記の3つが求められると真子さんは指摘します。
- 切り分け職人に徹する
- 必要なツールに精通する
- 制作チームと密に連携する
後半はビルドパイプラインについてです。
「ビルドパイプラインとは、今回はアセットバンドルをビルドしてデプロイするジョブフローを意味しています。KLabではプロジェクトごとに構築・運用しています。
共通基盤化の話はあったのですが、プロジェクト側で柔軟な対応ができないデメリットが大きく、その話は実現しませんでした。ですから、毎回プロジェクトごとに試行錯誤している状況ですね。どのプロジェクトでも1人か2人程度が担当するため、属人化しやすいことも課題のひとつです」(真子さん)
最後に真子さんは「リポジトリ分割」「マスタデータ生成」「アセットバンドル構成」「ジョブ構成」「増加する『Jenkins』パラメータへの対応」「生成物のバージョン管理」の観点から取り組みを説明し、講演を終了しました。
次のページ :
リズムゲームのためのパフォーマンスチューニング