夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

ゼロから始めるUnityShader開発 第五章 オブジェクトを回転させるシェーダー2 回転軸を変更する

本日はシェーダー枠です。

以前回転するシェーダーを書きました。

redhologerbera.hatenablog.com

このシェーダーでは回転行列を使用して頂点シェーダー内でオブジェクトを構成する頂点を回転させました。

今回はこの回転軸を任意に変更可能にしていきます。

〇回転軸を変更する

まずはマテリアルから回転軸を変更できるようにプロパティブロックに値を定義します。

    Properties
    {
        _RotationAxis ("Rotation Axis", Vector) = (0,1,0,0) // デフォルトはY軸周りの回転
    }

VectorはFloat4を意味し、マテリアル上でXYZWの4つの値を扱うことができます。

同時にSubShader内で_RotationAxisも定義します。

    SubShader
    {
  ・・・
        
        Pass
        {
            CGPROGRAM
   ・・・

        struct appdata
        {
     ・・・
        };
        struct v2f
        {
     ・・・
        };

        float4 _Rotation; // 回転角度を指定するためのプロパティ
        float3 _RotationAxis; // 回転軸を指定するためのプロパティ

Vector型ではFloat4で定義されますが、実際にはXYZの3次元の軸であるため内部ではfloat3型で定義します。

 次に回転行列を用いた計算を定義します。

 前回は頂点シェーダー内で直接回転行列を用いましたが、今回は複雑かつ冗長になるため、float3×3がたで3次元ベクトルとして関数を定義します。

float3x3 CreateRotationMatrix(float3 axis, float angle)
{
    float3x3 rotationMatrix; //回転行列
    float c = cos(angle);
    float s = sin(angle);
    float t = 1.0 - c;

    rotationMatrix[0][0] = c + axis.x * axis.x * t;
    rotationMatrix[1][1] = c + axis.y * axis.y * t;
    rotationMatrix[2][2] = c + axis.z * axis.z * t;

    float tmp1 = axis.x * axis.y * t;
    float tmp2 = axis.z * s;
    rotationMatrix[1][0] = tmp1 + tmp2;
    rotationMatrix[0][1] = tmp1 - tmp2;

    tmp1 = axis.x * axis.z * t;
    tmp2 = axis.y * s;
    rotationMatrix[2][0] = tmp1 - tmp2;
    rotationMatrix[0][2] = tmp1 + tmp2;

    tmp1 = axis.y * axis.z * t;
    tmp2 = axis.x * s;
    rotationMatrix[2][1] = tmp1 + tmp2;
    rotationMatrix[1][2] = tmp1 - tmp2;

    return rotationMatrix;
}

この関数では引数として指定された軸axisと角度angleに基づいて回転行列を作成します。

float c = cos(angle);, float s = sin(angle);, float t = 1.0 - c;では与えられた角度に対する cos と sin を計算します。これらは後の計算で再利用されます。

rotationMatrix[0][0] = c + axis.x * axis.x * t;, rotationMatrix[1][1] = c + axis.y * axis.y * t;, rotationMatrix[2][2] = c + axis.z * axis.z * tでは回転行列の対角成分を計算します。 この部分は回転行列のスケーリング成分に対応します。

体格成分とは行列の斜めの成分を指します。例えば次の行列の場合はa_11、a_22、a_33、a_mmがあたります。

 \displaystyle 
A=
\begin{bmatrix}
a_11 & a_12 & … & a_1n \\
a_21 & a_22 & … & a_2n \\
\vdots & \vdots & \ddots & \vdots \\
a_m1 & a_m2 & … & a_mn \\
\end{bmatrix} 
  \

スケーリング成分とは行列がオブジェクトの拡大・縮小をどの程度行うかを示します。

スケーリング成分が1の場合はオブジェクトのスケールは変化しません。

float tmp1 = axis.x * axis.y * t;, float tmp2 = axis.z * s;, rotationMatrix[1][0] = tmp1 + tmp2;, rotationMatrix[0][1] = tmp1 - tmp2;これらの行は回転行列の非対角成分を計算します。

これらは回転行列の回転成分に対応し、回転軸の直交成分を計算しています。

同様に、rotationMatrix[2][0], rotationMatrix[0][2], rotationMatrix[2][1], rotationMatrix[1][2]の計算も行われています。

最後に、作成された回転行列が返されます。

この行列を使用することで頂点を任意の軸で回転させることができます。

〇シェーダーコード全文

Shader"Custom/RotatingObjectShader"
{
    Properties
    {
        _RotationAxis ("Rotation Axis", Vector) = (0,1,0,0) // デフォルトはY軸周りの回転
    }
    
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
#include "UnityCG.cginc"

// 関数のプロトタイプ宣言
float3x3 CreateRotationMatrix(float3 axis, float angle);

struct appdata
{
    float4 vertex : POSITION;
    float3 normal : NORMAL;
};

struct v2f
{
    float4 pos : SV_POSITION;
};

float4 _Rotation; // 回転角度を指定するためのプロパティ
float3 _RotationAxis; // 回転軸を指定するためのプロパティ

v2f vert(appdata v)
{
    v2f o;

    // 回転行列を作成
    float angle = _Time.y * 0.3; // 0.3は回転速度 
    float3x3 rotationMatrix = CreateRotationMatrix(_RotationAxis, angle);

    o.pos = UnityObjectToClipPos(mul(rotationMatrix, v.vertex));

    return o;
}

half4 frag(v2f i) : SV_Target
{
    return half4(1, 1, 1, 1);
}

// 回転行列を作成
float3x3 CreateRotationMatrix(float3 axis, float angle)
{
    float3x3 rotationMatrix;
    float c = cos(angle);
    float s = sin(angle);
    float t = 1.0 - c;

    rotationMatrix[0][0] = c + axis.x * axis.x * t;
    rotationMatrix[1][1] = c + axis.y * axis.y * t;
    rotationMatrix[2][2] = c + axis.z * axis.z * t;

    float tmp1 = axis.x * axis.y * t;
    float tmp2 = axis.z * s;
    rotationMatrix[1][0] = tmp1 + tmp2;
    rotationMatrix[0][1] = tmp1 - tmp2;

    tmp1 = axis.x * axis.z * t;
    tmp2 = axis.y * s;
    rotationMatrix[2][0] = tmp1 - tmp2;
    rotationMatrix[0][2] = tmp1 + tmp2;

    tmp1 = axis.y * axis.z * t;
    tmp2 = axis.x * s;
    rotationMatrix[2][1] = tmp1 + tmp2;
    rotationMatrix[1][2] = tmp1 - tmp2;

    return rotationMatrix;
}

            ENDCG
        }
    }
}