(Unity) GL.GetGPUProjectionMatrix による変換
Projection 行列の取得
自分で MVP 行列を掛けたりややこしいことをする人向けです。シェーダー内で Unity のマクロを使っている限りは内部的にいい感じにしてくれるため、この記事の内容は気にしなくてよいです。
Unity で Projection 行列は Camera.projectionMatrix で取得できますが、用途やプラットフォームによってはこの行列はそのまま使えません。
というのも、上記の行列は常に OpenGL 系の NDC (Normalized Device Coordinate) への変換行列となっています。現在のプラットフォームに対して正しい行列は GL.GetGPUProjectionMatrix メソッドを使う必要があります。
この辺までのふわっとした説明はネットで調べればいくらでも出てくるんですが、具体的な数値変換の内容があまりなかった (というか Unity の公式リファレンスが非常に分かりにくい解説だった) ため自分で調べました。
各プラットフォームでの NDC
前提として、Unity の OpenGLES の NDC は、
- 左
x = -1, 右x = 1 - 下
y = -1, 上x = 1 - 手前
z = -1, 奥z = 1
です。
また、Unity の DirectX, Vulkan, Metal では Reversed-Z の実装になっており、これらのプラットフォームでは
- 左
x = -1, 右x = 1 - 下
y = -1, 上x = 1 - 手前
z = 1, 奥z = 0
です。
Reversed-Z についての詳細はこちら。
https://developer.nvidia.com/content/depth-precision-visualized
また、Unity で現在のプラットフォームが Reversed-Z かどうかは SystemInfo.usesReversedZBuffer で取得できます。
GL.GetGPUProjectionMatrix の中身
GL.GetGPUProjectionMatrix は
Matrix4x4 proj = camera.projectionMatrix; Matrix4x4 projActual = GL.GetGPUProjectionMatrix(proj, false);
のように、GL 系の Projection 行列を現在のプラットフォーム用に補正した行列を返します。第二引数は RenderTexture に対して描画するかどうかを指定します。これの中身を調べるために、
Matrix4x4 mat1 = GL.GetGPUProjectionMatrix(Matrix4x4.identity, false); Matrix4x4 mat2 = GL.GetGPUProjectionMatrix(Matrix4x4.identity, true);
のように、単位行列入力してどうなるかを各プラットフォームで調べました。
GL.GetGPUProjectionMatrix(Matrix4x4.identity, false)
DX11, DX12, Vulkan, Metal:
1 0 0 0 0 1 0 0 0 0 -0.5 0.5 0 0 0 1
GLES:
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
GL.GetGPUProjectionMatrix(Matrix4x4.identity, true)
DX11, DX12, Vulkan, Metal:
1 0 0 0 0 -1 0 0 0 0 -0.5 0.5 0 0 0 1
GLES:
1 0 0 0 0 -1 0 0 0 0 1 0 0 0 0 1
でした。
解説
GL.GetGPUProjectionMatrix は GLES 以外のプラットフォームにおいて、
x' = x y' = y (renderIntoTexture: false) or = -y (renderIntoTexture: true) z' = -0.5 * z + 0.5
となるような変換行列を左から掛ける操作に相当します。GLES の場合は何も変換を行いません。
これは GL 系の NDC から Reversed-Z な NDC への射影となっていることがわかります。第二引数のブール値は、NDC のY軸は上が正なのに対して、Texture は通常下向きが正であるため、上下反転させるためのものです。