OculusGo,OculusQuest向けUnityパフォーマンスガイド

OculusGoやOculusQuest向けにUnityのVRゲーム作るぞ！！！と思った人が

- ポリ数の制限はどんな感じなのかな？
- マテリアルどのくらい使えるの？
- Unity上の設定項目は何がおすすめ？

みたいなことで悩む事があります。なので日本語でまとめておきます。
ベースの文書は下記Oculusオフィシャルドキュメントです。(2019年10月現在)  

OculusのDeveloper Guide (Testing and Performance Analysis)
https://developer.oculus.com/documentation/unity/latest/concepts/unity-perf/

同じくOculusのUnity Mobile Performance Intro(Android)
https://developer.oculus.com/documentation/unity/latest/concepts/unity-mobile-performance-intro/#unity-mobile-performance-intro


# シーン内の描画制限
- 60FPSのキープ(審査が通らなくなるので)
- 50-100 drawcall(つまり、シーン内全部、ではなく見える範囲でのSetPassCall数の話です)
- 5万~10万ポリゴン(これも、シーン内全部、ではなく見えている範囲でのポリゴン数の話です)

これを満たすようにデザイナーさんやモデラーさんにお願いすると良いです。

満遍なくカリングが利くシーンなら、全体で20万ポリゴンあっても大丈夫かもしれませんが、パーティクルなどを出す余地を考えると、 **大雑把に10万ポリ制限** だと思っておくと良さそうです。

GameView上のStatsにチェックを入れて、この統計情報のTris:が100k以下で SetPassCallが100以下になっていればセーフです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/3d505dd6-4670-a3d0-8cc2-a72e1a8aeb06.png)

# レンダリング最適化の為に
## DrawCallの削減
DrawCallというかSetPassCallというか、要するにマテリアルやシェーダーの切り替える個数です。シーン内によく似た別マテリアルがいっぱいある場合は、同一マテリアルにしましょう。
あるいはMeshBakerとかで1マテリアルにまとめましょう。

上にも挙げた通り、SetPassCallは100以下になるようにしましょう。

## DrawCall Batching
Unity上だけで設定できることもあります。
- Static Batching
- Dynamic Batching
- GPU Instancing

などを利用することで、SetPassCallを減らせます。

## Culling
プレイヤーから見えないオブジェクトは非表示にする、という仕組みは
**Occlusion Culling**

で設定できます。例えば無限に広がるオープンワールドゲームだと遠くまで全てのオブジェクトを描く必要がありますが、室内のシーンなら廊下や別の部屋を描かなくても良いです。
(注意:Occlusion Cullingを計算するCPU負荷があります。有効無効を切り替えて計測しましょう)

また、地味なところですが
UnityのLayerごとにCameraからどの位距離が遠ざかったらレンダリングを止めるか、という設定ができます。チェックしてください。

https://docs.unity3d.com/ScriptReference/Camera-layerCullDistances.html

## Reducing Memory Bandwidth
メモリ帯域を削減しましょう。特にPCと違ってOculusGoやOculusQuestはメインメモリとGPUメモリが同居しているので、この帯域がネックになりやすいです。メモリ帯域を削減するとかなりパフォーマンスに効いてきます。

- Texture Compression:テクスチャ圧縮フォーマット。ETC2を古いドキュメントでは推奨していたけど、今はASTCが推奨です。(Snapdragon系はASTCをサポートしているので。ETC2でも悪くはないです)
- Texture Mipmaps: テクスチャのミップマップは常に有効化してください。UnityはテクスチャImport時に自動生成してくれます。
- Texture Filtering: テクスチャ設定のFilterModeです。Trilinear は負荷が上がりますが、それに見合うだけVRで見たときに奇麗に見えます。(もしギリギリまでパフォーマンスが必要ならBilinearも良いと思います)
- Texture Sizes: テクスチャ解像度は(Unity上の設定で)下げましょう。AssetStoreで買える奇麗なモデルは、往々にしてテクスチャ解像度がモバイルVRに不向きな位に高いです。実際にシーンに配置して、テクスチャ解像度を切り替えながら許容可能な値を探しましょう。大体の1オブジェクト毎のテクスチャはMax Size 1024でも足りると思います。(テクスチャをまとめてパックしてAtlasにするなら2048とか4096でも良いです)
- Framebuffer Format: デプスバッファはUnityデフォルトのモバイルでは24bitですが、16bitでも足りることが多いです。また、Unlitが多い（フォトリアルじゃなくてイラストタッチなら）カラーバッファも16bitで足りるかもしれません。Screen Resolution: UnityEngine.XR.XRSettings.renderScale=1.0fがデフォルトですが、0.9fや0.7fにすると一気に軽くなります。ただし綺麗に見せるには1.2f程度が必要だったりします。

## モデルのジオメトリ(メッシュ)の注意点
LODを使える場合は使いましょう。多くのAssetStoreで買えるモデルはハイポリすぎることが多いです。
また、ゲームオブジェクトは統合出来るならすべきで、ビルトインStandardShaderはとても重いです。

## テクスチャ及びオーバードロー
Pixel Complexity:マテリアルが参照するテクスチャの枚数は可能な限り減らしてください。ビルトインシェーダーを使うなら、スペキュラマップなどを焼き込んでUnlitかMobile/Diffuseマテリアルに出来ないか検討してください。

Overdraw: シーンに半透明オブジェクトがあると、描画順を考える必要があります。オーバードローと言います、確認できます。そのため密な半透明パーティクルを使ったりすると、レンダリング負荷が一気に上がります。
SceneViewでOverdraw表示にすることで確認できます。(真っ赤な場所があったらピンチです)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/88f410d8-538c-6e3a-b425-5365fe9cd4ea.png)

# Best Practices
- 可能な限りテクスチャはパック(アトラス化)しましょう。
- 動かないオブジェクトはライトマップベイクしてstatic なオブジェクトにしましょう。
- 動くオブジェクトのライティングもdynamic lightingではなくlight probesで対応しましょう。
- Precomputed GIではなくライトマップベイクを使いましょう。
- Non-Directional Lightmapを使いましょう。  https://docs.unity3d.com/Manual/LightmappingDirectional.html
- specular reflections, ambient occlusion などのテクスチャはdiffuseテクスチャに焼き込みましょう。
- カメラをシーンに一個だけにしましょう(RenderTextureは重い…)
- 3パス以上のシェーダーを使わないようにしましょう。
- シェーダーの中でalpha testを避けましょう。
- アルファブレンドも最小限にしましょう。半透明は避けましょう。
- テクスチャ圧縮はASTCにしましょう
- PlayerSettingsから Disable Depth and Stencilをチェックしましょう
- フルスクリーンのImageEffectを避けましょう
- LoadSceneAdditiveAsyncとかを使わず、真っ暗を出してAsyncじゃないロードをしましょう。
- StandardShaderを避けましょう。重いです
- Projectorコンポーネントを避けましょう。重いです。
- Unity Default Skyboxは重いので避けてマテリアルで指定しましょう。(CameraのClearFlagもSkyboxではなくSolidColorを使いましょう)

### CPU最適化
- シーン合計のGameObjectの数は少ない方が軽いです。
- Update()やFixedUpdate()を毎フレーム計算するオブジェクトは減らしましょう。
- Physicsは本当に必要なものだけ計算しましょう。
- 頻繁に破壊と生成が行われるものはObjectPoolを使いましょう。
- AudioSourceを使うときにPlayOneShotを避けましょう。重いです。
- 複雑な数値計算は避けましょう。
- キャッシュできるコンポーネント(例:Transform)は毎回探さずにキャッシュしましょう。

# 忘れがちポイント
- MultiThreadRenderingはオンにしないとめちゃくちゃ遅い
- TextureのRead/Write Enableを無効にする
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/e297193d-cf45-5604-4ff1-1c369e60d2c5.png)

# 補足
### プロファイラ起動してくると出てくる WaitForGPUについて
60FPS(あるいは72FPS)張り付きだと出てこないけどFPSが低いと出てくる、謎のWaitForGPUには、プロファイラ見てて悩まされる人が多いと思います。

**XR.WaitForGPUが16msより長ければGPU処理が重くて、16msより短ければCPU処理が重い。**

と覚えておきましょう。(16msは60FPSで、90FPSのハードウェアなら11msと読み替えてください)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/f76c886f-c3d3-71dc-5572-18a0e75c5adf.png)


### UE4事情
UE4のRobo Recallでは様々な最適化設定の上で **250ドローコール** で60FPSをキープしています。
https://www.unrealengine.com/ja/developer-interviews/learn-how-drifter-entertainment-leveraged-elegant-optimizations-to-bring-robo-recall-to-the-oculus-quest

### 将来の展望
Vulkan実装が動くようになるともうちょっといっぱい描けるようになりそう。
Unite Copenhagen 2019では、Unity2019.3からVulkan Rendering for Oculus Quest and Oculus Goがサポートされると発表されました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40627/264e2d1f-572e-f68c-4f06-6d7a12957a37.png)
https://drive.google.com/file/d/1h4u_vHmm6ISxwn6-NAlcwOsuvVqb4iyK/view