DirectXの話 第170回

NVIDIA RTXGIを使ってみる

20/05/05 up

NVIDIA RTXGI SDKとは

今回使ってみた NVIDIA RTXGI SDK はNVIDIA社が提供するGlobal Illuminationソリューションです。

特徴としては以下のようなものとなっています。

・プローブベースのDiffuse Global Illumination

・事前データ不要

・リアルタイムレイトレーシング(DXR)を用いて完全に動的に更新される

・遮蔽を考慮する

・時間軸方向でマルチバウンスに対応

・ある程度のプローブ位置調整を自動的に行うことができる

元となっているのはやはりNVIDIA社が2019年に発表した『Dynamic Diffuse Global Illumination』です。

Slide

プローブベースのGIは昔からよく使われる手法ですが、その手法の難点は遮蔽が考慮されずライトリークが発生する点です。

RTXGIは遮蔽がある程度は考慮されており、プローブの数や配置が妥当であればかなりのライトリークを抑えることができます。

ただ、完璧というわけでもないため過信は禁物です。

とはいえ、普通の実装であればまず考慮されない部分ですので、相当マシと言ってもいいと思います。

遮蔽が考慮されない通常のプローブベースGIではライトリークを抑えるために配置に気を付けるとか、遮蔽を考慮しなくても比較的問題ないシーンでの使用、背景物などには利用せず動的オブジェクトに対してのみの使用などが主な手法になります。

例えばUE4の場合、静的な背景オブジェクトはライトマップで、動的なオブジェクトはプローブで行っています。

動的GIソリューションとして有名な Enlighten の場合は静的なオブジェクトについては事前に遮蔽情報を生成する、動的なオブジェクトはプローブという形で実装されています。

プローブによる実装でもある程度遮蔽を考慮してくれるという点が事前情報を不要とする部分ではありますが、それゆえの不安定さなどもあるので他のソリューションより圧倒的に優れていて他のソリューションを駆逐するというものではないという点に注意してください。

しかもRTXGIはDXRのようなリアルタイムレイトレーシングAPIに対応していなければそのまま使用することができないという難点もあります。

最終的にはクオリティと使用するアプリとの相性を考えて実装することになるとは思いますが、特にGIソリューションに難を抱えるインハウスエンジン開発者などは検討の余地があるのではないかと思います。

NVIDIA RTXGI SDKを使うには

現在、RTXGI SDK はGitHubのプライベートリポジトリで公開されています。

アクセスするには登録が必要になりますので、以下のサイトから登録をしてください。

https://developer.nvidia.com/rtxgi

登録するとそのうちGitHubのプライベートリポジトリに登録してもらえますので、それをお待ちください。

登録されたらソースコードをダウンロードして普通にCMakeでプロジェクトを作成、とりあえずサンプルを動作させてみてください。

もちろん、Geforce RTXが必要となります。

実行ができたらサンプルを眺めるもよし、SDKのソースコードを解析するのもよしです。

ただ、サンプルは簡単な入口のある部屋のモデルとCornell Boxが用意されているだけなので、実際にテクスチャが貼られたメッシュなどを用いる場合は自前でサンプルを作る必要があります。

今回はこのようなサンプルを作ってみたので、簡単な使い方とともにご紹介します。

サンプル

Sample022が対象のサンプルとなります。

ビルドにはRTXGI SDKが必要ですし、インクルードディレクトリの設定とかが適当なのでその辺りは調整が必要です。

今回は実行ファイルも含めていますので、実行だけでも可能です。

なお、RTXGI SDKの利用についてはNVIDIA社のライセンス条項に従ってください。

実装してみよう!

RTXGI SDKを使用するにはそのヘッダファイル、ビルド後のライブラリファイル、シェーダファイルが必要になります。

ライブラリファイルは静的ライブラリのみですが、PIX関係のためにDLLファイルが必要になります。

RTXGI SDKの処理の流れは以下のようになっています。

1.レイトレーシングを利用して各プローブから複数回のレイトレースを行い、各レイのRadianceと衝突点までのDistanceをテクスチャに保存する

2.RTXGIのプローブ更新処理を行う

A. IrradianceとDistanceのプローブをRadianceとDistanceの情報から生成、前回情報とブレンドする

B. IrradianceとDistanceのプローブテクスチャにおいて境界部分を更新する

3.(オプション)プローブのRelocationを行う

4.(オプション)プローブのClassificationを行う

5.IrradianceとDistanceのプローブを利用してGIをシーンに適用する

ユーザーが作成する必要があるのは基本的には1と5の項目になります。他の部分はRTXGI側で実装されているので、初期化処理以外は呼び出しのみで対応できます。

私のサンプルでは RtxgiComponent というクラスの中に初期化と2~4の処理が含まれています。

まず、RTXGIの初期化を行いますが、この際にプローブの更新、Relocation、Classificationのシェーダが必要となります。

これらのシェーダはRTXGI側に存在しますが、一部の定数をユーザー側で指定する必要があるため、シェーダバイナリという形では提供されていません。

実際に定数を必要とするのはIrradianceとDistanceプローブのブレンド処理の部分で、各プローブが射出するレイの本数とプローブのテクセル数を指定してシェーダコンパイルする必要があります。

そのため、これらのパラメータを動的に簡単に変更するのは難しくなっていますので、予めご注意ください。

初期化では他にもRadiance、Irradiance、Distance、RelocationOffset、Classifierを保存するためのテクスチャが必要となります。

各テクスチャはサイズとフォーマットをRTXGIが指定してきますのでその通りに作成する必要があります。

また、この際にRTXGI側でDispatchを行う際に必要なDescriptorHeapと、各テクスチャのDescriptorを作成する必要もあります。

これらは RtxgiComponent::CreateTextures() 関数内で行っているのでそちらを参照してください。

各テクスチャについては以下に簡単に解説します。

・Radiance:自前のレイトレシェーダで更新する各レイごとのRadianceとDistanceを格納するテクスチャ。プローブの更新はこのテクスチャを元にして行う

・Irradiance:Irradianceのプローブ情報を保持するテクスチャ。GIレンダリングに使用する

・Distance:Distanceのプローブ情報を保持するテクスチャ。GIレンダリングに使用する

・RelocationOffset:プローブのRelocationを行った場合のオフセット値を保存するテクスチャ

・Classifier:プローブが安定して更新不要になったかどうかを保存するテクスチャ

初期化にはPSOの生成も含まれますが、これはすべてComputePSOなので作成したシェーダを利用したPSOをシェーダの分だけ生成すればOKです。

自前で用意するレイトレシェーダはプローブのRadianceとDistanceをレイトレースによって求めるシェーダとなります。

サンプルでは probe_lighting.lib.hlsl ファイルが対象のRGS、MSとなっています。CHSとAHSについてはコレクションを用いているので汎用的に使用できます。

このシェーダ内で前フレームに生成したIrradianceとDistanceを用いることでGIのマルチバウンスを時間方向で行うことができます。

この処理を省くことで1バウンスオンリーのGIを生成することもできますが、さほど重くない処理なので入れておいた方がいいでしょう。

Radianceテクスチャを更新したらRTXGI側の UpdateProbes() を呼び出すとGPUを利用してIrradianceとDistanceの更新が行われます。

最低ここまで実行すればGIが使用可能となります。

Relocation処理は RelocateProbes() 命令を呼び出すだけです。

注意点としては一度に終了させるのではなく、複数回で少しずつ処理するのがよいでしょう。

Classifier処理は ClassifyProbes() 命令を用います。

この処理によって更新不要なプローブについてはマークされます。マークされたプローブはRadiance取得のレイトレ不要になるので速度面で有利に働きますが、ライト環境が変化するシーンでは無効化するか、ライトが変化した段階で ActivateAllProbes() 命令を呼び出すとよいでしょう。

これらの処理はオプション処理なので、不要であればシェーダも含めて実装する必要はありません。

最後に生成されたIrradianceとDistanceのプローブ情報からGIライティングを行えばOKです。

この処理自体はマルチバウンスする際のレイトレシェーダと同じものです。

他のよくあるプローブライティングと比べると重い処理かと思われますが、正直さほど問題にならない処理なのではないかと思います。

ただ、レイトレリフレクションのようにライティング計算が多くなるような場合には問題が出てくる可能性はあるでしょう。

とはいえ、普通の直接ライティング計算の方がよっぽど問題になりそうですが…

とまあ、実装の流れはこんな感じです。

コード解説とかも入れようかと思ったのですが、さほど解説することがなかったので…

詳しくはサンプルコードを読んでください。

速度的にはどうよ?

動的なGIソリューションという点を考慮するとかなり高速なのではないかと感じます。

更新はGPUのみで行うことができて、CPUへの書き戻しは行いません。

Geforce RTXが必要ということで少しハードルは高いかもしれませんが、それを考慮しても十分高速です。

サンプルではプローブ数2240個、1プローブ当たりのレイの本数144本で実装しています。

この状態でノートPCのMaxQデザインRTX2060で、Radiance取得のレイトレシェーダが 1.07ms、更新処理(Relocation、Classification含む)で 0.68ms です。

速度はプローブ数とレイの本数で変化しますが、解像度には依存しません。

クオリティと速度のトレードオフは基本的にはプローブ数とレイの本数で行うことになります。

ただ、動的解像度のように処理が重くなった時だけクオリティを下げる、というのはその手法では難しいと思います。

そのような場合は1フレームあたりに更新するプローブ数を変更することで対応できるでしょう。

これはClassifierテクスチャを用いれば簡単にできるので、更新が必要なプローブだけClassifierテクスチャのフラグを書き換えてしまえばよいでしょう。

レイトレとプローブ更新両方を現在のSDKに合わせて制御できるのは魅力と言えます。

例えば、スポットライトを破壊された場合にそのスポットライトの範囲+アルファを更新するようにするとかですね。

マルチバウンスに対応しているので結構遠くまで影響を与える可能性があるので更新範囲はそこまで限定できないかもしれませんが、アプリによってはうまくいく可能性もあるでしょう。

一度生成したら更新しない、という使い方をするなら速度的には問題はほとんどないと思います。

クオリティ的にはどうよ?

動的に処理されるため、安定性に欠けることがあります。

ライティング環境やシーンの複雑さにもよるので一概に言えませんが、パタパタと変更するのが見えることが多いです。

調整項目としてはHysteresisとChange Thresholdを調整するのがおすすめです。

サンプルではChange Thresholdが 0.5、Hysteresisが 0.98 くらいだと安定するようになります。

ただ、Hysteresisが大きい場合は収束までに時間がかかります。

急激なライトの変化が起こってもGIの変化はゆっくりしたものになるので、その辺りが許されるかどうかですね。

どうしてもという場合は、ライト変化が起こった直後の数フレームはHysteresisを少し下げる、その後は急激に上げて0.98以上に持ってくるというやり方がいいかもしれません。

ゲームによって調整方法は変わってくると思うので、実装後は動作を模索するとよいかと思います。

プローブ数はあまりにも少なくなければ意外とクオリティに関わってこない印象です。

もちろん遮蔽をまたぎすぎるほどに少ないのはNGですが、ぎっしり詰めなくても意外と何とかなる印象があります。

まあ、あくまで印象なのでシーンによってはどうしようもない場合もあるでしょう。

レイの本数もそこまで強い影響があるように思えません。

レイの本数を倍の288本に変更してみましたが、Hysteresisなどを変更しなければやはり安定はしませんでした。

制限事項は?

現状ではGeforce RTXが必要というのが厄介でしょう。

AMDとIntelのGPUでは実装ができないということになります。

ただしキューブマップで代用することは可能だと思います。

その場合はキューブマップレンダリング→必要なレイをキューブマップ上に飛ばしてRadiance計算するか、改造するなりしてキューブマップから直接Irradiance計算するかですね。

改造したくないなら前者、効率よくやりたいなら後者でしょうか。

理屈上、後者は可能なはずです。

もちろん、オフラインでプローブ計算しておいてリアルタイムではプローブライティングのみという方法もありです。

また、テクスチャサイズにハードウェアの制限を受けることになります。

特にプローブ数は大きく影響を与えます。

例えばRadianceテクスチャは幅がレイの本数、高さがプローブ数なので、テクスチャの最大幅をプローブ数が超えてしまうとアウトです。

改造して3Dテクスチャやテクスチャ配列を使うように変更すれば少しはマシになるかもしれません。

最後に

個人的には結構おすすめできるソリューションではないかと思います。

何より実装が簡単というのが大きいです。

もちろん、レイトレが使えることが前提ではあるので楽ではないのですが、次世代機はレイトレ対応を公言していますのでほぼそのまま使えるのではないかと思います。

すべてCSで実行できるというのも利点で、非同期コンピュートが利用できるでしょう。

その辺も考えて作られているような気がします。

とりあえず、いい感じのGIソリューションを探しているという方は一度使ってみるとよいのではないかと思います。