シェーダーのVR上での描写対策として、描写のクリップ変換を独自に作成する必要がありました。
Unityの変換マトリックスに触れるのが初めてというのもありましたが、なかなか気づかなかった落とし穴があったため、それについてまとめます。
(私はシェーダーのエキスパートではないため、間違いがある可能性もあります。ご了承ください。)
(また、上記のVRシェーダーに関する記事もいつか書きたいと考えています。)
やりたいこと
独自に処理をしたカメラの位置情報を頂点シェーダーに反映させたい。かつシェーダー内だけで完結させたい。
そんなこと可能なのか?と思ったのですがそのシェーダー、マテリアルを持つオブジェクトに対してのみであればできるようです。
UnityWorldToClipPos
の中身
ShaderLabで簡単な頂点シェーダーを書く時に、よくお世話になっているUnityWorldToClipPosですが、この一つの関数の中で様々な処理が行われているようです。簡易的にですが中身を分解して内容を書いていきます。
float 4 ワールド空間座標 = mul(unity_ObjectToWorld, 頂点座標);
float 4 ビュー空間座標 = mul(UNITY_MATRIX_V, ワールド空間座標);
float 4 クリップ空間座標 = mul(UNITY_MATRIX_P, ビュー空間座標);
mul関数は行列の乗算です。3回の計算を通して頂点座標が変換されているのが分かると思います。
では、この3行のコードで変換をした空間座標というのは一体何を意味するのでしょうか?
ワールド空間座標
まず、シェーダーから取得できる頂点座標はモデル空間に準じます。要するにメッシュデータ内の位置づけであり、ワールド等で場所を変えたとしてもその座標の値は変動することはありません。
次にunity_ObjectToWorld
は、そのシェーダーが適応されているオブジェクトのワールド座標のことを指します。
更に言い換えるとmul(ワールド座標, 頂点座標)
ということになり、その結果は各頂点座標がワールド座標との行列乗算によってワールド空間に準じた座標、つまりUnityエディター上でも見るようなワールド座標に変換されるということになります。
ワールド座標になったことで直感的に位置が分かるようにはなっていると思います。しかし、シェーダーにおいてはこの情報だけで描写するのにはまだ足りません。
ビュー空間座標
適切な位置に描写させるには、自分自身の視点、つまりカメラの位置が必要になります。
名前の通り、カメラからの視界を元に更に座標を変換していきます。
では、mul関数内のUNITY_MATRIX_V
はカメラの位置、向きを表していることになるはずです。
しかし、ここに意外な落とし穴が。後述します。
クリップ空間座標
最後の変換処理を通すことで見覚えのある3D空間に変換、オブジェクトも正しく配置される様子が見えると思います。
クリップ空間への変換はスクリーン上での見え方に関係し、例えば奥行きの表現、座標の正規化(-1.0~1.0)など3D空間表現において重要な処理を担っています。
クリップ空間変換、プロジェクション変換とも呼ばれています。
他にも別の内部的な処理の話や、正確な表現もできたりすると思いますが割愛します…。
やりたいことの話へ戻りましょう。
ワールド空間上におけるカメラの情報unity_WorldToCamera
独自に処理をしたカメラの位置情報を頂点シェーダーに反映させたい。かつシェーダー内だけで完結させたい。
ということだったのですが、先程上げた空間変換の処理の中に手を加えれば良さそうなものがあったと思います。
もちろん、ビュー空間変換のところですね。
UNITY_MATRIX_V
に対して処理を加えるのもいいですが、ここはカメラの情報の代わりになりそうな別の変数を用意します。
それがunity_WorldToCamera
と言われるもので、こちらも同様にカメラの情報を含めたfloat4x4の行列変数…のはずでした。
諸事情
この変数を使いたかった理由としては、シングルパスステレオレンダリングというVR用のレンダリング方法があり、そちらで使える類似した変数、unity_StereoWorldToCameraというものがあり、左右の目のカメラ情報が必要だった事情がありました。予想が正しければUNITY_MATRIX_V
と完全に代用ができると思っていたのですが、ここでは上手くいかずしばらくつまづいていました。
それぞれの変数の違いとは?UNITY_MATRIX_V
に反映されている値に答えが載っていました。
Unity Discussionsにて、UNITY_MATRIX_V
とunity_WorldToCamera
に入っている値は以下のようになっているという話がありました。
UNITY_MATRIX_V (and unity_MatrixV which is the same matrix) is negative Z forward, and is the c# equivalent of:
camera.cameraToWorldMatrix
edit:camera.worldToCameraMatrix
unity_WorldToCamera is positive Z forward, and is the c# equivalent of:
Matrix4x4.TRS(camera.transform.position, camera.transform.rotation, Vector3.one)
The inverse matrices are of course the inverse of those.
引用:Unity Discussions | UNITY_MATRIX_I_V or cameraToWorldMatrix? | 2/4 Oct 2021 | 2024年11月11日
・引用元リンク
負、正のZ軸?一体どういうことでしょう。
続けて、camera.worldToCameraMatrix
という単語でUnityのドキュメントを探してみたところ、見つかりました。
cam.worldToCameraMatrix
に代入される値の前処理を見てみると、Unity Discussionsにも記述されていたMatrix4x4.TRS
という処理が入っていました。
ここで違いがあることが分かりますね?UNITY_MATRIX_V = camera.worldToCameraMatrix
とunity_WorldToCamera
を比較してみましょう。
camoffset = new Vector3(-offset.x, -offset.y, offset.z)
UNITY_MATRIX_V = Matrix4x4.TRS(camoffset, Quaternion.identity, new Vector3(1, 1, -1));
unity_WorldToCamera = Matrix4x4.TRS(camera.transform.position, camera.transform.rotation, Vector3.one)
引用:Unity Discussions | UNITY_MATRIX_I_V or cameraToWorldMatrix? | 2/4 Oct 2021 | 2024年11月11日
引用:Unity Documentation | Version:2022.1 Camera.worldToCameraMatrix | 2024年11月11日
・引用元リンク
camoffset
ではXYが負に反転しており、Matrix4x4.TRS
の三つ目の要素、Scale
ではVector3のZが負に反転しています。
二つのカメラ(ビュー)情報には、Z軸が反転されているかいないか、そういう違いがあったのでした。
なぜZ軸を反転しないといけないのか?
以下の検証、解説記事が参考になります。今回のケースはUnityのビュー変換の処理が非常に特殊な事情を抱えていることに所以するようです。
参考:武相荘 | UnityObjectToViewPos関数は右手系用? | 2024年11月11日
参考:Tech Inside Drecom | 空間とプラットフォームの狭間で – Unityの座標変換にまつわるお話 – | 座標変換で気をつけること ワールド空間 -> カメラ空間 | 2024年11月11日
参考文献
上記以外に、シェーダーや行列による変換等を学ぶにあたりとても参考になった記事を置きます。
参考: Qiita | CGに使用される行列についての考察
参考: LIGHT11 | 【Unity】MVP行列による座標変換の概念と用語の整理