Screen Space で色々やることになったときのメモ。慣れてなかったのでめちゃ混乱しました。
基本
画面の色をテクスチャで取得する
よく知られているように GrabPass
を使います。GrabPass {}
と書くと _GrabTexture
に、GrabPass { "Name" }
と書くと Name
に画面の色が入ったテクスチャが格納されます。名前を付けた場合は、複数のオブジェクトで結果が共有されます。
描画されるオブジェクトの位置に対応するテクスチャ座標を得るには、ComputeGrabScreenPos
を経由する必要があります。これはプラットフォームによって正しいテクスチャ座標が異なるためです。間違えると上下反転したり、VRで隣の目のデータに突っ込んだりする可能性があります。
画面の深度をテクスチャで取得する
_CameraDepthTexture
という名前のテクスチャを使います。このテクスチャには深度バッファの生データが入っています。
深度テクスチャを宣言する際は UNITY_DECLARE_DEPTH_TEXTURE
を使用する必要があります。これも深度テクスチャの型がプラットフォームによって異なるためです。テクスチャの名前は _CameraDepthTexture
で固定なので、シェーダ内では
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
と書くことになります。
深度テクスチャの参照は、スクリーン座標で行います。スクリーン座標を得るには、ComputeScreenPos
を使用する必要があります。これもプラットフォーム依存のためです。
さらに、同じ理由で参照の際は SAMPLE_DEPTH_TEXTURE
を使用する必要があります。引数は tex2D
の場合と一緒で、返り値は float
です。tex2Dproj
や tex2Dlod
に対応する SAMPLE_DEPTH_TEXTURE_PROJ
や SAMPLE_DEPTH_TEXTURE_LOD
もあります。
得られたデータは多くの場合非線形なので、利用する前に線形なデータに変換する必要があります。LinearEyeDepth
を使うと非線形のデータをビュー座標系におけるカメラからの距離に変換できます。一方、Linear01Depth
を使うとニアクリップ面で 0、ファークリップ面で 1 になる値に変換できます。
中身をいじる必要が出てきた場合
単純に対応するピクセルの値を参照したい場合は上記の方法に従っていればいいんですが、反射や屈折のエフェクトを実装する場合は ComputeGrabScreenPos
やら ComputeScreenPos
やらで得られた値をいじって使う必要が出てきます。このとき中身がどうなってるか知らないと困ることになります。
ComputeGrabScreenPos
で得られた値
直ちに tex2D
で画面の色をサンプリングできる値が入っています。つまり、それまでに生じた環境依存の変換が全て詰まっています。具体的には、
UNITY_UV_STARTS_AT_TOP
が定義されている場合、Y 方向の値が反転する_ProjectionParams.x
の値が -1 の場合、Y 方向の値が反転するUNITY_SINGLE_PASS_STEREO
が定義されている場合、現在描画中の目に依存して X 方向の値が変換される
があります。自分の環境では UNITY_UV_STARTS_AT_TOP
が定義されていてかつ _ProjectionParams.x
が -1 だったので、一見反転してないように見えていました。
下は XY を RG に割り当てた図です。Unity Editor 内では左下が原点に見えます。
一方同じシェーダを VRChat の中で見るとこうなります。_ProjectionParams.x
の値が 1 になって、Y 方向が反転しています。
ComputeScreenPos
で得られた値
これは自分の知る限り、環境に依存する反転は生じません。ただし、UNITY_SINGLE_PASS_STEREO
が定義されている場合は X 方向の値が現在描画中の目によって変換されます。
しかし、UnityCG.cginc
内にある ComputeScreenPos
の定義を見てみると、なんと Y 方向を _ProjectionParams.x
の値によって反転させているではありませんか。
実は、これはそもそもクリップ座標系の Y 座標が反転していることがあるためで、それを元に戻すための変換を行っています。したがって、_ProjectionParams.x
が -1 の環境では、反転が生じないからといって自前でクリップ座標からスクリーン座標を愚直に計算しようとすると Y 方向が反転します。しました。
まとめると次のようになります。
- クリップ座標は環境によって Y 方向がひっくり返っていることがある(実は Z 方向はもっと酷いことになっているが、幸い気にしなくて済む)
- スクリーン座標は左下原点が基本だが、
UNITY_SINGLE_PASS_STEREO
が定義されているときは目ごとに X 座標が変わる ComputeGrabScreenPos
で得た値はもう Y 方向がめちゃめちゃ入れ替わる、X 座標もUNITY_SINGLE_PASS_STEREO
の影響を受けるGrabPass
で得たテクスチャを参照する座標と_CameraDepthTexture
を参照する座標を一緒にしてはいけない
よって、一つ左のピクセルの色と深度を得たい、みたいなときは非常に慎重な作業が要求されます。Unity 側で「常に左下が (0, 0)
で、右上が (1, 1)
」みたいな正規化された座標系からの相互変換が行えるマクロを用意してくれていると楽なんですが、残念ながら存在しないようです。
おわり
どこか間違っている点があったら、恐らく後で困ることになると思うので Twitter で教えてもらえると助かります。