始めに
部活での勉強会にて「Quaternion理解している人いますか?」って質問で誰も手を上げる人がいませんでした。かくいう私も理解してませんでした。
ってことで5月6月ぐらいに勉強してブログでまとめようとしましたが、マークダウンで数式を書くのが面倒くさ過ぎてずっとやってませんでした。
しかし、前回GPUインスタンシングを勉強した際にシェーダー内で座標や回転を求めていたので覚悟を決めてブログにまとめていこうと思います。
今回参考にするのは以下のサイトと参考書です。
- 作者:Fletcher Dunn,Ian Parberry
- 発売日: 2008/10/04
- メディア: 大型本
準備
3D数学の学習はUnityのシェーダーを使用しながら確認していきたいと思います。
理由としては
実験用シェーダーを作成
行列をいじいじするためのシェーダーをとりあえず作ってCubeにはっつけます。
シェーダーでテキトーに行列の値を決めてそれを頂点座標と掛け合わせます。
Shader "Unlit/Test_3DMath" { Properties { _MainTex ("Texture", 2D) = "white" {} _XVector("X Vector", Vector) = (1, 0, 0, 0) _YVector("Y Vector", Vector) = (0, 1, 0, 0) _ZVector("Z Vector", Vector) = (0, 0, 1, 0) } 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; float4 _XVector; float4 _YVector; float4 _ZVector; v2f vert (appdata v) { v2f o; float4x4 mat = float4x4( _XVector, _YVector, _ZVector, float4(0, 0, 0, 1)); o.vertex = mul(v.vertex, mat); o.vertex = UnityObjectToClipPos(o.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); 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 } } }
マテリアルのプロパティは次のようになります。
プロパティをいじいじしてみる
テキトーに X Vector の X の値を変更するとオブジェクトのX軸での大きさが変わります。
次にZ Vector の値を (0, 0, 0, 0) に設定するとXY平面のQuadみたくなりました。
最後にすべての値をテキトーに設定するとえらいこっちゃになります。
行列って理解しないとえげつない形を生み出してしまうパワーを持ってると感じます. . .
行列の意味を考えてみる
先程の例で設定した値ってどんな意味があるのってなるので勉強していきます。(解説で間違っている個所があるかもしれません!)
ここでは簡単に2次元で考えます。 先程のZ Vectorの値を (0, 0, 0, 0) に設定した状態から始めていきます。
オブジェクトには軸が存在します。 赤い矢印がX軸、緑の矢印がY軸になります。
この2つの軸をベクトルで表すと X軸 = (1, 0)、Y軸 = (0, 1) になります。これを行列で表現すると次のようになります。(軸行列とする)
ではこの行列の値を掛け算で変えていきます。 行列の掛け算は次のようになります。
因みに、軸行列との掛け算は掛けられる行列と等しくなります。
ってことで、X VectorがX軸のベクトル、Y VectorがY軸のベクトルになります。
では、X Vectorに (2, 0, 0, 0) を代入した場合を考えてみます。
X軸のベクトルが2倍になるのでX軸線上に伸びます。
X Vectorに (1, -1, 0, 0) を代入した場合は次のようにX軸が右下方向を向くので平行四辺形のような形になります。
最後にX Vectorに(0.8, -0.7)、Y Vectorに(0.5, 1)を代入したときは次のようになります。
いくつかの例を試して、行列のそれぞれの行が軸ベクトルを表していることが分かりました。
2次元での回転
軸ベクトルをいじいじして回転を表現します。
回転は次のように表現されます。
これをベクトルで表現すると
これをシェーダーで実装します。 このシェーダーでも分かり易いよう2Dにしています。
v2f vert (appdata v) { v2f o; float4x4 mat = float4x4( float4(cos(_theta), sin(_theta), 0, 0), float4(-sin(_theta), cos(_theta), 0, 0), float4(0, 0, 0, 0), float4(0, 0, 0, 1)); o.vertex = mul(v.vertex, mat); o.vertex = UnityObjectToClipPos(o.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; }
結果、このように回転が出来るシェーダーが出来ました。
3次元の回転
では2次の回転を3次元に応用していきます。
X軸、Y軸、Z軸での回転行列は次のようになります。
これらの回転行列をシェーダーで実装します。
Properties { _MainTex ("Texture", 2D) = "white" {} _ThetaX ("Theta X", float) = 0 _ThetaY ("Theta Y", float) = 0 _ThetaZ ("Theta Z", float) = 0 } . . . . . . . . . . . . . . . float _ThetaX; float _ThetaY; float _ThetaZ; v2f vert (appdata v) { v2f o; _ThetaX = radians(_ThetaX); _ThetaY = radians(_ThetaY); _ThetaZ = radians(_ThetaZ); float sinX = sin(_ThetaX); float cosX = cos(_ThetaX); 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(_ThetaY); float cosY = cos(_ThetaY); 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(_ThetaZ); float cosZ = cos(_ThetaZ); float4x4 rotZ = float4x4( float4(cosZ, sinZ, 0, 0), float4(-sinZ, cosZ, 0, 0), float4(0, 0, 1, 0), float4(0, 0, 0, 1)); v.vertex = mul(v.vertex, rotX); v.vertex = mul(v.vertex, rotY); v.vertex = mul(v.vertex, rotZ); o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return o; }
結果が以下のようになりました。 シェーダー内でオブジェクトを回転することが出来ました。
(追記)スケーリング
回転の解説に一生懸命になりすぎて、スケーリング(拡大縮小)について解説するのを失念しておりました。
軸ベクトルの大きさを変えることでスケーリングを行えます。 スケーリングの際は以下の行列を掛けることで出来ます。
この機能もシェーダーに追加します。
v2f vert (appdata v) { v2f o; float4x4 scale = float4x4( float4(_ScaleX, 0, 0, 0), float4(0, _ScaleY, 0, 0), float4(0, 0, _ScaleZ, 0), float4(0, 0, 0, 1)); . . . . . . . . . . . . v.vertex = mul(v.vertex, scale); v.vertex = mul(v.vertex, rotX); v.vertex = mul(v.vertex, rotY); v.vertex = mul(v.vertex, rotZ); . . . . . . . .
注意ですが、スケーリング → 回転の順で行わなければ可笑しなことになります。
実験でやってみましたが凄いことになりました。
最後に
今回は回転を主なテーマとして扱いました。
行列を勉強した際、「何を表してんだこいつ. . . 」ってなり苦手意識が強かったです。(授業でも赤点取って追試を受けた)
それでもUnityとシェーダーを使って勉強するうちに行列の理解と凄さを実感しました。
次回はシェーダーでオブジェクトの平行移動についてまとめていく予定です。
追記
3D数学について学んだことをまとめました。
さらその次