14.Dx12、Dx11共有計算ライブラリ

DirectXアプリが利用する計算ライブラリとして、「DirectXMath」というライブラリがあり、BaseCrossはその計算関数を使用しています。「DirextXMath」を使ったBaseCrossライブラリは「Dx12」「Dx11」どちらからでも利用可能であり、共有プロジェクト「DxLib」に含まれます。
「DirectXMath」は「SSE2プロセッサ拡張命令」に対応しているので計算は速いのですが、ちょっと扱いに気を配る部分があって、これらの計算関数群をラッピングする形で、BaseCrossは構造体などを作成してます。
計算構造体、関数群は「MathVector.h」「MathVectorEX.h」「MathExt.h」「MathExtEX.h」の4ファイルです。

この記事は、
コミット「Dx12版のウインドウ作成」
から、
コミット「Dx11版ウインドウ作成と共有ファイルの追加」
の間の作業です。
GitHubサイト

https://github.com/WiZFramework/BaseCross

を参照して下さい。

「MathVector.h」にふくまれるもの
このヘッダには、「Vector2構造体」「Vector3構造体」「Vector4構造体」、そして未完成ですが「XMVector構造体」があります。その名のとおり、ベクトル演算をするクラスです。
Vector2、Vector3、Vector4に共通することを説明します。
「DirectXMath」はベクトル演算用のデータ型として「XMVECTOR」という特殊な型を使用します。このデータ型は、32ビットアラインメントされ、そのままハードウェアレジスタにマップされます。そのため、「XMVECTOR」のデータをC++から直接変更はせずに、ロード、あるいはストア系の関数を使って通常データとやり取りします。
「XMVECTOR」とやリとりできる型として、XMFLOAT2、XMFLOAT3、XMFLOAT4があります。予想どおりXMFLOAT2はfloat型のx,yを保持する型で、XMFLOAT3はx,y,z、XMFLOAT4はx,y,z,wとなります。
「DirectXMath」の関数は、例えば3Dベクトルの長さを求める「XMVector3Length関数」は、「XMVECTOR」を引数に取り「XMVECTOR」を返します。
先ほど、「XMVECTOR」のデータはロードかストアする、と書きましたが、つまりは、「XMVector3Length関数」を使用するためには

    XMFLOAT3 in_out_data;
    in_out_data.x = 2.0f;
    in_out_data.y = 1.0f;
    in_out_data.z = 5.0f;
    //XMVECTORにロードする
    XMVECTOR Vec = XMLoadFloat3(&in_data);
    //計算する
    Vec = XMVector3Length(Vec);
    //XMVECTORからin_out_dataにストアする
    XMStoreFloat3(&in_out_data, Vec);
    //結果はx,y,zすべて同じものが入る
    float len = in_out_data.x;
    //lenがベクトルの長さ

このように、XMVector3Length関数に渡す前に、「XMVECTOR型にロード」し、結果も、「XMFLOAT3型にストア」する、という手順を踏まなければいけないので、これを何とか簡略化する方法はないかと考えたわけです。
それで、以下のような構造体を考えました。

    struct Vector3 : public XMFLOAT3 {
        //中略
        //ストア用のコンストラクタ
        Vector3(const XMVECTOR& other) :XMFLOAT3() {
            XMVECTOR temp = other;
            XMStoreFloat3(this, temp);
        }
        //ストア用の代入
        Vector3& operator=(const XMVECTOR& other) {
            XMVECTOR temp = other;
            XMStoreFloat3(this, temp);
            return *this;
        }
        //ロード用のキャスト
        operator XMVECTOR() const {
            XMFLOAT3 temp = *this;
            XMVECTOR Vec = XMLoadFloat3(&temp);
            return Vec;
        }
    };

ほかにも値代入のコンストラクタとかは当然あるわけですが、XMVECTORとのやり取りを部分だけ抜粋です。
基本的にこのクラスは*this(つまりx,y,z)に値を保存しておき、必要な時に計算関数を動かし、場合によっては、*thisを書き換えるという構造体です。
こうしておくと、例えば先ほどのLength関数や、正規化する関数なんかは

    struct Vector3 : public XMFLOAT3 {
        //中略
        float Length() const {
            return ((Vector3)XMVector3Length(XMVECTOR(*this))).x;
        }
        //中略
        void Normalize() {
            *this = XMVector3Normalize(XMVECTOR(*this));
        }
    };

のように書けるわけです。
「Length()関数」のほうは「XMVector3Length関数」に渡す値は「operator XMVECTOR()」が動きます。戻り値もXMVECTOR型なので、それを「(Vector3)」でキャストすることで、「Vector3(const XMVECTOR& other)」のコンストラクタが動き、その「x」を参照できます。
「Normalize()関数」は*thisを正規化して結果を*thisに代入するので、「XMVector3Normalize関数」に渡す値は「operator XMVECTOR()」が動き、戻り値は「operator=(const XMVECTOR& other)」が動き、*thisに代入されます。
このような設計にしておくと、XMVECTORとのやり取りをコンストラクタやキャスト演算子、代入の多重定義にまとめられます。
結果として、Vector3型は以下のような記述ができるようになります。

    //V1,V2を何かで初期化
    Vector3 V1 = Func1();
    Vector3 V2 = Func2();
    //V1とV2の間のベクトル
    Vector3 V3 = V1 - V2;
    //V3の長さを求める
    float Len = V3.Length();
    //V3を正規化する
    V3.Normalize();

このように「V3.Length()」も「V3.Normalize()」も中でSSE2に最適化された計算ライブラリが動いているのですが、外からはわかりません。(わからなければいいってもんでもないですが、少なくともロードとストア地獄からは解放されます)
Vector2、Vector3、Vector4各構造体には「DirecctXMath」の関数がラッピングされています。ですから、ほかの計算ライブラリでそろっているものはおおむね網羅していると考えていただいて結構です。
上記紹介しました「Length()関数」や「Normalize()関数」の他にも、「内積」「外積」「反発」などなど、ベクトル処理に必要と思われる計算はほぼそろってます。
教育的側面からすれば、例えば「内積」は、自分で書いてこそ勉強にはなると思います。ただ、計算して出した「内積」を「どう使うのか」も重要です。ライティングや衝突判定など内積を使う局面はいたるところにあります。その部分の理解なくして「内積」や「外積」の計算方法を暗記してもあまりいいことはないと思います。
そういうわけで、「MathVector.h」に含まれる関数群をぜひ一通り目を通してもらえればいいかと思います。

「MathVectorEX.h」にふくまれるもの
Vector2、Vector3、Vector4の「namespace版」は「MathVectorEX.h」に含まれます。
「namespace版」というからには、名前が必要ですが、Vector2は「Vector2EX」、Vector3は「Vector3EX」、Vector4は「Vector4EX」という「namespace」です。構造体版との違いは「ただの関数群」ということです。
つまり、先ほどの例は以下のように書けます

    //V1,V2を何かで初期化
    Vector3 V1 = Func1();
    Vector3 V2 = Func2();
    //V1とV2の間の長さを求める
    float Len = Vector3EX::Length(V1 -V2);

単にV1とV2と間の距離が必要なだけなら、Vector3オブジェクトはいらないので上記のように書けるのです。
その局面局面で、オブジェクトを作成しておいたほうがいいと思えば構造体版を使えばいいし、計算だけすればいいのであればnamespace版を使用すればいいのです。

そんな風に「Vector2、Vector3、Vector4」と「Vector2EX、Vector3EX、Vector4EX」が関係しています。「namespace版」にも構造体版同様の関数が実装されています。

「MathExt.h」にふくまれるもの
「MathVector」がベクトル演算構造体なのに対して、「MathExt.h」には「Plane(平面)」「Color4(色)」「Quaternion(クオータニオン)」「Matrix4X4(4X4行列)」が構造体として実装されています。
「Plane」「Color4」「Quaternion」は根っこにあるのは「XMVECTOR」なので、ベクトル構造体と同じです。「コンストラクタ」や「operator XMVECTOR()」「代入演算子」に、ロードやストアは実装されています。
「Matrix4X4」は「XMVECTOR」の代わりに「XMMATRIX」というデータ型を使います。扱い方は「XMVECTOR」と変わりません。XMVECTORが4つ配列化したものと考えられます。
これも「XMVECTOR」同様、ロードやストアが必要です。ですがこれまで説明した方法で実装可能です。「コンストラクタ」や「operator XMMATRIX()」「代入演算子」でロードトストアを実装しています。

「MathExtEX.h」にふくまれるもの
「MathExt.h」がクオータニオンや行列の構造体なら、「MathExtEX.h」はこれらのnamespace版として対になってます。使い方はベクトルと同じです。
その局面局面で、構造体版を使うかnamespace版を使うことを選択できます。

これまで、計算系の構造体やnamespaceの使い方を説明してきました。これはどこの計算ライブラリもそうですが、実装されてるのは「基本的な計算の部分」だけです。実際にゲームで利用するためには、それらを組み合わせて「生きた計算」にする必要があります。それらはゲームごとに違うものであり、BaseCrossもほかのフレームワークも、どんなに「簡単に」始められても、最後はゲームごとの問題になります。

「DxLib」共有プロジェクトに含まれるものには、これまで説明してきたもののほかに「XmlDoc.h/cpp」と「TransHelper.h」があります。「TransHelper.h」は次項で説明しますが、「XmlDoc.h/cpp」は「XMLの読み書き」を実装するライブラリであり、CSVやバイナリデータ読み込み同様、どこかで個別に説明します。「XmlDoc.h/cpp」を使うには、「MSXML」というマイクロソフト社のXML読み書き用のCOM(コンポーネントオブジェクトモデル)の理解が必要です。これはここで説明するには大きすぎるので、またの機会にいたします。

次項は「TransHelper.h」に含まれる、おもに「衝突判定」の基本ライブラリを説明します。

カテゴリー

ピックアップ記事

  1. 2016092201
    今回は前回のサンプルを少し機能を追加しまして、いろんなオブジェクトを追加しています。FullTuto…
  2. 2016092001
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル003をアップしました。内…
  3. eyecatch
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル002で懸案となっていまし…
PAGE TOP