【Unity】【数学】Unityでのビュー&プロジェクション行列とプラットフォームの関係

投稿者: | 2017年1月19日

はじめに

こんばんは、代表の堂前です!
というより、明けましておめでとうございます。

堂前個人としてはだいぶ間が空いてしまいましたが、新年初のブログを書こうと思います!
(昨年末は地獄の進行でなかなか時間が取れませんでした。。。)

今回のテーマですが、Unityでのビュー&プロジェクション行列についてまとめていこうと思います。

これを書こうと思ったきっかけですが、これら周りでスクリプトやシェーダを作成する際に非常に混乱することが度々おきまして・・・。
早い話、自分のメモとして残しておこうと思いました。

※検証に利用したのはUnity5.5.0f3になります。


何がややこしいのか?

先程、「Unityのビュー&プロジェクション行列がややこしい」的なことを書きました。
今回の話を進めるにあたり、自分が何に対してややこしいと思っているかを先にまとめます。

★ビュー行列
・UnityのScene上は左手座標系が原則だが、シェーダ内の行列(UNITY_MATRIX_V)では右手座標系になっているという情報がある。
・それらにおいて、カメラの前方(Z軸)はプラスかマイナスかがたまに混乱する。

★プロジェクション行列
・シェーダ内でのnear〜farが「-1〜1」か「0〜1」かで混乱する。(プラットフォーム依存?)
・更に5.5.0からnearとfarが反転しているらしい?(つまり「0〜1」ではなく「1〜0」となる)
・シェーダではなくスクリプトでも上記ルールが適応されるか?

それぞれについて検証しましたが、そのためのプロジェクト&シーン構成は以下のようにしています。

・「New Scene」で作った状態で、カメラに対してスクリプトを付与。
・↑のスクリプトは各種行列やグラフィックスAPIなどの情報を画面に表示して確認。
・カメラ自体は初期状態。(near=0.3, far=1000)
・実行解像度は640×480固定。(念のため)

Editor 実行画面
 Unity_VPmatrix_testproject0 Unity_VPmatrix_testproject1

それでは結果を見てみましょう!


ビュー行列

Unity_VPmatrix_cameraaxis

まず、ビュー行列(カメラ行列)というのはどういうものかというのを軽く書きます。

カメラはオブジェクトと同様、ワールドに(世界)に配置されます。
そこから捉えた絵を最終的にディスプレイに表示するのですが、その計算都合のため、カメラを原点に置いた座標系に一旦変換するための行列です。

「New Scene」でのカメラの初期座標は(0, 1, -10)ですが、これをビュー行列で変換すると(0, 0, 0)になります。

座標だけではなく、方向も特定の方向を向きますが、Unityの場合はワールドのZ軸に沿った方向を向くような変換になります。

というわけで本題に入りますが、Unityのスクリプト上でビュー行列を取得する方法は(把握している限り)以下になります。

・カメラのGameObject(Transform)のTransform.worldToLocalMatrixを利用。
・CameraコンポーネントのCamera.worldToCameraMatrixを利用。

結論から言えば、後者(Camera.worldToCameraMatrix)はシェーダに渡されるビュー行列(UNITY_MATRIX_V)となります。

そしてそれぞれを画面表示した結果が以下です。
(上がTransform.worldToLocalMatrix、下がCamera.worldToCameraMatrixになります。)

Unity_VPmatrix_dispview

両者とも(0, 1, -10)を乗算すると原点(0, 0, 0)になります。
が、下図の様なカメラ前方にある(0, 1, -5)の物体について考えます。

Unity_VPmatrix_viewobject

カメラを原点に持ってくるということは、物体も相対的に移動するはずです。
上図はシーン上ですが、Z軸がプラス方向に物体があるので、変換後はプラスになることが予想されます。

が、結果としては上が(0, 0, 5)に対し、下が(0, 0, -5)と、Zの値がプラスマイナス逆になります
あと具体例は割愛しますが、x,yに関しては値(方向)が変わりません。
これにより、シェーダとそれ以外で座標系が異なることがわかります。

以下、一覧としてまとめます。

座標系 前方(Z軸) スクリプト算出方法
シェーダ以外 左手座標系 プラス Transform.worldToLocalMatrix
シェーダ内 右手座標系 マイナス Camera.worldToCameraMatrix

ビュー行列に関してはプラットフォームには依存しません。(のハズです)
普段は左手座標で考えてスクリプトを組んで良いと思いますが、シェーダにカメラを考慮したマトリクスを渡す際などは気を付ける必要があります。


プロジェクション行列(シェーダ編)

プロジェクション行列についても軽く書きます。

カメラから捉えた絵をディスプレイなどの2Dに投影しますが、そのための計算を行うための行列になります。

2Dなので(x,y,z)で(x,y)が残るイメージですが、実はZ値も大きな要素ですし、今回の話のポイントにもなっています。

そしてその算出後のZ値(厳密に言えばW値で除算)ですが、カメラのnear〜farに対応した値になります。
下図のイメージで考えると良いです。

Unity_VPmatrix_nearfar

これはシェーダ内でプロジェクション行列(UNITY_MATRIX_P)を乗算して得られますが、Z値に関しては結論から言えばプラットフォームとUnityのバージョンでマチマチでした。
大きく分けて3種類ですので、相関を表としてまとめてみました。
(プラットフォームはビルトインシェーダ内のSHADER_API_xxxxの形式で記しています。(xxxxの部分のみ表記))

(near〜far)→(0〜1) (near〜far)→(-1〜1) (near〜far)→(1〜0)
Unity5.5.0以降
(5.5.0f3参照)
D3D9
WIIU
D3D11_9X
←   →
これら以外
(OPENGL等)
D3D11
PSSL
XBOXONE
METAL
VULKAN
Unity5.5.0以前
(5.4.4f1参照)
D3D9
XBOX360
PS3
D3D11
D3D11_9X
METAL
PSSL
WIIU
←これら以外
(OPENGL等)
(なし)
検証結果表示
(下の行列)
Unity_VPmatrix_depth_0to1 Unity_VPmatrix_depth_m1to1 Unity_VPmatrix_depth_1to0

上記結果の行列がシェーダ内のプロジェクション行列(UNITY_MATRIX_P)に相当します。
GL.GetGPUProjectionMatrixで算出しました。)

基本は「near〜far」で「0〜1」もしくは「-1〜1」という、「手前から奥にかけて値が増える」のが基本です。
シェーダ内でnearをあてにした計算には注意が必要ですが、更に5.5.0以降では「1〜0」という逆方向のものも存在します。
(ざっくりというと、描画精度向上のための変更です。)

逆方向かどうかとnearの値はビルトインシェーダで以下の様に定義されています。

シェーダ定義 備考
nearの値 UNITY_NEAR_CLIP_VALUE
逆方向か? UNITY_REVERSED_Z スクリプトでは
SystemInfo.usesReversedZBuffer
でも情報を取得できる。

シェーダで処理を書く際は、直値などではなく上記を利用して処理を行うのが良いでしょう。


プロジェクション行列(シーン編)

ここまでのプロジェクション行列の説明は、あくまでシェーダ処理内(UNITY_MATRIX_P)での話です。
ビュー行列と同様、シェーダ以外では意味合いなど変わってきます!

まず先程の行列表示をもう一度掲載します。

(near〜far)→(0〜1) (near〜far)→(-1〜1) (near〜far)→(1〜0)
Unity_VPmatrix_depth_0to1 Unity_VPmatrix_depth_m1to1 Unity_VPmatrix_depth_1to0

行列を2つ表示していて、下の方の行列がシェーダ側の行列(UNITY_MATRIX_P)に相当しています。
これはGL.GetGPUProjectionMatrixで算出していて、引数はCamera.projectionMatrixを渡しています。

そして結果の上の行列はそのCamera.projectionMatrixを渡しているのですが、それはプラットフォームでの違いがなく、(near〜far)→(-1〜1)相当であると言えます。

ちなみにCamera.projectionMatrixではなくMatrix4x4.Perspectiveで算出した時もプラットフォームによる違いが出ず、(near〜far)→(-1〜1)となりました。
(試していないですがMatrix4x4.Orthoも恐らくそうです。)

ここでシェーダ内とそれ以外でまとめます。

near〜far範囲 スクリプト算出方法
シェーダ以外 -1〜1のみ Camera.projectionMatrix
Matrix4x4.Perspective
Matrix4x4.Ortho
シェーダ内 0〜1 もしくは
-1〜1 もしくは
1〜0
(プラットフォームで変わる)
GL.GetGPUProjectionMatrixで算出

スクリプトで算出して処理する時は-1〜1のみで考えて良いですが、スクリプトからシェーダに直に値を渡す時などは十分に注意が必要です。


まとめ

シェーダーでトリッキーな処理をする時にスクリプトからビュー&プロジェクション行列の結果を活用したり、シェーダ内でも前後関係などを把握する時などがたまにありますが、その時に意外とハマり易いのがこの部分です。

プラットフォーム依存もあり、不具合の特定が遅れるのもありますので、普段から内部ではこの様になっているのを意識すると良いかなと思います。


 

 

【免責事項】
本サイトでの情報を利用することによる損害等に対し、株式会社ロジカルビートは一切の責任を負いません。