目次
1)マルチパス
- シーン描画を 2 回実行する必要があります。つまり、両目を表す 2 つのテクスチャのそれぞれについて、オブジェクト/シーンを別々にレンダリングします。
- GPU の処理はテクスチャ間で共有されません。そのため、これは最も効率の悪いレンダリングパスになります。ただしデフォルトでほとんどのデバイスで動作するため、変更はほとんど必要ありません。
- カリングと、シャドウ生成レンダリングの一部が共有されます。
2)シングルパス
- この方法を使うと、2 つのテクスチャがより大きい 1 つのテクスチャ(2 倍幅のテクスチャと呼ばれます)にパッキングされます。
- 1 つ目のオブジェクトが描画されるように、ビューポートを左側に設定します。このオブジェクトが描画されたら、ビューポートが右側に切り替わり、オブジェクトがもう一度描画されます。
- シーン描画が 1 度しか実行されないため、CPU での処理速度ははるかに高速になります。ただし、これを実行するには、多くの GPU 状態変更が追加で必要になります。

シングルパスステレオレンダリングの簡単な例
3)シングルパスインスタンシング
- 2017.2 以上でご利用いただけます。
- 両目を表す 2 つのテクスチャが両方とも 1 つのテクスチャ配列(同じテクスチャ配列の 2 つのスライス)に格納されます。
- ビューポートは、設定されると、両方のスライスに自動的に適用されます。描画を実行すると通常の 2 倍のインスタンス描画が実行されることになります。そのインスタンス次第で、レンダリング先のスライスが決定されます。
- 必要とされる GPU ワークロード量は同じですが、ドローコールと CPU の処理量は少なくて済みます。そのため、この方法は非常に効率的です。
- 以下のデバイスで利用できます。
- D3D11 や最新のグラフィックスドライバが搭載された Windows 10
- HoloLens
- PS4
- ターゲットデバイスで拡張機能がサポートされていない場合はマルチパスに戻ります。
- DrawProceduralIndirect(...) を使うには手動による変更が必要です。

シングルパスインスタンシングの例
シングルパスインスタンシングを使用する場合はシェーダーに次のマクロを追加する
シングルパスインスタンシングを選択する場合は、頂点シェーダーとフラグメントシェーダーに次のマクロを追加することを強くお勧めします。これらのマクロの追加は非常に簡単です。また、Unity のビルトインシェーダーはすべて、これらをサポートするよう更新されています。
頂点シェーダーの場合:

マクロが含まれていない状態のシェーダーコード

1 つ目のマクロが追加された状態の同じコード

2 つ目のマクロが追加された状態

3 つ目のマクロが追加された状態

4 つ目のマクロが追加された状態
フラグメントシェーダーで unity_StereoEyeIndex を使用する必要が生じた場合は、以下の手配を行う必要があります。

4)RenderScale を使用した場合と RenderViewportScale を使用した場合のいくつかの違い
例えば、テクスチャのサイズを 1 から 0.5 に変更して、この処理を RenderScale を使って実行すると、オリジナルのテクスチャは削除されてしまい、サイズがオリジナルの 4 分の 1、つまり各次元が 0.5 となる新規テクスチャが生成されます。
この操作はゲームの実行中に動的に行うには負荷が大きく、まさにその点においてRenderViewportScale を使用する方が能率がよい理由となります。RenderViewportScale により、ビューポートは同じテクスチャのより少ない領域に設定され、次のように、その部分だけがレンダリングされます。

効率的なのはその通りなのですが、テクスチャの一部しか使用していないという事から、いくつか懸念点もあります(これについては下のセクションでレンダービューポートスケールに関するその他のヒントをご参照ください)。
つまり、レンダースケールでは実際にテクスチャが削除されてから新しいテクスチャが作成されるのに対し、レンダービューポートスケールではビューポートが修正されます。その他注意すべき違いは以下の通りです。
- XRSettings.eyeTextureResolutionScale の対象は実際のテクスチャサイズですが、XRSettings.renderViewportScale はレンダリングに使用されるビューポートに適用されます。
- XRSettings.renderViewportScale はディファードレンダリングの使用時にはサポートされません。
- スケールは 2 つの次元に適用されます。そのため、値が 0.5 の場合、画像のサイズはオリジナルの 25% になります。
5)XR でのポストプロセッシングスタック:課題と解決策
こちらでは、優れたポストプロセッシングスタックの最新バージョンについて説明します。XR コンテンツでこのスタックを使用するといくつかの課題が発生します。次のセクションでは、それらの課題のほか、現在利用できる主な解決策を紹介します。
課題
最新のポストプロセッシングエフェクトを多数使用するには、ソースターゲットサイズおよびプロパティーに基づいた中間レンダーターゲットへレンダリングする必要がありますが
- 2 倍幅の場合、レンダーテクスチャの幅は、それぞれ目の部分幅の 2 倍以上になります。
- シングルパスインスタンシングや類似の Android シングルパスの場合、それぞれの目に対してスライスを 1 つずつ適用することになるため、テクスチャ配列を作成しなければなりません。
解決策
- 正しいスクリーンスペースのテクスチャ形式の生成を試して当て推量する時間を減らすため、Unity は XRSettings.eyeTextureDesc を公開しています(2017.2 以上)。
- これでエンジンの XRTexture マネージャーから供給される RenderTextureDescriptor が返ってきます。つまり XRSettings.eyeTextureDesc は、エンジンで管理されているテクスチャに一致するよう構成されています。
- また、ソーステクスチャが利用可能な場合は、そこから RenderTextureDescriptor を問い合わせることもできます。たとえば、従来の MonoBehaviour.OnRenderImage インフラを使用している場合、そのソーステクスチャがあるので、そこから直接ディスクリプターを取得できます。
- eyeTextureDesc で、全レンダーテクスチャ割り当てを与えられた RenderTextureDescriptor に置換できます。この方がパラメーターを手動で生成するよりも効率的です。
- RenderTextureDescriptor はよりシンプルな API で、基礎として動く API の動作に一致するようになっています。明示的な引数を使用すれば Unity によって RenderTextureDescriptor でそれらの引数がパッケージ化され、コアエンジン(RenderTexture.GetTemporary または CommandBuffer.GetTemporaryRT)に渡されます。つまり、すべての管理を中間レイヤーに任せるのではなく、スクリプト側で管理しています。
課題
XR では、物理的なレンダーテクスチャのサイズと論理的な画面/目のサイズとの間で違いがあります。
解決策
- 中間レンダーテクスチャの割り当てには、eyeTextureDesc を使用できます。eyeTextureDesc を使用すると、これらの物理的なテクスチャが割り当てられるためです。
- 画面サイズに基づくスクリプトまたはシェーダーロジックがある場合(たとえば、シェーダーで画面サイズパラメーターを使用している場合や、テクスチャのピラミッドを作成している場合など)、論理的なサイズをベースにする必要があります。そのために、XRSettings.eyeTextureHeight と XRSettings.eyeTextureWidth を使用できます。
- これらは、「それぞれの目の」テクスチャサイズを表しており、操作している画面のサイズを把握するために必要です。
- 2 倍幅の場合に eyeTextureWidth が eyeTextureDesc の幅のちょうど半分ではない点に注意してください。これは、テクスチャ割り当てを目的としたディスクリプターによって幅が 2 倍になり、ミップマッピング用の設定のためにさらに少し増加するためです。
次に示すのは、eyeTextureWidth が利用できるようになる前に、画面幅に使用する幅を決定するために使用していたコードの例です。

現在は、eyeTexturewidth から画面幅を取得し、それを画面比率などに使用できるようになりました。次のスクリプト例をご覧ください。

課題
テクスチャ座標がステレオに適したものになり、そのサンプリングが正しい目への出力から行われるようにするにはどうすればよいでしょうか?
2 倍幅のテクスチャからサンプリングする場合は、テクスチャ座標のサンプリングが、テクスチャのちょうど半分(半分それぞれが左右の目に対応)から行われるようにする必要があります。
解決策
テクスチャ座標を修正するしくみがそれぞれの目に必要になります。Unity はこのために 2 つの解決策を用意しています。
1. TRANSFORM_TEX マクロ
- このマクロは、ShaderLab の _ST プロパティーと連携します。最もよく使われるのが、MainTex_ST との組み合わせです。
- それぞれの目がシングルパスの 2 倍幅モードになる前に _ST プロパティーが更新され、テクスチャ座標は目に適した範囲に変換されます。また、RenderViewportScale も自動的に処理されます。

- Graphics.Blit または CommandBuffer.Blitを使っている場合は、Unity によって _ST シェーダープロパティーに値が設定されます。
- MainTex_ST は常に設定されます。
- その他の _ST プロパティーは、テクスチャが XR テクスチャ(VRTextureUsage!=None)の場合に設定されます。
- このマクロは、最新のポストプロセッシングスタックに含まれている BlitFullScreenTriangle など、カスタムブリットプロシージャには対応していません。
2. unity_StereoScaleOffset ヘルパー関数とUnityStereoTransformScreenSpaceTex/TransformStereoScreenSpaceTex ヘルパー関数
unity_StereoScaleOffset は float4 の配列です。
- UnityStereoTransformScreenSpaceTex
- TransformStereoScreenSpaceTex
これは、シングルパスの定数ブロック(UnityStereoGlobals)の一部として宣言され、Unity_StereoEyeIndex でインデックスが付けられます。
2 倍幅の場合、unity_StereoEyeIndex が定数バッファにバインドされ、それぞれ適切な目への描画が更新されます。そのため、まず左目の値が定数バッファに代入され、左目が描画された後、右目の値が定数バッファに代入され、右目が描画されます。シェーダーインスタンスは、そこで使用されるすべての描画についてこの流れを把握しており、定数バッファ内を検索して、unity_StereoEyeIndex に適切な値を見つけることができます。

unity_StereoEyeIndex が適切に設定されることがわかれば、unity_StereoScaleOffset から正しい要素が選択されているという確信を持ってこれを使用することができるようになります。

unity_StereoScaleOffset の使用については、少々不都合な点もあります。
- シングルパスにしか使用できないため、ヘルパーメソッドを使用しなければなりません。
- 直接使用すると、マルチパスおよびモノスコピックになり、結果としてシェーダーコンパイルエラーが発生します。さらに、RenderViewportScale が考慮されません。
テクスチャ配列が正しく宣言されるようにどうすればよいでしょうか?また、正しいスライスからサンプリングするにはどうすればよいでしょうか?
解決策
UNITY_DECLARE_SCREENSPACE_TEXTURE を使用します。シェーダー言語では、テクスチャ配列が「通常の」テクスチャと異なる方法で処理されます。シングルパスインスタンシングと Android シングルパスではテクスチャ配列が使用されるため、テクスチャを確実に正しく宣言しなければなりません。

2 倍幅と同様に適切な目の部分からサンプリングする必要があり、目の部分はテクスチャ配列の複数のスライスに配置されます。UNITY_SAMPLE_SCREENSPACE_TEXTURE マクロを使用して、unity_StereoEyeIndex から正しいスライスを取得できます。

RenderViewportScale の管理:注意すべきいくつかのポイント
- unity_StereoScaleOffset にはサポートがありません。
- ほとんどのポストプロセッシングエフェクトでは CommandBuffer.Blit/Graphics.Blit が使用されます。つまりこれらでは、RenderViewportScale をサポートするために use_MainTex_ST(およびその他の _ST メソッド)を使用できます。
- ただし、Unity のポストプロセッシングスタックの v.2 ではこれらのメソッドを使用できません。この問題に対処するには、ご自分でサポートを用意する必要がありますが、その手順は非常に簡単です。v.2 のスタックでは独自のバージョンのシェーダーインフラが使用されており、オーバーライドが容易だからです。以下の実行手順をご覧ください。
シェーダー側:
・新しい定数を作成します。xrLib.hlsl 内で "float rvsGlobal" を宣言します。
・rvsGlobal が利用されるよう TransformStereoScreenSpaceTex を変更します。
スクリプト側:
・スクリプトから RenderViewportScale 値をグローバルなプロパティーとしてバインドします。
・Shader.SetGlobalFloat を使用します。

ステレオのクランプ
隣接するスクリーンスペースサンプルをシェーダーで実行する場合、誤ってシングルパスの 2 倍幅で間違った目の部分をサンプリングしたり、RenderViewportScale の有効範囲外でサンプリングしたりしないようにする必要があります。このような場合には UnityStereoClamp を使用します。これにより、座標のサンプルをテクスチャの正しい部分に簡単にクランプできます。

使用法は非常にシンプルです。ただし、隣接するサンプリングを見つけるには手動で確認する必要があります。
テクスチャ座標から生成された補間によりオフセットを行おうとする場合は、大体の場合において Unity.StereoClamp() を使用する必要があることがほとんどです。
- クランプによる同じ座標のサンプリングの影響に注意してください。
- 頂点シェーダーでは使用しないでください。
6)先に進む前のその他のヒント
- 使用しているポストプロセッシングエフェクトが XR に適しているかどうかを確認してください。
- 瞬間的な作用がぼやけやレイテンシを追加する
- ヘッドセット内の物理カメラのプロパティである被写体深度のシミュレーションが人間の目をシミュレートするが、これが VR 酔いの原因となる可能性が高い。
- マルチパスの使用時に履歴テクスチャを維持する場合は、それぞれの目に 1 セットの履歴テクスチャが必要です。
- 単一の履歴セットにより両目で履歴を共有することになりかねない
- シングルパスで自動的に動作する
- 問題が発生したときは、別のステレオレンダリングモードをお試しください。
- アーティファクトの性質によりどこに不具合が出ているのかヒントが提供されることがある。