知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】回転行列 #80

前回の成果

uGUIのImageを揺らした。

soramamenatan.hatenablog.com


今回やること

回転行列について理解します。


事前準備

Scene上にCubeを配置します。
Cubeに今回制作したマテリアルをアタッチします。
自分は回転を見やすくするためにUnityのデフォルトのDafault-Checker-GrayをTextureにアタッチしています。

f:id:soramamenatan:20201115165516p:plain


ソースコード

Shader "Unlit/SimpleRotation" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Theta2D ("Theta2D", float) = 0
        _ThetaX ("Theta X", float) = 0
        _ThetaY ("Theta Y", float) = 0
        _ThetaZ ("Theta Z", float) = 0
        [Toggle] _Is3D ("Is 3D", int) = 0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Theta2D;
            float _ThetaX;
            float _ThetaY;
            float _ThetaZ;
            int _Is3D;

            float4 Rotate2D(float4 pos) {
                float sinTheta = sin(_Theta2D);
                float cosTheta = cos(_Theta2D);

                float4x4 rot = float4x4(
                    float4(cosTheta, sinTheta, 0, 0),
                    float4(-sinTheta, cosTheta, 0, 0),
                    float4(0, 0, 0, 0),
                    float4(0, 0, 0, 1)
                );
                pos = mul(pos, rot);
                return pos;
            }

            float4 Rotate3D(float4 pos) {
                float radX = radians(_ThetaX);
                float radY = radians(_ThetaY);
                float radZ = radians(_ThetaZ);

                float sinX = sin(radX);
                float cosX = cos(radX);

                float4x4 rotX = float4x4(
                    float4(1, 0, 0, 0),
                    float4(0, cosX, sinX, 0),
                    float4(0, -sinX, cosX, 0),
                    float4(0, 0, 0, 1)
                );

                float sinY = sin(radY);
                float cosY = cos(radY);

                float4x4 rotY = float4x4(
                    float4(cosY, 0, -sinY, 0),
                    float4(0, 1, 0, 0),
                    float4(sinY, 0, cosY, 0),
                    float4(0, 0, 0, 1)
                );

                float sinZ = sin(radZ);
                float cosZ = cos(radZ);

                float4x4 rotZ = float4x4(
                    float4(cosZ, sinZ, 0, 0),
                    float4(-sinZ, cosZ, 0, 0),
                    float4(0, 0, 1, 0),
                    float4(0, 0, 0, 1)
                );

                pos = mul(pos, rotX);
                pos = mul(pos, rotY);
                pos = mul(pos, rotZ);
                return pos;
            }

            v2f vert (appdata v) {
                v2f o;
                v.vertex = lerp(Rotate2D(v.vertex), Rotate3D(v.vertex), step(1, _Is3D));
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

通常の2次元と3次元の回転行列のShaderになります。


回転行列

ユークリッド空間内における原点中心の回転変換の表現行列のことである。

回転行列 - Wikipedia:より引用

とのことです。
簡単に説明すると、2次元空間や3次元空間における回転を表す便利な行列のことです。


2次元の回転行列

float4 Rotate2D(float4 pos) {
    float sinTheta = sin(_Theta2D);
    float cosTheta = cos(_Theta2D);

    float4x4 rot = float4x4(
        float4(cosTheta, sinTheta, 0, 0),
        float4(-sinTheta, cosTheta, 0, 0),
        float4(0, 0, 0, 0),
        float4(0, 0, 0, 1)
    );
    pos = mul(pos, rot);
    return pos;
}
2次元の回転行列の公式  \displaystyle
R(\theta) = \begin{pmatrix} cos\theta&-sin\theta \\ sin\theta&cos\theta \end{pmatrix}

2次元における回転行列は上記の行列によって表すことが出来ます。

なぜこの行列で示すことが出来るのかはこちらで詳しく解説しています。

soramamenatan.hatenablog.com


3次元の回転行列

float4 Rotate3D(float4 pos) {
    float radX = radians(_ThetaX);
    float radY = radians(_ThetaY);
    float radZ = radians(_ThetaZ);

    float sinX = sin(radX);
    float cosX = cos(radX);

    float4x4 rotX = float4x4(
        float4(1, 0, 0, 0),
        float4(0, cosX, sinX, 0),
        float4(0, -sinX, cosX, 0),
        float4(0, 0, 0, 1)
    );

    float sinY = sin(radY);
    float cosY = cos(radY);

    float4x4 rotY = float4x4(
        float4(cosY, 0, -sinY, 0),
        float4(0, 1, 0, 0),
        float4(sinY, 0, cosY, 0),
        float4(0, 0, 0, 1)
    );

    float sinZ = sin(radZ);
    float cosZ = cos(radZ);

    float4x4 rotZ = float4x4(
        float4(cosZ, sinZ, 0, 0),
        float4(-sinZ, cosZ, 0, 0),
        float4(0, 0, 1, 0),
        float4(0, 0, 0, 1)
    );

    pos = mul(pos, rotX);
    pos = mul(pos, rotY);
    pos = mul(pos, rotZ);
    return pos;
}

まずは下記の画像のように、z軸を中心に回転する行列について考えていきます。

f:id:soramamenatan:20201123105702p:plain

回転行列(方向余弦行列)とは?定義・性質・3つの物理的な意味・公式のまとめ | スカイ技術研究所ブログ:より引用

\thetaを回転角とします。
そうすると、O' - x' y' z'の各軸方向の単位ベクトルは以下となります。

各軸方向の単位ベクトル  \displaystyle
n = \begin{pmatrix}
\cos\theta \\ \sin\theta \\ 0 
\end{pmatrix}
,

t = \begin{pmatrix}
-\sin\theta \\ \cos\theta \\ 0 
\end{pmatrix}
,

b = \begin{pmatrix}
0 \\ 0 \\ 1
\end{pmatrix}

ですので、z軸の回転行列は以下となります。

z軸の回転行列  \displaystyle
R(z) = \begin{pmatrix}
\cos\theta & -\sin\theta & 0
\\
\sin\theta & \cos\theta & 0 
\\
0 & 0 & 1
\end{pmatrix}

これをソースコードとして起こすと以下となります。

float sinZ = sin(radZ);
float cosZ = cos(radZ);

float4x4 rotZ = float4x4(
    float4(cosZ, sinZ, 0, 0),
    float4(-sinZ, cosZ, 0, 0),
    float4(0, 0, 1, 0),
    float4(0, 0, 0, 1)
);


これでz軸の部分は完成です。
x軸、y軸も同じように回転行列を出すと以下となります。

x軸の回転行列  \displaystyle
R(x) = \begin{pmatrix}
1 & 0 & 0
\\
0 & \cos\theta & -\sin\theta
\\
0 & \sin\theta & \cos\theta

\end{pmatrix}
y軸の回転行列  \displaystyle
R(y) = \begin{pmatrix}

\cos\theta & 0 & \sin\theta
\\
0 & 1 & 0
\\
-\sin\theta & 0 & \cos\theta


\end{pmatrix}


結果

回転すれば成功です。

2次元の回転

f:id:soramamenatan:20201123112323g:plain

2次元の回転のinspector

f:id:soramamenatan:20201123112315p:plain

3次元の回転

f:id:soramamenatan:20201123112346g:plain

3次元の回転のinspector

f:id:soramamenatan:20201123112318p:plain

2次元回転の時にThetaXYZの値を、3次元回転の時にTheta2Dの値を変更しても何も起きません。

ソースコードにコメントを付与

Shader "Unlit/SimpleRotation" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
        _Theta2D ("Theta2D", float) = 0
        _ThetaX ("Theta X", float) = 0
        _ThetaY ("Theta Y", float) = 0
        _ThetaZ ("Theta Z", float) = 0
        [Toggle] _Is3D ("Is 3D", int) = 0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Theta2D;
            float _ThetaX;
            float _ThetaY;
            float _ThetaZ;
            int _Is3D;

            // 2次元の回転
            float4 Rotate2D(float4 pos) {
                float sinTheta = sin(_Theta2D);
                float cosTheta = cos(_Theta2D);

                // 2次元の回転行列
                float4x4 rot = float4x4(
                    float4(cosTheta, sinTheta, 0, 0),
                    float4(-sinTheta, cosTheta, 0, 0),
                    float4(0, 0, 0, 0),
                    float4(0, 0, 0, 1)
                );
                pos = mul(pos, rot);
                return pos;
            }

            // 3次元の回転
            float4 Rotate3D(float4 pos) {
                float radX = radians(_ThetaX);
                float radY = radians(_ThetaY);
                float radZ = radians(_ThetaZ);

                float sinX = sin(radX);
                float cosX = cos(radX);

                // 3次元のX軸の回転行列
                float4x4 rotX = float4x4(
                    float4(1, 0, 0, 0),
                    float4(0, cosX, sinX, 0),
                    float4(0, -sinX, cosX, 0),
                    float4(0, 0, 0, 1)
                );

                float sinY = sin(radY);
                float cosY = cos(radY);

                // 3次元のY軸の回転行列
                float4x4 rotY = float4x4(
                    float4(cosY, 0, -sinY, 0),
                    float4(0, 1, 0, 0),
                    float4(sinY, 0, cosY, 0),
                    float4(0, 0, 0, 1)
                );

                float sinZ = sin(radZ);
                float cosZ = cos(radZ);

                // 3次元のZ軸の回転行列
                float4x4 rotZ = float4x4(
                    float4(cosZ, sinZ, 0, 0),
                    float4(-sinZ, cosZ, 0, 0),
                    float4(0, 0, 1, 0),
                    float4(0, 0, 0, 1)
                );

                pos = mul(pos, rotX);
                pos = mul(pos, rotY);
                pos = mul(pos, rotZ);
                return pos;
            }

            v2f vert (appdata v) {
                v2f o;
                // lerpとstepを用いて、boolで2次元、3次元を切り替えられるように
                v.vertex = lerp(Rotate2D(v.vertex), Rotate3D(v.vertex), step(1, _Is3D));
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}


今回は以上となります。
ここまでご視聴ありがとうございました。


参考サイト様

www.sky-engin.jp

shitakami.hatenablog.com