SilentClubを作る過程で得たワールド制作に関する知見

この記事は[VRChat Advent Calendar 2018](https://qiita.com/advent-calendar/2018/vrchat)の9日目の記事になります。

昨日の記事は@kleus_balutさんによる、[VRChatで使われている3Dオーディオ技術](https://qiita.com/kleus_balut/items/0164494c7f4c222c59fd)でした。

## はじめに

SilentClubのオーナーのryouです。
今回のアドベントカレンダーでは[SilentClub](https://www.doll-profile.com/vrchat_silent_club/)を作る過程で得た知見、使用した技術、細かいTips等を雑多に書こうと思います。


### SilentClubとは

![キャプチャ2.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/8ceeb0dc-c467-b649-76c5-4c2f55b54ab6.jpeg)


基本的に毎週土曜日の21:00～、Friend+で開いているボイスチャット強制ミュートワールドになります。

運営は[ryou](https://twitter.com/ryou_Ein)（技術担当。主にUnity）と[silinder](https://twitter.com/spilca_snowboot)（デザイン担当。主にBlender）の二人でやっています。



## PostProcessingStackのBloomに関して


### Unlitアバターが光らないよう調整する

Bloomですが、何も考えずに使用してしまうとUnlitシェーダーを使用したアバターが通常より明るくなってしまいます。一部ワールドでUnlitなアバターを使うと発光して見える経験をした人は結構いるはずです。

例を挙げましょう。以下画像はUnlitなUnityちゃんとEmissionを設定したQuadになります。

![img_bloom_02_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/46cc1cf3-f785-7c1b-5979-21da5e05041c.jpeg)

これにBloomを設定してみます。（わかりやすいようにIntensityを強めにしています。）

![img_bloom_02_02.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/a90091a3-2fb7-62ec-dcdb-64f4bb6c9b65.jpeg)

Quadが光ったのと同時に、Unityちゃんも光ってしまいました。このように、Bloomは設定に気をつけないとUnlitアバターが光るようになってしまいます。

では、どのように設定すれば光らないかと言うと、

![img_bloom_02_03.jpg](https://qiita-image-store.s3.amazonaws.com/0/319781/baf994b0-60c5-c832-c50c-58625d509481.jpeg)

Bloomの設定項目内`Brightness Response`に濃いグレーの範囲があると思うのですが、これの左端がグレーの縦線より右側になるようにしましょう。

濃いグレーの範囲の位置は`Threshold`の数値を大きくすることで右に移動します。`Soft Knee`がデフォルトの数値の場合は、`1.375`程度にしておけば良い感じの位置に来ると思います。

![img_bloom_hogehoge.jpg](https://qiita-image-store.s3.amazonaws.com/0/319781/0a894cb9-92a1-917a-4da3-39b21d5c975d.jpeg)

こう設定すると次の画像のようにアバターが光らなくなります。

![img_bloom_02_04.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/7cb8b6f7-60af-c84e-8076-032fbbdf13c7.jpeg)

Thresholdを調整した影響でQuadの光も弱くなっていますが、それはQuadのEmissionを強くすることで調整しましょう。


### 設定は控えめにする

Bloomに関する補足ですが、設定は基本的に控えめにしたほうがいいと思います。これは別に「Bloomマシマシだと初心者っぽくてダサい」といった理由ではありません。

Bloomの設定を強くしてしまうと、ユーザーのアバターが想定より輝いてしまい、結果的に周囲のユーザーに迷惑をかけてしまう、気まずくなってしまう状況が出てしまうからです。

当然Bloomが控えめであれば、「ちょっと光り方が物足りないな」という状況は多くなりますが、周囲に迷惑をかけて気まずくなってしまうよりよっぽど良いと考えています。

なので、ワールドのエフェクトをより輝かせたい場合は基本的にBloomを強くするのではなく、エフェクト自体のEmissionを強めにする等で対応するのがベターかと思います。





## RealTimeReflectionProbeを使用した動画・エフェクトの反射

![20181208021659_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/319781/9efc15df-1069-e07e-8dc5-6841d4cec62e.jpeg)


画像のように、ダンスホール内のステージに動画とエフェクトがリアルタイムに反射するようにしています。

これはReflectionProbeをRealTimeにして実現しています。ゴーストクラブでも動画が反射しているスクリーンショットを目にしましたが、おそらく同じ方法だと思います。ReflectionProbeを利用して動画の反射を表現することで、アバターの金属部分にも動画が反射するようになり、そこはかとなくイケてる感じになります。

ただ、RealTimeなReflectionProbeはかなり重い機能なので、単純に配置してしまうとめちゃくちゃ負荷がかかってしまいます。

そのため必要最小限の物のみを反射させるようにして、負荷を抑えています。

具体的には、反射させるオブジェクト用のレイヤー（今回は`DanceHallDisplay`という名前）を追加し、`Culling Mask`でそのレイヤーを指定しています。

![img_reflection_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/3e9933ef-e9c2-21c6-c9f3-a705c56fa5c4.jpeg)


この方法で必要最小限のオブジェクトのみ反射するようにして、RealTimeReflectionProbe負荷を出来るだけ抑えましょう。実装の際は負荷計測もしっかりして、処理負荷が許容範囲内に収まっていることも確認したほうが良いかと思います。









## 負荷の計測

負荷軽減作業をする際、まず鉄則として「CPU/GPU/Memory/Network等のどこがボトルネックになっているのか調べる」という事をしなければなりません。

その理由に関しては、まろんさんがツイートされていた以下の動画でわかりやすくまとめられています。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">おはろーん☆<br><br>今日から <a href="https://twitter.com/hashtag/%E6%9C%9DUnity?src=hash&amp;ref_src=twsrc%5Etfw">#朝Unity</a> を始めるよー！<br>どんどん勉強してこーね！ <a href="https://t.co/RX8vJs2LPz">pic.twitter.com/RX8vJs2LPz</a></p>&mdash; まろん@アカデミアVtuber (@Maron_Vtuber) <a href="https://twitter.com/Maron_Vtuber/status/1064287285124845568?ref_src=twsrc%5Etfw">2018年11月18日</a></blockquote>

動画で言われているように、例えば「CPUの処理が追いついていないせいでFPSが下がっているのに、GPUの負荷軽減作業をしても意味がない」という事になります。負荷計測をしないとこういうことが頻繁に起こってしまいます。

また負荷計測を行わなかった場合、行った作業が効果を発揮したのか、発揮したのであればどの程度負荷が軽減されたのかといったことがわかりません。「実はほとんど負荷が減っていないのに作業にめちゃくちゃ時間がかかった」や「負荷が大して減らないのにワールドのクオリティがかなり下がる」などといったパターンが結構あります。負荷計測を行いそのような作業がわかれば、次に活かすことが出来ます。

他にも例えば、アバターの負荷に関する話になりますが、[DynamicBoneのGravityが処理負荷に影響しない](http://www.doll-profile.com/blog/131)ということも負荷計測によって判明しました。以前はちらほら「DynamicBoneのGravityはめちゃくちゃ重い」という噂が立っていたのですが、負荷計測をきちんと行うことで噂に惑わされずに済みます。

### 計測方法

CPU/GPUの負荷に処理時間に関してはSteamVRの`フレームタイミング`という機能を使う事で計測が出来ます。

`SteamVR > 設定 > 動画`に`フレームタイミングを表示`というボタンがあるので、それをクリックすることでCPU/GPUの処理時間を確認することが出来ます。

![img_frametiming_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/b959e6b8-afa0-6d57-c3a5-2b73f0cde671.jpeg)

![img_frametiming_02.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/74570f27-dd90-4161-b79b-0c1e1c7a1197.jpeg)

グラフの横軸は時間（フレーム）、縦軸は各フレームにおけるCPU/GPUの処理時間になります。90FPSを実現するにはグラフが11ms以下、45FPSの場合は22ms以下に収まってる必要があります。

左下の`ヘッドセットに表示`にチェックを入れると、アバターの右腕にこのグラフが表示されるようになるのでHMDをつけてVRChatをプレイしながら処理負荷を確認することが出来ます。

フレームタイミングを利用して、CPU/GPUどちらの処理がボトルネックになっているのかを調べ、その負荷を減らすような負荷軽減作業をするようにしましょう。







## 音楽がこもったりこもらなかったりを切り替える

SilentClubに来て頂いた方であれば気づいた方もいるかもしれませんが、ダンスホール周辺では音がこもって聞こえるようにしています。

どう実装しているかと言うと、AudioSourceに`Audio Low Pass Filter`というコンポーネントを付けることで実装しています。

![img_lowpass_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/c38029b2-6871-f2b7-0cb7-a6430dfca73b.jpeg)

ダンスホールの入り口にこのコンポーネントの有効・無効を切り替えるトリガーを配置することで、ダンスホール内外でこもったりこもらなかったりするようにしています。

音のこもり具合は`Cutoff Frequency`を弄ることで調整出来るので同じことをする場合は良い感じに調整して下さい。（数値を下げるほどこもります）




## ジャンプしたり物を投げたりした際によく飛ぶようにする

基本的にジャンプする、物を投げる等の動作をした場合は若干大げさなくらい飛ぶようにしたほうがユーザーとしては気持ちがいいです。

デフォルトの設定では全然飛ばないのでSilentClubでは以下のように重力設定を弱めに、`VRC_Player Mods`のjumpPowerを高めに設定することでよく飛ぶようにしています。

![img_jump_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/4ae295aa-f6bc-c4e6-a9e6-83deaaa3fc2d.jpeg)

![img_jump_02.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/084614fc-0bcf-5215-5598-3388192b93a1.jpeg)




## ダンスホールの前方でぐるぐる回ってるエフェクト

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">楽しい <a href="https://t.co/1xE2jfYltV">pic.twitter.com/1xE2jfYltV</a></p>&mdash; ryou (@ryou_Ein) <a href="https://twitter.com/ryou_Ein/status/1017741043217752064?ref_src=twsrc%5Etfw">2018年7月13日</a></blockquote>

これ。シェーダー書いて実装してます。

[GitHub](https://github.com/ryouEin/unity-circle-fx)にプロジェクトとUnityPackageを置いてますので、もし使用したい場合はそちらをどうぞ。













## リアルタイムライトを使わない事で、Cubedシェーダーのアバターの顔に陰が出来ないように

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Silent Clubは「僕の顔に影が入ると可愛くない」という僕の独善的理由により一部を除いてリアルタイムライトは置いてないです<br>その代わりライトプローブちゃんと置いてるからゆるして</p>&mdash; スピルカ|Silent Club 12/8(土) OPEN (@spilca_snowboot) <a href="https://twitter.com/spilca_snowboot/status/1042315150290636800?ref_src=twsrc%5Etfw">2018年9月19日</a></blockquote>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">これに関してもうちょっと細かい話をすると、ライトプローブでも陰影は出ます。なので通常であればライトプローブのみでリアルタイムライト無しでも顔に陰影は出来ます。（続く） <a href="https://t.co/YXlVxcRixX">https://t.co/YXlVxcRixX</a></p>&mdash; ryou (@ryou_Ein) <a href="https://twitter.com/ryou_Ein/status/1042404831208497152?ref_src=twsrc%5Etfw">2018年9月19日</a></blockquote>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ただCubedシェーダーのFlatLitToonに限って言えばライトプローブによる陰影が（ほぼ）出ないような作りになっており、ワールドをライトプローブメインで作れば（当時）シェアの高かったFlatLitToonで顔に陰影が出来なくなるから良くない？という判断の結果ほぼライトプローブのみという形にしました。</p>&mdash; ryou (@ryou_Ein) <a href="https://twitter.com/ryou_Ein/status/1042404966504095744?ref_src=twsrc%5Etfw">2018年9月19日</a></blockquote>

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">当然顔以外の部分の陰影も出なくなるのでそこはトレードオフですが。顔が命とそっちを重視した結果ということで…</p>&mdash; ryou (@ryou_Ein) <a href="https://twitter.com/ryou_Ein/status/1042405340250173440?ref_src=twsrc%5Etfw">2018年9月19日</a></blockquote>


この一連のツイートが全てなのですが、SilentClubでは一部を除きリアルタイムライトを使っていません。ワールド全体を照らすディレクショナルライトもありません。

これは負荷軽減的な意味もあるのですが、ツイートの通り当時大きなシェアを占めていたであろうCubedシェーダーでリアルタイムライトによる強い陰影が出来なくなるというのが一番の理由になっています。

![img_face_shadow_02_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/19828762-daa3-6192-3a66-c33c31ed92a3.jpeg)


この画像はかなり悪いパターンですが、似たような状況は目にしたことがあると思います。これはCubedシェーダーの`FlatLitToon`のデフォルト設定を適用した状態のマテリアルにIntensityが1.0のディレクショナルライトを当てた状態になります。リアルタイムライトを配置するとこのように「折角写真を撮ったけど顔に変な影が入ってしまった」という状況に結構な頻度で遭遇してしまいます。

![img_face_shadow_02_02.JPG](https://qiita-image-store.s3.amazonaws.com/0/319781/aa4981e8-8cc2-36af-4a8e-6c09129d28b6.jpeg)

こちらの画像は、ディレクショナルライトをBakedにしてLightProbeを置いたパターンです。顔の陰影が無くなっています。同時に、ライトによる体の陰影も消えています。

リアルタイムライトによる顔の影対策としてはFlatLitToonのShadowを0にする、陰影対策がされているシェーダーを使う等あるのですが、そういう対策法を知らない人でも変な影が出ないようにワールド側で対策したいということでこういう形にしています。

もちろんリアルタイムライトが無いと床に影が落ちなかったり、体の陰影が出来なかったり、スペキュラが出なかったり等デメリットも多いため、そこはワールド製作者のポリシーによりますし絶対にこのやり方が良いとは言えません。こういう方針もあるよという参考にして頂ければと思います。


## あとがき

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">SilentClub作るに当たって活用した技術的な話は全く隠すつもりはなくて、そのうち記事にして公開したいと思っています（自分自身色々な方が公開している情報を活用させてもらったので）</p>&mdash; ryou (@ryou_Ein) <a href="https://twitter.com/ryou_Ein/status/1023554105350873088?ref_src=twsrc%5Etfw">2018年7月29日</a></blockquote>

そのうち（約４ヶ月後）。

今回記事にしていない内容でも、SilentClubで「これどうやって実装してるんだろう？」みたいな物があれば気軽に聞いてもらえればと思います。

明日のVRChat Advent Calendarは@nyakome306さんです。お楽しみに！