UnityアプリをRiftからGo/Questへ移植する人向けのTips集
こんにちは。株式会社Synamonでエンジニアをしております、岡村(@Sokuhatiku)と申します。
ついに本日5/21に、Oculus Questが発売されましたね!弊社にもまだ届いておりません!2019年6月2日追記:先週届きました!!
Questはスタンドアロンデバイスでありながら、デスクトップPCを要求するOculus Riftと同等レベルの操作が可能なデバイスです。VRを始めるための初期投資も少なく使う手間も少ないことから、多くの人がQuestを購入することを予測し、新たなユーザーを迎え入れるためにRiftで展開しているコンテンツをOculus Questに移植したい!という気持ちになったり、そういう要望を受ける方も多い事かと思います。幸いOculus Quest実機が無くてもGoと全く同じ環境で開発は出来るので、作業自体は今からでも始められるのですが、問題になってくるのが、Androidの仕様や、処理速度です。 デスクトップとスタンドアロンという厳しい制約を乗り越えるため、Unity及びOculusは様々な機能を提供してくれていますが、それらを正しく扱うためにはまだまだ不具合も多く、日本語のドキュメントも少ないように感じます。
本記事ではOculus Rift向けのUnityアプリケーションをGo/Questに移植する際の、アセットにあまり手を入れずに出来る負荷削減を中心としたTipsおよび、自分がそれを実践した際に踏み抜いた地雷から得た知見をまとめてみました。
Unityのバージョンは2018.4を前提としています。
参考リンク
Best Practices for Rift and Android
Oculus公式によるベストプラクティスガイドです。この記事では触れていない基本的な設定が書かれていますので、一読することをお勧めします。
パフォーマンス
Go/QuestとデスクトップPCの処理性能比較は以下の記事が詳しいです。
uploadvr.com ざっくり、Rift向け最小スペックのPCと比較した場合、
- CPU速度は約半分
- GPU速度は約6分の1
- メモリサイズは約半分
となっています。特にグラフィックの性能低下が顕著であることが分かります。
グラフィック機能レベル
Go/QuestのグラフィックスAPIは、OpenGL ES 3.1 + AEPです。シェーダーコンパイルターゲットレベル*1 5.0に対応している為、GeometryShaderやComputeShader、テッセレーション等の高度なAPIはデスクトップと同じようにそのまま利用できます。 ただし、DirectX→OpenGLに変化する関係で、DepthBufferの値の範囲が変化したり、PSIZEといった一部のセマンティクスが利用できない等の対応の必要が生じます。
OpenGL ES3.1 + AEPの機能を使用することを明示するには、Auto Graphics APIをオフにした上で、以下の画像のようにGraphics APIsのリストをOpenGLES3のみにして、Require ES3.1およびRequire ES3.1+AEPにチェックを付けると良いです。(Go/Quest向けにビルドするだけならば実は設定しなくても使えますが……。)

注)Graphics Emulationについて
Unity2018にはGraphics Emulationという機能がありました。これは現在のビルドターゲットのグラフィックスAPIの挙動をエディタ上でエミュレーションするものです。ところが、この機能はOpenGL ES 3.0までしか対応していないため、これを有効にしたままエディタ上でGeometryShaderやComputeShaderを使用しようとするとエラーを吐かれます(実機では動きます)。この機能はUnity 2019.1.0で削除されたため*2、Unity 2019環境であれば怒られません。

負荷削減機能
Single-Pass Stereo Rendering
立体視映像のパフォーマンスを向上する為に重要な要素の一つがSPSR(Single-Pass Stereo Rendering)です。CPUとGPU間の通信が遅くレンダリングの足を引っ張るので、右目と左目の描画命令を出来るだけまとめてしまおうというUnityの機能です。SPSRにはハードウェアの制約からくる方式の違いによりいくつかの種類があるのですが、Go/QuestはMultiviewと呼ばれるものを採用しています。
SPSRはProjectSettingsから設定するだけで使えます。

SPSRの機能詳細に関しては、凹みさんの以下の記事が非常に詳しいです。
Fixed Foveated Rendering
固定中心窩レンダリングと訳され、映像の隅のレンダリング解像度を落として解像度による負荷を削減します。内部的にはGo/QuestのGPUであるQualcomm Adrenoシリーズが搭載するTiled renderingという機能を利用しており、格子で区切った一定範囲の解像度を指定することで実現しています。
Oculus Integrationを入れていれば、OVRManager.tiledMultiResLevelを設定することで簡単に利用可能です。Highに寄るほど総合的な解像度が減っていきます。ただし、強いFFRをかけると視界の周囲が賑やかなシーンでちらつきが気になるので、Oculusはシーンと負荷に応じて動的に切り替えることを推奨しているようです。
OVRManager.tiledMultiResLevel = OVRManager.TiledMultiResLevel.Off; // 画面端の解像度を減らさない。 OVRManager.tiledMultiResLevel = OVRManager.TiledMultiResLevel.LMSHigh; // 画面端の広範囲の解像度を低下させる。
注)グラフィック周りの機能の競合
Unity2018現在、上二つの負荷削減機能と、MSAA等の表現強化手法との間には相性があり、特定の機能を有効にすると他の機能が無効になったり、グラフィックが破綻する事があります。簡単にまとめてみました。縦の軸と横の軸の交わるセルが×である場合、その2機能を同時に使う事は出来ず、グラフィックの破綻や描画されない系の不具合を招きます。
| 競合 | 症状 |
|---|---|
| IE*SPSR | Graphics.Blit()が白いテクスチャを返すようになる※後述 |
| IE*FFR | FFRが無効になる |
| IE*MSAA | MSAAが無効になる |
| GS*MSAA | GSが破綻、もしくは描画されなくなる |
| GS*SPSR | GSが破綻、もしくは描画されなくなる |
こうしてみると、ImageEffectはほぼ全滅ですね……負荷削減とImageEffectは相性がすごく悪いようです。競合した場合はPostProcessingStack等のアセットも使用不可になります。視界の暗転をImageEffectでやっている場合は、カメラの前に半透明の板ポリをレンダリングする方式に切り替えるといいかもしれません。
2019年6月2日追記:ImageEffectとSinglePassStereoRenderingの競合について
SPSRを有効にした状態でIEの処理中にGraphics.Blit(source, destination)を呼び出すとdestinationテクスチャが真っ白になってしまいますが、以下の公式ドキュメントを参考に、SPSR(Multiview)に適切に対応したImageEffectシェーダーを作成してGraphics.Blit(source, destination, material)を呼び出すと、正常に画像をコピーすることが出来ます。また、暗転やその他のポストプロセスも対応シェーダーを作成することで利用可能です。
ただし、FFRおよびMSAAは変わらず同時には使用できません。またBloom等の、処理に2枚以上の画像の合成が必要なImageEffectは、上記の対応だけでは動かない*3為、注意が必要です。
この情報は野生の男さんにご提供いただきました*4。ありがとうございました!
その他の機能紹介
IL2CPP
C#で書かれたコードをC++にトランスパイルして高速化を行う、Unityの機能です。 式木などの高度なリフレクションを行っていない限り、とりあえずリリースビルド時にはONにしておくという運用で大丈夫です。
めったにある事ではないですが、packageVersionにパス区切り文字が入っているとビルドに失敗します。

Lightweight Render Pipeline
LWRPはUnityのレンダリングの流れその物をカスタマイズして、不要な機能を提供しないことで高速化を図るプラグインです。 通常版とVR版があり、VR版はまだ2019になってもPreviewが剥がれていない為導入は試していません。通常版は2019で剥がれているそうなので、もうすぐ剥がれるかもしれません。レンダリングパイプラインの最適化が行われるとのことで、もしかしたら上に上げた相性の問題は解決するかもしれず、その点に期待はしているのですが、SPSR以上にシェーダー互換性が無いと聞いているので戦々恐々です……。もし触った事のある方がいらっしゃいましたら、詳細を聞かせてください……。
Linear Color Space
Go/Quest環境でのLinear Color SpaceはUnity 2018.2.13で使用可能になりました*5。 デスクトップ版をLinearカラースペースで開発していた場合、引き続き同じカラースペースでの開発が可能です。高速化とはあまり関係ないですが、デスクトップではLinearでの開発が一般的かと思われますので、移植のハードルを下げられるかもしれません。
以上
浅く広くではありますが、Oculus RiftからGo/Questに移植する際に使える負荷削減策と、それを適用する過程ではまった落とし穴について紹介させて頂きました。QuestだけではなくVIVE Focusも6DoFコントローラーを備え、今までデスクトップでしかできなかった体験をスタンドアロンに持ち込む流れは加速しつつありますが、この記事が波に乗る際の一助となれば幸いです。よきスタンドアロンライフを!
*1:https://docs.unity3d.com/ja/current/Manual/SL-ShaderCompileTargets.html OpenGL ESのバージョン及びシェーダーモデルとは異なります。
*2:https://unity3d.com/unity/whats-new/2019.1.0 "Graphics: Removed graphics emulation from Editor."
*3:試してみたところ、Graphics.Blit()の引数に渡す以外の方法で、TextureArray形式になっているRenderTextureを正常に渡すことが出来ていない様に見えます。
*4:https://twitter.com/yasei_no_otoko/status/1131436699609837568
*5:https://unity3d.com/jp/unity/whatsnew/unity-2018.2.13 "XR: Linear color space now works on Oculus Go and Quest. For Gear VR, driver issues may prevent linear from working with S7 Adreno phones on Android 7.0." OculusGo以外のVRデバイスでは基本、使えないようです……