こんにちは、エンジニアのオオバです。

Unity然り、FlashのActionScript3然り、表示オブジェクトの親子関係がデフォルト機能のように実装されていて、当たり前のように使ってきたのですが、一度理解してしまえば非常に簡単なのでしたが、自前で実装するとなると結構骨が折れました。

ということで、Unityで呼吸レベルで使用しているTransformクラスの親子関係部分を自前で実装してみます。

必要なデータ

親子関係を構築する際に親参照を保持するための_parent変数、親のモデル座標変換行列が必要なので、MMatrixプロパティ、MMatrixを作るための座標(LocalPositionMatrix)回転(LocalRotateMatrix)スケール(LocalScaleMatrix)行列プロパティを定義します。

ここから各プロパティの実装に移ります。

ローカル座標行列
private Matrix LocalPositionMatrix  
{
    get  
    {
        var localPositionMatrix = Matrix.Identity;  
        localPositionMatrix.set_Rows(3, new Vector4(  
            LocalPosition.X,  
            LocalPosition.Y,  
            LocalPosition.Z, 1f));  
        return localPositionMatrix;  
    }
}

DirectXの行列行と列がUnity(OpenGL)と逆なので注意が必要です。4行目に座標の要素が入ります。

1 0 0 0  
0 1 0 0  
0 0 1 0  
x y z 1  

このような形の行列になります。

回転行列
private Matrix LocalRotateMatrix  
{
    get  
    {
        // X軸回転行列  
        var localRotateXMatrix = Matrix.RotationQuaternion(  
            Quaternion.RotationAxis(new Vector3(1, 0, 0), LocalEulerAngles.X)  
        );  
        // Y軸回転行列  
        var localRotateYMatrix = Matrix.RotationQuaternion(  
            Quaternion.RotationAxis(new Vector3(0, 1, 0), LocalEulerAngles.Y)  
        );  
        // Z軸回転行列  
        var localRotateZMatrix = Matrix.RotationQuaternion(  
            Quaternion.RotationAxis(new Vector3(0, 0, 1), LocalEulerAngles.Z)  
        );  
        // ZXYの順で計算  
        return localRotateZMatrix * localRotateXMatrix * localRotateYMatrix;  
    }
}

Matrix.RotationQuaternionメソッドを使うとサクッと作れます。

回転行列は各軸の回転行列を作って、ZXYの順で行列をかけ合わせます。
このZXYの順番で乗算するというのは、安原さん(Unityの中の人)のクォータニオン完全マスターの動画がわかりやすかったです。
【Unity道場 大阪スペシャル in モリサワ 2017】クォータニオン完全マスター - YouTube
ここでも、DirectXの行列はUnityとは逆なので、Unityで実装するのであればYXZの順で行列を乗算する必要があると思います。

スケール行列
private Matrix LocalScaleMatrix => Matrix.Scaling(LocalScale);  

Matrix.Scalingメソッドを使うと簡単です。

モデル座標変換行列(MMatrix)
internal Matrix MMatrix =>  
        (LocalScaleMatrix * LocalRotateMatrix * LocalPositionMatrix)  
        * (_parent != null ? _parent.MMatrix : Matrix.Identity);  

モデル座標変換行列は、スケール回転位置の順番で計算する必要があるため、今回はDirectXなのでそのままの順で乗算しちゃいます。

重要なのは親階層が存在する場合は、親のモデル座標変換行列を反映する必要があります。
モデル座標変換行列 = ローカル座標変換行列 * 親のローカル座標変換行列
という計算式になります。
※繰り返しになりますがDirectX上での話です

ゴールはMVP行列を作る

ここまではあくまで準備です。
TransformクラスのゴールはMVP行列を作成することなので、作っていきます。

public Matrix MVPMatrix => MMatrix * _vpMatrix;  

とはいえ、準備がほぼ全てで_vpMatrix(ビュー変換行列とプロジェクション変換行列をかけた行列)はカメラ側から代入してもらう前提なので、モデル座標変換行列と乗算するだけでMVP行列は作成できます。

MPV行列の使い方としては以下のようにシェーダーに渡して頂点の座標変換してあげます。

// シェーダーに行列を渡す  
var mat = _effect.GetVariableByName("MvpMatrix");  
mat.AsMatrix().SetMatrix(MVPMatrix);  

全体ソースコードはこんな感じです。
SlimDX_Transform.cs · GitHub

Unity同様、継承して使用してもらうことを想定した実装になっています。

こちらのリポジトリに同梱されています。
GitHub - baobao/SlimDXSketch: さくっとライトにSlimDXを触りたいを思想に作成したスケッチ的なライブラリ

オススメ記事
検証環境