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

 

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

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

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

 

今回は、ここまでです。

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

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

 

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

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

頑張っていきましょう。

 

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