ゲームフレームワークをツクリタイ:数学編(2)

今回は、前回に引き続き数学編ということで 「数学クラス(行列)」についての記事を書いていきます。

前回のベクトル編ではC++20/23の機能を使ったリファクタリングを紹介しましたが、行列ではベクトルにあったような「Swizzle(スウィズル)機能」は不要です。 では、一体どこを重点的にリファクタリングしたのか、というところをメインに紹介していきます。

前回:

ゲームフレームワークをツクリタイ : 数学編 - ReU’s diary


データの持ち方

まず、今回の行列クラスにおける内部データの持ち方は以下のようになっています。

        union
        {
            TVector<Type, Column> vec[Row];
            Type value[Row * Column]{};
        };

Row(行)と Column(列)をテンプレート引数で受け取り、行ごとのベクトル配列(vec)としても、フラットな1次元配列(value)としてもアクセスできるように union を組んでいます。


1. パラメータパック展開

可変長引数を受け取って内部配列に詰め込むパラメータパックの展開処理ですが、基本的には前回のベクトルクラスと全く同じアプローチを取っています。そのため、今回の記事では詳しい説明は割愛します。


2. アクセサ関数の共通化

以前の実装では、行列の各要素へのアクセサ関数を、以下のように手動でひたすらコピペして量産していました。

Type _11()
{
    return vec[0][0];
}
Type _12()
{
    return vec[0][1];
}

※上記はコードを端折っていますが、実際には static_assert を使用して、_12() などのアクセスが本来の行列のサイズ(Row/Column)に対して安全かどうかをチェックしていました。3x3行列なのに _44() にアクセスされたら困りますからね。

当時は「まぁいいでしょ……」と思って耐えていましたが、今見るとタイポの温床が大量にある極めて危険な状態です。

しかも、グラフィックスの描画バグが起きた際にこの自作行列を使用していたら、原因究明時に以下のような疑念が次々と湧いてきます。

  • アクセサのタイポによる数値のバグではないか?(_34 の中で誤って別の要素を返していないか?など)
  • 計算式(乗算など)の間違いか?
  • 定数バッファ(Constant Buffer)へのデータ転送ミスか?

このように、デバッグ時にチェックしなければいけない容疑者が多すぎて、自作の数学クラスに対してまず怪しむところから入るという 文字通り「何をやってるんだ状態」になっていました。 結果として、作りはしたものの、結局は絶対に信頼できる DirectXMath をラッピングした方を実戦投入し、自作クラスはただの「勉強用の置き物」と化していました(笑)。

この「タイポの恐怖」と「サイズ不一致によるデバッグ地獄」を完全に解消するために今回作成したのが、requires とマクロを融合させたアクセサ自動生成機構です。


requires × マクロ

新実装では、以下のようなアクセサ自動生成マクロを定義しました。

// 個別要素アクセス用マクロ(_11, _12 など)
#define MATRIX_ACCESSOR(r, c) \
    inline Type& _##r##c() requires (Row >= r && Column >= c) { \
        return vec[r-1][c-1]; \
    } \

// 行ベクトルアクセス用マクロ(_1, _2 など)
#define MATRIX_ROW_ACCESSOR(r) \
    inline auto& _##r() requires (Row >= r) { \
        return vec[r-1]; \
    } \

これらをクラス内で並べるだけで、アクセサの定義が完了します。

        // _〇()
        MATRIX_ROW_ACCESSOR(1);
        MATRIX_ROW_ACCESSOR(2);
        MATRIX_ROW_ACCESSOR(3);
        MATRIX_ROW_ACCESSOR(4);

        // _1〇()
        MATRIX_ACCESSOR(1, 1);
        MATRIX_ACCESSOR(1, 2);
        MATRIX_ACCESSOR(1, 3);
        MATRIX_ACCESSOR(1, 4);
        // ...(2行目、3行目、4行目のマクロも同様に並べる)

これだけで済みます。昔あんなに泥臭くコピペしていた大量の関数が、一瞬で、しかもタイポの余地なく完璧な精度で自動生成されます。

これによって、 1. マクロ化によりタイポが物理的に不可能になり、 2. requires によりサイズ違いの誤用もコンパイル時に100%防げる、

という、自作フレームワークとして絶対の信頼を置ける最強の行列クラスへ生まれ変わりました。これならデバッグ時に行列のバグを疑う必要は一切ありません!


おわりに

C++20のテンプレートと requires、そしてレトロなマクロの力を少しだけ借りることで、コピペコードを一切排除した美しく安全な行列クラスを構築することができました。

……え?「DirectXMath が内部で行っている、SIMD最適化による爆速の行列計算と比べて速度はどうなんだ」って?

(SIMD..速度..うっ頭が..)

き、ききき綺麗に書くのが目的だから....速度は気になった時に最適化すればいいから!!(決していいわけではありませんよ?いいですね)

次回はどうしよう。 このペースで書いていったら今ある分のストックがなくなっちゃう;;

ゲームフレームワークをツクリタイ : 数学編

今回は、フレームワークの基盤となる「数学クラス(ベクトル)」についての記事を書いていきます。

筆者が上げている数少ない記事の中で、実は2回もベクトル周りのクラスについて既に上げているのですが、

https://hatenablog.com

https://hatenablog.com

この時から一貫して「HLSL風に書きたい」という思いのまま実装していました。 しかし、当時は妥協に妥協を重ねる形で作成したので、2026年現在の(C++23)環境でこれまでの不満点や力業実装をどう美しくリファクタリングしたか(新旧比較)を紹介します。


1. 「.X() がそのまま値として取れない問題」の完全解決

2021年当時、最大の不満点が vec.X() で値をそのまま取得できず、わざわざ float x = vec.X().At(0); と書いていた問題でした。

現在の実装では、普通に参照(Type&)を返す形式と const 版を分けるというシンプルなアプローチで大解決しました。

inline Type& X() { return value; }
inline Type& Y() { return value; }
inline Type& Z() { return value; }
inline Type& W() { return value; }

inline Type X() const { return value; }

これで vec.X() = 1.0f;float x = vec.X(); も両方できます。 当時は変換関数とか変なことをしようとしてできなかったのかなって今では思います。


2. パラメータパック展開の進化(再帰から畳み込み演算へ)

2024年の記事では、可変長引数を使って float4(vec2, float2(3.0f, 4.0f)) のような初期化を実現していました。 ただ、当時はポインタを強引にキャストしてバイトサイズを計算し、再帰関数(Expansion)でループを回すという、大分危ない力業実装でした。

(まぁある意味C++ぽい書き方ではあったんだけど)

それが、現在の実装ではC++20の畳み込み演算(Fold Expressions)を使うことで、以下のように一撃でスッキリ書けるようになりました。

template<typename... Args>
TVector(Args&&... args) noexcept
    : value{}
{
    UInt32 index = 0;
    // 畳み込み演算で引数を順番に処理
    (Append(index, std::forward<Args>(args)), ...);
}

template<typename T>
void Append(UInt32& index, const T& val)
{
    if (index >= N)
        return;

    // 基本型の場合
    if constexpr (std::is_convertible_v<T, Type>)
    {
        value[index++] = static_cast<Type>(val);
    }
    // Sizeという名前のメンバを持っているという前提(正確にはT::Sizeという書き方ができる前提つまりstatic)
    else if constexpr (requires { T::Size; })
    {
        for (UInt32 i = 0; i < T::Size; ++i)
        {
            if (index >= N)
                break;
            value[index++] = val[i];
        }
    }
}

ここで出てくる requires{} は、コンパイル時に条件判定ができるのでめちゃくちゃ面白い使い方ができます。


3. 演算の共通化

ベクトル同士の演算(加減乗除)のコピペコードを撲滅するため、C++20の conceptrequires を使った書き方で共通化しました。

template<typename T>
concept VectorLike = requires(T v, UInt32 i)
{
    typename T::ValueType;
    { v[i] };
    { T::Size } -> std::convertible_to<UInt32>;
};

template<typename Type, typename L, typename R, typename Func>
requires VectorLike<L> && VectorLike<R>
[[nodiscard]]
constexpr auto VectorOp(const L& lhs, const R& rhs, Func func) noexcept
{
    constexpr UInt32 N = L::Size;
    static_assert(L::Size == R::Size, "ベクトルサイズの不一致");

    TVector<Type, N> result{};
    for (UInt32 i = 0; i < N; ++i)
        result[i] = func(lhs[i], rhs[i]);
    return result;
}

conceptrequires を使うと、テンプレート引数で受け取った L, R がそれぞれ条件を満たしているのかをチェックすることができます。今回だと、

  • ValueType という名前の型が存在する
  • 添え字アクセス(v[i])が可能か
  • Size を持ったうえで、それが UInt32 型(に変換可能)か (UInt32型チェックはまぁあまりいらないんですけど... )

の3つの条件をクリアしたものだけが、引数で渡せるよという感じに強制させています。

で、「これが何のためにいるん?」という話ですが。

目的を思い出してください。「HLSL風に書きたい」……そう、皆さんご存じSwizzle(スウィズル)です! .xzy.wzx みたいに、本来メモリの並びは xyz なのを、自由に入れ替えても書けるようにする方法です。 (ちなみにスウィズルという名前自体はAIで初めて知りましたw 存在は知ってましたけど)

スウィズルの詳細な実装は今回は省きますが、この VectorLike というものを使うことで、TVectorTVectorSwizzle という異なるクラス同士の演算も可能になります。

以下のようなマクロを用意して(基本型との演算用もいるよ)、

#define VECTOR_ARITHMETIC_OPERATORS(OP) \
    template<VectorLike L, VectorLike R> \
    [[nodiscard]] \
    constexpr auto operator OP(const L& lhs, const R& rhs) noexcept \
    { \
        using Type = typename L::ValueType; \
        return VectorOp<Type>(lhs, rhs, [](auto a, auto b) { return a OP b; }); \
    } \

// *, / も入れてこんな感じ
    VECTOR_ARITHMETIC_OPERATORS(+);
    VECTOR_ARITHMETIC_OPERATORS(-);

昔は全部コピペして演算子を1つ1つ変えていくみたいな地味な作業がありましたが(実際にタイポもありましたしw)、それを綺麗に共通化することで、無駄なコードを一切書かずに済むということです。


4. おまけ:グラフィックスリソースの生成にも concept を応用してみる

この conceptrequires の組み合わせは、ベクトルの演算だけでなく、DirectX12周りのグラフィックス基盤(RHI: Render Hardware Interface)のオブジェクト生成なんかでも使用できます。

template<typename RHIObj, typename RHIDesc>
concept IsRHIObject = requires(RHIObj* obj, const RHIDesc& desc)
{
    { obj->Initialize(desc) } -> std::convertible_to<bool>;
};

// RHIオブジェクト生成共通関数
template<typename RHIObject, typename RHIDevice, typename RHIDesc>
requires IsRHIObject<RHIObject, RHIDesc>
TUnique<RHIObject> CreateRHIObject(RHIDevice* device_, const RHIDesc& desc_)
{
    if (ALPHA_CHECK_FATAL(!device_, L"CreateRHIObject: デバイスが有効ではありません"))
        return nullptr;

    auto object = std::make_unique<RHIObject>(device_);
    if (object->Initialize(desc_) != true)
    {
        // エラー処理など...
        return nullptr;
    }
    return object;
}

Initialize(desc) を持っていて、それが bool 型を返すクラス」を IsRHIObject として強制しています。

TUnique<RHIBuffer> D3D12Dynamic::CreateBuffer(const RHIBufferDesc& desc)
{
    return CreateRHIObject<D3D12Buffer>(m_device.get(), desc);
}

TUnique<RHITexture> D3D12Dynamic::CreateTexture(const RHITextureDesc& desc)
{
    return CreateRHIObject<D3D12Texture>(m_device.get(), desc);
}

という感じで統一した書き方ができます。


おわりに

同じテーマで何度もコードを書き直していますが、今回は文句のつけようのない綺麗な状態が出来上がったかなと思います。

(SIMD..速度..うっ頭が..)

ゲームフレームワークをツクリタイ

お久しぶりです。

前回の記事を書いてからかなりの月日が経ちましたが、筆者は紆余曲折あり、 絶賛ニート生活を謳歌中ですw

今回は、そんなニート中に制作している自作フレームワークについての記事を書いていきたいと思っています。 やる気燃料が続く限りやり切りたい!!(願望)

(てかブログを書くことでモチベーションの維持的なちょっとした下心もあったりなかったり...)


なぜ作ることになったか

理由は大きく分けて3つ。

  1. そもそもそういったフレームワークを作ることが楽しい
  2. 純粋な勉強目的
  3. 最大の目的である……昔のコード汚なすぎて滅

昔のコード:https://github.com/ReU1107/Project1

プログラマーあるあるの、 「昔の自分のコードを見て『誰だこんな汚ねーもんを作ったのは!出てこいぶっ飛ばしてやる!』……なんだ俺か」 ですね、ハイ。

(※ちなみに今の新しいプロジェクトは、未完というのもあってGitHubのPrivateリポジトリに置いています。公開するつもりで頑張りたい)


開発環境

  • 言語: C++23
  • IDE: Visual Studio 2026
  • グラフィック: DirectX12(Vulkanうっ頭が)

個人開発なんでバージョンなんてその時の最新にバンバン上げちゃって良かとですw


設計方針

個人ていうのもあって、自分の中での「気持ち悪い」を徹底的になくして、自分が「気持ちよく」作っていきます。


AIの使用について

昨今だとAIにコードを書かせるどうのこうのとありますが、このフレームワークに関してですが、AIの使用自体は禁止にしておりません。

ただ、AIの使用といっても、

  • 自身の中で迷った時の言葉だしのキャッチボール相手
  • 新しい技術を学ぶ時の情報集めの一環

として使用しています。

というか最近だと、日常生活でのちょっとした気になることがあればすぐにAIに聞く、みたいな会話を楽しむことが増えました。

(「白身魚だけなんで他のマグロ(赤身)やサバ(青魚)と違って、固有名詞であんまり呼ばれないの?」みたいなことを調べたり……)

そのせい(おかげ?)もあってか、いろんなことに対して疑問を持つという思考になるようになりましたw

日常のちょっとした疑問をスルーせずに掘り下げるの、結構楽しいですよ。


おわりに

UnrealEngineやUnityといった主要なゲームエンジンを使ったり 『ノーコード』や『AIによる自動生成』というのが主流な この令和の時代で時代錯誤もいいところですが、 こんな感じで、自分の「気持ちいい」を詰め込んだフレームワークをガシガシ作っています。

こだわった設計などを次回以降のブログで少しずつアウトプットしていければと思います。

やる気燃料を絶やさないためにも、進捗が出たらまた書きます! 生温かい目で見守っていただけると幸いです。

DXRの続きはこのフレームワークで実装するときに勉強しなおすのでその時にでも

可変長テンプレート引数使ってみた。

動機

   using Vector4 = TVector<float, 4>;

	Vector3 a = Vector3(0.0f, 1.0f, 0.0f, 1.0f);
	Vector4 b = Vector4(2.0f, 3.0f, 0.0f, 1.0f);
	Vector4 c = Vector4(a.XY(), b.XY());
	Vector4 d = Vector4(a, 10.0f);

templateを使用した
ベクトルクラスを作ったのですが、
↑のような書き方を実現しようとすると

        template<class Type, uint32_t N>
        class TVector
        {
        private; 
                 Type v[N];
        };
        // コンストラクタ
	template<class Type, uint32_t N>
	inline TVector<Type, N>::TVector(const Type x)
		: v()
	{
		for (uint32_t i = 0; i < N; i++)
		{
			v[i] = x;
		}
	}

	template<class Type, uint32_t N>
	inline TVector<Type, N>::TVector(const Type x, const Type y)
		: v()
	{
		static_assert(N >= 2, "");
		v[0] = x;
		v[1] = y;
	}

	template<class Type, uint32_t N>
	inline TVector<Type, N>::TVector(const Type x, const Type y, const Type z, const Type w)
		: v()
	{
		static_assert(N >= 4, "");
		v[0] = x;
		v[1] = y;
		v[2] = z;
		v[3] = w;
	}

	template<class Type, uint32_t N>
	template<uint32_t I>
	inline TVector<Type, N>::TVector(const TVector<Type, I>& vec0)
		: v()
	{
		// Vector4 = Vector3
		// Vector3 = Vector4
		const uint32_t count = (I > N) ? N : I;

		for (uint32_t i = 0; i < count; i++)
		{
			v[i] = vec0.At(i);
		}
	}

	template<class Type, uint32_t N>
	template<uint32_t I, uint32_t J, uint32_t K, uint32_t L>
	inline TVector<Type, N>::TVector(const TVector<Type, I>& vec0, const TVector<Type, J>& vec1, const TVector<Type, K>& vec2, const TVector<Type, L>& vec3)
		: v()
	{
		static_assert(N >= I + J + K + L, "");

		uint32_t index = 0;

		for (uint32_t i = 0; i < I; i++)
		{
			v[i] = vec0.At(i);
		}
		index += I;

		for (uint32_t i = 0; i < J; i++)
		{
			v[index + i] = vec1.At(i);
		}
		index += J;

		for (uint32_t i = 0; i < K; i++)
		{
			v[index + i] = vec2.At(i);
		}
		index += K;

		for (uint32_t i = 0; i < L; i++)
		{
			v[index + i] = vec3.At(i);
		}
	}

といった感じで一つ一つコンストラクタを書いていかないといけない。(長いので一部消してます。)
これが色々と面倒くさいので、短く実装したい。

実装

	void Expansion(size_t& index)
	{
	}

	template<typename Head, typename... Tail>
	void Expansion(size_t& index, Head&& head, Tail&&... tail)
	{
		if (index >= N)
			return;
		constexpr size_t ByteSize = sizeof(head);
		constexpr size_t TypeSize = sizeof(Type);
		constexpr size_t Count = (ByteSize / TypeSize);
		const Type* data = (Type*)(&head);
		for (size_t i = 0; i < Count; i++)
		{
			if (index >= N)
				return;
			v[index++] = data[i];
		}
		Expansion(index, tail...);
	}

	template<typename... Args>
	TVector(Args... args)
	{
		size_t index = 0;
		Expansion(index, args...);
	}

実装しました。

Expansion関数でパラメータパックを展開してます。
やっていることは、引数のバイト数から配列の数を調べて
アドレスのデータを渡しているだけです。

めちゃくちゃ短くなってすっきりしました。
使い方を覚えるとめちゃめちゃ便利だなぁー

DXRの始め方(2)

皆さんお久しぶりです。

だいぶ期間が空きましたが前回の続きのDXRサンプルの続きを

書いていきたいと思います。

 

今回見るサンプルはこちら

github.com

 

多分、DXRの根幹とも言っても過言ではないパイプライン君です。

(多分DirectX12のとも言ってよさそうな)

パイプラインオブジェクトはDirectX12から出てきたものですが、

DirectX12を触ったことのある人は、よく知っていると思います。

あの設定がだるくて、だるくてしょうがないあいつですね。

DirectX11を触ったことのある人は、

なんでこんなめんどくさいことをやらなきゃあかんのかと思ったと思います。

DirectX11では裏で作成していてくれてたっぽいです。 

 

そしてここがDXRをやる上での一番の関門だと筆者は思っています。

理由としては、単純に設定項目が多すぎることにあります。

GlobalRootSignature,LocalRootSignature,ShaderLibrary,

HitGroup,ShaderConfig等を適切に設定しないと

作成失敗を起こしどこで失敗しているのかが追いづらいですからね。

 

 

 

因みにパイプラインステートを作成する過程でシェーダーデータを渡す

必要があるのでシェーダーが入ってきます。

hlslのシェーダーモデルは6.3以降シェーダーの種類はライブラリとなっています。

後述しますがDXRのシェーダーは関数の集まり、つまりライブラリ的なものなので

ライブラリになったのかなと思っています。

f:id:ReU:20210825131229j:plain

シェーダーファイルのプロパティから確認できます。

(余談ですが VisualStudioでシェーダーファイル(hlslファイル)を作成する際に

頂点シェーダー、ピクセルシェーダー等はプリセットを作成してくれますが、

ライブラリシェーダーはないので頂点シェーダー等で一度作成してから

自分でプロパティを変更してください。)

 

DXRで登場、使用するシェーダー達

[RayGenerationShader]

レイを作成するシェーダー

ここでレイを飛ばす処理を行い結果をテクスチャに書き込んだりします。

(デバッグ目的で適当な色を入れ確認したりもできます。)

[ClosestHitShader]

一番近い交差点で処理するシェーダー

ここでライティング等を行うことができます。

レイを飛ばすこともできます。

[MissShader]

レイと物体が何も交差しなかった時に処理されるシェーダー

スカイボックス等の情報を貰う(Imaged Base Lighting)か0でも返しておきましょう。

(処理が走っているかだけの確認にも使えます)

[IntersectShader]

シェーダー内で形状を計算で表現できるシェーダー

三角形(ポリゴン)の処理はビルドインであるため、普段は使わないです。nullptrを渡しておきましょう。

[AnyHitShader]

レイと物体が交差した際に呼び出されるシェーダー

ここで衝突を無視するか否かを選択でき、アルファテスト等で使用します。

使用しない場合はnullptrを渡しましょう。

 

最初の3つは必ず必要になりますが、その他の2つは任意で作成します。

確認の為にシェーダーを見に行きましょう。

28行目

RaytracingAccelerationStructure gRtScene : register(t0);

名前が少しわかりにくいですが、TopLevelASです。

前回TopLevelASをSceneと表現したのはこのためです。

29行目

RWTexture2D<float4> gOutput : register(u0);

結果を受け取るテクスチャですね。

コンピュートシェーダーで画像処理等を行ったことのある人は、

よく知っていると思います。

 

linearToSrgbは今回は無視します。

 

41行目

本命が現れましたね。

普段のC++だけだったらあまり見かけないものがついてます。

[shader("raygeneration")]

void rayGen()

これは、C++にもある属性構文です。

中身は大したことはやっていません。

固定色を返しているだけで、レイも飛ばしていません。

 

49行目

struct Payload

これは、TraceRay()関数間でのデータの受け渡しに使用する構造体です。

サンプルではboolだけですが、もちろんカスタマイズできます。

 

miss,closesthit Shaderは衝突したか否かを返しているだけです。

  

では、ソースコードを見ていきましょう。

04-RtPipelineState.cppの648行目のcreateRtPipelineState()関数ですね。

早速英語でコメントが書いてあります。

Need 10 subobjects:(10個のSubObjectが必要)、そのままですね。

パイプラインステートオブジェクトを作成するのに必要な情報として

今回のサンプルの場合、10個の情報が必要ということです。

 

順番に見ていくと

1 for the DXIL library (1つのシェーダーライブラリ)

DirectX Intermediate Language(中間言語 なんでこの名前か忘れました specsに書いてあったような気がする

microsoft.github.io

)

シェーダーを設定してくださいですね。

やっていることはシェーダーのコンパイル

シェーダー内にあるエントリーポイント(関数)の名前と数を設定しています。

 

1 for hit-group

ヒットグループこれもDXRから出てきた言葉ですね。

名前の通りなのですが、

レイが衝突した際に呼び出される、シェーダー群をひとまとめにしたものになります。

ひとまとめにするものは、

ClosestHitShader,IntersectShader,AnyHitShaderで、

 

このシェーダー達をまとめたものに名前を付けて保存しておきます。

ヒットグループは一つのパイプラインに出てくる全てのグループを設定する必要があります。

プライマリレイ用のヒットグループ、シャドウレイ用のヒットグループ等です。

 

2 for RayGen root-signature(root-signature and the subobject association)

ルートシグネチャとサブオブジェクト

つまり、シェーダー(関数内)で処理するリソースバインドと関連付ける必要があります。

サンプルでは、RayGenerationShaderが

結果を受け取る用のテクスチャと

今後レイを飛ばす必要がある時に使用するTopLevelASにアクセスするので

それ専用のローカルのルートシグネチャを作成したのちに

関連付けを行っています。

 

2 for the root-signature shared between miss and hit shaders(signature and association)

ヒットシェーダー、ミスシェーダーでは、

今回は、リソースを使用していないので空のローカルルートシグネチャを作成し

関連付けをしています。

 

2 for shader config(shared between all programs.1for the config, 1 for association)

シェーダーのコンフィグ設定を行い

そのコンフィグ設定を適用するシェーダーを全て関連付けしています。

Attributeのデータサイズ、シェーダーで見たPayloadのデータサイズ

を渡しています。

Attributeは、

シェーダーファイルのchs関数の第二引数にある

BuiltInTraiangleIntersectionAttributesです。

これは、IntersectShaderを書くときに改めて説明しようと思いますが

とりあえずビルドインで用意されてるので中身は固定で

sizeof(float)*2になっています。

Payloadは、

シェーダーを見た際に説明したPayloadのサイズです。

4バイト境界なのでsizeof(float)*1を渡しています。

データサイズはここで設定したサイズよりシェーダーでのサイズが多いと

パイプライン作成時にエラーを吐きます。

 上記二つの設定を使用するシェーダーと関連付けされています。

 

1 for pipelinie config

TraceRay()関数が呼ばれる回数(ここで設定した回数まで呼び出す可能性があると教えているのだと思います)です。

サンプルでは飛ばさないので0が設定されています。

レイトレースの最大再帰数は31か32だったと思います。

 

1 for the global root signature

 グローバルのルートシグネチャを設定しています。

ここで設定したリソースは登録した関数全てがアクセスできるものになっています。

ここでは特にないので、空オブジェクトが設定されています。

シャドウレイなんかを飛ばしたい時は、ヒットシェーダー内でも飛ばすと思われるので

TopLevelASなんかはグローバルルートシグネチャに登録しておいた方がいいと思います。(フルレイトレの場合)

 

上記全てを設定したらデバイスさんに構造体を渡して、

パイプラインオブジェクトを作成してもらいましょう。

サンプルなので失敗することはないと思います。

 

今回は、ここまでです。

パイプライン周りはめんどくさい!

シェーダーリフレクションなんかで自動で作成なんかできたりしないかなー

 

次回は、シェーダーテーブルについて書きたいと思います。

シェーダーテーブルは、リソースを利用する際にとても重要なので

頑張っていきましょう。

 

間違い等ありましたらご指摘、ご鞭撻のほどをお願いいたします。

 

数学クラス

今回数学クラスを作成した理由は、

主にhlslでのfloat〇,float〇x〇等の使用感で書きたかったからです。

value.xy = 2.0f;

まぁ使い道があるかは微妙ですが。

 

github.com

実装する上でテンプレートを使用しており、

TVectorクラスから型と要素数の指定をする必要があります。

TVector<float,3>等

ここもhlslと一緒ですね。

よく使用するfloat2,float3,float4はusingで名前を付けています。

 

TVectorクラスを見たらわかると思いますが、可変長引数は使用していません。

float2 vec2 = float2(1.0f,2.0f);

のような基本型を引数とする場合と

float4 vec4 = float4(vec2,float2(3.0f,4.0f));

float4 vec41 = float4(float1(5.0f).vec4.XY(),float1(6.0f));

のように複数のTVector型を引数にする2つの方法がとれます。

後者の場合、一つの引数でもfloat1(0.0f)のような書き方をしないといけませんが

 

上で少し触れていますが、

.XY() = 2.0f;

のように、関数ではありますが最初の目的は達成しています。

.CX()などの関数は、const指定で変更できないようになっているだけです。

 

問題点として

float x = vec.X();

ができないです。変換関数を用意したのですが他のところで問題が起きたので

このままになりました。

上記の値を渡したいときは

float x = vec.X().At(0);と少し面倒な書き方をしてください

(もしいい方法があったら教えてください)

 

行列クラスもベクトルクラスと同様

TMatrix<float,4,4>のように型と行、列を指定して作成できます。

float4x4(flaot4(),float4(),float4(),floa4());

ここも多分hlslと一緒

._1() = float4(2.0f,4.0f,5.0f,1,9f);

等が使用することができます。

 

世の中には、DirectXMathやその他数学ライブラリが大量にあるなかで

わざわざ数学クラスを作成しましたが、

今回の目的の一環は最初に書いたような目的外に

float2x2 float3x3,float4x4みたいな全パターンのクラスを作成するのがめんどくさかった

後、勉強の為ですねはい。

既存のライブラリをラッピングするだけでも良かった気がしますが

気にしないでおきます。

 

速度うっ頭が

 

アニメーションシステム

今回の記事は、アニメーションに関する記事です。

設計の大まかな指標ができたので記事を書くことにしました。

(メモ代わり)

 

ゲームエンジンが発展してきた今、

下位層のシステムを触る人間は少ないかもしれませんが

知っておくことで損をするということはないと思うので皆も勉強してみよう。

 

アニメーションシステムを作り直すことに当たって

こちらの本を読みました。(執筆段階では、まだ半分・・・)
www.amazon.co.jp

 

以前にも、アニメーションシステムは作成していたのですが

上位層のシステムを作ってからの作成、

クラス設計もUnityを無理に真似たので

Runtimeにリフレクションを使用して変数をクラスから貰ってくる

等、完全に独立していない状態でしたね。

 

github.com

この時は、スキニングなんかでアニメーションとグラフィック依存しちゃうのも

仕方ないよねと自分を納得させながらも気持ち悪さが拭えない状態でした。

 

ただ今回は、そこを完全に独立させ

こちら側で行列パレット(スキニングで使用するあれ)を

作るからあとはよろしく状態にしてあります。

 

出来たもの(未完)

github.com

 

ここからは、一つずつ説明していきたいと思います。

(前回の反省を踏まえて)

 

*Keyframe

前回と変わりませんが

struct Keyframee {float value,float time;}の2つの変数しかありません。

ベジェ曲線をまだ実装していない為,ControlPointなし

これをOgreEngineのような(うる覚え)

TransformKeyframe{Vector3,Quaternion,Vector3;}

のようにするとスケールが動かなかったりと

使用しないものが現れた時の無駄が生じるので最小のものにしました。

(上記で紹介した本にも書いてあります)

 

*AnimationCurve

前回とあまり変わりないので説明なし

 

ここからが大きく変わった所

*Skeleton

 前回のAvatarと似たようなもの(変わったと言っておきながら似たようなものとは)

Avatarクラスは、上位層のシステムとの橋渡し的な存在だったもの。

行列パレットは上位層が持っておりその値を変更する流れ。

Skeletonクラスでは、行列パレットを持っており

上位層がSkeletonから行列パレット情報を貰ってくる仕組み。

上記の依存を取り払った感じになる。

 

*Joint

ジョイント階層構造用

親子関係処理が入っている。フォワードキネマティクス(FK)処理 

前回の上位層のGameObjectが行っていたものと同じ。 

 

*AnimationChannel

前回はなかったもの、正確には前回のAnimationProperty〇〇と似たようなもの

(変わったと言っておきながら似たようなものとは 2回目)

Propertyクラスは、Avatarクラスから変更するオブジェクトを検索

そのオブジェクトの変数ポインタを取得(なんちゃってリフレクション)

直接処理するという処理になっていた。

Channelの方では、取得したキーフレーム情報を返すだけにとどめておいてある。

(理由は後述 BlendTree,AnimationLayerに続く)

x,y,z,wでそれぞれのアニメーションカーブが、

時間に基づいたキーフレームを取得していた。

ここに関しては、

一長一短だが今回は時間を共通にしているのでキーフレーム検索回数が減る。

(非共通版も追加するかも)

 

*AnimationClip

 前回のがMap<処理対象オブジェクト名,処理プロパティ>

といった風に一つのAvatarに対して1:1の関係になっていた。

 

今回のでは、AnimationChannel*[]といった

一つのジョイントに対して1:1の関係になるようにした。

 

*AnimationState

前回とあまり変わらない

AnimationClipからAnimationに変更された

 

*Animation

今回新しく追加されたもの

StateMachineを他のアニメーションにも使用したい為、導入

 

*SkeletalAnimation : public Animation

スケルタルアニメーション用クラス

今回から実装、AnimationClip or BlendTreeから算出されたデータを

処理する。

レイヤー情報から処理方法を変える。

 

*AnimationLayer

前回のものがフェイシャルアニメーション用

スケルタルアニメーション用と

レイヤーとしての面目は立っていたがそれだけで

加算レイヤーなどの処理がなかった。

 

*FlipbookAnimation

*SpriteAnimation

今回は、触れません。 

 

今後、IKの処理等を入れられるようにしたい。

前回のはPMX依存の処理だった

歩きなどの地面との衝突などは考えていなかった。

 

グラフィックのクラス設計(うっ頭が) 

 

環境への適応頑張るぞい!