26/09/21 up
今回はIntelが開発したXeSS Super Resolutionを実装する話です。
良いか悪いかはともかく、現代のゲームはアップスケールを前提に開発されている状態にあります。
現代のゲームは大体4K解像度に対応していますが、ネイティブで描画されることはかなり稀です。
Nanite/Lumenで素晴らしい映像を高速に!と謳っているUE5もTSRを利用して50%解像度で描画するのがほぼ前提になっていますし、PS5 Pro限定とは言えSIEもPSSRを提供しています。Switch2ではDLSSも利用できますね。
これらはアップスケールとアンチエイリアシングを同時に行ってくれるので、その点でも便利ですね。
特にNVIDIA DLSSが使われるようになってからは機械学習を利用したアップスケーラーが注目されています。
TSRやFSR3のような解析的な手法もまだまだ使われていますが、残念ながら機械学習を用いた手法と比べると品質が劣ることは否定できません。
しかし機械学習系の手法はハードウェアが限定される傾向にあります。
NVIDIA DLSSはNVIDIA製のGPUでしか動きませんし(しかもRTX以上)、AMD FSR4はAMD GPUのRDNA3/4でしか利用できません。PSSRはPS5 ProのみでPS5でも使用できません。
なお、AMD FSRはRDNA3/4ではFSR4が使われ、それ以外のGPUではFSR3にフォールバックされます。
そんな中でDLSSやFSRより少し遅れて出現したのがIntel XeSSです。
Intelは基本的にeGPUを開発していたのですが、現在はIntel Arcシリーズを開発していて、dGPUも出しています。
これらのGPUはAIコアを積んでいて、これを利用して機械学習系のアップスケールを行うのがXeSSです。
これだけ聞くとDLSSやFSR4と同じようなものと思うかもしれませんが、それらとの大きな違いはIntel Arcシリーズ以外でも利用可能という点です。
Intel XeSSはオープンソースではありませんが、GitHubで公開されていてライセンス的にも扱いやすいです。
リバースエンジニアリングや改変することは禁止されていますが、そのまま利用するだけなら商用利用も可能です。
あと、Intel XeSSを利用しています!みたいな宣伝文句もまずいっぽいです。まあ、やらないと思いますが。
対応OSは今のところWindows 10/11のみっぽく、LinuxやMac、スマホ系には利用できません。
グラフィクスAPIはDirect3D 11/12とVulkanに対応しているので、今からWindowsでゲームを作る分には利用できないということはないでしょう。
実装も非常に簡単なので内製エンジンに組み込むのも悪くないでしょう。
しかしコンソールゲーム機には対応していないので、マルチプラットフォームエンジンであればTAAのような解析的なアップスケーラーは必要になります。
実装したプロジェクトは以下になります。
今回の実装はほぼ最低限の実装になります。
より詳細な実装についてはドキュメントを参考にしてください。
まず重要なことですが、UE5のTSRは動的解像度に対応していてかなり柔軟に解像度を変更できますが、XeSSはクオリティという形で決められた倍率が存在します。
例えばBalancedでは2.0倍となり、解像度は50%まで下げることが可能です。
固定解像度としてこの解像度まで下げることも可能ですが、最小・最大解像度の範囲で動的解像度にも対応できます。
ただし、クオリティが指定解像度に合わせて自動的に設定されるわけではない点に注意が必要です。
今回の実装では固定解像度で実装しています。
では実装を見てみましょう。
まず、XeSSを利用する場合はコンテキスを作成する必要があります。
コンテキストは初回利用時はもとより、解像度やクオリティを変更する場合にも作成する必要があります。
引数としてID3D12Deviceが必要なため、コンテキスト作成はデバイス作成後となります。
コンテキストを作成したらアップスケール後の解像度とクオリティ設定から描画解像度を取得できます。
xessGetOptimalInputResolution関数に表示解像度とクオリティを設定すると描画解像度と最小・最大解像度を取得できます。
今回は描画解像度を利用し、最小・最大解像度は利用しません。
引数にコンテキストを求めているため、この関数はコンテキスト作成後でなければなりません。
XeSSを使わないけれどXeSSで利用可能な解像度を取得したい場合にはコンテキストを作成しなければなりません。
初期化処理の最後にD3D12用のシステムを初期化します。
出力解像度は表示解像度を設定し、クオリティも解像度取得時に利用したものを同じものを設定します。
フラグは色々あるのですが、今回は描画側でReversed-Zを利用しているので XESS_INIT_FLAG_INVERTED_DEPTH を設定しています。
また、XeSSは内部でVRAMを使用することになりますが、何も設定しなければ自動的にXeSS内部でメモリを確保します。
メモリ管理的にこれは困るという場合、自前でHeapを作成して設定することも可能なようです。
初期化はここまでですが、表示解像度やクオリティを変更する場合は xessD3D12Init を再度呼び出す前にコンテキストを作り直す必要があるようです。
実装によっては不要なのかもしれませんが、私の実装ではコンテキストを作り直さないとクラッシュしました。
そして作り直す場合はGPUコマンドの実行が終了している必要がある点にも注意が必要です。
通常、解像度変更やクオリティ変更はユーザーがオプション画面で行うので、切り替え時にヒッチが発生するのは特に問題ないと考えますが、システムが自動的に動的に変更するような仕組みは避けたほうがよいでしょう。
初期化が終わって実際に利用する際の実装を見ていきます。
まず必要なのはカメラのジッターです。
ジッターはXeSS側で自動的に行なってくれないので、ジッターの値やカメラへの設定はエンジン側で対応する必要があります。
ジッターはHaltonシーケンスを用いて計算しますが、何フレームでループするかはクオリティによって違いがあります。
この計算式はドキュメントにもありますが、コードでは以下のようになります。
倍率の高いクオリティを利用する場合はジッターフレームが長めになるのでゴースティングやシマリングに注意が必要でしょう。
XeSSそのものの実行には描画解像度で作成されているカラー、モーションベクトル、深度のテクスチャとアップスケール先解像度のカラーテクスチャが必要になります。
これらは当然エンジン側が用意し、適切なタイミングで以下のように実行します。
コマンドリストもエンジン側で用意します。各バッファのバリアなんかも事前に行っておく必要があります。
今のところ、コマンド実行はグラフィクスキューのみ可能なようです。
カラー、深度テクスチャは描画結果をそのまま利用すれば良いのですが、速度バッファについては注意が必要です。
私の実装サンプルでは速度バッファをデノイザーなどのテンポラル系技術のために実装しています。
こちらはUV値として利用しているのですが、XeSSはデフォルトでピクセル値としてモーションベクトルを必要とします。
初期化時のフラグでNDCでのモーションベクトルを設定することもできるようなのですが、ジッターの値なども加工する必要があるようで、今回はそれらの設定がいまいちわからなかったのでピクセル値にモーションベクトルを変換する処理を入れています。
この際に前フレームからのジッターの差も考慮しています。
XeSSに渡すモーションベクトルはジッターが含まれていてはいけないようです。
モーションベクトルの再計算は xess_velocity.c.hlsl で実装されています。
実装は以上です。ね、簡単でしょ?
XeSSはアンチエイリアシングとしても利用できるので、ネイティブ解像度で描画するにしてもNoAAより綺麗になります。
ただしそれなりの負荷があるので注意が必要です。
では、実際の品質を見てみましょう。
NoAAと比べるとやはりXeSSは綺麗に見えますね。
本来であれば解析的なTAAを実装して比較するほうが正しいのでしょうけれど、TAAを実装していないもので…
Balancedは倍率2.0なので、720pで描画して1440pで表示しています。
拡大してみると場所によってはBalancedの方が綺麗に見える場所もありますが、全体的に見るとさほど差があるようには見えません。
遠距離で少しだけジラジラしたアーティファクトが発生しているように見えますが、Balancedだとほとんど気にならないですね。
さすがにUltra Performanceだと気になりますが、そこまで下げなければならない状態であるならパフォーマンス最適化を頑張るべきだと思います。
XeSS自体のパフォーマンスは1440pを表示解像度とした場合、Nativeで1.7ms前後、Balancedになると2ms前後かかります。
とても軽い、とは言えませんが、TAAなどを真面目に実装することを考えるとクオリティと比較してだいぶ安いのではないかと感じます。
UE5で試した感じでは、RTX4080上ではDLSSと同じ程度の速度で実行されていました。DLSSの方が少し高速かな?くらい。
TSRと比べると品質的にもパフォーマンス的にも高かったです。
また、DLSSは設定次第だとは思うのですが、小さなVFX(火花とか雨とか)が溶けて見づらくなる傾向にありますが、XeSSは残りやすいと感じました。
その分ジラジラしたアーティファクトはXeSSの方が発生しやすいです。
一長一短ではありますが、Windows環境であればどのGPUでも基本的に利用できるという点でXeSSはアドバンテージがあると思います。
ただ、ReSTIR GIを使った場合にぼやけるという問題が発生しています。
XeSS Nativeはまだぼやけている程度で済んでいるのですが、XeSS Balancedではシマリングも発生していてあまり良い結果を得られていません。
この問題についてはXeSSの問題というより私のReSTIR、もしくはSVGFの実装の問題ではないかと思います。
例えばモーションベクトルにジッターを考慮していないので、その結果としてGIの結果が溶けてしまっている可能性があります。
この点についてはもう少し調整してみようと思います。
Intel XeSSはWindows上のみではありますが、パフォーマンスとクオリティのバランスに優れたアンチエイリアシング+アップスケーラーだと思います。
そのまま利用する分にはライセンス的にも使いやすく、内製エンジンに載せるのに悪くない選択肢だと思います。
もちろん、Windows以外では利用できないため、マルチプラットフォームではフォールバック手法が必要にはなります。
解析的なアンチエイリアシング+アップスケーラーが必要ならFSR3が比較的実装しやすいのでオススメです。
なお、XeSSにはフレーム生成を行うXeSS-FGと低レイテンシを実現するXeLLが存在しています。
これらもIntel GPU以外でも利用できるようです。
マルチフレーム生成には対応していないなどの制約もあるようですが、Windows上の多くのGPUでフレーム生成が利用できるのは魅力的です。
まあ、個人的にはフレーム生成は色々と問題があるのであまり使いたくはないのですが…(ポスプロの制約とかとか)
というわけで、XeSSの紹介でした。
Intelの回し者ではないですよ!