2016年10月17日
『DOOM』(2016)-グラフィックス研究 – 後編
(2016-09-09)by Adrian Courrèges
本記事は、原著者の許諾のもとに翻訳・掲載しております。
静的キューブマップリフレクション
前のパスの様々な動的リフレクション(とその制限)について述べましたが、今度は イメージベースドライティング(IBL) を用いた静的リフレクションの出番です。
この手法は、あらかじめ生成された128×128のキューブマップに基づいています。キューブマップは、マップの様々なロケーションの環境ライティング情報を表すもので、「環境プローブ」とも呼ばれています。前に錐台のクラスタ化の際に見たライトやデカールとよく似ているのですが、各クラスタに対して、プローブも同じようにインデックス化されています。
そのレベルのキューブマップは全て1つの配列内に格納されています。キューブマップは数十ありますが、以下はこのシーンで主要なものです(この部屋のキューブマップ)。
ピクセルシェーダは深度バッファ、法線バッファ、スペキュラバッファから読み取りを行い、クラスタ構造の中でどのキューブマップがそのピクセルに影響を及ぼしているか調べて(キューブマップが近いほど影響大)、静的リフレクションマップを生成します。
静的リフレクションマップ
マップの組み合わせとブレンド
このステップでは、コンピュートシェーダが以前に生成された全てのマップを組み合わせます。
深度・スペキュラマップを読み取り、フォワードパスのライティングを以下にブレンドします。
- SSAO情報
- 当該ピクセルにSSRが利用できる時は、SSR
- SSR情報がない時は、静的リフレクションマップデータがフォールバックとして使われる
- フォグエフェクトの一部も計算される
ブレンド + フォグ:適用前
ブレンド + フォグ:適用後
パーティクルライティング
このシーンには煙のパーティクルがいくつかあり、ライティングは実際にはスプライトごとに計算されます。
各スプライトは、ワールドスペースにあるかのようにレンダリングされます。位置、ライトリスト、そしてそれぞれのシャドウマップが検索され、クアッドのライティングも計算されます。その結果は、4kアトラスのタイルに格納されますが、タイルはパーティクルのカメラからの距離や画質設定などに基づいて、様々な解像度になり得ます。このアトラスは、同じ解像度のスプライトに対する専用領域を持ちます。以下は64×64のスプライトの外観です。
パーティクルライティングアトラス
そしてこれは、そのような低解像度で格納されているライティング情報にすぎません。後にパーティクルが実際に描画される時にはフル解像度のテクスチャが用いられ、ライティングクアッドはアップスケールされてそれにブレンドされます。DOOMはここでは、パーティクルライティングの計算をゲームの実際のメインレンダリングから切り離しています。つまり、プレイしている解像度(720p、1080p、4kなど)にかかわらず、パーティクルライティングは常に計算され、そうした小さな固定サイズのタイルに格納されるのです。
ダウンスケールとぼかし
このシーンは数回ダウンスケールされて、40ピクセルになります。ダウンスケールされた小さなレベルでは、別個の垂直パスと水平パスを使ってぼかされます。
なぜ、これほど早い段階でぼかされるのでしょうか。そのような処理は通常、明るいエリアからのブルームエフェクトを出すために、ポストプロセッシングの最後に行われます。
ですがここでは、そうした様々なぼかしレベルは全て、ガラスの屈折をレンダリングする次のパスで役立つことになります。
透明オブジェクト
透明オブジェクト(ガラス、パーティクル)は全て、シーンの上にレンダリングされます。
透明オブジェクト:レンダリング前
透明オブジェクト:レンダリング後
ガラス、特につや消しガラスやくすんだガラスは、DOOMにおいて非常にうまくレンダリングされます。ガラスの屈折をある程度ぼかすため、デカールがガラスの一部だけに影響するように使われるのです。
ピクセルシェーダは屈折の「ぼかし度」の要素を計算し、その要素に最も近い2つのマップを一連のぼかしの中から選択します。そしてこの2つのマップから読み取りを行ったら、2つの値の線形補間を行い、その屈折に適した最終的なぼかしの色を概算します。ガラスがピクセルごとに様々なぼかし度で精密な屈折を生み出せるのは、この処理のおかげです。
歪みマップ
歪みマップ
画像には、非常に高温なエリアの熱歪みを作ることができます。この例では、 Gore Nest によって画像がわずかに歪んでいます。
歪みは深度バッファに対してレンダリングされ、低解像度の歪みマップが生成されます。
赤と緑のチャンネルは、水平・垂直軸に沿った歪み量を表しています。青のチャンネルは適用すべきぼかし量です。
実際のエフェクトは後ほど、どのピクセルを移動すべきか識別するための歪みマップを使って、ポストプロセスとして適用されます。
ただし、特にこのシーンでは、あまり目立たないわずかな歪みしかありません。
ユーザインターフェース
UI
UIは、LDR(低ダイナミックレンジ。1チャンネルにつき8ビット)フォーマットで格納された乗算済みアルファモードで、別個のレンダターゲットにレンダリングされます。
最終フレームの上に直接描画されるのとは対照的に、UI全体を単独のバッファに持つ利点は、ゲームが、色収差や視覚的歪みのようなフィルタやポストプロセッシングを、シングルパスで全てのUIウィジェットに同時に適用できることです。
このレンダリングは特別なバッチ手法を使わず、約120回のドローコールで、UIアイテムを1個ずつ描画します。
その後のパスで、UIバッファはゲーム画像の上にブレンドされ、最終結果を生成します。
テンポラルアンチエイリアシングとモーションブラー
テンポラルアンチエイリアシング(TAA) と モーションブラー は、ベロシティマップと以前のフレームのレンダリング結果を使って適用されます。
フラグメントは、現在処理されているピクセルが前のフレームでどの位置にあったかをピクセルシェーダが認識するために、再投影されることができます。このレンダリングは実際には、メッシュの投影を1フレームおきに半ピクセルだけずらします。これは、サブピクセルのエイリアシングアーティファクトを除去するのに役立ちます。
TAAとモーションブラー:適用前
TAAとモーションブラー:適用後
素晴らしい結果です。メッシュのエッジが滑らかになるだけでなく、スペキュラエイリアシング(あるフレームで1つの明るいピクセルだけがポップインする場合)の処理も行われるのです。画質は、FXAAのようなポストプロセスの方法を使った時よりもはるかに良くなります。
シーンの輝度
このステップでは、シーンの平均 輝度 を計算します。これは、後ほどトーンマッパーに渡されるパラメータの1つです。
HDRライティングバッファは、ループにおいて半分の解像度にダウンスケールされて2×2のテクスチャになり、それぞれの繰り返しでは、高解像度マップから4つの親ピクセルの平均輝度としてピクセルの色の値が計算されます。
ブルーム
ブルーム
明るいパスのフィルタは、シーンの中で暗いエリアを見えにくくするために適用されます。
その結果はループにおいてダウンスケールされ、前に見た類似の処理でぼかされます。
レイヤは ガウシアンぼかし でぼかされて垂直・水平パスに分けられ、そのパスではピクセルシェーダが1方向に沿って加重平均を計算します。
ぼかされたレイヤはその後組み合わされ、元の解像度の1/4であるHDRテクスチャのブルームを生成します。
最終的なポストプロセッシング
以下のステップは全て、単一のピクセルシェーダで実行されます。
- 熱歪みが、歪みマップのデータを読み取って適用される
- ブルームテクスチャが、HDRライティングバッファの上に追加される
- ビネット、ダートフレア、レンズフレアといったエフェクトが実行される
- 2×2の輝度マップの中心をサンプリングすることで平均輝度が検索され、エクスポージャパラメータを追加して、トーンマッピングとカラーグレーディングが適用される。
トーンマッピング:適用前
トーンマッピング:適用後
トーンマッピングは、広範な明度の色を含むHDRライティングバッファを受け取り、フレームをモニタに表示できるよう、そのバッファを変換して1つのコンポーネントにつき8ビット(LDR)にまで落とします。
式 (x(Ax+BC)+DE) / (x(Ax+B)+DF) - (E/F)
に基づいた フィルミックトーンマッピングオペレータ が用いられますが、この技法は 『アンチャーテッド2』のトーンマッパー や『 グランド・セフト・オートV 』でも見られます。
なお、このシーンに広がっている赤色は、全体に色補正が施されています。
UIと粒状性
最後に、UIがゲームフレームの上にブレンドされ、同時にかすかな 粒状性 が適用されます。
UI 粒状性:適用前
UI 粒状性:適用後
やりました! フレームが完成したので、モニタに送って表示できるようになりました。かなりの計算量でしたが、その全体が16ミリ秒以内に行われました。
DOOMが高画質の映像を高いパフォーマンスで提供できるのは、以前のフレームで計算された古いデータをうまく再利用するからです。合計では、1,331回のドローコールが実行され、132種類のテクスチャと50個のレンダターゲットが使われました。
追加説明
ガラスの詳細
ガラスのレンダリングは見事な出来栄えですが、これは前に説明した比較的単純なステップで実現されています。
- 不透明メッシュのレンダリングについて、ぼかしのレベルをいくつか用意する
- ガラス屈折の様々なぼかし値に対し、前回の一連のぼかしを使って、半透明アイテムをデカールやライティング、プローブリフレクションを適用しながら、前後を逆にフォワードモードで描画する。したがって、各ピクセルは独自の屈折値を持つことができる。
ガラス:レンダリング前
ガラス:レンダリング後
被写界深度
ここまでフレームを細かく研究してきましたが、 被写界深度(DoF) についてはよく分かりませんでしたので、以下のシーンについてDoFの適用前後を検討してみましょう。
DoF:適用前
DoF:適用後
全てのゲームがDoFを正確に適用しているわけではありません。単純なアプローチではガウシアンぼかしを使い、ピクセルの深度に応じて全てのぼかしを1つのパスで実行することがよくあります。このアプローチは簡単でコストが低いのですが、以下のような問題があります。
- ガウシアンぼかしはブルームに適しているものの、 ボケ を作り出すのには向いていない。円形や六角形の明るいピクセルのライトが一面に広がるようにするには、実際に一様カーネルが必要となる。ガウシアンぼかしでは、ボケの形がうまく出ない。
- DoFをピクセルシェーダのシングルショットで適用すると、アーティファクトのブリードにつながりやすい。
DOOMはDoFを正確に適用しており、私の経験から言えば、DOOMで取られているアプローチは最高レベルの結果を出しています。
遠視野画像と近視野画像が生成されるのですが、ピクセルの選択は、その深度とDoFパラメータによって行われます。
- 近視野は強くぼかすことができ、背景のピクセルにブリードすればするほど良くなる。
- 遠視野もぼかすが、合焦または近視野エリアのピクセルを読み取らないので、前景のオブジェクトが誤って背景にブリードしてしまう問題が回避される。
ボケぼかしを作り出すために、DOOMは半解像度で動作し、円形ぼかしを64のテクスチャタップで実行します。各サンプルは同じ重みを持つので、ガウシアンぼかしとは違って明るさが全体的に広がります。
円の直径は、ピクセルの 錯乱円(CoC) の値に応じてピクセルごとに変化します。
遠視野
遠視野 ぼかし#1
遠視野 ぼかし#1および#2
DOOMはその後、16タップでぼかしをさらに広げますが、今回は加重平均を計算せず、サンプル値を単純に蓄積していき隣接するタップのうち最も高い値を保持するので、最初のぼかしを広げるだけでなく、最初のパスの小さなアーティファクト(サンプル間の隔たり)を修正することも行います。この最後の部分は、 McIntoshの論文 から着想が得られています。
複数のパスにわたって行われるそのような繰り返しの手法のおかげで、パフォーマンス面の効率は落ちることなく、大きなぼかしが非常にうまく作り出されます。1ピクセルごとに実行される実際のテクスチャタップの数は、最終的に得られる円形ぼかしの半径の大きさを考えると、やはりかなり少なく抑えられています。
遠視野画像と近視野画像は最後に、アルファブレンディングで元のシーンの上に合成され、最終的な被写界深度エフェクトを生み出します。このパスは、モーションブラー適用の直前に実行されます。
参考資料
idTech 6の技術についてさらに詳しく知りたい方は、幸い数多くの講演や資料が公開されていますので、以下をご参照ください。
- Tiago Sousa、Jean Geffroy「 The devil is in the details: idTech 666 」(Siggraph 2016)(悪魔は細部に宿る:idTech 666)
- Digital Foundry「 Tech Interview: Doom 」(技術インタビュー:Doom)
- DSOGaming「 id Software Tech Interview 」(id Software技術インタビュー)
- QuakeCon 2016: Doom Uncapped – Part1 、 Part 2 (QuakeCon 2016:Doom解明 パート1、パート2)
- VentureBeat「 Doom: The definitive interview 」(Doom:完全インタビュー)
- Graphics Gems CryEngine 3 (Siggraph 2013)(珠玉のグラフィックス、CryEngine 3):多くのポストプロセスの手法がidTech 6で使われている。
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa