DirectX12の部屋。上に行くほど新しいが、なんとなく過渡期のAPIという認識なので何か変わったら使えない。自己責任で。
GDC2018で、
DirectX Raytracing (DXR)が発表されました。
SDKのサンプルをざっと読んだんだけど、レイのごとく(伝われ)前処理が普通にポリゴン出すときと同じくらい長い。
FallbackLayerのルートだけをざっと読んだ。
さっと入れてみた感じわしの持ってるカードはFallbackLayerだけサポート。
本家のマニュアルは「D3D12 Raytracing Functional Spec.docx」
[環境]
Win10-64bit, i7-860-2.80GHz, 16MByteMem, GTX950Ti
Driver Ver : 388.13
[C++側] ※DirectX12で必須なところは書いて無いっす。
・D3D12CreateRaytracingFallbackDeviceでデバイス作る
・ID3D12RaytracingFallbackCommandListを作る
・ID3D12RaytracingFallbackStateObjectを作る。レイトレ用のStatePipeline。
この時、RasterizerのShaderと同じで後述するヒット処理、ミス処理、レイを作る処理などコンパイル済みのShaderCodeから全部のエントリポイントを指定する。
・UAVとしてレンダリング対象を設定する
・頂点リソースを作る。
・作った頂点リソース周りをID3D12RaytracingFallbackCommandList::BuildRaytracingAccelerationStructureでTraceRayで幾何情報参照するオブジェクト「RaytracingAccelerationStructure」用に作る
・レイトレシェーダーに渡す、Shadertables(実態はID3D12Resource)をhit, miss, generateray時につかうものを全部作る
・D3D12_FALLBACK_DISPATCH_RAYS_DESCにShadertables設定してDispatchRaysをコール(ID3D12RaytracingFallbackStateObjectも指定)
・UAV -> RenderTargetにコピー
・終わり
[Shader側]
レイがヒットした時のふるまいを決めるためのものをひたすら書くことになりそうです。
ヒット判定もろもろ、シェーダーと合わせて参考になりそうなのは「D3D12 Raytracing Functional Spec.docx : TraceRay() control flow」
に記載されています。
Triangleだった場合は固定機能のレイトレ処理が走るようです。それ以外だったら自分でIntersection処理書かないとダメっぽい。
・[shader("raygeneration")] : レイ作る処理
・[shader("closesthit")] : レイがヒットした場合のヒット処理
・[shader("miss")] : レイがヒットしなかった場合のミス処理
無いと思うけどtriangleだった場合で、レイを進行方向まっすぐじゃなくてゆがんだエフェクト作りたい場合は自分で判定書けって意味かな。
サンプルではIntersection側が無いのでまだ。よくわからない。
手を動かすか。
ダウンロードはこちら ⇒
DirectX12_06_MINI.zip
結局いくつかきれいに書いたけど、あらゆる制御クラスを書かないといけなくなりそうだったので、
とにかく素朴に作ってあとは上から制御するようにしましょうという魂胆で雑に小さく書く。サンプルではとりあえずMRTしています。
世話してくれていたところをいい加減にやろうとするとReflectionとかつかって頑張ればできそうな気がするし、
調べるとすでにDirectX12の自前のDSLつきコンパイラ書いている人も多かったのですがなんかでかかったので、
手軽に使えるのが欲しかったので落としどころはここ。
あとはComputeShaderとBundle周りやったら大体やりたいことはできている認識。
とにかく最初にDescripterHeap確保したらGPU, GPU側のhandleを最初に取ってしまうのがごちゃごちゃしなくていいかなと思った。
後はRootDescripterTableの定義をHLSLにかけるならもう有効活用してテーブルのことあまり考えなくてもいいようにする。
とにかく面倒…。理解した後だとあとは割とどうとでもなるけど、興味があるのはDirectX12として特別な機能なので
今後はそこを中心に遊んでみる予定。
ダウンロードはこちら ⇒
DirectX12_00_RS.zip
初期化周りはマイクロソフト公式が一番よく紹介されてて親切です。
そして、一番面食らうのがRootSignatureだったのですが解説はほかのサイトに譲ります。
構造体のD3D12_ROOT_PARAMETERなりに定義してD3D12SerializeRootSignatureしてCreateRootSignatureするサンプルが多いですが
実は定義をHLSLに書くことができてID3DBlobから取り出すことができて、Pipeline作るときに使えるようです。参考は以下。
[DX12] HLSLにRoot Signatureを定義する
公式にもあります。
Specifying Root Signatures in HLSL
ピロピロっとラフを以下のように書く。
void CompileShaderFromFile(D3DShaderVector &vdest, const char *filename, const char *entryname, const char *profile, UINT flags, ID3DBlob **blobsig = 0)
{
ID3DBlob *blob = NULL;
ID3DBlob *blobError = NULL;
if (!filename) {
printf("%s : Invalid Filename\n", __FUNCTION__);
return;
}
std::string fstr = filename;
std::vector fname;
for (int i = 0 ; i < fstr.length(); i++) {
fname.push_back(filename[i]);
}
fname.push_back(0);
D3DCompileFromFile(&fname[0], NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE, entryname, profile, flags, 0, &blob, &blobError);
if (blob) {
vdest.resize(blob->GetBufferSize());
memcpy(&vdest[0], blob->GetBufferPointer(), vdest.size());
} else {
printf("\n===============================================\nCOMPILE ERROR\n");
if (blobError) {
printf("%s\n", blobError->GetBufferPointer());
}
printf("\n===============================================\n\n");
}
//https://glhub.blogspot.jp/2016/08/dx12-hlslroot-signature.html
if (blob && blobsig) {
if (S_OK == D3DGetBlobPart(blob->GetBufferPointer(), blob->GetBufferSize(), D3D_BLOB_ROOT_SIGNATURE, 0, blobsig))
{
}
}
RELEASE(blob);
}
D3D_BLOB_ROOT_SIGNATUREで取り出せる。
公式のサンプルは参考サイトにある通り、全部盛りで分かりづらいのですが書いてみると便利。
コンパイル済みなどを考慮するとこれ使えばOKじゃんとかという感じではないし、D3DReflect+α使って丁寧にレイアウトを決めるのもアリですが、
1つ増やすときにアプリ側で1つ増やして、HLSL側で共通で用意しているRootSignatureの定義を書き換えておしまい程度なら、
小規模にやる場合は別にいいんでないという感じなので便利。
ただ、まだ過渡期だし廃止されたりしてとか思うとまあ銀の弾丸ではないなとは思う。
もうこの辺はやりたいことのレベルに応じて好きにすればいい気がする。
サンプルはInstanceID使ってべたに四角形をそこそこ大量に出しています。FYI。
こっそりMRTにも対応しています。
Vulkanでも大体使いまわしが効くだろうという魂胆。
ほんとかな。
下手に重厚長大な気がするのでもっと手を抜いて書きたい(ごっつい設計にしている人はすでにしている)