3Dグラフィックスを描画するときに、ディスプレイ上の描画位置を計算する処理が MVP行列 を使った座標変換です。
モデル行列、ビュー行列、プロジェクション行列を使った変換のことですが、Unity上で ShaderLab を使ってシェーダーを書く場合、
それぞれシェーダー変数により定義されているため、手作業で計算する必要はありません。
UNITY_MATRIX_V 現在のビュー行列 UNITY_MATRIX_P 現在のプロジェクション行列 unity_ObjectToWorld 現在のモデル行列
今回はこれらをそれぞれ手作業で計算したく(主に悪巧みのため)、めでたく成功したので備忘録としてまとめます。
モデル行列
モデル行列は、メッシュの頂点位置をワールド座標系になおすための変換行列です。
Matrix4x4 m = Matrix4x4.TRS( transform.position, transform.rotation, transform.lossyScale );
Matrix4x4.TRS は変換行列を計算するためのAPIで、これを使うことで簡単に行列を求めることができます。
上記コードの transform は描画対象の GameObject を指しています。
Matrix4x4-TRS - Unity スクリプトリファレンス
Transform.localToWorldMatrix と同じ値になります。
ビュー行列
ビュー行列はカメラとの位置関係を反映するための行列です。
Matrix4x4 v = Matrix4x4.TRS(
cameraTransform.position,
cameraTransform.rotation,
new Vector3(cameraTransform.lossyScale.x, cameraTransform.lossyScale.y, -cameraTransform.lossyScale.z)
).inverse;
cameraTransform はカメラとする GameObject のTransformです。
今回はCameraコンポーネントでの描画ではなくグラフィックスのAPIを使って描画するため、実際に使用するのはただの空のGameObjectです。
inverseで求めた逆行列がビュー行列になります。
Z軸のスケールを負の値にしているのは、UnityとOpenGLの座標系が違うからです。詳しくは詳しい記事をググってください。
Cameraコンポーネントを使った際の Camera.worldToCameraMatrix で計算される値と同値になります。
プロジェクション行列
プロジェクション行列はカメラの 視錐台 を使ってディスプレイ上の描画位置を決定するための行列です。
var fieldOfView = 60; var aspect = 16.0f / 9; Matrix4x4 p = GL.GetGPUProjectionMatrix( Matrix4x4.Perspective(fov: fieldOfView, aspect: aspect, zNear: 0.03f, zFar: 1000), true );
Matrix4x4.Perspective 関数のパラメータは、テスト用のCameraコンポーネントから実際に見た目を確認し、
パラメータだけ転記するといいです。
Matrix4x4-Perspective - Unity スクリプトリファレンス
GL-GetGPUProjectionMatrix - Unity スクリプトリファレンス
スクリプトでマテリアルに設定する
Matrix4x4型をシェーダーに渡すには、Material.SetMatrix 関数を使います。
Material-SetMatrix - Unity スクリプトリファレンス
drawMaterial.SetMatrix("m_matrix", m); drawMaterial.SetMatrix("v_matrix", v); drawMaterial.SetMatrix("p_matrix", p);
シェーダーコード
渡された行列で変換するだけです。
Shader "Test/Custom" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; // 変換行列 uniform float4x4 m_matrix; uniform float4x4 v_matrix; uniform float4x4 p_matrix; v2f vert (appdata v) { v2f o; // デフォルトで記述されている変換 // o.vertex = UnityObjectToClipPos(v.vertex); /* // 上の一行を分解した計算 float4x4 mat_v = mul(UNITY_MATRIX_V, unity_ObjectToWorld); float4x4 mat_p = mul(UNITY_MATRIX_P, mat_v); float4 mat_m = mul(mat_p, v.vertex); */ // スクリプトから渡された行列で変換 float4 mat_m = mul(mul(p_matrix, mul(v_matrix, m_matrix) ), v.vertex); o.vertex = mat_m; o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { // sample the texture fixed4 col = tex2D(_MainTex, i.uv); // apply fog // UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } }
描画関数をスクリプトから呼ぶ
Graphics.DrawMeshNow で画面上に描画します。
Graphics-DrawMeshNow - Unity スクリプトリファレンス
Graphics.DrawMeshNow(_mesh, Matrix4x4.identity);
スクリーンショットでも載せようと思いましたが、カメラコンポーネント使ったのと見た目まったくかわらないのでおもしろくないのでやめます。
おわり