nVidiaのDXRチュートリアルやってみた
久々に技術ブログ書こうと思い、nVidiaのDXRチュートリアルをやってみました。
はじめに
元チュートリアルは英語ですが、chromeの自動翻訳に投げればかなりいい感じの日本語になるので臆せず進みましょう。
こういうチュートリアルやるときには、意識すべきことがあって、それは「きちんと解説読みながら、できるだけ理解しながら、できるだけ素早くコーディングすること」かなと思っています。
①できるだけ理解しながら、ってのは当然のことで、理解しようと試みずにチュートリアルをやるとただただコードを書き写しただけ、いわゆる「写経」になってしまいますね。それは避けましょう。でもどうしても分からない部分は理解を後回しにしてもいいです。
②素早くコーディングってのは、あんまし時間をかけちゃうと興味も薄れるし、せっかく手を動かして身につけようとしてるのに、知識が断片化して効果半減だからです。しっかり理解をしようとするのは良いのですが、あまり時間をかけるのも良くないと思っています。
チュートリアルに取り掛かる前に・・・
チュートリアルに取り掛かる前に、可能な限り初歩的なDirectX12および初歩的なレイトレーシングの仕組みは取り掛かる前に理解しておいた方がいいと思いました。また、特有の用語もバンバン出て来るため、それを把握したうえでないと「何言ってんのこいつ」となるでしょう。
一応ぼくがチュートリアルやるうえで「これ最初に知らんとあかんやろ」と思った用語を挙げておきます。
加速構造(Acceleration Structure)
はい、なんなんすかね、これ。細かく言うとこいつにもTLAS(Top Level Acceleration Stucture)とBLAS(Bottom Level Acceleration Stucture)があるんですが、それはさておき。AccelerationStructureってなんだよ!!って思ったので、検索するとやっぱりレイトレーシングに関する用語のようで、
なんてリンクがでてくるのですが、BVH(Bounding Volume Hierarchy)とか出てきます。大雑把に言うと、レイトレの効率を上げるために、バウンディングボックスやバウンディングスフィアなどのツリーを用いましょうって事みたい。
それの事をAccelerationStructureって言ってるようだ。DXRじたいの解説を見てもBLASーTLASの所でツリー構造になってるし、多分これの事だろうと思って先に進むことにする。
ペイロード(payload)
なにこれ・・・?一応調べると「正味のデータ部分」とか出てきます。わからんて!!
まぁ、どうやら、ヘッダなどを取り除いたデータ部分の事のようです。この程度の認識でも先に進むと何となくわかってくるのですが、大雑把に言うと、レイトレに関連するシェーダ同士のデータの受け渡しに使うものと思ってもらえればいいんじゃないかなと思います。
だいたいDXR特有の用語はこれくらいかなあ、と思います。
チュートリアルの大雑把な流れ
予めチュートリアルの内容というか、流れがどんなものか知っておいた方がいいと思うので、ザーッと書くと
- 元々のプロジェクトはラスタライズ用に作られたプロジェクトHelloTriangleである
- これにレイトレ用のコードを少しずつ追加していくことでラスタライズではなく、DirectXRaytracingを用いて三角形を描画する
いくつかトラップはあるものの、チュートリアル1およびチュートリアル2を「言われたとおりにコーディング」していけば、ラスタライズ時と同じように2Dの三角形が表示されます。
チュートリアル2の時点だと2Dのため「視点」の概念はなく、スクリーンに平行にレイを飛ばして描画するものとなっている。
また、チュートリアル2の最後には発展的チュートリアルへのリンクが張られており、カメラの追加(ここで3D化する)、インスタンシングなどが書かれている。ここ、順番が正直前後しているため、カメラの追加→インスタンシングの順にやった方がいい(罠1)。
では実際にチュートリアルを見ていくとしよう。
Tutorial1の流れと罠
流れ
Tutorial1は基本的に「DirectXRaytracingを使用するための準備」までなので、この時点ではレイトレ用のシェーダも出てこないし、レイトレで何かが表示されたりはしない。ただし、7および8の冒頭で出て来る説明は、難解に思うかもしれないができる限りしっかり読んでおいた方がいい。理解できる範囲で理解して、理解できない部分はノートに書いておく(どうせ後から何となくわかってくるので、その時の理解の助けになる)
罠①
ただし、罠がある。先ほど「言われたとおりにコーディングすりゃいい」と書きましたが、あれは嘘だ。
まずヘルパー関数が罠で、これを落としてプロジェクトに追加するように言われるが、エラー出まくる出まくる・・・マジで?ってくらい出まくります。
これはチュートリアルにも書いてますが、プリコンパイルヘッダーは使用しないにしてください。チュートリアルでは該当cppだけって書いてますが、全部「しようしない」にした方がいいです。
で、ヘルパーのエラーは殆ど「必要なヘッダーが読み込まれてない事」が原因なので、それを潰します。
必要なヘッダーは
#include<stdexcept>
なので、持ってきたcppのほぼすべてにインクルードしましょう。これで大半のエラーが消えるはずです。そのほかにもvectorがないだのなんだのあるので、あとは自分で判断してインクルード追加しておいてください。
罠②
ここまでで、なんとかかんとかコンパイルを通して、言われたとおりにチュートリアル1を終えて実行した時にある事に気づく・・・ログにエラーが出まくっている。どういうエラーかというと、
「HitGroupTable.StartAddressは64バイトアライメントに揃えないとダメ!」
というものだ。これを無視しておけば後の悲劇は生まれなかったのだが、気になってしまい、
alignedAdrs += (64 - alignedAdrs % 64) % 64;
といった感じのコードを書いてしまい、これがチュートリアル2において悲劇を生むことになる。このためここは余計な事をせずに今はエラーを無視して進んだ方がいい。
この後の解説で、このエラーを片付ける方法(ヘルパー関数を修正する)を解説するのでここは我慢しよう。
チュートリアル2の流れと罠
チュートリアル2はいよいよレイトレのために必要な最低限のシェーダの登場である。
- RayGenシェーダ
- ClosestHitシェーダ
- Missシェーダ
の3つです。Common.hlslもありますが、これは構造体的なのが定義されてるだけで、ヘッダとして用いるだけなので、DXR側から呼び出されるのはこの3つのシェーダです。
流れとしては、この3つのシェーダをロードし、をDXRパイプライン上で働くように登録していくという流れですね。
取り掛かる前に「Raytracing Gemsの第3章」のこの図を頭に入れておくと理解しやすいと思います。
とりあえずこのチュートリアルにおいては、RayGenシェーダ内でTraceRay()が呼ばれて、レイと三角形の衝突判定は勝手にやってくれてるようです。
なので、当たったらClosestHitシェーダが呼び出され、外れたらMissシェーダが呼び出される感じです。
罠③
これは元のチュートリアルじしんにも書かれてますが、ここに出て来るシェーダをVisualStudioのプロジェクトに含めてはいけません。コンパイルエラーが起きます。何故かというと、DXRをコンパイルするためにはFXCではなく、DXCでないといけないのですが、現時点のVisualStudio2019では、デフォルトがFXCなのです。このためプロジェクトに含めてしまうとビルドが止まってしまいます。
とはいえ、あとはチュートリアル通りに進めれば三角形が表示される・・・はず?
罠④
なのですが、いや、三角形は表示されてるのですが、色がついてないのです。これは罠②でいらん事やった(アライメントをそろえる)ために、本来Hitシェーダがあるべき場所がズレてしまい、Hitシェーダが呼ばれてなかったのです。しかし分かりやすいエラーがでるわけでもなく、かなり悩んでしまいました。
どういうことかというと、ヘルパ関数の中でMissの後にHitを配置しているのですが、その場所と、アライメント済みの場所が食い違っていても特にエラーが出ないため、まともに機能していませんでした。
罠④への対処
ではどう対処したのかというとヘルパ関数をいじりました。アライメント対処しなければまともに表示されたのですが、それではエラーログが出っぱなしになって気に入らなかったのです。
ShaderBindingTableGenerator.cpp内のGetEntrySize関数の中で
entrySize = ROUND_UP(entrySize, D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT);
とし、ヘルパ関数内で64バイトアライメントになるようにしました。チュートリアルが作られた後にDXRの仕様が変わったんすかね?教えて詳しい人。
さて、そんなこんなで、ちまちま対処していけば、レイトレで三角形が表示されるようになります。
さて、この後ですが、やっぱり3Dにしたいと思います。チュートリアルではインスタンシングが先に書かれていますが、前述のとおり罠です。カメラパースペクティブを先に実装しましょう。
カメラパースペクティブに入るときの注意点
このチュートリアルでは右手系になっています。DirectXを左手系でプログラミングしている人はうっかりするとハマりますので注意しましょう(1敗)。
基本的にこれも言われたとおりに進めれば問題ないのですが、やっぱり1か所罠があります。
罠⑤
大した罠ではないのですが1.9の
ray.Origin = mul(viewI, float4(0, 0, 0, 1));
これは通りません。なぜならray.Originはfloat3のxyzだからです。正しくは
ray.Origin = mul(viewInv,float4(0,0,0,1)).xyz;
です。もっといい書き方があったら、誰か教えてください。
インスタンシング
次のインスタンシングですが、これは全然難しくないです。基本的には言われたとおりに進めればいいです。僕はわざと言われたとおりに進めなかったので色々ハマりましたが、余計なことしないほうが時間の無駄にならないと思います。
と、そこまで進めれば、レイトレで三角形と平面が表示され
こういうのが表示されます。全然シェーディングしてないので違和感ありますが、とりあえずDXRやったぜという達成感はありました。
ここでは解説は控えめにして、ハマりポイントだけを中心に紹介しました。それでは皆様よいレイトレーシングライフを・・・