シングルパスステレオレンダリング は、PC と Playstation 4 ベースの VR アプリケーション機能です。左右の眼の画像を同時に 1 つのまとまったレンダーテクスチャ (眼単位のテクスチャの 2 倍の幅があります) に描画します。Unity は Renderer コンポーネントを持つ各ゲームオブジェクトに対し 2 回ドローコールを使用して、シーンを 2 回描画します。ただし、左眼と右眼の両方を描画するときにシーングラフを通して 1 回だけ行います。シングルパスステレオレンダリングでは、両眼はカリングと影の計算に必要な作業を共有します。GPU は各ゲームオブジェクトをピンポン方式でレンダリングするため (左右の眼のオブジェクトのレンダリングを交互に行うため)、グラフィックスコマンドの状態変更スイッチも少なくなります。
シングルパスステレオレンダリングを使用すると、GPU は両眼のカリングを共有できます。GPU は、カリング処理のため、シーン内のすべてのゲームオブジェクトを 1 回通ります。それから、カリング処理後に残ったゲームオブジェクトをレンダリングします。
下の 2 つの画像は、通常の VR レンダリングとシングルパスステレオレンダリングの違いを比較しています。
通常の VR レンダリング
シングルパスステレオ VR レンダリング
この機能を有効にするには PlayerSettings を開きます (Edit > Project Settings > Player)。PlayerSettings で XR Settings に移動し、Virtual Reality Supported にチェックを入れ、その下の Stereo Rendering Method ドロップダウンで Single Pass を選択します。
Unity のビルトインのレンダリング機能と Standard Assets はすべてこの機能をサポートします。ただし、カスタムシェーダーや Asset Store からダウンロードしたシェーダーにシングルパスステレオレンダリングサポートを加えるには、変更しなくてはならない場合があります (例えば、1 つにまとめたレンダーテクスチャの適切な片側にアクセスするために、スクリーンスペース座標にスケールとオフセットが必要な場合があります)。
UnityCG.cginc
の既存のヘルパーメソッドはシングルパスステレオレンダリングをサポートします。アプリケーションが XR かどうかに関わらず、頂点の変換を実行する必要があります。例えば、任意のタイプのアプリケーションを作成するとき、頂点はモデル空間の頂点シェーダーステージに入り、クリップスペースで終了します。頂点シェーダーは、クリップスペースの頂点座標を出力する必要があります。シェーダーの影響を受ける頂点のセットは、通常、頂点シェーダーがそれらをクリップ空間に変換する前にモデル空間で開始します。ただし、これらの頂点がクリップ空間に到達するためには、まず頂点シェーダーはそれらをワールド空間に変換し、次にビューポート空間に変換します。
XR の場合、複数のビューの行列があります。1 つは左右の眼のためのものです。ビルトインメソッド UnityWorldToClipPos
を使うと、Unity は計算で複数のビュー行列を扱う必要があるかどうかを考慮します。UnityWorldToClipPos
メソッドを使用すると、アプリケーションをが実行するプラットフォームに関係なく、シェーダーが自動的に変換の計算を正しく実行します。
UnityCG.cginc
には以下のヘルパーメソッドもあり、ステレオスコピックシェーダーを作成する支援をします。
プロパティー | パラメーター | 説明 |
---|---|---|
UnityStereoScreenSpaceUVAdjust(uv, sb) |
uv : UV テクスチャ座標。標準 UV には float2。2 つの UV を 1 つにまとめたものには float4。sb : 2D スケールと 2D バイアスを含む float4 が UV に適用されます (xy にスケール、zy にバイアス)。 |
UNITY_SINGLE_PASS_STEREO が定義されている場合は、sb のスケールとバイアスを uv のテクスチャ座標に適用した結果を返します。 そうでない場合は、テクスチャ座標を変更せずに返します。これは、シングルパスステレオレンダリングモードの場合にのみ、それぞれの眼にスケールとバイアスを適用するために使用します。 |
UnityStereoTransformScreenSpaceTex(uv) |
uv : UV テクスチャ座標。標準 UV には float2。2 つの UV を 1 つにまとめたものには float4。 |
UNITY_SINGLE_PASS_STEREO が定義されている場合は、現在使用している眼のスケールとバイアスを uv のテクスチャ座標に適用した結果を返します。そうでない場合は、テクスチャ座標を変更せずに返します。 |
UnityStereoClamp(uv, sb) |
uv : UV テクスチャ座標。標準 UV には float2。2 つの UV を 1 つにまとめたものには float4。sb : 2D スケールと 2D バイアスを含む float4 が UV に適用されます (xy にスケール、zy にバイアス)。 |
UNITY_SINGLE_PASS_STEREO が定義されている場合は、sb から取得した幅とバイアスによって決定した x 値にクランプを適用した結果を、uv のテクスチャ座標に返します。 そうでない場合は、テクスチャ座標を変更せずに返します。これは、シングルパスステレオレンダリングモードでそれぞれの眼にクランプを適用して、両眼の間に色の漏れが発生するのを防ぎます。 |
加えて、ビルトインの constant 変数 unity_StereoEyeIndex
はシェーダーからアクセス可能で、そのため、眼に依存した計算が可能です。unity_StereoEyeIndex
の値は、左眼のレンダリングには 0、右眼には 1 です。
この UnityCG.cginc
の例は unity_StereoEyeIndex
を使ってスクリーンスペース座標を変更する方法を示しています。
float2 TransformStereoScreenSpaceTex(float2 uv, float w)
{
float4 scaleOffset = unity_StereoScaleOffset[unity_StereoEyeIndex];
return uv.xy * scaleOffset.xy + scaleOffset.zw * w;
}
ほとんどの場合、シェーダーを変更する必要はありません。 ただし、シングルパスステレオレンダリングのソースとして 平面視テクスチャ をサンプリングする必要がある場合があります。例えば、フルスクリーンのフィルムのグレインまたはノイズ効果を作成する場合、ソース画像は 1 つの ステレオスコピック画像 にまとめるのではなく、両眼に同じ画像があることが必要です。このような状況では、ComputeScreenPos()
の代わりに ComputeNonStereoScreenPos()
を使用して、完全なソーステクスチャから位置を計算します。
シングルパスステレオレンダリングを使用する場合に、ポストプロセスエフェクト にはより注意を払う必要があります。各ポストプロセスエフェクトは、1 つにまとめられたレンダーテクスチャ (これには左眼と右眼の両方の画像が含まれています) で 1 回実行されます。しかし、ポストプロセス中に実行される描画コマンドはすべて、左眼側に描かれるレンダーテクスチャと右眼側レンダーテクスチャで 2 回行われます。
ポストプロセスエフェクトはシングルパスステレオレンダリングを自動的に検出するわけではないので、1 つにまとめたステレオレンダーテクスチャの読み込みの場合は、正しい眼からの読み込みがレンダリングされるように調整する必要があります。これを行うには、ポストプロセスエフェクトのレンダリング方法に応じて 2 つの方法があります。
上の調整を行わないと、各描画コマンドはソースのレンダーテクスチャのすべて (左眼と右眼の両方のビューを含む) を読み込み、画像全体を出力レンダリングテクスチャの左眼と右眼の両方に出力します。結果的に、誤って各眼にソース画像全体が描画されます。
これは、各ポストプロセスエフェクトを描画するために Graphics.Blit
や、テクスチャマップを使用したフルスクリーンポリゴンを使用するときに発生します。どちらの方法も、チェーン内の前のポストプロセスエフェクトの出力全体を参照します。1 つにまとまったたステレオレンダーテクスチャ内の領域を参照する場合は、半分だけではなく 1 つにまとまったレンダーテクスチャ全体を参照します。
Blit()
でレンダリングされたポストプロセスエフェクトは、1 つにまとまったステレオレンダリングテクスチャの正しい部分を自動的には参照しません。デフォルトでは、テクスチャ全体を参照します。これにより、誤って両眼にポストプロセスエフェクトを広げてしまいます。
Blit()
を使用したシングルパスステレオレンダリングの場合、シェーダーのテクスチャサンプラーには自動的に計算された追加の変数があり、描かれる眼に応じて、1 つにまとまめられたステレオレンダーテクスチャの正しい半分を参照します。この変数には、ターゲット座標を正しい位置に変換するためのスケール値とオフセット値が含まれています。
この変数にアクセスするには、サンプラーと同じ名前のシェーダーで half4
を宣言し、suffix _ST
を追加します (コード例は以下を参照してください)。UV 座標を調整するには、 _ST
変数を scaleAndOffset
に渡し、UnityStereoScreenSpaceUVAdjust(uv, scaleAndOffset)
を使用します。このメソッドは非シングルパスステレオビルドでは何もコンパイルしません。つまり、このモードをサポートするように変更されたシェーダーは非シングルパスステレオビルドと互換性があります。
以下のコード例は、シングルパスステレオレンダリングをサポートするために何を変更する必要があるかを示しています。
ステレオレンダリングなし
uniform sampler2D _MainTex;
fixed4 frag (v2f_img i) : SV_Target
{
fixed4 myTex = tex2D(_MainTex, i.uv);
...
}
ステレオレンダリングあり
uniform sampler2D _MainTex;
half4 _MainTex_ST;
fixed4 frag (v2f_img i) : SV_Target
{
fixed4 myTex = tex2D(_MainTex, UnityStereoScreenSpaceUVAdjust(i.uv, _MainTex_ST));
...
}
メッシュを使用して (例えば、 低レベルのグラフィックス API) を使用して直接モードで四辺形を描画することによって) ポストプロセスエフェクトをレンダリングするには、それぞれの眼をレンダリングするときにターゲットテクスチャの UV 座標を調整する必要もあります。この状況で座標を調整するには、UnityStereoTransformScreenSpaceTex(uv)
を使用します。このメソッドは、シングルパスステレオレンダーモードで 1 つにまとめられたステレオレンダーテクスチャを正しく調整し、シングルパスステレオレンダーモードが無効の場合は、自動的に 1 つにまとめないレンダーテクスチャにコンパイルをします。ただし、同じモードで 1 つにまとめられたステレオレンダーテクスチャと 1 つにまとめられていないレンダーテクスチャの両方にシェーダーを使用する場合は、2 つの別々のシェーダーを用意する必要があります。
スクリーンスペースエフェクトは、事前にレンダリングされた画像の上に加える視覚効果です。 スクリーンスペースエフェクトには、アンビエントオクルージョン、被写界深度、ブルーム などがあります。
例えば、画面全体にイメージの描画が必要なスクリーンスペースエフェクトがある場合を想像してください (例えば、画面上に飛び散った何らかの汚れの描画など)。 この場合、汚れの描画を両眼の範囲に引き伸ばして出力表示域全体にエフェクトを適用するのではなく、片方の眼に 1 回ずつ、描画を 2 回適用する必要があります。 これを処理するには、1 つにまとめられたレンダーテクスチャ全体を参照するテクスチャ座標を、各眼を参照する座標に変換する必要があります。
以下のコード例は、入力テクスチャ (_Detail) を出力イメージ全体に 8 x 6 回繰り返すサーフェスシェーダーを表しています。2 番目の例では、シェーダーはシングルパスステレオモードで目的座標を変換し、現在レンダリング中の眼を表す出力テクスチャの部分を参照します。
例 1 シングルパスステレオのサポートがない Detail テクスチャ
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
screenUV *= float2(8,6);
o.Albedo *= tex2D(_Detail, screenUV).rgb * 2;
}
例 2 シングルパスステレオのサポートがある Detail テクスチャ
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
#if UNITY_SINGLE_PASS_STEREO
// シングルパスステレオモードが有効の場合は、
// 現在処理中の眼の正しい出力 UV を取得するように座標を変換します。
float4 scaleOffset = unity_StereoScaleOffset[unity_StereoEyeIndex];
screenUV = (screenUV - scaleOffset.zw) / scaleOffset.xy;
#endif
screenUV *= float2(8,6);
o.Albedo *= tex2D(_Detail, screenUV).rgb * 2;
}