Negipoyoc

はじめに

これまで幾度となく使用してきた「EasyBabiniku」というVtuberシステムの技術解説をしようと思います。

自分はVtuberを動かすシステムの業務経験がありますが、そこでわかったのは Vtuberシステムの自作は決して難しいことではなく、実は小さく簡単なこだわりの集合 だということです。

それを今回の記事では伝えたいなと思っております。やっぱり、自分が思う最高のkawaiiを目指すには自分で作るしか無いのだよな…。

EasyBabinikuとは

以下のように、ハンドトラッキングと表情生成などでニアちゃんがいきいきと動くシステムです。

応用

実は、EasyBabinikuはBitValleyの「バ美肉キャリア相談おじさん」という企画展示のために作ったものでしたが、予想以上に幅が効いてていろんなところで応用しています。

バ美肉ナイトクラブでの受付嬢

バ美肉ナイトクラブというPANORA主催(弊社協力)のイベントで受付嬢をさせていただきました。

QRコードをディスプレイ備え付けのWebカメラに対してかざすことでPeatixを介して受付ができるというフローを開発し、「バ美肉受付」を達成しました。

プログラミング作業配信

EasyBabinikuがそのままこういう用途にも応用できることに気づきました。

システム概要

簡単に言えば3Dモデル用のバストアップ映像撮影用システムです。

  • VR機材を使わない。
  • なるべく環境に左右されにくい設計にする。(無線デバイスは使わない)
  • 低コストながらもキャラクターの表現力は豊かであることを目指す。

という感じです。

使用機材は マイク・PS4コントローラー・LeapMotion の3点で、ポイントとしてバストアップに限定することで、実装に注力するべき箇所を上半身のみに集中し 最終的な表現力を高めつつ工数を小さくしました。

技術解説

以前の記事(バ美肉登壇をしてみた)では、簡単にしか触れていませんでしたが、今回はソフトウェア方面を中心にどのように実装したかを中心に書いていきます。

頭部操作(PS4コントローラー)

PS4コントローラーを採用したのは、以下の3点が理由です。

  • 左手で操作できる入力が多い。
  • 意図した表情作りが可能になる(フェイストラッキングを使いたくなかった。)
  • 有線である。

UnityでPS4コントローラーを利用する。

Rewiredというアセットを使用しています。

Rewiredについての解説はこちらの記事(アセット触ってみたシリーズ:多種多様なコントローラーに対応しよう!「Rewired」)にとても詳しく書かれておりますので自分が書くまでも無いでしょう。


余談ですが、最初はPS4コントローラーだけでやろうとしていました。

PS4コントローラーって結構便利で、ジャイロの値が取れるし、タッチパネルのタッチ位置をVector2で取れるし…という感じなのですよね。

コントローラーだけでやる場合は、頭の傾きはコントローラーの傾き、手を振るのはタッチパネル上で指をシュッシュッってやる感じで…。まぁかわいくならなかったので辞めたんですが。

Final IKの採用

最初はコントローラーのスティックの傾きで直接首の傾きを制御しようとしていました。

しかしどうやってもかわいくなりませんでした。というのも、人間はどこかを向いた時首だけが動く生き物ではありません。目や瞼が動いたり胴を少し捻ったり傾いたりします。

ならばもうFinalIK採用して、そこらへんの小さな動きを自動で制御してもらえばよいという結論に至りました。


FinalIKのHeadTargetとして適当なGameObjectを指定しています。

そしてそのObjectはAnimationCurveとLerpを利用することによって、よってカクカクと動かないようにしています。人間はカクカク動かない生き物なので。

該当の処理は大体こんな感じになってます。

            //スティック入力がある時だけ呼ばれる処理です。
            //vという変数はスティックの縦横の傾き度合いを示すVector2
            InputBroker.Receive<Vector2>().Subscribe(v =>
                {
                    var targetPos = new Vector3(-v.x / 10, 1.5f, 0);
                    var targetEuler = Quaternion.Euler(new Vector3(-20 * v.y, 15 * v.x, 20 * v.x));

                    t.localPosition = Vector3.Lerp(t.localPosition, targetPos, Time.deltaTime * speed * curve.Evaluate(Mathf.Abs(v.x)));
                    t.localRotation = Quaternion.Lerp(t.localRotation, targetEuler, Time.deltaTime * speed * curve.Evaluate(Mathf.Abs(v.x) - 0.25f));
                });

表情操作

十字キーを押した時に表情が操作されます。

表情の生成にはEditor拡張とScriptableObjectによる制御を用いています。(下画像参照)

左がEditor拡張です。BlendShapeを持つキャラクターを読み込むと、BlendShapeが列挙されSliderで値を調整できます。
最終的に表情セットがScriptableObjectとして出力されます。

右が生成されたScriptableObjectのコントローラーへの割当です。事前に入力手法を定義しておき、Input.Get(KeyCode)でその表情セットを再現します。
当然人間は1フレームで表情が切り替わると不気味すぎるので、Lerpで徐々に変えてます (0.15秒かけて変わる)


Scriptable Objectで管理した理由ですが、

  • ボタンに対して表情の差し替えが楽
  • 単純にわかりやすい(ハードコーディングすると調整が地獄)
  • 「Max」パラメータによる最大値の制御をハードコーディングしなくて済む(例えば「20%目を閉じる表情」を作りやすい)

(ここの設計は割と面倒なことをしているので、別途ScriptableObjectを用いた設計みたいな感じでブログ記事を書きたいです。)

もう少しこだわるところがあるとすれば、 人間の目は閉じるのは一瞬だけど開けるときは少し時間がかかるので、その差をプログラムで再現してやる ことですね。

ボタンで表情をいじるシステムを作るが必ず一度やる失敗として、瞬きと表情が重複することです。
例えば、瞬きと笑顔が重複すると目が埋まったりして変な顔になります。

これを防ぐためにやっていることを図解しました。(シンプルだと思います)

表情操作とまばたきを別モジュールにしてやることで、分岐を楽にしてるだけです。

目線操作

目線操作ということで「カメラ目線モード」と「キョロキョロモード」の2つのモードが存在します。

  • カメラ目線モード:どこを向いていても目線はカメラの方を見るモード
  • キョロキョロモード:向いた方向に対して目線を移動させるモード

特に後者に対しては、よく見るとわかりますが 首よりも若干早く目線を動かす というタイミングで動かしています。

本当は 「大きく目線を動かした時は、一度まばたきをはさんで目線が移動する」という処理を加えるとより生々しく なります。いずれ実装します。


とはいえ、上記については大体XVIさんのUnite講演で得た知識から実装してみたという感じです。動画が上がってるのでこちらを見ると良いと思います。

【Unite Tokyo 2018】AniCast!東雲めぐちゃんの魔法ができるまで

手の操作(LeapMotion)

LeapMotionを採用した理由は、以下の3点です。映るのはバストアップのみになるため必然的に手と顔に目線が行くことになります。なので精度の高いトラッキングは必要不可欠でした。

  • 低コスト(8000円)で安定し精度の高いハンドトラッキングが可能である。
  • 有線である。
  • 撮影時、周りの環境に左右されにくい。

指の曲げ同期

ここらへんは、公式で配布されているLeapMotionSDKにおんぶにだっこです。

ここを参考にしました。LeapMotion Orionでキャラクターの手を動かす

注意点として、まずそのままRiggedFingerを各指に割り当てても向きがおかしくなりがちということです。 LeapMotionのサンプルの手とボーンの回転軸が合ってない可能性があるので、その場合自前で直さなくてはなりません。(ニアちゃんは直しました)

上の画像のModelFingerPointingとModelPalmFacingのところです。ニアちゃんの場合は、全てそれぞれ(0,1,0)、(0,0,-1)でした。

Final IKによる手の位置操作

LeapMotionによって手の位置が取れるので上の記事を参考に設定しました。

しかし問題として、 LeapMotionは簡単にトラッキングが外れてしまう ことを考慮しなければいけません。

この問題は、キャラクター表現との兼ね合いで解決が難しいところです。
例えば、「トラッキングが外れたら腕の位置はそのまま」ということをしても良いです。というか普通に作ったらそうなります。
ただ、 トラッキングが外れる時は往々にしておかしな位置に手がある + 指の形も適当 なので映像で見える所に放置すると最悪 です。

というわけで、今回はトラッキングが外れたら画面外のところに手のデフォルト位置を設定し、そこにフォールバックするようにしました。

このDefault Positionというところがそうです。

また 瞬時にここに手が移動するとおかしいので、Lerpを使って徐々に向かう ようにしています。

LipSync(マイク)

AniLipSyncを使用しています。

OVRLipSyncをベースにリミテッドアニメっぽいリップシンクを実現するためのライブラリです。

ニアちゃんのようなアニメ調のキャラクターの口パクを表現をするにあたり、なめらかな口の変化は不要です。
アニメのようなわかりやすい口パクのほうが割り切っていてスッキリします。それを実現するライブラリです。

動画ではボイチェンしていますが、LipSyncで必要な音声解析のソースとして、声は地声(VT-4 DRY)を割り当てています。

キャラクターを魅力的に見せる工夫

ここまで技術的にどう動かすかということにフォーカスしてきました。しかしこれだけでは不十分です。

というのも人間もキャラクターでも可愛く見えない角度や見せ方は存在します。それを避けなければいけません。

イラストでは「嘘をつく構図」ということがこれに該当するのだと思います。幸いにも人間に比べてキャラクターでは嘘をつきやすいです。 キャラクターはそれだけでかわいくなるものではなく、魅力的な見せ方の上で成り立ってる のではないかと思います。

それではいくつかEasyBabinikuで採用している手法を紹介していきます。

カメラのFoVを10~15にする

Fov=Field of View = 画角です。
(見せたい内容に依りますが) 簡単なキャラクター表現では画角が浅いほうがかわいくなります。 以下の画像を見てください。

EasyBabinikuではFoVは13で、Unityのデフォルトでは60なので比べてみました。十中八九左のほうが可愛いと答えるのではないでしょうか?

実際のレンズで例えると

  • FoV:13 =望遠レンズ
  • Fov:60 =広角(または標準)レンズ

となり、これらのレンズの特性として、

  • 望遠レンズ:映す範囲は小さくなるが、遠近感が少なくなり平面的に映る
  • 広角レンズ:映す範囲が広い。一方で遠近感が強調され歪みが生じやすい

となっています。

今回は用途として、 ニアちゃんを正面からイラストのように平面的に見せたいです。そのため 望遠レンズで顔やツインテールの距離感をつぶして平面的に見せる ようにしました。
そのかいあって、上の画像のように近くから写しても歪みが少なくかわいいニアちゃんをお届けできるようになりました。

PostProcessingによるお化粧

Unityのライティングでは限界があるので、PostProcessingで画をお化粧していきます。

以下は比較画像です。PostProcessing前の段階では全体の色が暗くくすんでいます。

  • 「Color Grading > Post-Exposure(EV)」で露光量を上げ、全体の色を明るくしています。
  • 「Color Grading > Saturation」で彩度を上げています。
  • 「Bloom」で光をあふれさせ、ニアちゃんにふわっとした雰囲気を与えています。

とはいえ、まだ髪の下部分が(影色も相まって)少し暗いので、まだ改善の余地があるかもしれません。継続して開発していきます。

Shader調整

ユニティちゃんトゥーンシェーダー2.0(UTS2)を使用しています。

お手軽にトゥーンなキャラクター表現ができるシェーダなのでEasyBabinikuに限らず重宝しております。

ここで1つポイントになるのが、 影色に灰色は使わない ということです。以下の図を見てください。

Pixivなどに行って、イラストの影に注目すると無彩色影を乗算しただけではない絵が多く見受けられます。それを再現していきます。

また服と皮膚と髪などでパキッとした影を作るか、ぼやけた影を作るか、という作り分けも重要になってきます。
ここは自分も説明するほどまだわかってないので詳しく書きませんが、光が当たった物質が光を吸収するのか反射するのかで作り分ける必要があります。

呼吸をする

呼吸を実装すると生々しくなりますが、先人が呼吸を再現するスクリプトを既に作成しありがたいことに配布しております。

http://mebiustos.hatenablog.com/entry/2015/08/31/201902

ニアちゃんを生き生きさせたいので、このスクリプトを使用させていただいております。実装するとこんな感じ。

人間の体は完全に静止することはない

人間の体は完全に静止しないという所までこだわると良いですね。

↑の呼吸スクリプトで前後に若干の揺れが生じているため、ここでは 僅かな横揺れ を与えます。
理由は、 正面から見ても静止してないことが十分に伝わって ほしいからです。呼吸だけでは少し分かりづらいのです。

該当の実装はこんな感じです。IKの頭のTargetに対して以下のようなSin関数を用いた揺れを与えます。

                    //vという変数はPS4コントローラースティックのxy軸の傾きを表します。

                    //もしスティック入力がない時は揺れます
                    if (Mathf.Abs(v.x) < 0.01f && Mathf.Abs(v.y) < 0.01f)
                    {
                        var x = Mathf.Sin(Time.time * 100 * Mathf.Deg2Rad) * 0.01f;
                        targetPos = new Vector3(x, 1.5f, 0);
                        targetEuler = Quaternion.Euler(new Vector3(0, 30 * x, 40 * x));
                    }

まとめ

というわけでEasyBabinikuで使われた技術を棚卸ししてみました。

人間の動きに注目したり、どんな絵を作りたいのかによってゴールに近いもの(イラストとか)を注意深く観察することが大事だと思います。それを可能にする実装はググると出てきますので。

だいぶざっくり書いたので、もしここがもっと詳しく知りたいなどあれば @CST_negi までご連絡ください。

(EasyBabinikuを使ってみたい企業さんなどがもしいれば、Twitterやメールでご連絡ください~(評価版をお渡しできますので))


次はVRMとかScriptableObjectとかそういう話で記事書きたいなぁと思いますが、この前作ったスマホで完結する6DoFなVRキャラクタービューアのような小ネタも技術の棚卸しが済んでない…。

そもそもニアちゃんと共に作りたいものが無限にあるので、本当に早くいろんなものを作って紹介して記事を書きたいです。一生そういうことばっかりしていく。