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

動機

   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依存の処理だった

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

 

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

 

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

DXRの始め方(1)

最近、よく耳にするレイトレーシング

そんなレイトレーシングを簡単に?実装するためのAPI

DirectX12に付随(拡張)する形で現れたDirectXRaytracing(DXR)です。

DXRでできることの一つとして、反射、透過(屈折あり)処理を行うことができます。

f:id:ReU:20200923131847p:plainf:id:ReU:20200923131626p:plain 

 

 

 まずは、これぐらいの画像を出せるように頑張りましょう。

それより先(AO、GI)は、この処理をできてからでも遅くはないと思います。 

 

冒頭が長すぎると嫌われてしまうと、誰かに聞いたので

早速始めていきましょう。

 

DXRを始める際にまず行わなければいけないことは、

サンプルを見ることです。

材料(API)だけを渡されて作れる人がいるなら

そんな天才さんは、こんなブログ見なくてもできますからね。

サンプル置き場のURLを張っておきます。

github.com

 

ReadMeにも書いてありますように、

WindowsSDKのバージョンが1809以降にしておきましょう。

グラフィックボードもRTX20代以降ならなんでもいいです。

(GTXのやつは対応しているものもあったはず)

ドライバーは、 更新したくないという人以外は、最新にしておきましょう。

 

 サンプル1:CreateWindow

その名の通りウィンドウを作成しているだけなので

 無視してかまいません。

(サンプル全てが前のサンプルを拡張して書いてあるので全サンプルで見れます。)

 

サンプル2:InitDXR

ここからがDXRの始まりですね。

まずは、起動してみましょう。

ここでエラーのメッセージボックスが表示されるなら、環境が対応していないので

環境を整えてきましょう。

 

エラーメッセージが表示されないのを確認したら

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

大して難しいことは行っていません。(DirectX12触った人)

 特別なことは.cppの84行目にあるDXRを起動するにあたって

対応しているかどうかをデバイスで確認をとってメッセージボックスを出している

だけです。↑の処理を行っているだけですね。

 

サンプル3: AccelerationStructure(AS)

知らない言葉が出てきましたね。

Acceleration:加速

Structure:構造

うーん🤔 加速する構造 なんぞやと

レイトレーシングを少しでも触ったことがある人はわかると思いますが。

レイトレーシングとはレイとオブジェクトとの交差判定を行い、

交差先の色を表示するものになっています。

つまり、レイとオブジェクトとの衝突検知を行うということになります。

ゲームをプレイするとわかると思いますが、

ゲーム中にはたくさんのオブジェクト(人や建物等)が画面上には存在します。

その一つ一つとレイとの交差判定をリアルタイムで総当たりで行うなど

愚の骨頂です。お天道様が許しても、筆者が許しません。(何様)

 そこで衝突検知でも使用されている

BoundingBoxHierarchy(BVH)を使用して高速化を図ろうという魂胆です。

レイと詳細な形状との衝突検知は、

その形状を包んだAABBと当たった後でいいじゃない

管轄外の物体との衝突検知なんてしなくていいじゃないということですね。

このBVHを構成するものをASと呼んでいると筆者は思っています。

(間違っていたらごめんなさい)

 

ソースコードに戻って、cppファイルの

282行目のcreateBottomLevelAS

325行目のcreateTopLevelAS

関数を見ていきましょう。

 ASはわかったとならでもその前に何かついてますね。

BottomLevel、とTopLevelですね。

BottomLevelは、メッシュ一つのことを表し

TopLevelは、BottomLevelを全て格納するものと思ってください。

SceneとGameObject(Actor)の関係性と思っても差し支えないかなと。

 

 

DXRは冒頭にも書いたようにDirectX12に付随するAPIです。

DirectX12をある程度把握していないとそもそもできないので

先に勉強しておきましょう。