概要
現在レンダリング中のカメラのアスペクト比を Shader コードだけで完結して得る。 _ScreenParams
を用いると求める値が得られないシチュエーションが存在するため、代わりの計算手法としてプロジェクション行列の逆行列を用いる。
問題
Unity の Built-in shader variables にアスペクト比を得るための _ScreenParams.xy
が用意されているが、これはあくまで RenderTarget の解像度であるためカメラのアスペクト比とは一致しない。
一致しない状況としてたとえば、あるオフスクリーンレンダリング用のカメラにセットされたレンダーテクスチャの解像度は 1024x1024 だが、カメラのアスペクト比自体は 16:9 である、が挙げられる。その疑似コードを以下に示す。
// MonoBehaviour
var camera = GetComponent<Camera>(); // オフスクリーンカメラ
camera.targetTexture = new RenderTexture(1024, 1024, 16); // RenderTarget は 1024x1024
camera.aspect = 16f / 9f; // カメラのアスペクト比は 16:9
// ShaderLab HLSL
float aspect = _ScreenParams.x / _ScreenParams.y; // -> 1.0 :(
手法
プロジェクション行列の逆行列 unity_CameraInvProjection
が用意されているため、プロジェクション空間での近平面のもっとも右上の頂点を用意してビュー空間に逆変換する。
// ShaderLab HLSL
// 右上なので (x, y) = (1, 1)
// かつ近平面に存在するので z は DirectX のとき 0, OpenGL のとき -1
// w は nearClip の値
float4 projectionSpaceUpperRight = float4(1, 1, UNITY_NEAR_CLIP_VALUE, _ProjectionParams.y);
// プロジェクション行列の逆行列で変換
float4 viewSpaceUpperRight = mul(unity_CameraInvProjection, projectionSpaceUpperRight);
// 幅 / 高さ でアスペクト比
float aspect = viewSpaceUpperRight.x / viewSpaceUpperRight.y;
まとめ
プロジェクション行列の性質を利用してアスペクト比を計算した。プロジェクション行列の周りは DirectX や OpenGL の違い、Reversed Z の違いによる環境依存が大きいので、もうちょっと賢い方法があったら教えて欲しい。