unity_WorldToCameraと、UNITY_MATRIX_Vは同じではない？

シェーダーの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の行列変数…のはずでした。

<details><summary>諸事情</summary>
この変数を使いたかった理由としては、シングルパスステレオレンダリングというVR用のレンダリング方法があり、そちらで使える類似した変数、unity_StereoWorldToCameraというものがあり、左右の目のカメラ情報が必要だった事情がありました。
</details>

* * *

予想が正しければ`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日

・引用元リンク

https://discussions.unity.com/t/unity_matrix_i_v-or-cameratoworldmatrix/858641/2

**負**、**正の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日

・引用元リンク

https://docs.unity3d.com/ja/2022.1/ScriptReference/Camera-worldToCameraMatrix.html

`camoffset`では**XYが負に反転**しており、``Matrix4x4.TRS``の三つ目の要素、`Scale`では**Vector3のZが負に反転**しています。
二つのカメラ(ビュー)情報には、**Z軸が反転されているかいないか**、そういう違いがあったのでした。

## なぜZ軸を反転しないといけないのか？

以下の検証、解説記事が参考になります。今回のケースはUnityのビュー変換の処理が非常に特殊な事情を抱えていることに所以するようです。

https://buaiso.blogspot.com/2020/12/unityobjecttoviewpos.html

>参考:武相荘 | UnityObjectToViewPos関数は右手系用？ | 2024年11月11日

https://tech.drecom.co.jp/knowhow-about-unity-coordinate-system/

>参考:Tech Inside Drecom | 空間とプラットフォームの狭間で – Unityの座標変換にまつわるお話 – | 座標変換で気をつけること ワールド空間 -> カメラ空間 | 2024年11月11日

## 参考文献
上記以外に、シェーダーや行列による変換等を学ぶにあたりとても参考になった記事を置きます。

https://qiita.com/yuji_yasuhara/items/8d63455d1d277af4c270

>参考: Qiita | CGに使用される行列についての考察

https://light11.hatenadiary.com/entry/2019/01/27/160541

>参考: LIGHT11 | 【Unity】MVP行列による座標変換の概念と用語の整理