Vive + Unity で VTuber を実装するための Tips

しばらく VTuber 周りのエンジニアリングをやってますが、いろいろ情報が散っていてちょっと集約しておきたくなったので Tips としてまとめておこうという記事です。
より基礎的なことについてはようてんさんの「[バーチャルYouTuberのやり方](http://greety.sakura.ne.jp/redo/2018/01/how-to-vtuber.html)」[^1]を参照してください。
基本的に Vive + Unity + SteamVR + VRIK を想定しています。
スクショがないのは筆者の不精です。

#1. CameraRig の下にモデルを置かない
ようてんさんの記事からもリンクされている「[Unity＋Vive＋MMD＋VRIKで、キズナアイちゃんになりきりVR](https://qiita.com/halne369/items/daadf0e00330a1019830)」では

> まず、SteamVR > Prefabs > CameraRig の階層下に、キズナアイのキャラモデルを配置します。

と書かれているわけですがやめた方がいいです。
理由はモデルとアクターの身長合わせがやりにくくなるから。モデルを CameraRig の外においておけば、 CameraRig のスケールを変えることでアクターとモデルの身長差を補正できます[^2]。
モデルと CameraRig の親として「VROrigin」とでも名前を付けた親オブジェクトを作っておくと、まとめて移動したりするのにいいです。

#2.VRIK の各パラメータの意味を知りたい。
「[Final IK の VRIK の Solver にある各値の説明](https://qiita.com/_karukaru_/items/b74bb5bdf08f5de32d0e)」をどうぞ。

#3.キャラモデル用のライティングを分離する
VTuber のキャラはほぼトゥーンシェーダーを使っています。つまり光はいい感じに見える向きと強さで常に当てたいわけです。そうすると背景にいろいろ照明を置いたとき、その照明がキャラに当たると色が白く飛んでしまったり、影が不本意な形になってしまったり、します。
Light の Culling Mask で、照明を適用するレイヤーを設定できるので、キャラモデルと背景オブジェクトを別レイヤーに分けてそれぞれ照明を別個に設定しましょう。
ただし [Indirect lighting が Culling Mask を無視するというバグ](https://issuetracker.unity3d.com/issues/cullingmask-lightmapping-ignores-lights-culling-mask)が少なくとも 2018.2 時点ではあるので lightmap を bake するときには注意です。

#4.VRIKで膝の曲がり方がおかしくなるのを直す
VRIK で膝が内側に曲がってXの字になって困ることってありませんか？　私はありました。
Swivel Offset などで頑張って補正しても変にねじれてきれいにならなくて困っていたんですが、結論としては VRIK が当たる前の時点で膝を正しく軽く曲げておくと、きれいに曲がるようになります。
VRIK が関節の曲げ方向をモデルの曲がりをもとに推測するのですが、美少女の脚線美って膝が内側に入りがちになるためにそちらに向かって曲がってしまう……ということのようです。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">IKの特徴として、「ボーンが曲がりやすい方向に曲がる」っていうものがあるよ。この画像例は肘のボーンだけど、他のボーンも大体同じのはず。Blenderなら編集モードでボーンの端を移動させるだけで、ウエイトは触らなくて大丈夫。<br>（でも手首は気合で不自然にならない手首moveをしてる人が多いと思う） <a href="https://t.co/kZqvZ1POyi">pic.twitter.com/kZqvZ1POyi</a></p>&mdash; おぐら@VDRAW ver1.2.1公開 (@OGOG_Ogura) <a href="https://twitter.com/OGOG_Ogura/status/1000299234820734976?ref_src=twsrc%5Etfw">2018年5月26日</a></blockquote>
こういうはなしもあるので、 VRIK に限らず IK 一般でそうなのかもしれません。


#5.AniLipSync で Oculus Lipsync Unity (OVRLipSync) の遅延を軽減する
「[AniLipSyncを用いてOVRLipSyncの遅延（レイテンシ）を軽減する](https://qiita.com/kaikiofkaiki/items/8c3e396abaee014460ef)」をどうぞ。
バージョン上がりましたがこの通りにいきます。

#6.モデルのスケールを変更する
VTuber が不思議の国のアリスよろしくスケールを変えたいことがあるのかどうかわかりませんが……でも「[あいえるらいぶ #003](https://youtu.be/qWhV50m0F7E?t=1614)」での TRPG とか VR らしくていいですよね。
「[VRIK でスケールを小さくすると大股開きになる問題を解決する](https://qiita.com/chiepomme/items/aef42df2d46aa0f79fbe)」が参考になります。ただこれのスクリプト中で補正しているパラメータからは「Min Head Height」が抜けているので注意。
あと、揺れもののコライダーがスケール変更に合わせたサイズ変更に対応していない場合はそちらも補正する必要がありますね。

#7.モーフを UniVRM の BlendShapeClip で制御する
モーフとかシェイプキーとかブレンドシェイプとかいろんな呼ばれ方がありますが、Unityで扱うときに何が面倒って、名前がついているにもかかわらず、インデックス番号で指定させられることですね。あと、複数のモーフを組み合わせて表情を定義したいということもよくあります。
VRM 形式ではそれを BlendShapeClip という形で管理する仕組みがあるわけですが、この仕組みは VRM でなくても使えます。便利です。
モデルに「VRMBlendShapeProxy」を付けて、定義としてアセットの「BlendShapeAvatar」を用意して適用する感じです。
設定の仕方は[このあたりとか](https://dwango.github.io/vrm/univrm/components/univrm_blendshape/#blendshapeavatar)を参考にするといいでしょう。
リップシンクはどうすればいいか？　この[スクリプト](https://gist.github.com/toiro/0c7fbc38fa6eda18fafce6ec9b7d8417)をどうぞ。



#8.VIVE Input Utility で Vive Tracker を割り当て可能に
VIVE で全身トラッキングなどで複数トラッカーを運用しようとしたときに、トラッカーが入れ替わってひどい目にあうのはあるあるだと思います。デバイスIDで割り当てるなどいろいろ方法はありますが、「[VIVE Input Utility](https://assetstore.unity.com/packages/tools/integration/vive-input-utility-64219)」だと GUI で動的に割り当てが行えるようになります。
詳細は「[Embedded Binding Interface](https://github.com/ViveSoftware/ViveInputUtility-Unity/wiki/Embedded-Binding-Interface)」を見てください。デフォルトでは「右Shift + B」で設定GUIを呼び出せます。
ほかにもコントローラーのインプット周りがいろいろ扱いやすくなったりと、結構便利です。
欠点は公式ドキュメント含めて資料に乏しいこと[^3]、「[SteamVRのInputEventをUniRxで扱えるようにするAsset](https://github.com/TORISOUP/SteamVRInputObservables)」が使えなくなること[^4]でしょうか。

[^1]: 半年以上たってるのにいまだにこれ以上の記事がないんですよね。その更新版が[この同人誌のpdf](https://github.com/youten/howto-create-vr-contents3)ではありますけれど、オープンソースに寄せていたり、でちょっと癖があるので……
[^2]: ようてんさんの記事中の時点で既に外に出してありますね……
[^3]: Example はあるのでそれを読み解くしかない。あと[wiki](https://github.com/ViveSoftware/ViveInputUtility-Unity/wiki)。
[^4]: もっとも素の状態での Input 周りの取り回しはよくなるのでそんなに問題はないかもしれないですが、すでに使ってしまっていると困るところです。