UE4用NVIDIA Image Scalingプラグイン実装について

UE4 Advent Calendar 2021 12/5の記事です。

はじめに

PS4/XboxOne世代は1080p出力が、PS5/XboxSeriesX世代は4K出力が基本になっています。
しかし、大体の場合でレンダリング解像度はそれより低い解像度で行い、ハードウェアのアップスケーラーを利用するか、もしくは何らかのアップスケール技術を使うのが割と基本です。
残念ながら、これからもその状況はあまり変わらないでしょう。

そうなってくると、どのようなアップスケール技術を使うか?が問題になってきます。
現在、アップスケール技術としてよく取り沙汰されるのはNVIDIA社のDeep Learning Super Sampling (DLSS)です。
名前の通り、Deep Learning技術を利用した超解像技術で、Temporal Reprojectionも利用するためアンチエイリアシングの機能も兼ね備えているようです。
UE4でいうならTemporal AA Upsampling (TAAU)の代替手段になります。
ただし、この技術はGeforce RTX以上を必要とするため、ハードウェアが限定されます。
当然、コンソールゲーム機には使用できません。

そのような状況で新しく現れたのがAMD社のFidelityFX Super Resolution (FSR)です。
こちらはDLSSと違い、ハードウェアを限定しません。Shader Model 5.0?くらいが使用できれば使えます。
これは昨今のPCならほぼ全て使えますし、PS4以降のハードでも使用が可能です。
しかもUE4.27.1用ではありますが、すでにプラグインとして公開もされています。

https://gpuopen.com/learn/ue4-fsr/

また、FSRは入力として低解像度の最終レンダリング画像のみを求めます。
アンチエイリアシングは別途行う必要がありますが、FSR自体にゴースティングの心配はありません。

UE5ではTemporal Super Resolution (TSR)が追加されています。
こちらはUE4への移植も可能ではありますが、一応PS5/XboxSeriesX世代以降対応ということになっています。
PS4世代で動作するかは不明です。
この技術はTAAUの代替手段ですので、アンチエイリアシング機能も含まれます。

さて、つい先月、DLSSのバージョンが2.3になりました。
しかしそれと同時に、新しい超解像技術も発表されました。
それがNVIDIA Image Scaling (NIS)です。

https://github.com/NVIDIAGameWorks/NVIDIAImageScaling

このアップスケーラーもFSRと同様に、最終出力のみを受け取りアップスケールします。
アンチエイリアシングは行いませんが、やはりNIS自体にゴースティングは発生しません。
加えてコンピュートシェーダによる実装であり、Shader Model 5.1以上に対応していれば利用可能です。
今どきのPCならどんなGPUでも使えますし、PS4世代でも使えるはずです。

残念なことに、まだNISUE4プラグインは提供されていません。
発表されたばかりだからということもあるかと思いますが、そのうち出てくるんじゃないかとは思います。

プラグインを作った話

が、そのうち出てくるだろうと思っていまテストしてみたい!FSRと比較したい!
そう思ったので作ってみました。

https://github.com/Monsho/NISPluginForUE4

一応ライセンスはMITにしています。NIS SDKがMITライセンスなので。
インストールについてですが、Readmeを読んでください。
残念ですが、ビルド済みバイナリは含めていませんので、エンジンに突っ込んでビルドしてください。
プラグインの追加だけなのでエンジンコード自体は汚しません。

なお、UE4.27.1以上対象のプラグインです。
UE4.26以下では利用できません。UE4.27.0なら使えるかも。
というのも、FSRプラグインを参考にして作ったのですが、そこで使用されている機能がUE4.27で追加された機能なので。

以降ではその辺の機能や、プラグイン作成時の注意点について簡単に解説します。

プラグイン解説

ISpatialUpscaler

実は、FSRUE4.26から対応していたのですが、その頃はエンジンコードを変更するパッチとして提供されていました。
UE4.26ではTAAをサードパーティ製に置き換える手段は用意されているのですが、FSRは最終段のPrimary、もしくはSecondaryのUpscalerを置き換えるものなのでTAAの置換手段は利用できません。
なお、DLSSはこのTAAを置換する手段を用いてプラグインを実装しています。

しかしFSRプラグイン実装のためなのか、UE4.27ではこのPrimary、SecondaryのUpscalerを置換する手段が用意されました。
これを利用するにはISpatialUpscalerを継承したクラスを作成し、FSceneViewFamily::SetPrimarySpatialUpscalerInterface()、もしくはFSceneViewFamily::SetSecondarySpatialUpscalerInterface()で登録する必要があります。
これが登録されているとデフォルトのアップスケーラーの代わりに、プラグインで実装したアップスケーラー、つまりFSRNISを実行できるという仕組みです。

FSceneViewExtensionBase

ここで注意点。
FSceneViewFamilyは基本的に毎フレーム生成・削除が行われるようです。
そのため、ISpatialUpscalerを継承したクラスは毎フレーム登録する必要があります。
そこでFSceneViewExtensionBaseを利用します。

このクラスを継承したクラスは生成時に自動的にエンジンが持っているViewExtensionsに登録されます。
ViewExtensionsに登録されているクラスは、ViewFamilyのセットアップ時や描画の開始・終了時にViewFamilyに対して追加処理を行うことができます。
NISプラグインでは、FNISViewExtensions::BeginRenderViewFamily()関数が描画開始時に実行される関数で、ここでPrimaty UpscalerにFNISUpscalerを登録しています。

登録する場合は常にnewする必要があります。
FSceneViewFamilyが削除されるタイミングで登録したUpscalerも勝手に削除されるためです。
寿命管理はこちらで制限できなので、TSharedPtrなどで作成してポインタだけ登録する、というような手法は使えないと考えてください。

FNISUpscaler

実際にNISを実行しているのはこのクラスです。
FNISUpscaler::AddPasses()関数でNISを実行するパスを追加しています。

ここでもまた注意点があります。
FSRはコンピュートシェーダでもピクセルシェーダでも実装することができるのですが、NISはコンピュートシェーダのみの実装となっています。
つまり、NISの出力テクスチャはUAVでなければならないのですが、最終出力となるPassInputs.OverrideOutputはUAVとして使用できません。
そのため、NISプラグインではUAVとして使用できる2Dテクスチャを作成し、ここにNISレンダリングしてから最終出力にコピーするという手段を用いています。
ピクセルシェーダでも実装できるなら良かったのですが、NISはLDSを使用してるっぽいので無理なんですよね…

また、NISでは係数を保存したテクスチャを渡す必要があります。
この係数はNISが用意しているもので、入力画像によって変化するものではありません。
そのため最初に生成するだけでよいのですが、FNISUpscalerは毎フレーム削除されてしまうので、コンストラクタで生成・デストラクタで破棄とやるのは大変無駄です。
そこで、コンストラクタで外部から渡すという手法を用いているのですが、ここでちょっと面倒なことが…
この理由も含めて次節で解説します。

2つのモジュール

NISプラグインImageScalingImageScalingExtensionという2つのモジュールから成り立っています。
FSRも同様の仕様なのですが、わざわざ2つのモジュールに分けているのは初期化タイミングの問題のためです。

まず、FNISUpscalerはグローバルシェーダを利用するため、独自のシェーダを利用できるように登録する必要があります。
そのためにモジュール開始時にシェーダのディレクトリを登録することになるのですが、この場合のモジュールはPostConfigInitで実行されなければなりません。
このタイミングはエンジン初期化より前のタイミングであるため、この設定になっているモジュールの初期化段階ではエンジンの機能を利用することができないのです。

エンジン初期化の前にできないこととは何かというと、それはViewExtensionの登録とテクスチャの作成です。
FNISViewExtensionを作成してしまうと自動的に登録されてしまうわけで、その登録先はエンジンなわけです。
テクスチャ生成もRHIなどを利用するため、エンジンが初期化されていなければ実行できません。
グローバルシェーダを使うためにはPostConfigInitでなければならないが、ViewExtensionを生成するのはPostEngineInitでなければならないのです。

そのためにViewExtensionを生成するためのモジュールが必要になります。それがImageScalingモジュールというわけです。
テクスチャ生成もこのモジュール開始時に行い、FNISViewExtensionに登録しています。
FNISUpscalerが利用するテクスチャはさらにFNISViewExtensionから渡されるという形になっています。
面倒ではありますが、こうする以外の方法を見出すことができませんでした。

使い方の注意

NISプラグインを使う場合の注意点として、FSRとの併用はできないという点に注意してください。
プラグインを両方とも有効にすることはできますが、その状態でFSRNISを有効にするとISpatialUpscalerの登録時にチェックで引っかかります。
チェックを無視すれば先に進めることはできますが、多分メモリリークとなります。

FSRはデフォルトで有効になってしまうので、NISはデフォルトで無効にしています。
NISを利用する場合はまずFSRを無効にしてから(r.FidelityFX.FSR.Enabled = 0)、NISを有効にしましょう(r.NVIDIA.NIS.Enabled = 1)。 その逆もまた同じです。無効にしてから有効にする。これを守りましょう。

もう1つ。これはFSRでも同様ですが、テクスチャのミップレベル調整は自動的に行われません。
r.ScreenPercentageで設定したパーセンテージに合わせて、r.MipMapLODBiasの値を調整しましょう。

比較

1440pネイティブ f:id:monsho:20211205022850p:plain

720p->1440p デフォルトアップスケーラー f:id:monsho:20211205022946p:plain

720p->1440p FSR f:id:monsho:20211205023317p:plain

720p->1440p NIS f:id:monsho:20211205023241p:plain

さいごに

このプラグインは現段階ではWindowsPCのD3D12バージョンでしか動作を確認していません。
他の環境で動作するかという点については不明です。
多分動くんじゃないかとは思うのですが、シェーダコンパイルで失敗するとかの可能性はあると思います。
そうなった場合はバグを修正してプルリクでも投げてください。

というわけで今年のAdvent Calendar記事はこれにて終了!
前回の更新も去年のアドカレだったんだなぁ…