コンテンツにスキップ

シェーダーのVR対応

シングルパスステレオ

VRChatなどのUnityのVRアプリはシングルパスステレオレンダリングという方法でレンダリングされている。 これは左右の目のオブジェクトを一つのパスで書き出すことで、SetPassCallを減らすことができる最適化手法である。

シングルパスステレオの場合、ポストエフェクトなどの画面全体を対象とするシェーダーでは特に注意が必要になる。

普通にフレームバッファをキャプチャした場合、それは両目分の画面が含まれてしまう。

シングルパスステレオのサポート

シングルパスステレオが有効かどうかはUNITY_SINGLE_PASS_STEREOを使って確認できる。

左右の目でそれぞれ左右の目の画面のフレームバッファを取得したい場合は以下のような書き方をする。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void surf(Input IN, inout SurfaceOutput o) 
{
    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

}

UnityCG.cgincで定義済みな関数

ComputeScreenPos

通常VR、非VR対応でScreenPosを取得する場合はこの関数を使用すればOK。 上記のような左右の目に合わせたUVの調整を行ってくれる。

入力はMVP変換後の座標。

1
2
3
4
5
6
7
inline float4 ComputeScreenPos(float4 pos) {
    float4 o = ComputeNonStereoScreenPos(pos);
#if defined(UNITY_SINGLE_PASS_STEREO)
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
    return o;
}

ComputeNonStereoScreenPosはMVP変換後の座標を正規化する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
inline float4 ComputeNonStereoScreenPos(float4 pos) {
    // この時点でxyは-wからwの値を取る(MVP変換後だから)
    // xyzwに0.5をかけるのでxyは-0.5w~0.5wになる
    float4 o = pos * 0.5f;

    // この処理でxyが0~wになる
    // yがプラットフォームにより上下反転する問題も吸収
    o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;
    o.zw = pos.zw;
    return o;
}
1
2
3
4
5
float2 TransformStereoScreenSpaceTex(float2 uv, float w)
{
    float4 scaleOffset = unity_StereoScaleOffset[unity_StereoEyeIndex];
    return uv.xy * scaleOffset.xy + scaleOffset.zw * w;
}

Pimax対応