仙台で3Dゲームクライアント開発を行っております、にしきんです。
前世代においてリアルタイムレンダリングでの最重要トピックといえば、やはりテンポラルリプロジェクションではないでしょうか。
これが何であるかを理解するためには、テンポラルアンチエイリアスの実装をすることからスタートするのが良いのかなと思います。ということで今回は軽くその話を書きます。例によってUnityのSRPを用いた、独自レンダリングパイプラインを利用します。
そもそもテンポラルリプロジェクション・テンポラルアンチエイリアスって何?
うーん、何でしょうね笑
実は、テンポラルリプロジェクションは俗称な気がします。よく目にするんですけどね。まあ、言葉通り現在のある地点に関連した情報を、過去の情報から頑張って探すというような動きのことだと思ってます。
テンポラルアンチエイリアスは、この考え方を用いてアンチエイリアスを行うものです。時間軸上で情報を集めて、それによってアンチエイリアスを成立させようということですね。
古くからアンチエイリアスには色々な手法があります。しかし、どれもリアルタイムグラフィクスでは微妙なものばかりでした。例えばスーパーサンプリングは重すぎますし、有名なMSAAはDepthしか参照しないことで弱点を抱えるうえに、Deferredが一般的な今日日使える状況が少ないでしょう。そして、FXAA等の画像処理系AAはサブピクセルの情報を利用できないために品質が厳しいです。
この、どれを選んでも微妙という状況で新たに現れた、パフォーマンスと品質のいい落としどころになる手法がテンポラルアンチエイリアスだというところでしょうか。(個人の認識です)
では、いくつかの重要な項目について、さらっと考え方を示していきます。
Jitter
高品質なアンチエイリアスのためにはサブピクセルの情報が必要になりますが、テンポラルアンチエイリアスでは時間軸上で情報を集めるという性質上、カメラが定位置にいる限りは新たな情報を蓄積させることができません。カメラをJitter(微細に振動)させる必要が出てきます。
それはつまり、プロジェクション行列をサブピクセル範囲で微細に移動させることになる訳です。どのように行列を改変すればよいでしょうか?
このような図で示されるパースペクティブ射影行列は知ってるものとしますが、各パラメータを上記のように置きます。ある時点での元のピクセル中心からの水平方向、垂直方向のサブピクセルの参照(範囲は(-0.5,0.5))をそれぞれax,ayと置くと
水平方向jitterは
ax × (|Left| + |Right|) / アンチエイリアスを掛ける対象の解像度x
垂直方向jitterは
ay × (|Top| + |Bottom|) / アンチエイリアスを掛ける対象の解像度y
となります。
テンポラルアンチエイリアスは、このJitterで入手した情報を指数移動平均で平滑化するのが基本的な戦略なのです。ご想像の通りJitterによって情報が集められるまでの間はエイリアシングが発生しますが、60FPS以上のレンダリングにおいてはさほど問題ない事が多いです。今やハイリフレッシュレートは出せて当たり前……と言う認識で全体観を設計することが、グラフィクスをやる際に課せられた責任だと思っておきましょう。
数値の取り扱い
GPUで取り扱われるFP16は恐らくみなIEEE 754準拠でMantissaが10bit、 Exponentが5bitです。実際に算出してもらえばよいのですが、表せる数の精度にはクセがあることが分かります。幅広い数値にアンチエイリアスが掛かるとまずそうですね。特に物理的なライティングを取り扱う場合は問題になりがちでしょうか。
また、似たようで微妙に異なる話として、輝度について大きな値と小さな値で平均をとると非線形トーンマッピングの際に計算上好ましくない事象が発生しそうなイメージが湧きます。
この打開策として、アンチエイリアスの中でトーンマッピングを適応・復元することが提案されています。これについてはHigh Quality Temporal Supersampling(SIGGRAPH 2014)が詳しいです。
ReprojectionとColor Clipping
時間軸で色情報を蓄積するには、現在のピクセルの位置の直前フレームまでの色を取得する安全な方法が必要です。それには現在フレームのベロシティマップを実装し、利用します。(これがリプロジェクションです。)
しかし、速度バッファを利用して直前の画面上UVを参照したとしても、前回別の物体によってさえぎられていた個所などでは正しい情報が得られるわけではありません。無関係な過去を引きずってしまうと、上のGIFのような残像が問題として発生します。
この対策としてはClampingやClippingと呼ばれる、関係ありそうな色の範囲に収める手法が行われます。特にClippingについて、PlaydeadさんがGDCでお話をされたTemporal Reprojection Anti-Aliasing in INSIDE内でAABBを使って行う方法を提案されていて有名です。私もそれを参考にした実装をしております。
実装結果
では、実際にSRP上で実装した結果を見てみましょう。
なめらかさはGIFでは分かりづらいですが、ほぼ残像が発生していないことを確認できます。
拡大してみるととても綺麗です。これはほとんど、スーパーサンプリングですね。
・画面、過去バッファはR16G16B16A16
・ComputeShaderで8x8x1単位のDispatch
・関連領域を事前にLDSに格納
・適度にNative FP16を利用している
というような実装で、パフォーマンスはIntel UHD 770で1.4ms程度でした(1080p)。大した試行錯誤はしていませんが既に品質に対して軽量だと思います。
最後に
Jitterやベロシティマップといったテンポラルアンチエイリアスで必要とされる下準備は、近年話題のDLSSといった超解像度技術でも必要となる……つまり使用されているものです。
タイトル回収ですが、実は今回の描画パイプラインで既にDLSS2を実装しているので以下に画像としてお見せします。その品質の高さには舌を巻くばかりです。(何故かドキュメントにSRPはDLSS非対応と書いてあるが、頑張ればできる)
DLSSのような超解像度技術は、「ライティングまでを小さな解像度で行ってから最終的な出力をなんらかの独自手法で高解像度にして、高解像度状態でいくつかのポストエフェクトを掛ける。」というようなものですが、DLSSでは高解像度且つTAAを利用した相当の画質を出力します。見ての通り今回の実装の結果と遜色ありません。
これがパフォーマンス改善と同時に行われるのは驚異的ですね。一応TAAでも、応用で高解像度アップスケールを作成することが可能です。ただ、移動している物体の輪郭がどうしてもぼんやりとしてしまい、妥協案でしかないようなものになりがちです。DLSSはこの点において非常によく出来ています。僕はAIに興味はないですが、SM6.4あたりから機械学習系の命令が追加されていたと思うので、500年後ぐらいまでには一度触ると思います。
IntelのXeSSやAMDのFSR2もDLSSと似たような手法で、必要な材料も似通っています。今後このような外部の手法を組み込めるようにしておくためにも、テンポラルな手法を受け入れる実装と理解は、持っておくと便利でしょう。
ILではサーバー関連のお仕事を取り扱うことが多いですが、クライアントゲーム開発のお仕事も行っております。特に3Dについてはモバイルゲームの範疇に留まらず日々能力を高めています。興味を持った方は是非採用情報をご確認ください。