アプリスペースワープ 更新日時: 2024/08/16
このガイドでは、アプリスペースワープ(AppSW)の背後にある技術的な詳細、またQuestオペレーティングシステムで実装する方法について説明します。アプリにAppSWを実装する方法については、Unreal 、Unity 、ネイティブ の各ドキュメントのページをご覧ください。 アプリスペースワープ(AppSW)は、パフォーマンスと遅延の両面で階段関数を向上させる機能です。これは、Quest開発者に提供されている重要な最適化の1つです。Metaの初期テストでは、問題はほとんどまたは全くなしで、コンピューティング能力が最大70パーセント向上しました。
これは劇的な最適化ですが、技術的な考慮事項について、また適切な実装のためのトレードオフについて詳細に理解することが必要になります。このガイドは、AppSWを最適に使用できるよう作成されました。
このガイドは以下のセクションに分かれています。
技術的概要 - AppSWの詳細な概要について説明しますAPIと統合についての注意事項 - エンジンとネイティブのどちらのアプリ開発者も、開発を始める前に知っておいたほうがよい重要な注意事項について説明します。メモリの要件 - AppSWを使う際のメモリに注意事項について説明します。トラブルシューティング - テストとデバッグについての情報、そして既知の問題点や近日公開予定の機能についての情報が含まれています。ベストプラクティス - AppSW実装時のベストプラクティスについて説明します。AppSWを利用すると、新しいフレームを合成するためにシステムで使うモーションベクトルバッファと深度バッファを低解像度でレンダリングすることにより、アプリは半分のレート(72 FPSに対して36 FPS)でレンダリングを行えます。その結果、ディスプレイ出力は72 FPSになります。
AppSWはアプリ上の1つのコンポジターレイヤー(通常はゲーム内のカメラをレンダリングするレイヤー)でのみ実行するように設計されています。UIを別のコンポジターレイヤーでレンダリングするアプリケーションでは、3D環境をAppSWを使って半分のフレームレートでレンダリングし、UIをAppSWを使わないで別のレイヤーでフルフレームレートでレンダリングする必要があるかもしれません。この技術は主にモーションベクトル生成とフレーム合成の2つの主要コンポーネントで構成されています。それぞれのコンポーネントについては、続くセクションでさらに詳しく説明します。
AppSWにおいて、モーションベクトルと深度バッファデータの生成はアプリが行います。モーションベクトルバッファは、速度バッファとも呼ばれるもので、3Dレンダリングにおいて、モーションブラーや一時アンチエイリアシングなどのグラフィックス機能のために広く使用されています。アプリがモーションベクトルを生成する方法は柔軟に決定できます。
AppSWはモーションベクトルを、現行フレームと先行フレームの間での対応するピクセルのNDC空間位置の差として定義します。基本的にこれによって、画面空間と深度バッファ内でのピクセルの移動量がトラッキングされます。この差を計算するため、アプリでは次のことをする必要があります。
現行フレームと先行フレームの両方について、頂点シェーダーが、あらゆるオブジェクトの各頂点をクリップ空間に変換します。 フラグメントシェーダーが、先行フレームと現行フレームの位置を受け取り、それらをNDC空間に変換します。モーションベクトルは、2つの値の差に過ぎません。 次のコードスニペットは、この処理をより詳しく説明しています。
/// <vertex shader>
out vec4 currentClipPos;
out vec4 prevClipPos;
void main()
{
...
currentClipPos = ViewProjectionMatrix[viewId] * (LocalToWorld * localVertPos);
prevClipPos = PrevViewProjectionMatrix[viewId]* (PrevLocalToWorld * prevVertPos);
gl_Position = currentClipPos;
}
/// <fragment shader>
in vec4 currentClipPos ;
in vec4 prevClipPos;
out highp vec4 outMotionVector;
void main()
{
vec3 CurrentNDC = currentClipPos.xyz / currentClipPos.w;
vec3 PrevNDC = prevClipPos.xyz / prevClipPos.w;
outMotionVector.xyz = (CurrentNDC - PrevNDC);
outMotionVector.w = 0.0f;
}
上記のコードスニペットのモーションベクトルは、2Dベクトルではなく3Dベクトルです。ここでは、フレーム外挿/再投影の精度をさらに上げるため、深度モーションを考慮に入れているからです。
Unity Meta XR SDKとUnreal統合では、モーションベクトルを低解像度バッファでレンダリングするための専用モーションベクトルパスが導入されています。これは、実際にはモーションベクトルをフル解像度にする必要がないためです。現時点で、Quest 2でのデフォルトのアイバッファ解像度は1440×1584ですが、デフォルトのモーションベクトルテクスチャーは368×400でしかありません。これはGPUコストが節約できるので有効です。フル解像度のMVバッファでは、GPUコストが現行のものよりけた違いに大きくなってしまいます。現状では、モーションベクトルパスはGPUコストがほとんどない 状態です(完全にないわけではありません)。
モーションベクトルパスの深度バッファも収集しますが、それもモーションベクトルと同じ解像度であり、その深度バッファをコンポジターに渡すことにより、コンポジターがそのバッファを使えるようになります(下記の位置タイムワープの説明を参照)。
注 : 専用パス内のレンダリングモーションベクトルは、1つの方法に過ぎません。開発者は、ほかの機能のためにモーションベクトルを生成することがよくあり、同じことを達成する別の方法を自由に考え出して使うこともできます。
モーションベクトル/深度バッファとアイテクスチャーのレンダリングが完了すると、アプリはそれらを
OpenXR API を通じて送信できます。Unity/Unreal開発者の場合、これはすべて自動で処理されます。
この2つのステップからなる処理を理解すると、ランタイムがほぼ完全にこの処理を行うとはいえ、テクノロジーの限界を完全に理解することができます。
フレーム外挿 - ランタイムは、モーションベクトルデータを使うことによって、ピクセルが次のフレーム内のどの位置になるかを予測できます。ランタイムは、ピクセルを合成フレームの中の予測位置に移動することにより、合成処理の一部としてコンポジターの中でこの作業を行います。 深度ベースの再投影(位置タイムワープ) - タイムワープは、Questにおいて当初から利用可能になっています。タイムワープでは、合成段階で最新のHMDポーズを用いてアイバッファを再投影することにより、ヘッド回転遅延を軽減します。タイムワープは、合成ソフトウェアスタックの中で最も重要なテクノロジーの1つであり、Questのあらゆるアプリで自動的に有効になります。しかし、アプリから深度が送信されていなかったため、タイムワープはピクセルがどれだけ離れているかを把握できず、再投影は回転の修正だけに限定されていました。そのため、合成段階ではHMD変換の修正がなされていないということになります。深度バッファがアプリから送信されると、深度ベースの再投影によりHMD変換遅延も低減できます。その結果、位置タイムワープという、タイムワープのさらに高度なバージョンが実現します。AppSWの下でアプリは半分のレートでレンダリングするため、継承されるHMDレンダリング遅延は、アプリが最大FPSで実行される場合に比べて大きくなる傾向にあります。位置タイムワープを使うとHMDレンダリング遅延を大幅に低減できるため、AppSWを使わないフルFPSアプリより遅延が少なくなります。 AppSWを有効にするには、余分のメモリリソースをシステムが事前に割り振ること、またアプリで低解像度モーションベクトルと深度スワップチェーンを作成することが必要です。メモリの要件の推定値は以下のとおりです。
HMDのタイプ システム(OpenXR拡張機能が有効になった時点で事前割り振り) モーションベクトル+深度スワップチェーン(アプリにより割り振り) 合計 Quest 3 (1680x1720)
~5.5MB
~13.5MB
18MB
Quest 2 (1440x1584)
~4MB
~10MB
14MB
Quest 1 (1216x1344)
~3MB
~7.5MB
10.5MB
アプリでAppSWが有効になったら、adb logcat -s VrApiで以下の内容を確認できます。
09-13 22:11:30.350 4270 4365 I VrApi : FPS=36/72,Prd=39ms,Tear=0,Early=0,Stale=0,VSnc=0,Lat=-3,Fov=0D,CPU4/GPU=3/3,1382/490MHz,OC=FF,TA=0/0/0,SP=N/N/N,Mem=1353MHz,Free=2868MB,PLS=1,Temp=44.5C/0.0C,TW=1.86ms,App=11.42ms,GD=0.00ms,CPU&GPU=20.90ms,LCnt=1,GPU%=0.55,CPU%=0.18(W0.25),DSF=1.00
09-13 22:11:30.351 4270 4365 I VrApi : ASW=72, Type=App E=-0.000/0.250,D=0.000/0.000
FPS=36/72 - アプリが半FPSモードで実行されていることを示します。ASW=72, Type=App - AppSWが有効になっていること、また直前の1秒間に72 ASWフレームが存在することを示します。FPS - アプリによって送信される1秒あたりのフレーム数(ASWフレームを含まない )。ASWが有効な72 FPSアプリの場合、これが36であることが予期されます。ASW FPS - アプリによって送信される1秒あたりのフレーム数(ASWフレームを含む )。ASWが有効な72 FPSアプリの場合、これが72であることが予期されます。ASWが無効のアプリの場合、これが0であることが予期されます。ASW TYPE - この値が0でない場合、ASWが有効になっています。AppSWを使用している間、アプリの要素のカクつき、波のような動き、または引き伸ばされた表示に気付くことがあります。
これは、オブジェクトの動きに対するプレイヤーの期待とモーションベクトルデータのミスマッチが原因で起こります。
モーションベクトルをレンダリングしないマテリアルを持つオブジェクトは、AppSW生成のフレームでは動かないため、表示が途切れたりします。 AppSWでは透明なオブジェクトを扱うのは難しいです。AppSWのアルゴリズムは各ピクセルが一方向に動くシナリオだけをサポートしています。透明なオブジェクトとその背後にある不透明なオブジェクトを含むピクセルは二方向に「動く」ことが想定されています。 どのオブジェクトがAppSWアーティファクトを生成しているかを発見する最も効率的な方法は、フレーム出力をモーションベクトルバッファと比較することです。これには2つの方法があります。
RenderDoc for Oculus でキャプチャされたフレームのオフライン分析を行うことができます。これにより、モーションベクトルバッファ内のすべてのドローコールを1ステップずつ実行して、欠けている個々の呼び出しや予期せぬ値を分析することができます。次のコマンドでモーションベクトルバッファをリアルタイムでスクリーンにレンダリングすることができます。(ワンクリックアプリケーションの場合は、Meta Quest開発者ハブのカスタムコマンド を使えます。)
adb shell setprop debug.oculus.spaceWarpDebug 1 && adb shell setprop debug.oculus.MVOverlay 4 && adb shell setprop debug.oculus.MVOverlay.Alpha 0.8 && adb shell input keyevent "KEYCODE_POWER" && adb shell input keyevent "KEYCODE_POWER"
コマンド 動作 setprop debug.oculus.spaceWarpDebug 1
アプリスペースワープデータのデバッグレンダリングを有効にします。
setprop debug.oculus.MVOverlay 4
オーバーレイでレンダリングするデータ/スタイルを選択します。指定可能なその他の値については、次の表を参照してください。
setprop debug.oculus.MVOverlay.Alpha 0.8
アプリスペースワープデータオーバーレイの不透明度を設定します。必要に応じて修正してください。
input keyevent "KEYCODE_POWER"
Meta Questヘッドセットの電源ボタンの長押しと同じです。これを再び行うとヘッドセットが再起動し、OSが新しいバッファを設定してオーバーレイを表示できるようにします。
有効なMVOverlay値 # 出力 説明 1
MotionVectorバッファを直接レンダリングします。負の軸での動きはこのビューでは見えないことに注意してください。
2
深度バッファをレンダリングします。AppSWは深度バッファを使用してエッジを特定します。
3
MotionVectorバッファをレンダリングし、乗算してコントラストがより高くなるようにし、小さな動きを特定しやすくします。負の軸での動きはこのビューでは見えないことに注意してください。
4
MotionVectorバッファをレンダリングし、乗算して動かし、モーションがないものをグレーでレンダリングします。これは、ゼロ以外のすべての動きが容易に見えるようにすることを目的としています。
オーバーレイが有効になっている間、シーン周辺のプレイヤーカメラのデータを変換します。カメラがデータを変換するのに合わせて、またはカメラがワールド内で動くのに合わせて、オブジェクトが「ライトアップ」します、つまり、カメラに近いオブジェクトがより濃い色で「ライトアップ」します。オブジェクトが「ライトアップ」しない場合、またはオーバーレイがシルエットとマッチしない場合、AppSWのレンダリング動作に問題があることを示しています。
デバッグが終わったら、次のコマンドを実行してモーションベクトルバッファのレンダリングを停止してください。(Meta Quest開発者ハブのカスタムコマンドでも構いません。)
adb shell setprop debug.oculus.spaceWarpDebug 0 && adb shell input keyevent "KEYCODE_POWER" && adb shell input keyevent "KEYCODE_POWER"
開発者は、独立してレンダリングされるコンポジターレイヤーを使って一部のゲーム内UI (HUDなど)を処理することがよくあります。コンポジターレイヤースペースワープを利用すると、フレームレートに関係なく、コンポジターレイヤーの動きがはるかにスムーズになります。一般に、AppSWアプリでゲームUIにコンポジターレイヤーを使ってみることをおすすめします。その場合、レイヤーは独立してレンダリングされるため、AppSWの透明性の限界を気にすることなく透明UIを処理することができます。
AppSWは開発者にとって非常に強力な機能ですが、AppSWがあらゆるタイプのアプリに有効というわけではありません。このツールをいつどのように使うかを決めたり、アプリを徹底的にテストしてグラフィックスの不具合や新たなバグが発生していないか確認したりすることは、開発者の責任です。AppSWを効果的に活用する方法を理解するには、AppSWのパフォーマンスが十分に発揮されるシナリオだけでなく、最適にならないシナリオについても開発者が理解しておく必要があります。
AppSWを利用する開発者にとって注意すべきシナリオには、次のようなものがあります。
AppSWの技術的詳細を理解すると、それを使う場合に透明性のレンダリングが課題となることに気付くはずです。AppSWでは、モーションベクトルと深度に対して可能な値は1つだけです。透明性の基本原則は、異なる深度で重なり合う2つのピクセルを組み合わせ、その色をブレンドできるようにすることにあります。右に移動中の不透明オブジェクトの手前で透明オブジェクトが左に移動しているとしましょう。両方のオブジェクトが含まれるピクセルでは、ピクセルが2つの方向に動くため、モーションベクトルがあいまいになります。そのため、透明オブジェクトをレンダリングしてモーションベクトルパスに入れることは普通おすすめしません。
とはいえ、状況によっては問題の重大性が異なります。例えば、透明性サーフェスがカメラから遠く離れている場合、フレームごとの実際の投影モーションは非常に小さいため、大抵、結果に問題はありません。また、透明性レンダリングの重要な用途の1つであるパーティクルエフェクトにおいては、エフェクトが高速アニメーション(爆発など)と組み合わせられることが多いため、モーションジッターが少々あってもあまり目立ちません。
近くにある、透明の速く動くオブジェクトは、AppSWで問題となることがあります。例えば、レンダリング対象コントローラーは、通常は近景平面に非常に近いところにあって高速で動く場合があるので、問題になることがあります。このシナリオでは、コントローラーとその子オブジェクトが、AppSWで扱いやすいものとなるようにデザインしてください。
AppSWはフレーム外挿テクニックであり、ピクセルをその周囲に動かすことになります。この処理は潜在的なアーティファクトが少なくなるように調整されるものの、画像の歪みがいくらか発生することがあります。特に背景でそうなることがあります。多くのビデオゲームでは、豊富なテクスチャーパターンの背景が使われているため、ほとんどのユーザーはそのような歪みに気付くことがありません(例: 上の左側の画像)。しかし、上の右側の画像のように単純ですっきりとした背景の場合、人間の目は微妙な歪みにも気付くほど繊細です。アプリでこの問題があることに気付いたら、背景がAppSWにより適したものになるように変更を加えることを検討できます。許容できる歪みのレベルは主観的な指標であり、アプリで妥当な品質が達成されるようになるまで、テストと調整をするようおすすめします。
非常に高速で回転するゲームオブジェクトがシーンにいくつか含まれている場合、ピクセルの歪みの影響がそのオブジェクトの周りに表示されることがあります。例えば、1秒間に100回転というような極端に高速回転する立方体があるシーンを考えてみましょう。この場合、特定のフレームから次のフレームまでのその立方体の向きは、ほとんどランダムと言ってよいでしょう。立方体がそこまで高速で回転しているため、モーションベクトルを正確に構築することができません。モーションベクトルが正反対の向きを指すことさえあり得ます。
立方体の回転速度を下げれば、問題が緩和されます。別の解決策として、現在UE4統合で使われているのは、モーションベクトルシェーダーの中で高速回転を検出し、モーションベクトルのうちそのオブジェクトの回転に由来する部分を無効にすることです。UE4 GitHubで、VelocityCommon.ushの中のIsSmoothRotation()のリファレンスをご覧ください。
この解決策の場合、オブジェクトのモーションベクトルを無視しながらも、位置タイムワープを利用できます(ヘッドモーションがスムーズになる)。テストシナリオの中で、この解決策が非常に効果的であることをすでに確認しました。ある時点でオブジェクトが激しく回転している場合、脳は多くのオブジェクトジッターがすでに見えたものと認識するため、オブジェクトに気付かなくなります。
理論的には、ゲームオブジェクトのモーションベクトルは常に計算可能です。アプリでは、ゲームオブジェクトの各頂点が直前フレームのどこにあったかが分かっているからです。しかし、頂点シェーダーでの頂点アニメーションが非常に複雑な場合には、モーションベクトルの計算が困難になることがあります。このシナリオでは、モーションベクトルシェーダーのアニメーションを、前のフレームと現在のフレームについてそれぞれ1回ずつ、合計2回実行することになります。これは相当に面倒であり、そのようなマテリアルを避けるほうが簡単です。それができないのであれば、前のセクションで言及したようにモーションベクトルに頂点アニメーションを含めないというのが手軽な妥協案になります。
以下の代わりに
currentClipPos = ViewProjectionMatrix[viewId] * (LocalToWorld * currAnimatedPos);
prevClipPos = PrevViewProjectionMatrix[viewId]* (PrevLocalToWorld * prevAnimatedPos);
次のようにします
currentClipPos = ViewProjectionMatrix[viewId] * (LocalToWorld * currAnimatedPos);
prevClipPos = PrevViewProjectionMatrix[viewId]* (PrevLocalToWorld * currAnimatedPos);
頂点アニメーション自体は少し不規則に揺れることがありますが、ヘッドモーションとオブジェクトモーション全体はスムーズになります。あらゆる場合にモーションベクトルを正しく生成することをおすすめしますが、いくつかの極端なケースでは、上記の方法が誤ったモーションベクトルの影響を低減するための回避策となります。
FB Connectの話の中で触れたように、AppSWの遅延最適化のためにかなりの作業がなされてきました。OpenXR AppSWアプリでは、フェーズ同期と位置タイムワープが自動的に有効にされています。また、アプリでレイトラッチングを有効することが強く推奨されています。
それらのテクニックをすべて組み合わせれば、HMD遅延の結果は、非AppSWアプリより上とは言わないまでも、ほとんどそれに匹敵するものとなります。しかし、それでもコントローラー入力遅延は非AppSWアプリに比べて高くなります。コントローラー遅延の要件が非常に高いアプリの場合は、そのアプリでこのトレードオフが適切かどうかを考慮すると良いでしょう。
適切に利用すれば、AppSWは、アプリのコンピューティング能力を高める強力な機能となり、目立つようなマイナスの影響もほとんどまたは全くありません。上記に示したもの以外に異常や歪みが発生する特殊なケース、あるいはアプリの一部があるかもしれません。開発者は、コンテンツを変えたり独自の回避策を編み出したりして、それを軽減できます。
前述のようにAppSWはあらゆるアプリへの万能薬ではないかもしれませんが、開発者は多くの問題点を軽減するために創造性を発揮することができます。例えば、透明性のことを重要な問題として扱いましたが、プロキシーメッシュをレンダリングしてモーションベクトルパスに入れることにより不透明サーフェスのモーションベクトルを生成できるようにするなど、さまざまな異なるアプローチを試みることによって対処することができます。場合によっては、ジッターが全く目立たないこともあります。開発者が創造性を発揮できる別の例は、モーションベクトルテクスチャーを専用パスでレンダリングすることです。これはおすすめの方法ではありますが、MRTを使う方法や、静的オブジェクトのモーションベクトルを深度から再構成する方法など、ほかのオプションもいろいろあります。同じ目標を達成するのにも、多くの異なる手段があります。開発者がモーションベクトルの所有者であり、好きなようにそれを変更できるということを忘れないでください。
最後になりましたが、AppSWはどのフレームでも有効にしたり無効にしたりできるためそれをどう利用するかを開発者は自由に制御できます。可能な場合にはAppSWのメリットを利用し、品質の水準を満たさない場合にはオフにするというのがベストプラクティスです。