道徳の授業

こんにちわ、Pocolです。
今日は私が最近取り組んでいる内容の一つをご紹介しようと思います。

私が小学生ぐらいの頃に,学校の授業で「道徳」という科目があったのですが,自分は結構話の内容が面白くて好きだったなーとふと思い出しまして,こういうのってあんまり会社では教育してくれないよなって思って,実験的にちょっとやってみようかと思って最近新人さんを捕まえて勝手にやってます。
当然、自分の昔話しても単なる「老害」になってしまうし,全く意味が無いし,自分だったら「お前の話聞いても面白くないよ」と思ってしまうので,これはダメ。
また,よく社会人になりたての頃にやる研修とかでやるような内容をやっても,対して業務に役に立たないことが多いので,違う内容をやろうと思いました。
…で,色々と考えて面白く学べる内容かつ業務内容にも効きそうな内容をやると良いんじゃないかと。毎年CEDECとかで基調講演とかやってますけど,自分はああいうのが結構面白くて好きな人間でして,ガイアの夜明けとかカンブリア宮殿,がっちりマンデーとかが好きなので,そういうテイストの動画を見てもらって,実績ある人から開発で心がけたこととか,作る際に気を付けたこと,どうやってアイデアが出てきたか?などを知ってほしいなと思って,最近色々と動画を漁って,その動画を見て気づきを得てもらう。
…というようなことをやっています。

初回にやったのは,任天堂の山内溥さんの話を見てもらいました。
NHKアーカイブス あの人に会いたい File.494 山内溥

結構色々と面白い話があって,その中の一つに

今まで過去に売れた そういうゲームのいいところだけを取ってきてね
面白い箇所だけ寄せ集めてきてね
それでまとめてしまう
ということをやる人もいますけど
そんなことをしてもユーザーは知っているわけですよ
ユーザーが求めているのは独創的な楽しさ面白さをもとめているわけです 絶えず

という言葉があって,「確かになぁ!」と自分は思いました。
自分がユーザーだとしたら一度見せられたものを見ても新鮮さは無いわけです。もっと言えば,飽きちゃう。飽きちゃうから面白くない。

あと面白いと思ったのは,次のような話もありました。

大衆が買える価格でないと広がらないんですよ
16であろうと32ビットであろうと64ビットであろうとユーザーは関係がないんです ユーザーは
ゲームというのはインタラクティブで遊んで楽しい面白いものなんです
映像がすごい 音声がすごいと言ったって
遊んだら全然面白くも楽しくもなかったら
ユーザーはどうするんですか こんなもん

買いません ユーザーは絶対に。

…まぁ、ものすごく当たり前のことを言っていますよね。
でも,正直なところ開発する際に,こういう当たり前のことを忘れてしまっているな,と自分は気づかされました。

こういう動画を見て感じることは人それぞれ違うと思うんです。
それを動画を見た後に感想会みたいな感じで言い合って,共有するということをやっています。
ただ,動画見るだけども面白いですけども,人それぞれ解釈の仕方のも違うので,色々な新しい考え方も取り込めて面白いんじゃないかと思ってやっています。

この「道徳の授業」がどういう効果をもたらすかは未知ですが…
少なくとも,人が何を面白いと思って開発しているのか,どういう思いを持って開発しているのかについては「知らない」よりは「知っていた方がよいことがある」という信念をもってやっていますし,単純に自分が面白いというものを推し活しているだけなのですが,今後何かいいことがあるといいなとワクワクしながら取り組んでいます。
何か良いことが起こったら,皆さんにも共有したいと思います。

超雑訳 Stupid Spherical Harmonics (SH) Tricks

こんにちわ、Pocolです。
今日は,
[Sloan 2008] Peter-Pike Sloan, “Stupid Spherical Harmonics (SH) Tricks”, GDC 2008
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘していただけると幸いです。
(さらに…)

GPU最適化備忘録

こんにちわ,Pocolです。
今日はGPU最適化のための自分用のメモを書いておこうと思います。
自分がある程度理解できればいいことを前提に書いているので,「そもそも間違っている」とか「全然正しくない」とか「こうした方がもっといい」とかあれば,ご指摘ください。

P3 Method

基本的には,Louis Bavoil氏が書いている「The Peak-Performance-Percentage Analysis Method for Optimizing Any GPU Workload」に従ってボトルネックを探し,改善を行っていきます。GDCでも講演があるので,興味がある方はそちらの動画を見ると良いと思います。
この手法は次のステップで最適化を行います。

(1) “Top SOL%”の値をチェックする。
– (A) 80%より大きいの場合: SOLユニットから処理を取り除くことを試みる。
– (B) 60%より小さいの場合: top SOL%の値が増加するように試みる。
– 上記以外の場合: (A)と(B)の両方を行う。

(2) Top SOLユニットがSMの場合(あるいは,SOL%の項目が近い場合)
 “SM Throughput for Active Cycles”の値をチェックする。
– (C) 80%より大きい場合: 命令群を適宜スキップしてみたり、特定の計算をルックアップテーブルに移行することを検討してみる。
– (D) 60%より小さい場合: SM占有率(実行中のアクティブワープ数)の増加を試みる & SM発行ストールサイクルの数を減らすことを試みる
– 上記以外の場合: (C)と(D)の両方を行う

(3) その他がTop SOLユニットの場合、そのユニットに送られる作業量を減らすことを試みる。

ここまで出てきた用語は次の意味です。

  • SOL:Speed Of Light。最大スループットを意味する。
  • SM:Streaming Multiprocessor。共有メモリやコンスタンスとキャッシュ,テクスチャキャッシュ,複数のスカラープロセッサなどから構成されるもの。ものすごく雑に言えばUnified Shaderのこと。
  • Throughput。ハードウェアが単位時間あたりに処理できるデータ量のこと。あるいは,その数値を使ってデータ処理能力やデータ転送速度を表す。

さて,最適化する上で肝心なのは「Top SOLをどうやって調べるか?」ということになります。これはNVIDIA Nsight Graphicsを使って調べることができます。
まず,Nsightを立ち上げる前にパフォーマンスカウンターなどを取得できるように設定を変更しておきます。NVIDIAコントロールパネルを開きます。Windowsのタスクバー右下にある^をクリックして,NVIDIA設定を右クリックし,NIVIDIAコントロールパネルを選択します。選択してもコントロールパネルが開かない場合は,何らかの異常が発生している恐れがあるのでグラフィックスドライバーを再インストールして改善するか試してみてください。コントロールパネルを開いたら,メニューの「デスクトップ」から「開発者設定を有効にする」にチェックを入れておきます。これでカウンター等の値がとれるようになるはずです。

図1.NVIDIAコントールパネルでの設定

続いて計測を行います。Nsight Graphicsを立ち上げFrame Profilerとしてアプリケーションを立ち上げます。立ち上げ方はConnect to processのウィンドウで,Activityの項目をFrame Profilerに設定して,Application Executableにアプリケーション実行パスを指定,Working Directoryに作業ディレクトリを設定してください。自動的にアタッチする場合はAutomatically ConnectにYesを,Steamなどのようにいったん別exeをかましてから立ち上げる場合はNoに設定しておき,Target PlatformでAttachタブを選択してから,プロセスにアタッチするという方法を取ります。
アプリケーションが立ち上がったら,左上にオーバーレイが表示されるので,F11を押して測定したいフレームをキャプチャーします。

図2.オーバーレイの表示

キャプチャーすると,画面右上にRange Profilerというものが表示されるので,調べたいドローコール部分をRange Profilerでクリックします。クリックすると,少し時間をおいてから次のようにRange InfoやPipeline Overviewなどが表示されます。

図3.フレームキャプチャー

あとは,Pipeline Overviewに表示されるSM / L2 / VRAM / CROP / RAS の値を見て上記のステップに沿って改善を行います。

図4.Pipeline Overview

(A) Top SOL% > 80% の場合

GPU上で非常に効率的に動作していることが分かります。高速化するためには,このTop SOLユニットを処理から削除し,他のユニットのボトルネックを探していきます。例えば SM が Top SOL ユニットの場合は,命令群をスキップしてみたりなど。もう一つの例としては構造化バッファのロードを定数バッファに移して高速化するなどです。

(B) Top SOL% < 60% の場合

トップ SOL%の値が60%未満場合、トップ SOL ユニットと SOL% が低い他のすべての GPU ユニットは、使用率が低い(アイドルサイクル)、非効率的に動作している(ストールサイクル)、または与えられた作業負荷の仕様により高速パスに当たらないということを意味します。このような状況の例としては、以下のようなものがあります。

  • アプリケーションが一部CPUに制限される
  • Wait For IdleコマンドやGraphic←→ComputeスイッチでGPUパイプラインを何度も消耗している
  • テクスチャオブジェクトからのTEXフェッチは、フォーマット、次元、またはフィルタモードによって、設計上、スループットが低下して実行されます(GTX 1080のこれらの合成ベンチマークを参照)。例えば、3Dテクスチャをトライリニアフィルタリングでサンプリングする場合、50%のTEX SOL%が期待されます。
  • TEXやL2ユニットのキャッシュヒット率が低い、VRAMアクセスがまばらでVRAM SOL%が低い、GPU VRAMではなくシステムメモリからVB/IB/CB/TEXをフェッチするなど、メモリサブシステムの非効率性。
  • 入力アセンブリの32ビットインデックスバッファのフェッチ(16ビットインデックスに比べ半減)

この場合、トップSOL%の値を使用して、非効率な処理を削減することによってこの処理負荷で達成できる最大利得の上限を導き出すことができます。

(C) SM Throughput For Active Cycles > 80% の場合

SM がトップ SOL ユニットで、「SM Throughput For Active Cycles」が 80%より大きい場合、現在の処理負荷は主に SM スケジューラの発行率によって制限されているため、SM の占有率を上げてもパフォーマンスは大きく向上しません(少なくとも、処理負荷は 5%以上増加しない)。

この場合,次のステップは、SMスケジューラの帯域幅を飽和させているのはどのような命令かを把握することです。典型的なのは算術命令(FP32や整数演算)ですが、テクスチャフェッチや共有メモリアクセスなどのメモリ命令もあり得えます。また、SMがトップSOLユニットで、SM Throughput for Active Cyclesが80%以上のワークロードでは、TEXユニットもSMのSOL%に近い値でなければ、TEX命令(SRVおよびUAVアクセス)が性能を制限している可能性はないでしょう。

(D) SM Throughput For Active Cycles < 60% の場合

このGTC 2013の14分の講演のスライド15にあるように、あるワープ命令が発行できない場合(オペランドの準備ができていない、あるいは実行に必要なパイプラインサブユニットの準備ができていないため、これをワープストールと呼びます)、SM命令スケジューラは、別のアクティブワープに切り替えてレイテンシーを隠蔽しようとします。そこで、SMスケジューラがSMアクティブサイクルあたりにより多くの命令を発行できるようにするには、2つの方法があります。

(1)SMの占有率(スケジューラが切り替え可能なアクティブなワープの数)を上げる
(2)SM発行ストール待ち時間を短縮する(ワープがより少ないサイクルでストール状態に留まるようにする)。

アプローチ1:SM占有率を増加する

SMがトップSOLユニット(またはそれに近い)、「アクティブサイクルのSMスループット」60%未満であれば、SMの占有率を上げるとパフォーマンスが向上するはずですが、まず何が制限になっているかを把握する必要があります。

ピクセルシェーダとコンピュートシェーダで最も一般的な SM 占有率のリミッタは、シェーダが使用するスレッドごとの ハードウェアレジスタの数です。

ハードウェアのレジスタ数が最大理論占有率(アクティブサイクルあたりのアクティブワープ数)に与える影響は、CUDA Occupancy Calculatorで確認できます。
Maxwell、Pascal、Volta GPU では、レジスタの他に、以下のリソースでも SM 占有率を制限することができます。

グラフィックスシェーダについて:

  • 頂点シェーダー出力アトリビュートの合計サイズ。
  • ピクセルシェーダーの入力アトリビュートの合計サイズ
  • HS、DSまたはGSの入力および出力アトリビュートの合計サイズ。
  • ピクセルシェーダの場合、ピクセルワープのアウトオブオーダー完了(通常、動的ループや早期終了ブランチなどの動的制御フローに起因する)。CS のスレッドグループは任意の順序で完了できるため、CS にはこの問題があまりないことに注意してください。Gareth Thomas & Alex Dunnによる「Practical DirectX 12」についてのGDC 2016講演のスライド39をご覧ください。

コンピュートシェーダについて:

  • スレッドグループのワープがSM上でall-or-none方式で起動するため、スレッドグループのサイズはSMの占有率に直接影響します(つまり、スレッドグループのすべてのワープが必要なリソースを利用でき、一緒に起動するか、または何も起動しないかです)。スレッドグループのサイズが大きくなると、共有メモリやレジスタファイルなどのリソースの量子化が粗くなります。アルゴリズムによっては純粋に大きなスレッドグループを必要とする場合もありますが、それ以外の場合は、開発者はできるだけスレッドグループのサイズを64スレッドまたは32スレッドに制限するようにすべきです。これは、64 または 32 スレッドサイズのスレッドグループが、シェーダプログラムに最適なレジスタターゲットを選択する際に、シェーダコンパイラに最も柔軟性を与えるからです。
  • さらに、レジスタ使用量が多く (>= 64)、SM (HLSL の GroupMemoryBarrierWithGroupSync()) におけるスレッドグループバリアのストール時間が長いシェーダでは、スレッドグループのサイズを 32 に下げれば、64 と比較してスピードアップする可能性もあります。64 レジスタのガイダンスにより、SM あたり最大 32 スレッドグループの制限(CUDA Occupancy Calculator のアーキテクチャ依存の「Thread Blocks / Multiprocessor」制限)が、SM 占有率の主要制限要因にならないようにします。
  • CUDA Occupancy Calculatorに様々な数値を入力することで、スレッドグループごとに割り当てられる共有メモリの総バイト数もSMの占有率に直接影響を与えることが分かります。
  • 最後に、短いシェーダ(例えば、TEX命令と2つの算術命令)を持つDispatchコールのシーケンスでは、SMの占有率は、上流ユニットのスレッドグループの起動率によって制限される場合があります。この場合、連続したDispatchコールをマージすることが有効な場合があります。

CUDA Nsightのドキュメントページ「Achieved Occupancy」には、Compute ShadersのSM占有率制限の候補がいくつか挙げられています。

  • スレッドグループ内の処理負荷が偏っている。
  • 起動したスレッドグループが少なすぎる。これは、GPU Wait For Idles の間に SM を完全に占有するのに十分なワープを起動しないグラフィックシェーダの問題にもなりえます。

注:スレッドグループのサイズを小さくするには、実際には2つのアプローチがあります。

  • アプローチ1:スレッドグループのサイズをN倍に下げ、同時にグリッドの起動次元をN倍にする。
  • アプローチ2:N>=2個のスレッドの作業を1個のスレッドに統合する。これにより、マージされたN個のスレッド間でレジスタを介して共通データを共有したり、アトミック演算(HLSLのInterlockedMinなど)を用いて共有メモリではなくレジスタで削減を実行したりすることができます。さらに、このアプローチには、N個のマージされたスレッド間でスレッドグループの統一操作を自動的に償却する利点もあります。しかし、この方法によるレジスタの肥大化の可能性には注意する必要があります。

注:もし、あるワークロードの SM 占有率が、主にフルスクリーン・ピクセルシェーダとコンピュー トシェーダのどちらでレジスタカウントが制限されているかを知りたい場合は、次のようにしてください。

  • スクラバーで追加し… “Program Ranges” を実行し、調査したいシェーダプログラムの範囲を見つけます。Program Range を右クリックし、Range Profiler を起動し、Pipeline Overview Summary の “SM Occupancy” 値を確認します。
  • スクラバーの「Time(ms)」行をクリックして、Range内のいくつかのレンダーコールを選択します。タブを API Inspector に切り替え、ワークロードのサイクルのほとんどを占めるシェーダステージ(PS または CS)を選択し、シェーダ名の横にある「Stats」リンクをクリックします。
  • 「Shaders」Nsightウィンドウが表示され(下図14参照)、「Regs」列にシェーダのハードウェアレジスタ数が表示されます。シェーダの統計情報が入力されるまで数秒待つ必要があることに注意してください。
  • CUDA Occupancy Calculator グラフを使用して、このレジスタカウントに関連する Max Theoretical Occupancy を調べ、このシェーダの Range Profiler が報告する実際の 「SM Occupancy」 と比較します。
  • もし、達成した占有率が最大占有率よりずっと低ければ、SMの占有率はスレッドごとのレジスタの量だけでなく、他の何かによって制限されていることが分かります。

あるシェーダに割り当てられるレジスタの総数を減らすには、DX シェーダのアセンブリを見て、シェーダの各ブランチで使用されるレジスタの数を調べればよいです。ハードウェアは、最もレジスタを必要とするブランチに対してレジスタを割り当てる必要があり、そのブランチをスキップするワープは、最適ではない SM 占有率で実行されます。

フルスクリーンパス(ピクセルまたはコンピュートシェーダ)の場合、この問題に対処する典型的な方法は、ピクセルを異なる領域に分類するプリパスを実行し、各領域に対して異なるシェーダの並べ替えを実行することです。

  • コンピュートシェーダについては、このSIGGRAPH 2016のプレゼンテーションでは、シェーダの順列ごとにスレッドブロックの数を変えながら、画面上の異なるタイルに特殊なコンピュートシェーダを適用するためにDispatchIndirectコールを使用したソリューションが説明されています。
    “Deferred Lighting in Uncharted 4” – Ramy El Garawany (Naughty Dog).
  • ピクセルシェーダについては、異なる特殊化アプローチを使用することができます。フルスクリーンのステンシルバッファをプリパスで満たし、ピクセルを分類することができます。それから、ピクセルシェーダ実行の前に起こるステンシルテストに依存し(これは我々のドライバによって自動的に行われるべきです)、現在のシェーダの順列が触れていないピクセルを破棄するステンシルテストを使用することによって、複数の描画コールを効率的に実行することができます。このGDC 2013のプレゼンテーションでは、このアプローチでMSAA遅延レンダリングを最適化します。”The Rendering Technologies of Crysis 3″ – Tiago Sousa, Carsten Wenzel, Chris Raine (Crytek).

最後に、あるコンピュートシェーダのSM占有率制限をよりよく理解するために、我々のCUDA Occupancy Calculatorスプレッドシートを利用することができます。使用するには、GPU の CUDA Compute Capability とシェーダのリソース使用量(スレッドグループサイズ、Shader View からのレジスタ数、共有メモリサイズ(バイト))を記入するだけです。

アプローチ2:SM発行ストール待ち時間を減らす

SM占有率を上げる以外の方法でSM Throughput For Active Cyclesを上げるには、SM issue-stall cyclesの回数を減らすことです。これは、命令発行サイクル間のSMアクティブサイクルで、オペランドの1つがレディでない、または命令を実行する必要があるデータパス上のリソース競合が原因で、ワープ命令がストールしている間です。

PerfWorksのメトリクスsmsp__warp_cycles_per_issue_stall_{reason}は、ワープが{reason}で停止した命令イシュー間の平均サイクルを示しています。PerfWorksメトリックsmsp__warp_stall_{reason}_pctは、サイクルごとにその理由で停止したアクティブなワープの%です。これらのメトリックは、Range ProfilerのUser Metricsセクション、およびSM Overview Summaryセクションで、降順にソートされて公開されています。ワープが停止する理由としては、以下のようなものが考えられます。

  1. “smsp__warp_stall_long_scoreboard_pct” PerfWorks メトリクスは、L1TEX (local, global, surface, tex) オペレーションのスコアボード依存を待つためにストールしたアクティブワープのパーセンテージを示します。
  2. “smsp__warp_stall_barrier_pct” PerfWorks指標は、スレッドグループのバリアで兄弟ワープを待つためにストールしたアクティブワープのパーセンテージを示します。この場合、スレッドグループのサイズを下げるとパフォーマンスが向上する場合があります。これは、各スレッドが複数の入力要素を処理するようにすることで実現できる場合があります。

“sm__issue_active_per_active_cycle_sol_pct” が 80% より低く、 “smsp__warp_stall_long_scoreboard_pct” がワープストールの理由のトップなら、そのシェーダは TEX-latency limited であることが分かっているはずです。これは、失速サイクルのほとんどが、テクスチャフェッチ結果との依存関係 から来ていることを意味します。この場合は…

  1. シェーダのコンパイル時に反復回数がわかるループがある場合(ループ回数ごとに異なるシェーダの並べ替えを使用する場合もある)、HLSL の [unroll] ループ属性を使用して FXC にループを完全に展開させるようにしてみてください。
  2. シェーダが完全にアンロールできない動的ループ(たとえば、レイマーチ ングループ)を実行している場合は、テクスチャフェッチ命令をバッチして、TEX 依存のストールの数を減らしてください(HLSL レベルで、 独立したテクスチャフェッチを 2~4 のback-to-back命令のバッチに まとめることによって)。
  3. シェーダが与えられたテクスチャのピクセルごとにすべての MSAA サブサンプルを反復している場合、そのテクスチャの TEX 命令の 1 バッチで、すべてのサブサンプルを一緒にフェッチします。MSAA サブサンプルは VRAM 内で隣り合わせに保存されるため、一緒にフェッチすれば TEX ヒット率は最大になります。
  4. テクスチャの負荷が、ほとんどの場合、真になると予想される条件に基づいている場合(例えば、if (idx < maxidx) loadData(idx))、負荷を強制して座標をクランプすることを検討する(loadData(min(idx,maxidx-1)))。
  5. TEXキャッシュとL2キャッシュのヒット率を向上させることで、TEXレイテンシーを減少させてみてください。TEX および L2 ヒット率は、サンプリングパターンを微調整して隣接ピクセル/スレッドがより多くの隣接テクセルをフェッチするようにし、該当する場合はミップマップを使用し、さらにテクスチャのサイズを小さくしてよりコンパクトなテクスチャフォーマットを使用することによって向上させることが可能です。
  6. 実行されるTEX命令の数を減らしてみてください(おそらく、TEX命令述語としてコンパイルされる、テクスチャ命令ごとのブランチを使用します、例としてFXAA 3.11 HLSLを参照してください、例.”if(!doneN) lumaEndN = FxaaLuma(…);”).

トップSOLユニットがSMではない場合

どのユニットかを確認して,下記に従って対処を行います。

(1)トップSOLユニットが,TEX, L2, あるいは VRAMの場合

トップ SOL ユニットが SM ではなく、メモリサブシステム ユニット(TEX-L1、L2、および VRAM)の 1 つである場合、性能低下の根本原因は、GPUフレンドリーではないアクセスパターン(通常、ワープ内の隣接スレッドが遠く離れたメモリにアクセスする)により発生した TEX または L2 キャッシュのスラッシングである可能性があります。この場合、最上位制限ユニットは TEX または L2 であるかもしれませんが、根本的な原因は SM によって実行されるシェーダにある可能性があるため、最上位 SOL ユニットが SM の場合を使用して SM のパフォーマンスをトリアージ方法で決定する価値があります。

トップ SOL ユニットが VRAM であり、その SOL% 値が悪くない(60% 以上)場合、このワークロードは VRAM スループットが制限されており、別のパスでマージすることでフレームが高速化されるはずです。典型的な例は、ガンマ補正パスと他のポストプロセッシングパスをマージすることです。

(2)トップSOLユニットがCROPあるいはZROPの場合

CROP がトップ SOL ユニットである場合、より小さいレンダーターゲット形式(例:RGBA16F の代わりに R11G11B10F)を使用してみたり、Multiple Render Targets を使用している場合、レンダーターゲットの数を減らしてみたりすることができます。また、ピクセルシェーダでより積極的にピクセルをキルすることは価値があるかもしれません(たとえば、特定の透明効果では、不透明度が1%未満のピクセルを破棄する)。透明レンダリングを最適化するための可能な戦略については、「透明(または半透明)レンダリング」を参照してください。

ZROP が Top SOL ユニットである場合、より小さい深度フォーマット(例:シャドウマップ の D24X8 の代わりに D16、または D32S8 の代わりに D24S8)を使用し、さらに不透明オブジェクトを前から後ろへの順番に描いて、ZCULL(粗視化深度テスト)が ZROP とピクセルシェーダを起動する前に多くのピクセルが廃棄できるようにするとよいでしょう。

(3)トップSOLユニットがPDの場合

前述したように、PDは入力された頂点のインデックスを収集するために、インデックスバッファのロードを行います。PDがトップSOLユニットである場合、32ビットではなく16ビットのインデックスバッファを使用してみることができます。それでも PD の SOL%が増加しない場合は、頂点の再利用とローカリティのためにジオメトリを最適化することを試してみることができます。

(4)トップSOLユニットがVAFの場合

この場合、頂点シェーダーの入力アトリビュートの数を減らすと効果的です。
また、位置ストリームを他のアトリビュートから分離することは、z-only またはシャドウマップレンダリングに有効な場合があります。


頂点アトリビュートを減らす

前述した,P3 Methodでも出てきますが,頂点アトリビュートを減らすと高速になるケースがあります。プラットフォームによってはドキュメントに「XXX以下にすると速くなります」みたいな記述が載っていたり,実際にどのぐらい数でどういう結果が得られたのかをグラフ化してくれていたりします。
ここで重要なのは,なるべくパッキングして数を減らすということです。例えば,下記のようにします。

//変更前
struct VSOutput
{
    float4 Position : SV_POSITION;
    float2 TexCoord0 : TEXCOORD0;
    float2 TexCoord1 : TEXCOORD1;
    float2 TexCoord2 : TEXCOORD2;
    float2 TexCoord3 : TEXCOORD3;
};

// 変更後
struct VSOutput
{
    float4 Position : SV_POSITION;
    float4 TexCoord01 : TEXCOORD01; // TEXCOORD0 and TEXCOORD1
    float4 TexCoord23 : TEXCOORD23; // TEXCOORD2 and TEXCOORD3
};

データ容量は変わっていないですが,これだけで高速化します。過去の経験ではモデル描画のシェーダに対して適用したところ全体で1[ms]程度の高速化が出来た記憶があります。とにかくピクセルシェーダで使っていないアトリビュートがあったら,削る。削れないときはまとめることによって数を減らすということを考えると良いです。


サイズを減らす

計算等に利用するテクスチャサイズを減らすことによってテクスチャキャッシュに載りやすくし,高速化するテクニックがあります。例えば,Deinterleaved RenderingCheckerboard Renderingといったものがこれに当たります。特に縮小バッファにして描画品質があまりにも落ちすぎるのを避けたい場合などに使うと良いです。


テクスチャフェッチをバッチする

NVIDIAのBlog記事にもありますが,レイマーチループのような動的ループ内でテクスチャフェッチをバッチかすることによってTEXのレイテンシーを隠すことができます。
典型的なSSRレイマーチングループのHLSLは次のような感じです。

float MinHitT = 1.0;
float RayT = Jitter * Step + Step;

[loop] for ( int i = 0; i < NumSteps; i++ )
{
     float3 RayUVZ = RayStartUVZ + RaySpanUVZ * RayT;
     float SampleDepth = Texture.SampleLevel( Sampler, RayUVZ.xy, GetMipLevel(i) ).r;

     float HitT = GetRayHitT(RayT, RayUVZ, SampleDepth, Tolerance);
     [branch] if (HitT < 1.0)
     {
          MinHitT = HitT;
          break;
     }

     RayT += Step;
}

ループ内で,隣り合わせで2回テクスチャフェッチするように処理を置き換えます。

float MinHitT = 1.0;
float RayT = Jitter * Step + Step;
[loop] for ( int i = 0; i < NumSteps; i += 2 )
{
     float RayT_0 = RayT;
     float RayT_1 = RayT + Step;

     float3 RayUVZ_0 = RayStartUVZ + RaySpanUVZ * RayT_0;
     float3 RayUVZ_1 = RayStartUVZ + RaySpanUVZ * RayT_1;

     // batch texture instructions to better hide their latencies
     float SampleDepth_0 = Texture.SampleLevel( Sampler, RayUVZ_0.xy, GetMipLevel(i+0) ).r;
     float SampleDepth_1 = Texture.SampleLevel( Sampler, RayUVZ_1.xy, GetMipLevel(i+1) ).r;

     float HitT_0 = GetRayHitT(RayT_0, RayUVZ_0, SampleDepth_0, Tolerance);
     float HitT_1 = GetRayHitT(RayT_1, RayUVZ_1, SampleDepth_1, Tolerance);

     [branch] if (HitT_0 < 1.0 || HitT_1 < 1.0)
     {
          MinHitT = min(HitT_0, HitT_1);
          break;
     }

     RayT += Step * 2.0;
}

ブログ記事では,GTX1080で0.54[ms]だったものが0.42[ms]に高速化したと記載されています。また2回ではなく4回にすることで0.54[ms]から0.33[ms]になるという記述もあります。レイマーチ処理は基本的に重くなりやすいので,アルゴリズム的に最適化出来ない場合は,こうしたバッチ化により高速化するとよさそうです。

超雑訳 A Survey of Temporal Antialiasing Techniques

どーも。Pocolです
今日は
[Yang 2020] Lei Yang, Shiqiu Liu, Marco Salvi, “A Survey of Temporal Antialiasing Techniques”, Computer Graphics Forum, Vol. 39, No.2, pp.607-621, 2020.
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘していただけると幸いです。
(さらに…)

超雑訳 The Process of Creating Volumetric-based Materials in Uncharted 4

こんばんみん。Pocolです。
今日は,
[Jiang 2016] Yibing Jiang, “The Process of Creating Volumetric-based Materials in Uncharted 4”, SIGGRAPH 2016 Advances in Real-Time Rendering in Games course.
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘していただけると幸いです。
尚,図は[Jiang 2016]より引用しています。

(さらに…)

超雑訳 Physically Based Hair Shading in Unreal

こんにちわ。Pocolです。
今日は,
[Karis 2016] Brian Karis, “Physically Based Hair Shading in Unreal”, SIGGRAPH 2016 Course: Physically Based Shading in Theory and Practice.
のスライドを読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘していただけると幸いです。
尚,図は[Karis 2016]より引用しています。
(さらに…)

超雑訳 Single-Pass Stable Cascaded Bounding Box Shadow Maps

こんにちわ。Pocolです。
今日は,
[Anderson 2009] Johan Andersson, Daniel Johansson, “Shadows & Decals:D3D10 techniques from Frostbite”, GDC 2009
のシャドウマップに関するセクション部分を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。


※図は,[Anderson 2009]より引用

私たちのシャドウマップレンダリング技術は、実際には複数の技術を組み合わせて拡張したもので、私はこれを…と呼んでいます。なぜかというと私が長いタイトルが好きなので。


※図は,[Anderson 2009]より引用

ここでは、シャドウマップの部分について、高レベルの概要を説明します。
まず最初に、カスケードシャドウマップの基本的な部分を説明します。
次に、DX10 でシャドウマップを効率的にレンダリングする方法について詳しく説明します。
また、スタビライゼーションを使用してフリッカリングを低減する方法と、それをシーンレンダリングと組み合わせてシャドウマップを最大限に活用する方法について説明します。

セッションの最後には、両方のパートについて共通のQ&Aを行います。


※図は,[Anderson 2009]より引用

カスケードシャドウマップとは何かを簡単に説明します。

最近の多くのゲームでは、太陽からの完全にダイナミックな影を、かなり大きな影の表示距離と多くのオブジェクトで表現したいと考えています。すべてのオブジェクトをカプセル化する単一のシャドウマップを使用すると、低解像度のシャドウになり、利用可能な複数の透視投影技術は、より一般的なシーンで動作させるのが難しく、フリッカリングに悩まされます。

単純なアプローチは、代わりにビューフラスタムを複数のスライスに分割し、各スライスに対して個別のシャドウマップを生成することです。これにより、シャドウマップの解像度がより良く分散され、調整や管理が容易になります。

カスケードシャドウマップの重要な部分は、スライス分割平面をどこに保持するかを選択することで、シャドウマップがどのように分散され、どれだけ高い効果的な解像度が得られるかが決まります。


※図は,[Anderson 2009]より引用

分割した平面をどこに配置すべきかを選択するために、実際によく機能することがわかった1つの方法は、論理的に名付けられた平行分割シャドウマップ論文の「実用分割スキーム」です。

これは、線形分布と対数分布の間の補間に基づいていますが、どちらも個別にはそれほどうまく機能しません。しかし、それらの間の単純なブレンドを得ると、レンダリングしているゲームやカメラシーンの種類に応じて、それを調整することが非常に簡単になります。

ここではweight=0.8を使用しています(経験的に)。


※図は,[Anderson 2009]より引用

個々のシャドウマップがカスケードされたシャドウマップで典型的にレンダリングされる方法についての続きです。

緑は1つのスライスと交差しているオブジェクト
黄色は2スライス
赤は3スライス

オブジェクト数やスライス数が多いほど、レンダリングは重くなります。

シャドウビューの距離を長くしたり、影を落とすオブジェクトの数を増やしたりすると、この問題がどんどん大きくなっていきました。


※図は,[Anderson 2009]より引用

もちろん、これはCPUとGPUの両方の問題です。

PCでは、主にCPUのオーバーヘッドを減らすことに関心がありました。DX10では、追加のドローコールやステートの設定によるオーバーヘッドがまだかなり大きいためです。また、シャドウキャストオブジェクトの数を増やし、より高品質なシャドウを得るためにスライス数を増やし、同時にシャドウの表示距離を長くしたいと考えました。


※図は,[Anderson 2009]より引用

DX10 パスでは、シャドウマップレンダリングの CPU オーバーヘッドを軽減するために、シャドウのドローコールをスライスごとに発行するのではなく、複数のシャドウマップスライスに同時にレンダリングする方法を開発しました。

これは、シャドウマップスライスを個別のテクスチャではなくテクスチャアレイに格納し、深度ステンシルアレイビューにレンダリングするとともに、特別なSV_RenderTargetArrayIndex出力を使用して、ドローコールの各プリミティブがどのスライスにレンダリングされるべきかを選択するジオメトリシェーダで行います。

これにより、複数のスライスと交差するオブジェクトのための余分なCPUレンダリングコストが発生しません。

この実装には複数の方法がありますが、ここでは素朴な実装と効率的な実装の例を示し、どちらを使用するかはご自身で選択してください。

しかし、実装方法の詳細を説明する前に、コアとなる要件をもう少し詳しく見てみたいと思います。


※図は,[Anderson 2009]より引用

DX10のテクスチャ配列の問題点として、HW-PCFフィルタリングのためのSampleCmp関数が10.1(およびそれ以上)でしか動作しないことが挙げられます。そのため10.0では、予備としてシェーダ内で手動のPCFフィルタリングを行うことができます。


※図は,[Anderson 2009]より引用

シングルパスシャドウマップレンダリング技術のもう一つの重要な要件は、ジオメトリシェーダの出力値SV_RenderTargetArrayIndexを使用することです。これは、プリミティブがラスタライズされるべきテクスチャ配列のスライスを指定するために、GSからプリミティブと一緒に出力されるシンプルなインデックスで、D3D10のすべてのバージョンで利用可能です。


※図は,[Anderson 2009]より引用

ここでは、ナイーブな実装をご紹介します。

このシェーダはまず、サポートするスライスの最大数を固定し、それぞれにviewProjection行列を定義します。

複数のビュー射影行列を持つ理由は、各スライスが独自のビューフラスタムを持つため、頂点シェーダがクリップ空間の位置ではなくワールド空間の位置を出力するためです。

このシェーダの出力は、クリップ空間の位置、ピクセルシェーダに渡す必要のあるその他のデータ、そしてプリミティブをどのスライスにレンダリングするかを知るためにSV_RenderTargetArrayIndexを使用するこのスライスインデックスです。

シェーダから出力する頂点の最大数を指定する必要がありますが、この場合はかなり大きくなります。

次に、レンダリングしているオブジェクトが交差している各スライスを調べ、最適化のために各オブジェクトの最初と最後のスライスインデックスをアップロードすることができます。そして、スライス内の各頂点をスライスのviewProjection行列で単純に変換して出力します。


※図は,[Anderson 2009]より引用

この実装の主な利点は、まさに私たちが求めていたもので、各オブジェクトが何枚のスライスと交差していても、1回のドローコールで済むため、CPUのレンダリングオーバーヘッドを削減することができます。

しかし、いくつかの欠点もあります。
– GSには高いmaxvertexcountアノテーションがあり、これはGSが重いデータ増幅を行うように最適化されていないため、レンダリングが非常に重くなります。
– また、同じオブジェクトの複数のコピーを異なるトランスフォームでレンダリングするようなインスタンス化には完全に対応していません。各オブジェクトは異なるスライスと交差することができるため、ドローコールごとに最初と最後のスライスインデックス定数をアップロードする必要があります。
– また、シェーダにハードコードされた固定のスライス数は、非効率的であり、効率的な管理をするのは困難です。


※図は,[Anderson 2009]より引用

これらの欠点を改善するために、私たちはGSデータの増幅ではなく、インスタンス化に基づいた別の方法を導入しました。

つまり、複数のスライスと交差するオブジェクトに対して、交差するスライスごとにオブジェクトのダイナミックなインスタンスを追加でレンダリングするのです。これは、ダイナミックな「sliceIndex」を、例えばワールドトランスフォームやカラーなどの値と一緒に、追加のダイナミックなインスタンスデータとして保存することで、このインスタンス化を、すでにうまくいけば行っている通常のインスタンス化と組み合わせることができるからです。

この方法では、GSはよりシンプルで効率的になり、どの出力スライスにレンダリングするかを選択するためだけに使用されます。


※図は,[Anderson 2009]より引用


※図は,[Anderson 2009]より引用

このテクニックの主な利点は、通常のインスタンス化と一緒に機能することで、交差するシャドウのインスタンスごとに1回ではなく、固有のシャドウオブジェクトタイプごとに1回のドローコールを行うことができることです。いくつかのヘビーなシーンでは、インスタンス化のみを使用した場合と比較して、ドローコールの回数が40%、従来のシャドウマップレンダリングと比較して90%削減されました。

また、この手法では、サポートするスライスの数がハードコードされていないため、シーンやエリアによって任意に変更することができます。

ただし、この手法の欠点は、GPUシャドウレンダリングの時間に若干の影響があることです。Radeonカードではわずか1%程度でしたが、Geforceでは5%程度遅くなっているようです。ただし、今回テストしたドライバーでは、パフォーマンス測定がそれほど安定していなかったので、それほど心配はしていません。ドライバーはまだこの使用シナリオにあまり最適化されていないのでしょう。


※図は,[Anderson 2009]より引用

さて、別の話題に移ります。多くのゲームで影に関する一般的な問題は、カメラや光源を動かしたときに発生する典型的なフリッカーやエイリアシングのアーティファクトです。

これは高品質のシャドウマップフィルタリングで解決できますが、通常は非常に広いフィルタを必要とするため、GPUに大きな負担がかかります。

しかし、いくつかの制限に耐えることができれば、ほとんどのゲームで最も重要な原因の1つである、プレイヤーがビューを移動または回転させたときの静的ジオメトリからのフリッカリングを修正することができます。

ここでは、BFBCのシャドウフリッカリングの例を紹介します。プロジェクターで見やすくするために、シャドウマップの解像度を1024ではなく512に下げています。


※図は,[Anderson 2009]より引用


※図は,[Anderson 2009]より引用


※図は,[Anderson 2009]より引用

そこで、安定してちらつかないシャドウを作成する方法として、カスケード接続された各シャドウマップスライスに正射影を使用し、これらをシーンに依存せず、固定サイズにすることにしました。

シーンに依存しないことは完全には必要ではありませんが、管理が非常に容易になります。また、一般的には、特殊なケースやシャドウキャスターとレシーバーがどこにあるかに関わらず、シーン全体で一定のシャドウ品質を求めます。

固定サイズは、カメラの回転に伴って発生するライトスペースのスケーリングを排除するために重要であり、以前は各スライスフラスタムに対して最適なライト空間バウンディングボックスを取得しようとしていました。

これらの固定サイズのスタビライズされたシャドウビューは、最悪のケースを表現できる必要があるため、ベストフィット法よりも大きくなります。これには重要な意味がありますが、後で説明します。


※図は,[Anderson 2009]より引用

各スライスのライト空間スケーリングが同一であることを確認した後に必要なもう 1 つのステップは、シャドウマップビューのライト空間変換を偶数テクセル増分に丸めることです。これにより、サブピクセルオフセットのためにカメラの移動に伴ってオブジェクトのラスタライズが変更されるのを防ぐことができます。

これらの安定化手順を実行しても、FOVの変更時や太陽の回転時に静的ジオメトリの影がちらつくことがありますが、多くのゲームでは、単に変更しないことで回避できます。

高品質のシャドウマップフィルタリングであっても、シャドウを安定させることは、最も一般的なちらつきの原因のいくつかを取り除くことになるので、良いアイデアであると言えます。


※図は,[Anderson 2009]より引用

シーンをレンダリングする際には、各ピクセルに対して、そのピクセルがある領域をカバーする適切なシャドウマップスライスをサンプリングする必要があります。

これには複数の方法がありますが、最も簡単な方法の1つは、ビューフラスタムをより小さなフラスタムに分割するスライス平面を使用することで、それぞれにシャドウマップを生成します。
シェーダは、ビューポートZを使ってスライスシャドウマップを選択するか、クリップ平面を使ってシーンをレンダリングするかを選択できます。しかし、この方法はスタビライズ処理とは完全に互換性がなく、シャドウマップビューがスライスフラスタム上のベストフィットを使用するので、影がちらつくことになります。

より良い結果が得られ、スタビリゼーションと互換性のある別の方法は、各シャドウマップスライスに境界球の比較を使用することです。これは『Killzone2』で使用されており、見た目は素晴らしいですが、バウンディングスフィアを正射影フラスタム(実際にはバウンディングボックスに過ぎない)内にフィットさせようとしているため、バウンディングスフィア間に若干のオーバーラップが発生します。この重なりにより、各スライスで得られる効果的な解像度が低下します。

BFBC/Frostbiteで使用しているこの第3の方法は、シャドウスライスビューのバウンディングボックスを直接使用するもので、どのスライスを使用するかを正確に決定するために複数のピクセルごとのバウンディングボックステストを行います。これはバウンディングスフィア法ほどオーバーラップしないので、より高い効果的な解像度が得られますが、シェーダーがより複雑になるという代償を伴います。

バウンディング・スフィアはバウンディング・ボックスとほぼ同じ範囲ですが、バウンディング・ボックス法に比べてスライス内の解像度が低くなります。

バウンディングボックスは、バウンディングボックスが拡大され、より多くの領域をカバーするため、スタビライゼーションとの相性が特に良いです。フィット感と解像度の利用率が格段に向上します。


※図は,[Anderson 2009]より引用



※図は,[Anderson 2009]より引用


※図は,[Anderson 2009]より引用

パフォーマンスのためテクスチャ座標は[-0.5, +0.5]の範囲内です。


※図は,[Anderson 2009]より引用

この部分の話の締めくくりとして:

シャドウマップ・ビューの移動と回転を安定させることで、静的なジオメトリからのフリッカーを低減します。

これには、シャドウマップビューを拡大するという副次効果がありますが、これは、シェーダのバウンディングボックステストを使用して、どのシャドウマップスライスを使用するかをピクセルごとに選択することで、有利になります。このバウンディングボックステストにより、長方形のシャドウマップの利用率が最大化され、より高い実効解像度と長い実効シャドウビュー 距離が得られます。

シャドウマップレンダリングの CPU コストを削減するために、シャドウマップテクスチャ配列に直接レンダリングし、インスタンス化と GS を併用することで、シャドウキャストオブジェクトの数にかかわらず CPU のレンダリングコストを一定にし、GPU コストを小さくしています。

なねぃと調査

こんばんわ。Pocolです。
もうすぐSIGGRAPHですね。SIGGRAPHのコースで「なねぃと」についての話があるそうで,そのコースを受ける前にある程度は調査しておこうと思いました。Sさん、問題あればご連絡を。

個人的に知りたいこと

今回の調査では下記のようなことを知りたいなと思ったので,調査してみました。

  • どんなレンダリングフローなのか?
  • 実際にどんなシェーダ使っているの?
  • どうやってストリーミングするものを決めてるの?
  • ストリーミングデータはどうやって作るのか?

大雑把な流れ

なねぃとは仮想化マイクロポリゴンジオメトリシステムです。いわゆるVirtual Textureみたいなテクスチャストリーミングのメッシュ版という感じのやつです。細かいポリゴンを扱えるのが売りになっていて,ものすごくいい感じのディテールが表現できます。
なねぃとを実現するためのキーだと思っているものは次の通りです。

  • GPU駆動描画
  • 高速なソフトウェアラスタライズ
  • Deferred Material(Visibility Buffer)
  • トライアングルデータの圧縮
  • 階層LODの構築

まずGPU駆動描画はその名の通り,GPU上で描画するかどうかを判定を行い,その結果で描画が駆動する手法のことを言ったりします。これはカプコンさんだったり,アサクリだったり,Trialsだったりと色々な会社さんがすでに取り組まれています。内容について知らない方がいたら下記の資料などを読むと良いと思います。

UE5はバウンディングのスケールに応じてソフトウェアラスタライズとハードウェアラスタライズの分岐がコンピュートシェーダ上で決定されます。
小さな三角形はコンピュートシェーダを用いたソフトウェアラスタライズが実行され,大きな三角形に対してハードウェアラスタライザが実行されます。
ソフトウェアラスタライザですが,小さな三角形に対しては平均で3倍高速化したと「Nanite | Inside Unreal」の動画でKarisが言っていました。恐るべき速度ですね。

※図は”Nainte | Inside Unreal”より引用

あとは,この高速なソフトウェアラスタライズを支える技術として,Deferred Materialを使用しています。いわゆるVisibility Bufferというやつです。
これの何が良いかというと,深度とマテリアルインデックスをバッファに出力してしまい,マテリアル評価を遅延実行できるというメリットがあります。つまり,いちいちシェーダの切り替えをしなくて良いということです。これによりUE5は不透明物体の描画を1ドローで実現しています。

※図は”Nainte | Inside Unreal”より引用

さらにNaniteを見ていてがんばっているなーと思うのがデータの圧縮です。1トライアングルにつき平均14.4Byteだそうです。一応自分でもソースコードを追ってみて,確かにそうだなということを確認しました。

※図は”Nainte | Inside Unreal”より引用

どんなレンダリングフローなのか?

まずは,レンダラーの流れをつかみ大雑把にどんなことをやっているのかを把握してみました。
レンダラーの実装は,Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cppにあります。これがディファードレンダリングの実装になっており,Render()メソッドに実装があるので,これを地味に読み解いていきました。なんとこの関数1600行ほどあります。

この関数をところどころを端折った疑似コードが下記のような感じになります。

ざっくりですが,処理をまとめておくと

  • グローバルリソースを更新
  • 非同期でファイル読み込みを開始
  • 読み込みタスクの完了待ちをして,GPUにデータを転送
  • Visibility Bufferをクリア
  • ビュー情報をパッキングしてカリングとラスタライズを実行
  • 深度バッファに出力
  • マテリアルごとにドローコールを発行して,タイル描画を行うことによりG-Bufferを構築

…という感じです。

実際にどんなシェーダ使っているの?

CullRasterize()

下記のシェーダが実行されるようです。

  • InstanceCull :パス Engine/Private/Nanite/InstanceCulling.usf
  • InitArgs :パス Engine/Private/Nanite/ClusterCulling.usf
  • InstanceCullVSM :パス Engine/Private/Nanite/InstanceCulling.usf
  • PersistentClusterCull : パス Engine/Private/Nanite/ClusterCulling.usf
  • CalculateSafeRasterizerArgs : パス Engine/Private/Nanite/ClusterCulling.usf
  • HWRasterizerVS : パス Engine/Private/Nanite/Rasterizer.usf
  • HWRasterizerPS : パス Engine/Private/Nanite/Rasterizer.usf
  • MicropolyRasterize : パス Engine/Private/Nanite/Rasterizer.usf

左側は実行される関数で,右側はその関数が実装されているファイルパスを表します。
PersistentCull()がおそらく一番巨大なシェーダコードになると思うのですが,クラスタ階層のトラバーサルとかカリング,あとはソフトウェアラスタライズかハードウェアラスタライズかどうかの切り分けなんかも行っています。
MicroPolyRasterize()がソフトウェアラスタライズを行っているのですが,R32G32のバッファに書き込みをします。Rチャンネルの25bit分がVisibleIndexで,残りの7bitがTriangleIDになっています。GチャンネルはDepthに割り当てられているようです。

EmitDepthTargets()

下記のシェーダが実行されるようです。

  • DepthExport : パス Engine/Private/Nanite/DepthExport.usf
  • EmitSceneDepthStencilPS : パス Engine/Private/Nanite/ExportGBuffer.usf
  • EmitSceneSStencilPS : パス Engine/Private/Nanite/ExportGBuffer.usf

名前通りの処理っぽいです。

DrawBasePass()

下記のシェーダが実行されるようです。

  • ClassifyMaterials : パス Engine/Private/Nanite/MaterialCulling.usf
  • FullScreenVS : パス Engine/Private/Nanite/ExportGBuffer.usf

ClassifyMaterials()の中では,VisibleなMaterialとMaterial Rangeを決定するようです。Material Rangeにはマテリアルのマスクビットが格納されています。
大雑把な理解として,描画対象となるマテリアルの矩形範囲を決めているようです。
FullScreenVS()では,決定したマテリアルの矩形範囲にあるタイルで,本当にそのマテリアルの描画必要かどうかを判定して,要らないところはNaNを頂点シェーダで設定して,タイルをカリングするという処理を行うようです。タイルカリングには5つのモードがあるようです。

ストリーミングの仕組みは?

ストリーミングの管理はFStreamingManagerというクラスで管理されているようです。
Engine/Source/Runtime/Engine/Public/Rendering/NaniteStreamingManager.hにクラスの宣言があります。
・クラスタページデータ
・クラスタページヘッダ
・クラスタ修正更新バッファ
・ストリーミングリクエストバッファ
・ストリーミングリクエストリードバックバッファ
・ペンディングページ
・リクエストハッシュテーブル
などを保持しているようですが,まだ理解しきれていないので,理解できるようになったら別の記事として書くことにします。
ページリクエストは,PersistentClusterCull()というシェーダが実行されてRequestPageRange()という関数内でリクエストが書き込まれるようです。
このシェーダで書き込んだページリクエストの取得はディファードレンダラー内のAsyncUpdate()内でバッファをmapすることでCPU側で取得されるようです。
リクエストハッシュテーブルにGPUから取得したものを登録して,被るものがあるかどうかをチェック。チェックにヒットしたらストリーミングするページとしてプッシュし,優先度でソートして,LRUを更新しています。
一方,検索にヒットしない場合は,優先度付きリクエストヒープにいったんプッシュするようです。その後、このヒープからポップしてストリーミングするページを選択しています。
ストリーミングするものは読み込みされている状態なので,送ればいいのですが,これから読み込みしないといけないペンディング状態のものについては,ペンディング状態のページを収集し,ランタイムリソースIDがコンピュートシェーダで書き込まれるので,これを利用してFbyteBulkDataを取得するようです。取得したデータをFIORequestTaskに登録し,ParallelFor文を用いて,ファイルの非同期読み込みが実行さるようです。
ストリーミングマネージャのEndAsyncUpdate()という関数で,読み込み完了待ちをしてからResourceUplodTo()関数を使ってGPUに転送しているみたいです。

ストリーミングデータはどうやってつくるのか?

メッシュデータの構築

FStaticMeshBuilder::Build()経由でBuildNaniteFromHiResourceModel()が呼び出されて,データが作成されるようです。
BuildDAG(), ReduceDAG(), FindDAGCut()などの内部の処理がまだ全然理解できていないので,鋭意調査中です。

エンコード

ジオメトリデータはEncodeGeometryData()という関数でエンコードされるようです。
Positionデータは63bitに。
法線ベクトルはOctrahedronで表現し,XYで18bitに。
テクスチャ座標はXYで32bitに。
ここまで合計14byteになるので,確かにKarisが言っていた平均14.4Bという数字は納得できます。
ちなみに頂点カラーやUV数を増やす場合はさらにデータ容量が増えていく感じです。

おわりに

今回は,えらくざっくりですが,どんな感じで動くのかについて調査しました。
レンダリングフローについては理解できたのですが,実際のストリーミングの詳しい処理内容や,階層LODの作成方法など,まだまだわからない箇所もあるので次回調査してみたいと思います。

資料:THE LAST OF US PARTⅡ

実装用の資料として,THE LAST OF US PARTⅡのスクショを張っておきます。動画も上げようと思ったのですが,サイズが大きくて上げられませんでした。

The Last of Us® Part II_20210124181203
The Last of Us® Part II_20210124181223
The Last of Us® Part II_20210124181237
The Last of Us® Part II_20210124181253
The Last of Us® Part II_20210124181308
The Last of Us® Part II_20210124181326
The Last of Us® Part II_20210124181344
The Last of Us® Part II_20210124181413
The Last of Us® Part II_20210124181429
The Last of Us® Part II_20210124181444
The Last of Us® Part II_20210324214828
The Last of Us® Part II_20210324214847
The Last of Us® Part II_20210324214923
The Last of Us® Part II_20210324215312
The Last of Us® Part II_20210324215326
The Last of Us® Part II_20210324215343
The Last of Us® Part II_20210324215354
The Last of Us® Part II_20210324215403
The Last of Us® Part II_20210324215601
The Last of Us® Part II_20210324215609
The Last of Us® Part II_20210324215620
The Last of Us® Part II_20210324215919
The Last of Us® Part II_20210324215930
The Last of Us® Part II_20210324215941

超雑訳 A Journey Through Implementing Multiscattering BRDFs & Area Lights

こんにちわ、Pocolです。
今日はSIGGRAPH 2019 Advanced in Real-Time Rendering in Gamesのコースで発表された,
Stephne McAuley, “A Journey Through Implementing Multiscattering BRDFs & Area Lights”を読んでみようと思います。
図は引用しませんので,図についてはhttps://advances.realtimerendering.com/s2019/index.htmのページからスライドをダウンロードして参照してください。


Slide.1
ノートなし


Slide.2
ご紹介にあずかりましてありがとうございます。そして、皆さんおはようございます。

この話は、今年の2月にNatashaからI3Dでの講演を依頼された時のことです。ご存知ない方もいらっしゃるかもしれませんが、I3DはSIGGRAPHと同じようなコンピュータグラフィックスのカンファレンスで、インタラクティブレンダリングに焦点を当てており、学術関係者とゲーム業界の専門家が一堂に会しています。Natashaは、最初から最後まで研究してビデオゲームに仕上げるということがどういうことなのかを説明できるような講演ができたらいいと提案してくれました。当時は、実際に何か話すことがあるのかどうか、本当に自信がありませんでした。私が最後に出荷したゲームは1年以上前の『ファークライ5』でしたが、制作開始時にリサーチをする傾向があり、それはもう何年も前のことで、私の記憶からは消えてしまっていました。しかし、Natashaが尋ねてきたので、私はとにかくイエスと答え、かなり一般的な話のタイトルと説明をして、考え始めました。

それと同時に、私は多重散乱BRDFやエリアライトの研究をしていました。最初のうちはすぐに結果が出たのですが、数週間が経つにつれ、解決しなければならない問題がどんどん出てきました。そこで、一方では研究を製品に移すためにどうやって話をするかを考え、他方では研究を製品に移すために研究を実施していましたが、多くの困難にぶつかっていました… そしてしばらくして、ようやく2つと2つを合わせた結果、このような話になったのです。

ここでは、私がこれまでに行ってきた多重散乱BRDFやエリアライトの研究、直面してきた問題点、そしてその解決策について説明します。もちろん、未解決の問題もありますので、皆さんのご協力をお願いします。また、これには有用なビジュアルや、あなたのゲームにもこれらの機能を搭載したいかどうかの情報が含まれていることを期待しています。最後に、これを実装したときに学んだことを簡単にまとめておきます。


Slide.3
ファークライ5の最後に、私たちのマテリアルシステムが少し古くなってきていると感じていました。確かに、以前はかなり良かったのですが、2012年のディズニーのBRDFモデルをベースにしていたのですが、ディズニーのディフューズモデルではなく、Lambertianのディフューズを使用していました。多くの研究が出てきていたので、そろそろアップグレードをして、そこに何があるのか、そしてそれが私たちにどんなメリットをもたらすのかを見てみようと思ったのです。

アーティストの問題点もいくつか挙げられていました。一般的に、人々はまだより多くのスペキュラが見えることを望んでいましたし、もう一つの願いは、拡散のための少し柔らかいフォールオフでした。さらに、もちろん誰もがゲームをよりリアルに見せたいと思っていますし、より良いマテリアルととライティングはそのための重要な手段です。


Slide.4
ここ数年で最も興味を持った論文は、Eric Heitzらによるこの優れた論文でした。 多重散乱を真に私の目に留める最初の論文であり、私たちが標準的なスペキュラーモデルからどれだけのエネルギーを失っているのかを実感させてくれました。 これは私にとって啓示でした。私はこれまでエネルギー保存の啓蒙に多くの時間を費やしてきましたが、実際には多くのエネルギーを失うことに満足していたことに気付きました。

しかし、私はこの問題をどうすればいいのか全くわかりませんでした。この論文では、いくつかのかなりブルートフォースな解法が書かれていましたが、リアルタイムの近似値を見つけるための明確な道筋が示されているわけではありませんでした。

もちろん、最終的には誰かがそれをやろうとしていたのですが…。


Slide.5
そこでソニー・イメージワークスのクリス・クラとアレハンドロ・コントフの出番です。彼らは、エネルギーを維持するために設計された簡単なトリックによって、SIGGRAPH 2017 で多散乱スペキュラへの近似を発表しました。それはいくつかの簡単な数式と簡単に計算可能なLUTに減少したので、これは非常に多く成功したものに見えました。


Slide.6
かなりシンプルなアイデアです。失われたエネルギーを補うBRDFが必要だと述べています。


Slide.7
そこで、BRDFによって与えられた方向において反射されたエネルギーそして平均エネルギーに基づいて、フィットするBRDFを見つけます。これは、すべてうまく動作します…


Slide.8
…しかし、実際には、エネルギーはバウンスごとに(サーフェイスによって吸収され)失われるので、いくつかの失われたエネルギーが必要です。そこで、無限のバウンスの損失を合計して、次の式で多重散乱BRDFを計算します。ここでの\(F\)はもちろんフレネルであり、各バウンスで失われる平均エネルギーの何分の一かを与えてくれます。


Slide.9
しかし、これを実施する必要があるとしたら、何をすればいいのでしょうか?そうですね、3つのことを計算する必要があります。
与えられたサーフェイスラフネスについて1から与えらた方向におけるエネルギーを減算する,与えられたラフネス対する平均エネルギー,そして与えられたスペキュラーカラーについての平均フレネルの3つです。これには半球上での積分とフィットが必要になるので,Visual StudioとMathematicaを取り出しました。


Slide.10
最初に私は、反射エネルギーを差し引いたものを得るために、半球上についてBRDFを積分しなければなりませんでした。そして,これを角度の余弦とサーフェイスののラフネス(または、我々の場合ではスムースネス)でパラメータ化された2D LUTに格納します。


Slide.11
次に,与えれた方向において反射されたエネルギーを取る必要があり,与えられたラフネスに対して平均エネルギーを得るために半球上について積分します。そして,そのデータを Mathematica に取り込み,フィットを見つけました.


Slide.12
平均フレネルを得るためにShclickのフレネルの単純な解析的積分に投げて,次の関数とシェーダ上で我々が使用することができる参照テーブルで終わります…

この論文の実装は特に難しいものではありませんでしたが,Mathematica の知識とLUTを生成するためのC++コードを書く時間が必要でした.

それでは結果を観てみましょう!


Slide.13
ここからは球の列から始めます。左から右へとスムースネスが増していきます…


Slide.14
多重散乱スペキュラーをオンにして……そして……..何か違いがあると思いますか?実際には、前後に切り替えれば、大まかな球体に小さな変化が 見られますが、はっきりとは分かりません。これでは、私たちが望んでいたような大成功は得られません。

でも、あきらめる前に、金属で試してみましょう。


Slide.15
再度、金色の球体の多重散乱スペキュラーオフから始めます。


Slide.16
…そして、切り替えてみると、すごい違いがあります。本質的には、鏡面反射率が大きいほど エネルギーが少なくて済むので、 跳ね返りごとに失われ始め、 大きな効果が得られます。

実際、多重散乱スペキュラーに関する論文では、最も粗い金属を助けることに言及していますので、私はそれらにもっと注意を払うべきだったかもしれません。

だから、私たちが求めている全体的なイメージでは大成功にはならないかもしれません。典型的なファークライの「ポストカード」ショットには、誘電体である木や岩や土などの自然が多く含まれています。しかし、私たちは金属製の乗り物や武器を持っています。実際、私たちの担当アーティストはこの効果をとても気に入ってくれています。


Slide.17
多重散乱スペキュラーがうまく機能していたので、新しい拡散モデルを導入することにしました。


Slide.18
Lambertianディフューズはかなり貧弱なので、次のような改善が欲しかったです。

1. 多重散乱が考慮されている - 私たちは、単一散乱からエネルギーを失うことをスペキュラーから学んでいますが、これを拡散についても解決できるか?
2.サーフェイスのラフネスに反応する。これは、より興味深い材質を提供してくれるでしょうし、うまくいけば遠方でのディテールの保存性も向上します。例えば、岩や木の幹のような強い法線を持つ物体には問題があります。それらが距離を置くと、ミップマップは平坦になり、以前見たような強い拡散照明は見られなくなります。理想的には、ある種の見た目のフィルタリングを行いたいのですが、これは既存のスペキュラの見た目のフィルタリングとうまく調和するかもしれません。
3.法線の分布に反応する – 鏡面にGGX分布を使用している場合、拡散にも同じことができるか?
4.拡散 vs 鏡面はエネルギー保存です。たぶんこれは拡散が明るすぎる vs スペキュラという意味で、本来改善しようとしていたスペキュラの量が減ってしまうということなのでしょう。


Slide.19
ありがたいことに、他の誰かがこれらの条件をすべて満たすモデルを考え出していました。Sledgehammer gamesのDanny Chan氏は、昨年、Advances in Real-Time Renderingという素晴らしいプレゼンテーションを行いましたが、その中にはゲーム内のマテリアルを改善するための素晴らしいアイデアがたくさんありました。


Slide.20
新しい拡散モデルのベースは、我々が多重散乱スペキュラーBRDFに興味を持ったのと同じ論文に遡る。Danny Chanは、提供されたソースコードを使って多重散乱拡散モデルのデータを生成し、フィットを見つけました。


Slide.21
プレゼンテーションで説明されているフィッティングプロセスは非常に複雑ですが、最終的にはシェーダで使用できる比較的シンプルな関数になります。

[このコードについてWillam Bussiereありがとうございます]


Slide.22
でも、一行だけ強調させてください。彼の論文では、Dannyは我々とは異なる表面の平滑性のパラメータ化に対してフィットしています。そのため、2つの間で変換するためにいくつかの余分な命令を追加しなければなりません…もちろん、自分自身でモデルを実行し、自分自身のフィットを行うことに時間を費やさなければなりません。


Slide.23
いくつかの結果を見てみましょう。ここでは、左から右に向かって滑らかさが増している球体を示していますが、ランバート拡散では全く効果がありません。

では、多散乱拡散をオンにしてみましょう。


Slide.24
粗い球について良い再帰反射が発生しているのを見え,そしてよりスムースな球については柔らかなファールオフが見えます。
実際、私たちのアートディレクターの一人は、常に拡散のための厳しいフォールオフについて文句を言い、フォールオフの柔らかさのために線形空間照明よりもガンマ空間照明を好むようになりました。だから、これはゲームに役立つものになりそうですね。

もう一つ興味深いのは、これらの写真の中の地上面がどのように変化しているかを見ていることですが、多重散乱拡散によって暗くなっています。これは実際にゲームで見ることができる多重散乱拡散の最大の効果の一つであるので、私はこれを指摘しています。絵葉書のように、空と地形の比率が変化し、画像全体に影響を与えています。これが私たちのライティングアーティストにどのような効果をもたらすのかは、今後見ていかなければなりません。


Slide.25
プロジェクターでは違いがわかりにくいかもしれないということを踏まえて、もう少し違いを強調してみることにします。右側の滑らかな球体の場合は、ちらっと見るような視野角でのエッジのフォールオフに加えて、グレージング光の角度でのより滑らかなフォールオフを見ることができます。左の粗い球についてはライティングを平らにする追加の再帰反射性を見ることができます。


Slide.26
新しい拡散モデルと鏡面モデルができたので、これで終わりかな?

悲しいことに……そうではありません……..まだまだです……..


Slide.27
ファークライには、スペキュラ用のGGXや拡散用のLambertだけでなく、実際には置き換えなければならないBRDFがたくさんあります。拡散照明をアップグレードしたいのであれば、理想的には事前積分された表面下散乱も更新し、さらにLambertのラップドライティングを模倣する方法を見つける必要があります。鏡面照明については、すべてのケースでエネルギーを節約したいのであれば、髪の毛や布にもそれを行う必要があるかもしれません。


Slide.28
でも、ライトの種類もいろいろあります。直接照明の場合は、単純な解析評価がありますが、これは簡単です。しかし、間接照明のための球面調和関数やキューブマップもあります。率直に言って、間接照明をBRDFに従わせることは必ずしも得意ではありません – 例えば、SHを使ったスキンの評価は今のところ何もしていません。しかし、環境ではなく、直接照明だけを対象としたスペキュラ照明の量を増やす大きな機会を逃しているように感じます。


Slide.29
問題点をまとめてみましょう。間接照明には多散乱BRDFがなく、表面下散乱BRDFもありません。これは、私たちのラップされたディフューズとスキンを含みます – そして最終的に私たちは髪のための解決策を持っていません。BRDFsは、布素材のエネルギー損失をほとんど補うことができるちょっとしたハックです。また、我々のエンジンでは、スキン上の間接的な拡散のためのソリューションを持っていないことにも注意してください、したがって、我々はすでに一緒に暮らしている既存の問題があります。


Slide.30
最初に解決しようとする問題は間接的なスペキュラで、これはほぼ確実にゲーム、特に我々のラフメタルに最も大きな影響を与えるからです。


Slide.31
ということで、やってみましょう。これが環境照明のために 解こうとしている基本式です この場合、環境照明はキューブマップから来ており、半球上で積分しています。問題は、ゲームではリアルタイムでピクセル単位での積分ができないことです。そこで、いくつかの近似案を用意しました。


Slide.32
2013年のBrian Karisの研究から、分離総和積分を用いて結果を近似しています。異なるラフネスに対してキューブマップを事前に積分し(高いラフネスの結果はキューブマップの下位のミップレベルに格納されます)、BRDF部分をLUTに積分します。これは、スクリーン空間の反射に対しても非常に有効です。- それらは事前積分されたキューブマップを置き換えるだけで、同じLUTを使用しています。


Slide.33
LUTを環境BRDFと呼び、Schlickのフレネル近似を使えば、2つのコンポーネントに分割することができます …


Slide.34
つまり、LUTは、出射角(視野方向)の余弦と表面のラフネスの2つの次元に過ぎません。\(F_0\)項、つまり鏡面反射率は外に因数分解されています。


Slide.35
最初は、これに多重散乱BRDFを加えるのは簡単だと思っていました。事前に積分しているので、積分しているBRDFにMSのBRDFを追加で追加するだけです。環境BRDFの多重散乱部分はこんな感じです。


Slide.36
しかし、一度平均的なフレネル項を展開してみると、結局のところ、物事はそれほど単純ではないことに気づきます。もしこれを既存の環境BRDFに追加するのであれば、\(F_0\)に線形依存性を持たせたいのですが、ここではそれがありません。そこで私は思いついたのですが、関数の指数を展開をして、何が得られるかを見てみませんか?


Slide.37
このような近似値が得られ、これは素晴らしくシンプルであり、環境マップBRDFに追加することができます。また、\(F_0\)の指数項をもっと増やして、また望めば2つの代わりに3つの私たちの4つのコンポーネントの環境BRDFを取得して環境マップBRDFに組み込むこともできます。

当時は、係数は2つで十分だと思っていました。誘電体の\(F_0\)項は0.1以下になる傾向があると思っていたので、その後の指数項はあまり重要ではないだろうと思っていました…そのときは、私は多くの高い鏡面反射率を持つ粗い金属のための最大の違いを作る多重散乱スペキュラを忘れていたと思います…


Slide.38
これは、環境マップBRDFを多分散項で更新したときに、うーん……うーん……..ほとんど違いが見られないことを意味しています。実際,8ビットでは違いがあるとは思えませんが,16ビットでは間違いなく違いがあります。


Slide.39
我々はこれが金属上でどのように見えるかを見た場合で、これは標準的な環境マップBRDFです…


Slide.40
… そして、これは多重散乱環境BRDFです。我々は、金属のための小さな違いがみえますが、どこにも我々が直接光で我々の結果を比較して、見えるべきはずの大きな違いはどこだかわかりません。

だから私はこれのための解決策に困惑していました…


Slide.41
…そして、この論文が出たのと同じ頃だ!自分が研究していたものと全く同じことに取り組んでいる人を見るのはいつも本当に刺激的です…そしてこれはゲームに本当に関連する問題を解決した論文でした。また、既存の研究を拡張して、Kulla と Conty の研究をリアルタイムの画像ベースの照明に適用できるようにしたのはとても素晴らしいことだと思います。


Slide.42
この論文は、環境BRDFの赤と緑のチャネルの合計は、実際にはその方向の出射エネルギーと同じであるという(他の人が持っているように)賢い観察を行います。また、この項が平均エネルギーを近似することができることを観察しています。


Slide.43
そして、この論文は、照明の多重散乱部分について、次のような簡単な式に到達します。もちろん、平均フレネルのような項が簡単に計算できるのは助かります。しかし、多重散乱光は拡散性があることに注意して、キューブマップの低いミップレベルをサンプリングするか、放射照度を球面調和関数に格納することを推奨しており,放射輝度ではなく光の放射照度を取ることにしました。

今はそれでいいんだけど、ゲーム開発者の自分は怠け者なんです。また、たくさんの命令は節約したいです。特にテクスチャサンプルは可能な限り省きたいです。そのため,放射照射の部分はあまり好んではいません。


Slide.44
私たちは粗い金属で多重散乱の最大の影響を受けていますが、これは、私たちがサンプリングしている鏡面照明がかなり拡散していることを意味していますので、あまり重要ではないかもしれません。おそらく、放射照度のために放射輝度を代入すれば、まだ問題ないでしょう。

では、いくつかの結果を見てみましょう。


Slide.45
我々はもちろん金属を見ていきますが、これは標準的な環境マップBRDFを使用しています。


Slide.46
これはFdez-Agüeraの近似で、失われたエネルギーを本当に補っているので、これは素晴らしいことです。


Slide.47
そして私のFdez-Agüeraへの近似値です。…..これは……..ほとんど変わらないように見えます。少なくとも私の場合は 私の近似値が有効だったようです。


Slide.48
これが素晴らしいのは、単一散乱BRDFをスケーリングすることができる多重散乱BRDFの公式を提供してくれることです。他の事にも応用できるかもしれないので,かなり素晴らしいです。面白いのは、これを理解した後、私は…


Slide.49
… Emmanuel Turquin氏の技術レポートが出てきましたが、Heitz氏の元の論文から、多重散乱ローブが単一散乱の縮尺版に非常に似ていることを観察し、この近似を思いついたとのことです。これは、我々がFdez-Agüeraから導き出したものと大きな違いはありません。同じ分野の研究をしている人がいて、すぐに試すことができる非常に実用的な近似法を考え出したのですから、これは素晴らしいことです。既存の非常に成功した研究の上に構築し、議論に貢献したもう一つの例です。

結果を見てみましょう。


Slide.50
これが私のFdez-Agüeraの近似値です。


Slide.51
そして、これはEmmanuel Turquinのものです。この2つはかなり違っていて、特に色の面では、Emmanuel Turquinの方が少し明るいようです。根拠のある比較をする時間がないのですが、ここでは2つの選択肢があります!

しかし、この2つの多散乱スペキュラーBRDFへのアプローチがダイレクトライティングでどのように機能するのか、また、私たちが最初に実装したSony Imageworksのアプローチとどのように比較しているのかを見てみましょう。もう一度、金属球でテストしてみましょう。


Slide.52
ソニーイメージワークスのオリジナルアプローチです。


Slide.53
これはFdez-Agüeraに近似して、単一散乱ローブをスケーリングしたものです。ハイライトの形状にかなり大きな違いがあることがわかります。特に、中程度の粗さの球体では、エッジで多くのエネルギーを失っています。下端でもエネルギーが失われています。


Slide.54
そして、こちらはEmmanuel Turquinのものです。Fdez-Agüera の問題点はそのままに、ハイライトの形状が Sony Imageworks のアプローチと一致していませんが、粗い面では少し明るくなっていて、Sony Imageworks のアプローチと一致していることがわかります。このことから、環境照明ではTurquin氏の手法がベストだが、直接照明ではソニーイメージワークスの方がグレージング角度でエネルギーを保存しているのではないかと考えてしまうのです。しかし、やはり、グランドトゥルースとの比較が必要です。

私はまた、あなたが環境ライティングのための多散乱スペキュラのためのまだ別のソリューションが必要な場合は、ここで言及する必要があります、Stephen Hillは最近、同じ領域でいくつかの作業を行っており、最近のブログ記事では、彼は少し異なる方法で環境照明のための多散乱の問題を解決する4項の環境BRDFを提唱しています。あなたがこの問題にアプローチするためにさらに多くの方法を考えたいと思っているならば、十分に読む価値があります。


Slide.55
しかし、私が環境照明のこのトピックを見始めたとき、Fdez-Agüera の論文に何かがあり、私は完全に脱線しそうになりました。平均フレネルを計算する式の一つを見てみましょう。


Slide.56
私が本当に頭を悩ませたのは、この方程式にも他の方程式にも存在するπという係数が追加されていたことでした。Sony Imageworksの論文を自分で実装していたときは、これらの積分をπで割って正規化していたので、正しい結果が得られたように見えたのですが、自分の数学的能力にはすでに自信がありませんでした。しかし、自分の数学的な能力には自信がなかったので、自分が何をしたのかちょっと自信がありませんでした。だから、査読付きの論文で、自分とは違う方程式を見て、本当に混乱して、どうしたらいいのかわからなくなってしまったのです。私がやったことはすべて間違っていたのでしょうか?

さて、私は調べてみて、円周率が間違っていることを確信するようになりました。著者に問い合わせするためにメールを送ろうとしていたのですが、このツイートを見たとき…


Slide.57
ほっこりしました!(笑)。先に報告してくれた善良なサマリア人に感謝です。


Slide.58
問題のリストに戻りましょう。次は髪の毛の多散乱スペキュラについてですが…。


Slide.59
悲しいことに、これは今のところTODOリストに入っています。我々は単一散乱BRDFのスケーリングファクターを使って検討することができましたが、透過率と第二鏡面ローブではもう少し厄介です。この話はまた後日にしなければならないので、今のところはここまでにしておきます。


Slide.60
しかし、最後の3つの問題、表面下散乱の多重散乱と間接拡散についてはどうでしょうか?さて、これらの問題については、次のトピックに移ってから解決策を考えることにしましょう…


Slide.61
…それは、エリアライトです。
これはマテリアルの改良を補うもので、今度はライト自体の改良に目を向けたいと思います。


Slide.62
ライティングアーティストにはシネマティクスでの選択肢を増やしてもらいたいですし、エリアライトを使うことで、光の大きさを介した拡散のフォールオフをより制御できるようにしたいです。我々はまた、より目に見えるハイライトで、より広くよりスペキュラーを得られるでしょう。これは、アーティストがより滑らかなマテリアルをオーサリングするようになり、100%滑らかな表面が、無限に小さな鏡面ハイライトで少し変に見えなくなるという追加の利点があります。


Slide.63
エリアライトを見るとき、Eric Heitz氏らが2016年に発表した論文「Linearly-Transformed Cosines」を使って調査することは、技術的にはどんなBRDFでも動作するポリゴンライトを使ったリアルタイムのシェーディング方法を説明したもので、迷うことはありませんでした。


Slide.64
LTC を選択したのは、デモを含めた完全なソースコードが利用可能であることを知っていたため、ゲームに追加してすぐに試すことができたからです。実際、最も難しかったのは、GLSLデモをHLSLに変換する際に行列を転置しなければならなかったことです。完全なソースコードは、後になって変更を加えなければならないときにも、本当に役に立つことがわかりました。彼らは、すでにクワッドライト、ディスクライト、ラインライト、テクスチャマップされたライト、そしていくつかのスケーラブルなパフォーマンスオプションを提供してくれていたので、これで物事がうまくいくという大きな自信がつきました。さらに、デモやコードを見ると、これらのライトが堅牢で、ゲームに実装するのに適した性能を持っていることが明らかでした。


Slide.65
みなさんが同じ認識を持つようにLTCとは何かを簡単に説明しましょう。

LTCはかなりわかりやすいので、テクニック全体のマジックですね。まず、多角形のエリアライトを評価するためには、球面分布上で多角形を積分する必要があることを観察することから始めます。これは多くのBRDFにとって解けない数学的な問題ですが、多角形の形状を解析的に積分することができるクランプされた余弦分布があることがわかります。


Slide.66
そして、この分布を線形変換して任意のBRDFに適合させることができます。


Slide.67
そこで、線形変換された余弦にBRDFをフィットさせ、多角形上で積分するときに逆変換を適用すれば、答えはクランプされた余弦分布上で多角形を積分することになりますが、これは解法がわかっています。


Slide.68
しかし、LTCは常に1に積分しますが、BRDFは1以下に積分することがあります。 BRDFの大きさでスケーリングし、フレネル項も考慮する必要があります。そのため、我々は半球上のBRDFを積分し、LUTにデータを格納するだけです…


Slide.69
実際には、Schlickの近似をフレネルに適用すると、2つの成分が得られ、それをサーフェイスの\(F_0\)項に基づいて合成し、LTCを拡大縮小することができます。

さて、これは非常に馴染み深いものに見えるかもしれません……正確にはそうです。これは、わずかに異なるパラメータ化ですが、環境BRDFのそれと非常によく似た考え方です。これは後で非常に役に立ちそうです。


Slide.70
提供されたすべての優れたコードとデモのおかげで、実装は比較的簡単になりました。オフラインでは、BRDFのために2つのルックアップテーブルを生成します。最初のルックアップテーブルには、逆行列変換の座標が含まれており、その角度と粗さでBRDFに線形トランスフォームされた余弦近似を形成し、半球上でクランプされた余弦に戻します。2番目のルックアップテーブルは、与えられた視野角でその大きさによってBRDFをスケーリングするために、以前に議論した環境BRDFに非常に似ています。


Slide.71
スムースネスが増加する誘電体の球でこの見た目をみてみましょう。太陽の光の代わりに クワッドライトを使ってみます


Slide.72
特に滑らかな表面のスペキュラハイライトはかなり素晴らしいですね。ディスクもできますよ!


Slide.73
これもまた、かなりカッコいいですよね!

ディフューズも変わってきていますね。Lambertianディフューズは球体の上にクランプされているので、LambertianディフューズのLTCを実装することは些細なことです。我々は、エリアライトのサイズから素敵な柔らかいフォールオフを得ます。そして,それはアーティストが好きになることを知っています。


Slide.74
GGXとLmabertディフューズに対してエリアライトが実装されているので、これで万事順調ですよね!?


Slide.75
でも…また組み合わせの問題に戻ってしまいました。GGXのBRDFだけではありません。Lambert BRDFは半球上のコサインをクランプしただけなので、些細なことですでに解けていますが、ラップドディフューズはどうでしょうか?pre-integrated scatteringは?髪の毛と布?これらの問題は、我々の新しい拡散BRDFと鏡面BRDFで直面した問題と似ています。


Slide.76
しかし、実際には悪化している。あとは、エリアライトを多散乱拡散光や多散乱鏡面光にも対応させなければなりません。


Slide.77
本質的には、BRDFを評価しなければならない別のライトタイプを追加しただけです。


Slide.78
ということで、現在の状況をまとめてみましょう。布、多重散乱拡散、髪の毛の3つの基本BRDFについては、LTCを実装していません。また、非エリア照明パスのために実装した多散乱スペキュラもありません。最後に、ラップされたディフューズと事前積分済み肌散乱とLTCをどのように組み合わせればいいのかわかりません。


Slide.79
しかし、私たちにはお祝いの理由があります!LTCのソースコードが利用可能になりました 最も重要なのは 任意のBRDFに対してLTCを適合させるコードです これにBRDFを差し込んで、フィッティングを実行してLUTを作成すればいいのです。


Slide.80
ということで、まずは布と多散布のディフューズを動作させることから始めてみましょう。


Slide.81
そこで、まず多散乱拡散用のLUTの生成から始めましょう。逆行列はGGXと比較してあまり多くはありませんが、ラフネスと視野角はこのBRDFに対してより小さな効果をもつということを期待しています。


Slide.82
Lambertian拡散の結果から始めましょう…


Slide.83
望ましい効果が起こっているのがわかります。ライティングは、粗い面ではやや平坦になり、再帰反射性が高くなっていますが、滑らかな面ではわずかにフォールオフがあります。滑らかな表面は、グレージング視野角でも暗くなってきています。マテリアル上の拡散照明がポイントライトと全く同じ反応をしているので、求めていた効果が得られています。


Slide.84
Ashikhminの布BRDFにも試してみましょう! これはどのように動作するかを見てみましょう…


Slide.85
今回はポイントライトクロスの実装と比較してみます。私たちのクロスモデルで最大の再帰反射とシャープな効果を発揮するのは、粗いサーフェイスです。


Slide.86
これはかなり良さそうですね! 確かに違いますね、少し拡散しているような気がします。でも、今のサイズのライトがあるので、それはそれで期待できそうです。これならまだ、いろいろな素材に布の効果が得られそうだし、これはこれでいいかな。


Slide.87
しかし、私たちのエンジンでは、Ashikhminの布BRDFだけでなく、ディズニーのBrent Burleyが提案したような光沢のある素材を使用しています。これはBRDFなので、\(N \cdot L\)の項でスケーリングされています。つまり、効果的には、これはフレネルを使ったLambertianディフューズです。


Slide.88
ですから、これはエリアライトとして計算するのは実はかなり簡単なのです。私たちはLambertian拡散の評価を使い、大きさのフレネル成分と多散乱拡散のフレネルLUTを使って計算しています。技術的にはLambertian拡散のために1つのLUTを使うべきですが、すでに持っているLUTを使えばいいのに、なぜ別のLUTが必要なのでしょうか?我々は、Lambertian拡散に最も近いと仮定して、スムースネス0.5でサンプルを作成しています。


Slide.89
ポイントライトで照らすとこんな感じの光沢になります。ここでは効果を誇張して見やすくしていますが、左から右に向かって強さが増しています。


Slide.90
エリアライトで有効にすると、実際には、より多くのリム効果が得られるような微妙なものになります。しかし、このリム効果が残っているので、布のエリアライトが使えるようになり、この素材が布であることが伝わります。エリアライト用のAshikhminの布BRDFに少し似ているような気がします。


Slide.91
次の問題は多散乱スペキュラです。欲しいBRDFを実装したばかりですが、LTCで使えるようにしないといけませんね…。

今、最初は、これは布や多散乱拡散と全く同じで、異なるBRDFでフィッティングコードを使用するだけだと思うかもしれませんが、実際には、これはすでに議論したトピックと同じ問題を持っています – 間接的なスペキュラと環境BRDFです。


Slide.92
以前は、BRDFを(F_0\)への線形依存性に分解し、2つのコンポーネントをLUTに格納することができました。しかし、今回も多散乱BRDFは非線形に\(F_0\)に依存しているので、このトリックは使えません。


Slide.93
でも、解決策があります!環境BRDFと同じソリューションを使うことができます!


Slide.94
今、シングルスキャッタリングBRDFをスケールすることが可能な多重散乱BRDFについての定式を得ました。これは素晴らしいです。なぜかというと高速でシンプルかつLTCsに適用できるからです。環境BRDFから我々ができることと同じように大きさとフレネルLUTからエネルギー項を得ることができます。


Slide.95
Emmanuel Turquin公式を使うこともできますよ!試してみましょう。


Slide.96
まずは単一散乱スペキュラーから見ていきましょう。


Slide.97
多重散乱BRDFを得るためにFdez-Agüeraへの私の近似を使用してみましょう。


Slide.98
そして最後に、Emmanuel Turqinの解答を見てみましょう。もう一度言いますが、これは Fdez-Agüera のアプローチよりもわずかに明るいのですが,色が少なくなっています。


Slide.99
次に解決すべき問題は、ラップドライティングです。面白いのは、この問題はエリアライトだけでなく、多散乱拡散に対しても解決しなければならないので、もしかしたら両方に使える解決策があるかもしれません。


Slide.100
まずは、ラップドディフューズを簡単におさらいしてみましょう。入射光角の余弦であるランバーティアン拡散を球体にラップします。ラップ係数は、何の効果もない 0 から、照明を一周させる 1 の間で変化します。

これは素晴らしいことですが、他のBRDFではどうなのでしょうか?例えば、我々の新しい多散乱拡散モデルでは、入射角に依存する項と、出射角に依存する項があります。両方をラップする必要があるのでしょうか?エリアライトについてはどうでしょうか?

いい考えがあります…ここでのように入射光の余弦を操作するのではなく、直接法線を変えればいいのではないでしょうか?


Slide.101
光に向かって法線を回転させれば、確かに同じ効果があるのではないでしょうか?そして、我々は、我々が望むどんなBRDFでも、我々のすべての通常のライティング方程式で法線を使用することができます。


Slide.102
これは、単純な軸と角の回転だけに集約されます。しかし、回転角度は球体の周りにどれだけ遠くにするかに依存します。法線が光の方向を向いているとき、cosθが1のときは、全く回転したくありません。一方、ラップしたい球体の周りで一番遠いところにいるとき、cosθが-wのとき、θがπ/2になるところまで回転したいです。

しかし、シェーダコードでは、実際にはコストのかかる逆余弦を避けたいので、代わりに角度の正弦をブレンドすることができます…


Slide.103
…それははるかに、はるかに速い!


Slide.104
光の方向とラッピングファクターに基づいて法線を調整するために使用できる比較的シンプルなシェーダ関数になります。


Slide.105
でも、正規化はどうなんでしょうか?もし照明を球体の周りにさらにラッピングしているのであれば、効果的にエネルギーを追加していることになります。


Slide.106
球体のキャップの表面積を見ることで正規化できることがわかりました。


Slide.107
球体の最遠距離におけるcosθの定義を思い出し、それを球体のキャップの表面積に代入すると、次の式が得られます。


Slide.108
そして、それを半球の表面積と比較すると、次のような単純な正規化係数が得られます。

興味深いことに、実は過去にラップした拡散照明の正規化についてのブログ記事を書いたことがあるのですが、その時に考えた正規化係数がこれです。突然、別の正当な理由を見つけたのには驚きました。


Slide.109
より良く見える結果をご紹介します。

ちょっと不正確なのは認めます。パフォーマンスのために角度の代わりに角度の正弦波をブレンドしているので、正規化係数はBRDFには正しくありません。しかし、これはゲームなので、パフォーマンスのためには「十分」であるべきだと考えています。

試してみましょう。


Slide.110
点光源を見ることから始めましょう。そして,われわれの多重散乱ベースのディフューズモデルです。


Slide.111
球体の50%を包むことができます…


Slide.112
…と100%で、すべてが持ちこたえているようです。ラフな球体は、フラットな照明ではまだラフな感じがしますが、これはまさに私たちが目指しているものです。


Slide.113
次はエリアライトとLTCで結果を確認します。


Slide.114
再度、球体の50%を包むことができます…。


Slide.115
…そして100%です。期待通りの結果が出ています。これはかなり素晴らしいですね!これでラップドライティングをすべてに適用できるようになりました この問題が解決したと考えてみましょう。


Slide.116
いま、ラップドディフューズがが終わったので、次にpre-integrated skin scatteringを見てみましょう。それはある意味では非常に似ています…


Slide.117
スキンのためのPre-integrated scatteringは、SIGGRAPH 2011でEric Penner氏が発表した技術です。pre-integrated scatteringのトリックは、異なる曲率の球体上のランバート拡散を、スキン散乱の拡散プロファイルで積分することです。


Slide.118
多重散乱拡散のためにこれを解かなければならないとしたら、新しいBRDFの上に積分する必要がありますが、これはLambertと比較して2つの余分なパラメータ、すなわち視野角とラフネスを持っています。もちろん、もし実際にこれを研究していたら、フィットを見つけたり、次元を小さくしたりできるかもしれませんが、エリアライトの問題を解決することはできません。


Slide.119
しかし、ゲームで使用されているスキンの散乱については、ここでは別の解決策があります:分離可能なスクリーン空間のサブサーフェス散乱です。これはスクリーン空間内で実行され、既存のライティングバッファをぼかすので、技術的にはどんなBRDFでも動作します。さらに良いことに、これは間接的な拡散照明でも動作します。

ファークライ3では、プレインテグレーテッド・スキャッタリングとスクリーンスペース・スキャッタリングのどちらかを選択しなければならなかった時、我々は間違った馬にベットしていたように思えます。まあ、前者の方が実装時間とパフォーマンスの両方ではるかに速かったのですが、今は… 後者の方がはるかに柔軟性があるように思えます。本質的には、BRDFとライティングの選択と直交しており、その直交性はしばしばレンダリング技術において本当に重要になることがあります。


Slide.120
悲しいことに、これは今のところ、私たちのTO Doリストに入っていますが、それはまた後日に戻ることになります。
しかし,より将来性をもって問題を解決してくれそうな気がします。


Slide.121
最後の未解決問題は、髪の毛にLTCをどのように適用するかです。


Slide.122
まあ、悲しいかな、これもTO DOリストに載っている。マーシュナーの髪には 3つのローブがあります 本質的には反射、透過、第2バウンド反射です。おそらく、これは本当に高価なもので、各ローブのLTCルックアップをする必要があるでしょうか?あるいは、別のエリアライト近似を見つけなければならないかもしれませんし、点光源のソリューションに戻ってしまうかもしれません。しかし、繰り返しになりますが、多重散乱スペキュラーのように、髪の毛のための直感的な答えはないようです。もちろん、どんな助けももちろん大変ありがたいです!


Slide.123
しかし、もう一つ・・・この未解決の問題を覚えていますか?どのようにして我々の多散乱拡散を 球面調和関数に保存された間接照明に適用するのでしょうか?


Slide.124
我々は…またしても解決済みの問題にリンクしています…

クランプされたコサイン分布を球面調和関数に投影し、それを利用してSHに投影された照明を評価することができます。しかし、我々が求めているのは、任意のBRDF(この場合は多散乱拡散BRDF)を球面調和関数に投影することです。


Slide.125
しかし、たまたま、線形変換された余弦から、BRDFを近似することができるクランプされた余弦分布への写像ができたのです。これを使って問題を解決することができるかもしれません。


Slide.126
エリアライトの場合は、光源の座標を取り、LTCの逆行列を使って回転させます。SHバンドをエリア光源のように扱い、LTC逆行列を使って回転させ、コサインローブで評価します。そして最後に、エリアライトにLTCを使うときと同じようにBRDFの大きさで拡大縮小します。


Slide.127
しかし、SHは回転不変なので、実際にはコサインローブを回転させることができます。


Slide.128
シェーダコードの例です。SH も行列 R で法線の空間に回転させているので、コサインローブを上に向けて回転させていることに注意してください。つまり、このコードをさらに最適化することができるということです。

それでは、いくつかの結果を見てみましょう。


Slide.129
これらの写真のために照明の設定を少し変えてみました。これは夜明けに、スカイライティングだけで撮影したものです。右手に太陽が昇ろうとしているのがわかりますが、その方向の空はきれいなピンク色をしています。

今、私たちは私たちのマルチ散乱間接拡散をオンにすると…


Slide.130
ラフな左手側の球体の上に平坦な照明を得ますが、球体が右に向かってスムースになると、視野角を見るためのより強いフォールオフを見ることができます。多重散乱直接拡散で見たように、地上面も少し暗くなっています。

グランドトゥルースと比較してみましょう。


Slide.131
これは本当に近い!実際には、我々が見る唯一の本当の違いは、球体の底部がグランドトゥルースにたいして明るくなっていることですが、我々はランバートを比較するならば、我々は実際に同じことを見るでしょう – これはSHのリンギングによって引き起こされています。

グランドトゥルースにアスタリスクを付けたのは、このような状況での絶対的な真実の主張にはいつも少し警戒しているからです。我々は、半球上のSHをサンプリングし、BRDFでスケーリングすることにより、これを生成しました。

近似を見ることにに戻りましょう …


Slide.132
だから、これは素晴らしいことですが、あなたがそれらの行列の乗算と正規直交基底の構築があまりにも多くの時間がかかるかもしれないと感じる場合には… もっと安い方法があります SHを全く回転させずに、BRDFの大きさでスケールします。比較してみましょう。


Slide.133
そのため、色は移りますが、全体の強度は変わりません。これはかなり極端な例ですが、SHでも照明が違うので、一般的にはそれほど問題にならないのかもしれません。しかし、マグニチュードによるスケーリングでは、滑らかな表面のちらっと見える視野角でのフォールオフを得ることができ、オブジェクトにより明確な定義を与えます。

これは、もう少し研究が必要ですが、私はこれが打ち破るための有望な道であることを願っています。


Slide.134
結論としては、今の状況を振り返ってみましょう。多重散乱拡散とスペキュラ、そしてエリアライトを実装しました BRDFは、直接照明、間接照明、エリア照明に対応しています。ラップドライティングはあらゆる状況で機能し、布用のエリア照明を実装しました。


Slide.135
しかし、悲しいかな、まだ次のような問題が残っています。現在のものは何も私たちの髪の毛のシェーディングでは動作しませんし、スキンの表面下散乱についても動作しません。少なくともスキンについては、分離可能なスクリーン空間部分散乱を使用する計画がありますが、髪の毛は間違いなく研究領域にあります。


Slide.136
ということで、旅も終盤に差し掛かってきました。多重散乱BRDFとエリアライトの最新の研究をゲームに取り入れてみましたが、最初に期待していたよりも少し複雑なものになってしまいました。しかし、この旅が、研究とゲーム制作の融合について一緒に学ぶことのできるいくつかの洞察を与えてくれたことを願っています。


Slide.137
1つ目は、ソースコードがかけがえのないものであるということです。Danny Chanの多重散乱拡散モデル用のシンプルなシェーダコードを持っていたことは、すぐに実装できたので、とても良かったです。また、LTCを使用したエリアライトのデモがあったので、ゲーム内でのエリアライトの動作バージョンをすぐに作ることができました。しかし、おそらくそれ以上に重要なのは、LTCのフィッティングコードがあったことです。


Slide.138
このことは、私たちを「結論 #2」(洞察力を実装から分離すること)にうまく導いてくれます。LTCの特徴は、エリアライトを使ったGGXのスペキュラの結果をリアルタイムで得られるということではありません。研究はその実装の詳細についてではありません。LTCを使ってどんなBRDFでも表現できるという洞察力を研究しているのです。私はその洞察をさらに利用して、球面調和関数による間接的な拡散を助けるためにLTCを使用しています。

これは、論文を書くときだけでなく、論文を読むときにも覚えておくといいと思います。私のチームのメンバーが、月のためのBRDFについての論文を実装したことを覚えています。その論文には太陽の光からのBRDFが含まれていましたが、空の光のモデルも提供していました。本当に、スカイライトモデルは、研究者がレンダリングしているイメージを完成させるために、ただそこにあっただけなのです。しかし、私の同僚は太陽モデルと空のモデルの両方を実装しました。さて、論文の中の真の洞察を見極め、周辺情報を忘れてしまうのがゲームの私たちの仕事ですが、時には研究者の仕事として、論文の中の重要な洞察が、恣意的な実装の詳細ではなく、何であるかを確認することもあります。

だからと言って、100%実装に関する論文ができないわけではありません。ある意味では、Fdez-Agueraの論文は、ある特定のケースでどうやって何かを動作させるかに焦点を当てた、素晴らしい例です。そして、ゲーム開発者として、私はこれらの論文が大好きです。*しかし*個人的には、このモデルを使用して、それをより多くのユースケースに拡張し、それが他の問題に提供する洞察を見ていました。


Slide.139
既存の作品の上に構築していたように思えます。Fdez-Agüeraが環境照明に対する多重散乱を見ていたように、Emmanuel Turquinが独自の多重散乱モデルを開発していたように。ある分野での研究が多ければ多いほど良いのです。研究が成功すれば、それをゲームで使う可能性が高いので、それはさらに重要なことです。そして、使用されているものをさらに推し進めるために研究を行うことは、本当に役立ちます。

環境マップBRDFという概念でさえ、何度も出てきています。それを利用して BRDFのエネルギーを与えられた方向に求めることができましたし,LTCのスケーリングにも非常に似た概念が使われていました。ゲームの中ではすでにこの技術の一部を持っているという事実は、それに基づいたさらなる論文の実装を容易にします。

ここにも注意の言葉があります。近い将来、私はエリアライトを使いたいので、BRDFの評価にLTCを使うことになりそうです。つまり、もしBRDFを実装するのであれば、ぜひともLTCで動作するようにしたいのですが、そうでなければ問題が生じます。成功している既存の仕事の上に構築するということは、すでに使われているものを理解し、それを使って研究を進めようとすることでもあります。あるいは、15年後にはLTCをやめてレイトレーシングの研究に戻ってくるかもしれないということを理解することです。

しかし、既存の仕事の上に構築することのもう一つのメリットは、その中にある問題を解決しているかもしれないということです。そして今日はたくさんの問題にぶつかりました。


Slide.140
実装時間を決して甘く見てはいけません。そして、それは私から来ているのですが、私は最初、新しいBRDFに加えてエリアライトをゲームに入れることの早さに本当に喜んでいました。そして、自分に足りないケースがたくさんあることに気がつきました。スキン、髪の毛、布。新しいBRDFは必ずしもエリアライトとうまく機能していなかったし、この2つの研究はかなり独立していたので、私はそれを統合しなければならなかったのです。まだまだやるべきことはたくさんあります – スキングレンダリングのための全く新しいテクニックを実装し、パフォーマンスを確認し、髪の毛のためのソリューションを発見する必要があります。

私はこれを多くの人に言っています – 私が一緒に仕事をしているアーティストやプロデューサー、ハードウェアメーカーから最新の機能を実装してほしいと言われている人、彼らの研究を実装してほしいと言われている学者、そして最新の機能をエンジンに搭載しようとしているグラフィックプログラマーなどです。あなたが思っている以上に時間がかかることがよくあります。確かに、最初の実装は簡単かもしれませんが、そんなに時間はかからないかもしれません。しかし、これは80:20のルールの一つだと思うのが好きです。私たちのエンジンは信じられないほど複雑で、機能がいつもうまく噛み合っているとは限らないので、最後の20%の作業には80%の時間がかかっています。


Slide.141
さて、旅の最後に短いエピローグの時間です。実際にゲーム内にエリアライトを入れて、使えるようにしてみたらどうなるのかな?そうですね、アーティストが使えるようにして、パフォーマンスを管理する方法を与えなければなりません。さらに、エリアライトを有効にしたり無効にしたりできる機能にすることも試してみたいと思いました。


Slide.142
それによって、次の2つの大きな目標ができました。私たちは、アーティストに光のフォールオフをコントロールできるようにしなければなりませんでした。また、エリアライトが無効になったときに、オムニやスポットライトにフォールバックできるようにすることも決めました。


Slide.143
まずはこれらのコントロールを見てパフォーマンスを上げていきましょう。既に持っている 2 つのファールオフコントロールがありますが、それをエリアライトに使用します。最初の最も重要なものは半径のフォールオフで、ライトから一定の距離を超えると何もレンダリングされないようにします。


Slide.144
SIGGRAPH 2013でBrian Karis氏が提案したこのウィンドウ機能を使って、それをやろうと思っています。\(d\)を光からの距離、\(r\)をその半径とすると、この関数は境界でゼロへの滑らかなフォールオフを与えます。


Slide.145
次はコーンフォールオフです。技術的には、片面クアッドライトの場合、光源の前の180度の平面全体に光をキャストする必要があります。しかし、これでは画面上のスペースがいっぱいになってしまうので、スポットライトと同じように範囲を制限したいのです。ファークライでは、スポットライトの内側と外側の円錐角をアーティストがコントロールできるようにしています。それがどのように機能するのかについては、ポイントライトに戻ってしまう解決策についてお話しするときに、もう少し詳しく説明します。


Slide.146
最後に、我々が使用する傾向がある別のフォールオフ関数は、逆二乗法距離で,それは距離で光強度を減少させます…


Slide.147
… しかし、エリアライトでは、これは半球上の光源の統合ですでに説明されています – 光が遠ざかるにつれて、ポリゴンの投影サイズが小さくなり、光の強度が減少します。つまり、これは上で説明した窓関数であるフォールオフ関数とは根本的に異なるものであり、これは光の統合自体の特性です。ですから、エリアライトの場合は無視しています。


Slide.148
パフォーマンスを適切にコントロールできるようになったので、ポイントライトへのフォールバックの話もできるようになりました。これはローエンドのプラットフォームのためのもので、エリアライトはハイエンドのためのものです。


Slide.149
私たちの場合は、同じ場所にあるスポットライトに置き換えて、内側と外側の角度、半径が同じになるようにしています。単純に同じ場所にある点状の光に置き換えただけで 同じフォールオフ特性(つまり半径、内角、外角)を持っていれば 光源付近の光が足りないことに すぐに気がつくでしょう しかし、これは、そもそもエリアライトのフォールオフとして内側と外側の円錐角を使用することの問題でもあります – 円錐はどこから来るのでしょうか?もし光源の中心にあるのであれば、本質的にはエリアライトを少なくとも光源の近くで点光源にすることになりますが、これは理想的ではありません。

(また、ここではクアッドライトの場合を中心にお話しますが、ディスクライトへの拡張は些細なことです。)


Slide.150
代わりに、我々は、エリアライト光源を包含する錐台の位置に光を戻して移動することができます。実際、エリアライトの影をどこに落とすかについて、これに似たアイデアがSIGGRAPH 2014 [Lagrade 14]でSebatien LagradeとCharles De Rousiersによって提案されました。そして、光源の後ろにある光の部分をカリングします。


Slide.151
これは、コーンの角度が落ちるためのコーンの頂点をどこに置くかを教えてくれるので、エリアライトが有効になっているときにも役立ちます。これは、同じ位置から同じ角度でフォールオフするので、2つが一致するのに本当に役立ちます。

これは、プロジェクターの距離を計算しなければならないことを意味します。円錐が出ているところから、光の後ろの方へプロジェクターと呼んでいるものを移動させるための距離です。


Slide.152
これは、外角と光源の直径を使ったかなり簡単な三角法です。

光の直径は、このように四角い光源の対角線の長さを使います。


Slide.153
そう、これはつまり、光が正方形であることから逸脱していくと、もっと悪くなる近似値だということです。これは楕円コーンをサポートすることで改善されるかもしれません。そして、これは言うまでもなく、四角いライトよりもディスクライトの方がはるかに良く動作します。しかし、ディスクライトであっても、楕円になることを考えると、理想的には楕円コーンのサポートが必要になります。

ここでは、異なるコーン角度のライトに対して、実際にどの程度フォールバックが優れているかを見てみましょう。


Slide.154
外角が100度と比較的狭いクアッドライトです。内角を90度にしており,外角に近いです。内側と外側を使用して光に任意の形を提供するために使用する必要はありませんので、かなり外角に近いですが、むしろちょうどソフトなフォールオフです。


Slide.155
これはスポットライトのフォールバックで、少し暗めの色になってしまいます。


Slide.156
120度のクアッドライトに戻りましょう。


Slide.157
繰り返しになりますが、フォールバックは非常に近いのですが、わずかに暗くなっており、フォールオフの形状の変化がより目に見えてわかります。


Slide.158
最後に、140度のライトを見てみましょう。


Slide.159
ここで変化が顕著に出てきます。フォールバックは、実はエッジに近い方が明るいのです。

これは予測通りとみることができます。スポットライトに関してコサイン角から来るのはフォールオフのみですが,クアッドライトについては与えられた角度において光源が見える度合いから来る”フォールオフ”を持ち、より多くの視射角を得ると見える度合いがより少なくなります。

しかし、全体的には、これらのフォールバックは非常に受け入れられています。実際には、ライティングアーティストの一人がエリアライトでは十分な違いが出ていないと苦情を言ったほどです。私はこの方法の勝利としてその苦情を取りました。


Slide.160
聴いてくださった皆さん、ありがとうございました。そして、どんな質問にも喜んでお答えします!