DirectXの話 第174回
Inline Ray Tracing
21/01/17 up
Inline Ray Tracingとは?
DirectX Ray Tracing は現在バージョン1.1となっています。
個人的に DXR1.1 で入った機能として注目しているのは Indirect Ray Tracing です。Draw Indirect や Dispatch Indirect と同様に ExecuteIndirect() 命令を利用したGPU駆動のレイトレーシング機能です。
リアルタイムレイトレーシングと言ってもレイトレーシングは重いので、可能な限りGPUを効率良く回さなければならないわけで、そのためにもGPU駆動のレイトレーシングは必要でしょう。
しかし今回はそちらではなく、Inline Ray Tracing をやってみました。
Inline Ray Tracing は通常のレイトレーシングパイプラインでしか利用できないレイトレーシング機能をピクセルシェーダ、もしくはコンピュートシェーダから利用できるようにする機能です。
ピクセルシェーダから起動できるということは通常のグラフィクスパイプラインから使用できるということで、レンダリングされたメッシュのレンダリング時にレイトレーシングが可能ということです。
コンピュートシェーダからの起動についてはあまり使い道が思い浮かびません。レイトレーシングパイプライン自体がほぼコンピュートシェーダなので。
ただしこの Inline Ray Tracing は通常のレイトレーシングパイプラインと違って使える機能が限られます。
特に問題となるのがヒットグループやミスシェーダが利用できないことです。当然シェーダテーブルも使用できません。
何ができるかというとレイを飛ばしてASとのヒット判定を取ることだけです。
もちろん、ヒット判定を取った時にその座標やヒットしたASの情報などを取得できるので、そこからマテリアル計算とかもできるのですが、シェーダテーブルはないので別のシェーダコードを呼び出すなどは出来ません。
Callable Shader だけでも使えればな、と思わなくもないですが、残念ながら使えません。
使い道としては Deferred Rendering で対応しづらい半透明などの Forward Rendering しなければならないものに対するレイトレーシングでしょう。
遮蔽を取るだけならレイトレーシングの衝突判定を取るだけで対応できるのでシャドウの計算くらいなら簡単にできます。まあ、αテストをやるとなると話は別ですが。
また、バインドレステクスチャを使うことでシェーダテーブルのリソース部分だけを擬似的に対応することが可能です。これによってリフレクションも対応できます。
前回、バインドレステクスチャを利用して Deferred Texture を実装した理由がまさにこれ。これをやりたかったというだけの理由で実装した次第です。
ピクセルシェーダでレイトレーシングを行う
ピクセルシェーダでレイトレーシングを行う場合、レイトレーシングパイプラインと同様にレイを飛ばしてシェーダテーブルを参照して自動的にAHSやCHSが実行されるということはありません。
そのため、レイトレーシングを行った後、行っている最中の処理を自前で実装する必要があります。
まず、レイトレースを行います。
このために使用するのは RayQuery オブジェクトです。
このオブジェクトはレイトレースを行い、レイトレースの結果を取得するためのインターフェースです。
RayFlags は RAY_FLAG_~ を指定します。このレイクエリで利用するフラグをここで指定しますが、トレースの際に追加のフラグも指定することができるので、特にここで指定しなくても大丈夫そうですが、今回はここですべてのフラグを指定しています。
レイトレースにはこのクエリオブジェクトのメソッドを利用します。
TraceInline() メソッドはレイトレースを実行する命令です。
第1引数に Top Level AS を指定します。第2引数は追加のフラグ、第3引数はインスタンスマスク、第4引数は RayDesc です。
通常のレイトレーシングパイプラインで利用する TraceRay() 命令と比べるとかなり引数が少ないですが、シェーダテーブルを参照するためのパラメータ類が存在しないためです。
さて、レイトレーシング処理はテクスチャサンプリング処理と同様に命令実行から完了までに時間がかかる処理です。
しかしテクスチャサンプリングと違って命令を実行したら取得してハイ終わり、とならないところが大きく違います。
そのため、レイトレーシングの結果を受け取る命令を実行しなければなりません。
Proceed() 命令はレイトレーシング処理から戻ってくるのを待つ命令です。基本的にはこの命令を実行後にレイトレの結果を取得できます。
ただし、この命令後にレイトレーシングが”完了”しているかどうかは別問題です。
この命令は戻り値で bool を返します。この戻り値が true の場合はレイトレーシングが完了していません。レイトレーシングを完了させる前にユーザーが判断しなければならないパターンになったことを示します。
この場合はどのような判断が必要とされるかを取得し、それに合わせて処理を行う必要があります。
このパターンになるのは Any Hit Shader が呼ばれるパターンか Intersection Shader が呼ばれる場合です。
今回は IS を利用していないため、AHS のパターンに対して処理をする方法を示します。
CandidateType() 命令を利用するとどのようなタイプの判断が必要かを取得できます。CANDIDATE_NON_OPAQUE_TRIANGLE の場合は AHS が必要な非 Opaque トライアングルです。
Opaque か非 Opaque かは BLAS 生成時にジオメトリに対して設定を行います。今回のサンプルではすべてのジオメトリが非 Opaque になっているので、レイフラグとして RAY_FLAG_FORCE_OPAQUE を指定しない限りは必ず Proceed() で true が返ってくることに注意してください。
そしてヒット候補状態の場合は Candidate~() 命令でさまざまな情報を取得できます。例えば CandidatePrimitiveIndex() 命令ならヒット候補のプリミティブインデックスを取得できます。
上のコードではそれらの情報を元にUV値とマテリアルインデックスを求め、ベースカラーテクスチャからアルファ値を取得しています。
αテストの結果としてこのヒット候補をヒットしたものとして扱う場合は CommitNonOpaqueTriangleHit() 命令を呼び出します。
ただしあくまでここでコミットしたとしてもレイトレが完了するとは限りません。
RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH フラグが立っている場合は最初にコミットされた結果が返ってくることになりますが、そうでない場合は最近点になるまでレイトレーシングが続きます。
このループが完了すればレイトレは完了です。ヒットしたかどうかは別途確認する必要がありますが、少なくとも完了はしています。
次のコードはヒットしているかどうかを確認し、ヒットしている場合はマテリアルの処理を行っています。
CommittedStatus() 命令でヒットしたものがわかります。COMMITTED_TRIANGLE_HIT ならトライアングルとヒットしています。ミスした場合は COMMITTED_NOTHING が返ってきます。
レイトレの結果は Committed~() 命令で取得できます。ヒット候補だった場合の Candidate~() 命令と同じようなものです。
サンプルではマテリアル計算時にシャドウレイを飛ばすようにしています。
シャドウレイではαテストをしないようにしています。これは高速化のためですが、正確な映像が必要な場合はこちらでもαテストを行いましょう。
AHSの起動が必要でないなら RAY_FLAG_FORCE_OPAQUE フラグを利用します。
この場合には Proceed() 命令で true は返ってこないので、呼び出した直後に結果を利用できます。
Inline Ray Tracing を利用する場合、シェーダパイプラインは特に特殊な処理は不要です。
もちろんシェーダリソースとして各ジオメトリの頂点、インデックス情報やマテリアルのテクスチャ配列が必要だったりします。
これらはバインドレスリソースとして設定しています。
この部分は前回の Deferred Texture と同じ実装なので特に難しいことはないと思いますが、テクスチャテーブルのみならずジオメトリの頂点情報も必要なので面倒ですね。
特にデスクリプタのコピー戦略を採用している場合は Draw Call のたびにバインドレスリソースのコピーが発生してしまうので、何らかの工夫でバインドレスだけはコピーしないようにする対応が必要になるかもしれません。
で、これって使えるの?
いやぁ、どうなんでしょう?
実装してみて思ったのですが、かなり面倒ですし、マテリアル情報を使用する場合は頂点情報も含めてバインドレスリソースは必須です。
例えばUE4のように GBuffer に情報を書き込む際にさまざまな処理が行われるようなマテリアルシステムの場合はまともに使用できません。
使用できるのはαテストを利用しない遮蔽情報の取得くらいです。それだってαテストが使えないので、作成するゲームによってはかなり厳しいです。
UE4はAO計算ではデフォルトでマテリアル情報を利用していませんが、メッシュ描画時などにAOを求めるようなことがあるかと言われると、まずないでしょう。
なので、ぎりぎりシャドウに使えるかもね?ってくらいなんじゃないかという気がします。
いや、まあ、ほんと、リンクなしでシェーダテーブルを使用できるようにならないと使い道が見えません。
もし、こういう使い方あるよ!というアイデアがあったら教えてほしいです。