いんでぃーづ

ゲームいろいろ、いろいろ自由

Unityで最大級の
セール実施中!!
20210721063114

Unity : MVP行列をスクリプト側で計算してシェーダーに渡す

3Dグラフィックスを描画するときに、ディスプレイ上の描画位置を計算する処理が MVP行列 を使った座標変換です。

モデル行列ビュー行列プロジェクション行列を使った変換のことですが、Unity上で ShaderLab を使ってシェーダーを書く場合、
それぞれシェーダー変数により定義されているため、手作業で計算する必要はありません。

ビルトインのシェーダー変数 - Unity マニュアル

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 で計算される値と同値になります。

プロジェクション行列

プロジェクション行列はカメラの 視錐台 を使ってディスプレイ上の描画位置を決定するための行列です。

視錐台を理解する - Unity マニュアル

        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);

スクリーンショットでも載せようと思いましたが、カメラコンポーネント使ったのと見た目まったくかわらないのでおもしろくないのでやめます。

おわり

learning.unity3d.jp


“Unity” and Unity logos are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere, and are used under license.


免責事項

当サイトの広告バナー、リンクによって提供される情報、サービス内容について、当サイトは一切の責任を負いません。

また、当サイトの情報を元にユーザ様が不利益を被った場合にも、当サイトは一切の責任を負いません。

すべて自己責任でお願いします。