Unity Google VR Video Async Reprojection
HoloLens のシングルパスステレオレンダリング

シングルパスステレオレンダリング

シングルパスステレオレンダリングは、PC と Playstation 4 ベースの VR アプリケーション機能です。左右の目の画像を同時に 1 つのまとまったレンダーテクスチャにレンダリングします。つまり、シーン全体が 1 回だけレンダリングされ、CPU 処理時間が大幅に短縮されます。この機能がないと、Unity は、最初は左目画像を次に右目画像をというように、シーンを 2 回レンダリングします。

下の 2 つの画像は、通常の VR レンダリングとシングルパスステレオレンダリングの違いを比較しています。

通常の VR レンダリング

左目の画像を左にレンダリング、右目の画像を右にレンダリング
左目の画像を左にレンダリング、右目の画像を右にレンダリング

シングルパスステレオ VR レンダリング

左右の目の画像を 1 つにまとめてレンダリング
左右の目の画像を 1 つにまとめてレンダリング

この機能を有効にするには PlayerSettings __ を開きます (Edit > Project Settings > Player)。PlayerSettings__ で Other Settings に移動し、Virtual Reality Supported にチェックを入れ、それからその下の Single-Pass Stereo Rendering にチェックを入れます (2017.3 の新しいバージョンでは、PlayerSettingsXR Settings に移動し、Virtual Reality Supported にチェックを入れ、その下の __Stereo Rendering Mothod __ ドロップダウンで Single Pass を選択します)。

Unity のビルトインのレンダー機能とスタンダードアセットはすべてこの機能と互換性があります。 ただし、カスタムシェーダーや Asset Store からダウンロードしたシェーダーは変更しなくてはならない場合があります (例えば、1 つにまとめたレンダーテクスチャの適切な半分にアクセスするために、スクリーンスペース座標にスケールとオフセットが必要な場合があります)。

シングルパスステレオレンダリングをサポートするシェーダーの作成と修正

UnityCG.cginc の既存のヘルパー関数はシングルパスステレオレンダリングをサポートします。

UnityCG.cginc には以下のヘルパー関数もあり、ステレオスコピックシェーダーを作成する支援をします。

プロパティー 説明
UnityStereoScreenSpaceUVAdjust(uv, sb) パラメーター:
uv - UV テクスチャ座標。標準 UV には float2。2 つの UV を 1 つにまとめたものには float4。
sb - 2D スケールと 2D バイアスを含む float4 が UV に適用されます (xy にスケール、zy にバイアス)。 
説明: sb のスケールとバイアスを uv のテクスチャ座標に適用した結果を返します。 これは、UNITY_SINGLE_PASS_STEREO が定義されている場合にのみ発生します。そうでない場合は、テクスチャ座標は変更されずに返されます。 これは、シングルパスステレオレンダリングモードの場合にのみ、目ごとのスケールとバイアスを適用するためによく使用されます。
UnityStereoTransformScreenSpaceTex(uv) パラメーター:
uv - UV テクスチャ座標。標準 UV には float2。2 つの UV を 1 つにまとめたものには float4。
説明: uv のテクスチャ座標に現在の目のスケールとバイアスを適用した結果を返します。 これは、UNITY_SINGLE_PASS_STEREO が定義されている場合にのみ発生します。そうでない場合は、テクスチャ座標は変更されずに返されます。
UnityStereoClamp(uv, sb) パラメーター:
uv - UV テクスチャ座標。標準 UV には float2。2 つの UV を 1 つにまとめたものには float4。
sb - 2D スケールと 2D バイアスを含む float4 が UV に適用されます (xy にスケール、zy にバイアス)。 
説明: sb が提供する幅とバイアスによって決定された x 値にクランプされた uv を 返します。これは、UNITY_SINGLE_PASS_STEREO が定義されている場合にのみ発生します。そうでない場合は、テクスチャ座標は変更されずに返されます。 これは、目と目の間の色の漏れを避けるために、シングルパスステレオレンダリングモードで各目のクランプ値を適用するためによく使用されます。

加えて、定数 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()

  • メッシュベースの描画

これらの方法に関しては以下を参照してください。

これらの調整を行わないと、各描画コマンドはソースのレンダーテクスチャのすべて (左目と右目の両方のビューを含む) を読み込み、画像全体を出力レンダリングテクスチャの左目と右目の両方に出力します。結果的に、誤って各目にソース画像全体が描画されます。

これが発生する理由は、各ポストプロセスエフェクトが Graphics.Blit を使用するか、またはテクスチャマップを使用してフルスクリーンポリゴンを描画するかのいずれかによって行われるためです。どちらの方法も、チェーン内の前のポストプロセスエフェクトの出力全体を参照します。1 つにまとまったたステレオレンダーテクスチャ内の領域を参照するために使用されるときは、半分だけではなく 1 つにまとまったレンダーテクスチャ全体を参照します。

Graphics.Blit()

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;
}

Unity Google VR Video Async Reprojection
HoloLens のシングルパスステレオレンダリング