Unity を検索

GPU ライトマッパーについての技術的解説

2019年5月20日 カテゴリ: テクノロジー | 15 分 で読めます
シェア

Is this article helpful for you?

Thank you for your feedback!

ライティングチームは、一丸となってイテレーションの高速化に取り組んでいます。プログレッシブライトマッパーは、その目的を念頭に設計したものです。目標は、プロジェクトでライティングが変更されたときにすばやくフィードバックを提供することです。プログレッシブライトマッパーは、Unity 2018.3 にて GPU バージョンのプレビューをリリースしました。現在、機能もビジュアルクオリティーも CPU バージョンと同等にするための取り組みを進めており、GPU バージョンを CPU バージョンよりも桁違いに高速化するのが目標です。これにより、アーティスティックなワークフローへのインタラクティブなライトマッピングが実現し、チームの生産性も格段に向上します。

このことを念頭に、私たちは RadeonRays を使用することに決めました。RadeonRays は、AMD によるオープンソースのレイトレーシングライブラリです。Unity と AMDGPU ライトマッパーを共同開発し、複数の主要機能と最適化を実装しました。具体的には消費電力のサンプリング、レイのコンパクション、カスタムの BVH トラバーサルです。

GPU ライトマッパーのデザイン上の目標は、以下のように CPU ライトマッパーと同じ機能を提供しながら、パフォーマンスをさらに強化することでした。

  • バイアスのないインタラクティブなライトマッピング
  • CPU バックエンドと GPU バックエンドでの機能の同一化
  • コンピュートベースのソリューション
  • パフォーマンスを最大化するためのウェーブフロントのパストレーシング

Unity では、アーティストがビジュアルクオリティーを高めて創造性を発揮するための鍵がイテレーションの高速化であると考えています。目指すのはインタラクティブなライトマッピングです。全体的なベイク時間を短縮するのみならず、フィードバックが即座に提供されるユーザー体験を実現することが理想です。

この目標を達成するためには、興味深い問題をいくつも解決する必要がありました。このブログ記事では、私たちが下した決断についていくつかご紹介します。

プログレッシブなフィードバック

ライトマッパーがユーザーにプログレッシブな更新を提供できるように、いくつかの設計上の決断を下す必要がありました。

データのプリコンピュートとキャッシュはなし

直接光を配置する際、放射照度や視界はキャッシュしません(直接光はキャッシュして間接光に再利用することが可能です)。私たちは概して、データをキャッシュせず、低速化を避けるために計算のステップを少なくすることを好みます。そして、ベイク中にプログレッシブかつインタラクティブな表示を提供します。

このコンテンツはサードパーティのプロバイダーによってホストされており、Targeting Cookiesを使用することに同意しない限り動画の視聴が許可されません。これらのプロバイダーの動画の視聴を希望する場合は、Targeting Cookiesのクッキーの設定をオンにしてください。

これは、GPU ライトマッパーの制御フローを大まかに示したものです。この Producer-Consumer アプローチを用いれば、GPU ライトマッパーが非同期的に動作している間、シーンを継続的に編集することが可能になります。結果は、準備ができ次第エディターで表示されます。

シーンの中には、非常に大きく、多数のライトマップを含むものもあります。ユーザーにとってのメリットが最も大きい場所でワークが実施されるようにするには、現在の見えている領域でのベイクに集中することが重要です。そのためにはまず、画面上で可視テクセルの収束が最も進んでいないライトマップを検出します。その後、これらのライトマップをレンダリングして、可視テクセルに優先順位を付けます(画面外のテクセルは、可視テクセルがすべて収束してからベイクされます)。

テクセルが「可視」であるとは、現在のカメラの錐台の中にあって、シーンの静的なジオメトリに隠れていないことを指します。

(高速なレイトレーシングを活用するために)このカリングは GPU 上で行います。カリングジョブのフローを以下に示します。

カリングジョブの出力は次の 2 つです。

  • ライトマップの各テクセルが可視かどうかの情報を格納する、カリングマップバッファー。このカリングマップバッファーは、その後レンダリングジョブによって使用されます。
  • 現在のライトマップの可視テクセルの数を表す整数。この整数は、今後のライトマップのスケジューリングを調整するため CPU によって非同期的にリードバックが行われます。

カリングの効果は、以下のビデオで確認できます。デモということを考慮して、ベイクを途中で停止しています。そのため、シーンビューを動かすと、カメラの初期の位置と向きでは見えないところに、未ベイクのテクセルがあることがわかります(黒の部分)。

パフォーマンス上の理由で、可視範囲に関する情報が更新されるのはカメラの状態が「固定」されているときのみです。また、スーパーサンプリングは考慮していません。

パフォーマンスと効率

GPU は、大量のデータすべてに同じ操作を実行するように最適化されています。つまりスループット向上のために最適化されているということです。さらに GPU は、メニーコア CPU よりも優れた電力効率とコスト効率でこの高速化を実現します。ただし、GPU はレイテンシの点では CPU よりも劣っています(これはハードウェアの設計上の理由で、意図的なものです)。したがって、GPU が本来持つ並列計算という性質を最大限に生かすため、CPU と GPU の同期ポイントがないデータ駆動型のパイプラインを使用します。

ですが、本来の性能だけでは不十分です。肝要なのはユーザー体験であり、その評価には経時的なビジュアルインパクト、つまり収束率を使用します。したがって、効率的なアルゴリズムも必要になります。

データ駆動型のパイプライン

GPU は大規模なデータセットで使用するものであり、レイテンシと引き換えに高いスループットを実現することができます。また GPU は通常、事前に CPU が満たす命令のキューによって駆動されます。複数の大きな命令を連続して流す目的は、GPU をワークによって確実に飽和状態にすることです。以下では、スループットを最大化して本来の性能を最大限に引き出すために私たちが採用している方法のうち、主なものを紹介します。

パイプライン

GPU ライトマッピングのデータパイプラインについては、以下の原則に基づいたアプローチを取っています。

1. データを 1 回準備する。

この時点では、メモリ割り当てを減らすために CPU と GPU が同期している場合があります。

2.ベイクが開始されたら CPU と GPU の同期ポイントは許可されない。

CPU は定義済みのワークロードを GPU に送信しています。場合によっては、このワークロードが過度に保守的になることがあります(たとえば 4 つの反射を使用しており、2 回目の反射の後にすべての間接光が終了した場合でも、キューにカーネルが残っているなど。これらのカーネルは実行されるがアーリーアウトされる)。

3. GPU はレイもカーネルもスポーンすることができない。

GPU はむしろ、空のジョブ(または非常に小さなジョブ)の処理を求められる場合があります。こうしたケースに効率的に対処するため、カーネルはデータと命令の一貫性が最大になるように記述されています。後述しますが、私たちはデータの「コンパクション」でこれに対処しています。

4.いったんベイクが開始されたら、CPU と GPU の同期ポイントや、あらゆる種類の GPU バブルを避ける。

たとえば、clEnqueueFillBuffer や clEnqueueReadBuffer などの一部の OpenCL コマンドは、非同期バージョンであっても小さな GPU バブル(つまり GPU が処理するものが何もない時間)が発生する可能性があるため、可能な限り回避するということです。また、データ処理はできる限り長く GPU にとどまる必要があります(つまりレンダリングとコンポジティングから完了まで)。追加の処理を行うために CPU にデータを戻す必要がある場合は非同期的に対処し、再び GPU に戻すことはありません。たとえば現時点では継ぎ目の縫合は CPU によるポストプロセスです。

5. CPU は、GPU の負荷を非同期的に適応させる

カメラビューが変わったとき、またはライトマップが完全に収束されたときに、レンダリングされているライトマップを変更するとレイテンシがいくらか発生します。CPU スレッドは、ミューテックスの競合を避けるためにロックしないキューを使用してこれらのリードバックイベントを生成および処理します。

GPU に適したジョブのサイズ

GPU アーキテクチャの主な機能の 1 つが、多岐にわたる SIMD 命令のサポートです。SIMD とは、Single Instruction Multiple Data の頭字語です。一連の命令は、「ワープ/ウェーブフロント」内の一定量のデータに対し、ロックステップ式で順番に実行されます。これらのワープ/ウェーブフロントのサイズは、GPU アーキテクチャに応じて 64、32、または 16 のいずれかになります。したがって「Single Instruction Multiple Data」(1 つの命令、複数のデータ)の文字どおり、1 つの命令が複数のデータに同じ変換を適用します。ただし柔軟性を高めるために、GPU は SIMD の実装においてさまざまなコードパスをサポートすることも可能です。このために、再結合の前にサブセットを処理している間、一部のスレッドを無効化できるようになっています。これを SIMT(Single Instruction Multiple Threads)と呼びます。ただし代償もあり、ワープ/ウェーブフロント内の多岐にわたるコードパスは、SIMD ユニットのほんの一部からしかメリットを得られません。詳細については、この詳細なブログ記事を参照してください。

また、SIMT という考え方をうまく拡張したのが、SIMD コアごとに多数のワープ/ウェーブフロントを保持する GPU の機能です。ワープ/ウェーブフロントが低速のメモリアクセスを待機している場合、スケジューラーはその間に別のワープ/ウェーブフロントに切り替えてその作業を続けることができます(保留中のワークが十分にある場合)。ただしこれを実際に機能させるためには、コンテキストごとに必要なリソースの量を少なくして、占有率(保留中のワーク量)が高くなるようにする必要があります。

まとめると、目標は以下のようになります。

  • 実行中のスレッド数を多くする
  • 分岐を少なくする
  • 占有率を良好な水準に保つ

十分な占有率を実現するために最も重要なのがカーネルコードです。ただし、このブログ記事で扱うトピックとしては大きすぎるため、以下に詳細なリソースの一部を紹介します。

概して、目標はローカルリソース、特にベクトルレジスターとローカル共有メモリの使用量を分散させることです。

GPU で直接光をベイクするフローを見てみましょう。このセクションは主にライトマップについて扱いますが、可視性や占有率に関するデータがない点を除けばライトプローブもほぼ同じように機能します。

注:BVH とは「バウンディングボリューム階層」の頭字語であり、レイとトライアングルの交点の加速構造です。

以下に示すように、ここにはいくつかの問題があります。

  • この例におけるライトマップの占有率は 44%(占有テクセルが 9 個中 4 個)なので、使用可能な作業を実際に生成しているのは GPU スレッドの 44% のみです。それに加え、メモリに有用なデータがわずかしかないため、占有されていないテクセルについても帯域幅分のコストを払うことになります。実際にはライトマップの占有率は通常 50% ~ 70% なので、潜在的なメリットは多くあります。
  • データセットが小さすぎるのも問題です。この例では簡潔さを重視して 3x3 のライトマップを示していますが、一般的な 512x512 のライトマップでも、最新の GPU が効率を最大化するにはデータセットとして小さすぎます。
  • 前のセクションで、ビューの優先順位付けとカリングジョブについて触れました。占有テクセルの一部は、現在シーンビューで可視状態にないためにベイクが行われず、さらに占有率が低下しデータセット全体が減少します。このため前述の 2 点がより一層当てはまることになります。

この問題を解決するため、AMD とのコラボレーションの一環としてレイのコンパクションを追加しました。この方法により、レイトレーシングとシェーディングの両方のパフォーマンスが大幅に強化されます。簡単に説明すると、連続したメモリ領域にすべてのレイの定義を作成して、ワープ/ウェーブフロントの全スレッドがホットデータで動作できるようにしています。

実際には、関連があるテクセルのインデックスを各レイが把握している必要もあるので、そのインデックスをレイのペイロードに格納します。また、グローバルにコンパクションされたレイの数を格納します。

コンパクションを使ったフローは以下のとおりです。

レイにシェーディングを行うカーネル、およびレイのトレーシングを行うカーネルがどちらも、ホットメモリのみで実行することが可能になりました。また、コードパスの多岐化は最小限に抑えられています。

次に解決するのは、とりわけビューの優先順位付けが有効になっている場合に、GPU にとってデータセットが小さすぎる可能性があるという問題です。このために、G バッファー上の表現とレイの生成との相関を無くします。今回の単純なアプローチでは、単にテクセルごとに 1 つのレイを生成します。どのみちレイはさらに生成する必要が出てくるので、前もってテクセルごとに複数のレイを生成しておいた方がよいでしょう。これにより、GPU が処理する、より有意義なワークを作成することが可能になります。フローを以下に示します。

コンパクションの前にテクセルごとに多数のレイを生成していますが、これをエクスパンション(拡張)と呼びます。また、メタ情報も生成します。メタ情報は、適切な対象テクセルに蓄積するために収集のステップで使用します。

 

エクスパンションカーネルも収集カーネルも、それほど頻繁には実行しません。実際には、最後に一度だけ集まるように、エクスパンションの後にすべての光をシェーディングするか(直接光の場合)、すべての反射を処理します(間接光の場合)。

 

これらの手法によって目的を達成しています。つまり GPU を飽和状態にできるくらい十分なワークを生成し、重要なテクセルのみで帯域幅を消費します。

テクセルごとに複数のレイを照射するメリットは以下のとおりです。

  • 一連のアクティブなレイが、ビューの優先順位付けモードでも常に大きなデータセットになる。
  • エクスパンションカーネルが、連続メモリ内の同一のテクセルをターゲットにするレイを作成するので、準備、トレーシング、シェーディングはすべてコヒーレンシの高いデータで動作する。
  • エクスパンションカーネルが占有率と可視性に対応するので、準備カーネルが格段に単純になり、高速化が実現される。
  • 拡張されたデータセットバッファー、ワーキングデータセットバッファーのサイズが、ライトマップのサイズから切り離される。
  • テクセルごとに照射するレイの数をどのアルゴリズムでも操作でき、自然なエクスパンションが適応的なサンプリングにつながる。

間接光は以下のように、より複雑ではあるものの非常によく似た方法を使用します。

注:最初の反射の環境レイは直接光として扱われます。

間接光では複数の反射を実行する必要があり、各反射がレイを無作為に破棄することがあります。したがって、ホットデータで動作を続けるためにコンパクションを繰り返し行います。

現在採用しているヒューリスティックでは、テクセルごとのレイの量を等しくすることが好まれています。その理由は、出力を非常にプログレッシブにするためです。ただし、これを自然に拡張するのであれば、アダプティブサンプリングによってヒューリスティックを強化することになります。つまり、現在の結果にノイズが多い場所でより多くのレイを照射します。またこのヒューリスティックは、メモリにおいてもスレッドグループの実行においても、ハードウェアのワープ/ウェーブフロントのサイズを認識することで高いコヒーレンシを目指すことができます。

透明性と透光性

GPU ライトマッパーでベイクした ArchVizPRO のアセット

透明性と透光性については多くのユースケースがあります。遭遇したマテリアルが透明または透光している場合、レイを照射して、交点を検出し、マテリアルをフェッチしたら新しいレイをスケジューリングするというのが、透明性と透光性を扱う一般的な方法です。しかし、今回の例では、パフォーマンス上の理由から GPU がレイをスポーンすることができません(上記の「データ駆動型のパイプライン」セクションを参照してください)。また、前もって十分なレイをスケジューリングするよう CPU に適切に指示することはできないので、パフォーマンスが大幅に低下する可能性がある最悪のケースを扱っていることは確かです。

したがって、ハイブリッドな解決策を用いることにしました。上記の問題を解決するため、以下のように透光性と透明性にそれぞれ異なった方法で対処します。

透明性(穴があるためにマテリアルが不透明ではない場合):この場合、レイは確率分布に基づいてマテリアルを通過するか反射します。したがって、CPU が事前に準備するワークロードには変更の必要がなく、引き続きシーンには依存しません。

透光性(マテリアルが、マテリアルを通過する光をフィルタリングする場合):この場合、屈折に関しては近似を行い、考慮しません。つまり、マテリアルが光に色を付けられるようにしますが方向は変えません。これにより、BVH の中で透光性を扱うことが可能になるので、多数のカットアウトマテリアルを簡単に処理し、シーンにおける透光性の複雑さに合わせて非常に適切にスケーリングすることができます。

ところが、おかしな点があります。BVH トラバーサルが順番どおりではありません。

オクルージョンレイについては実際のところ問題ありません。私たちにとって重要なのは、レイに沿って交差する各トライアングルの透光性からの減衰のみだからです。乗法は可換なので、BVH トラバーサルが順番どおりでなくても問題にはなりません。

ただし交差レイについては、(トライアングルが透明であれば確率的な方法で)トライアングルで停止できるようにすることと、レイの原点から衝突地点までの各トライアングルの透光減衰を収集することが必要です。BVH トラバーサルが順番どおりでないことの解決策として私たちが選んだのは、まず交差のみを実行して衝突地点を見つけ、衝突した透光があればレイをマーキングするというものです。このため、マーキングされたレイにはすべて、交差レイの原点から衝突された交差レイまで、追加のオクルージョンレイを生成します。これを効率的に行うため、オクルージョンレイの生成時にコンパクションを使用します。つまり、交差レイが透光の処理を必要としているとマーキングされた場合のみ追加のコストが必要になります。

このすべてを可能にしたのが、RadeonRays のオープンソース的性質です。RadeonRays は、AMD とのコラボレーションの一環として、Unity のニーズに合わせてフォークおよびカスタマイズされています。

効率的なアルゴリズム

本来の性能を最大限に引き出すために何を行っているのか説明してきましたが、これはパズルの最初の部分でしかありません。サンプルレートが高いのは良いことですが、結局のところ本当に重要なのはベイク時間です。つまり、照射する 1 つ 1 つのレイを最大限に活用することが必要です。これは実のところ、数十年にもわたって続けられている研究の根源となるものです。以下に詳細なリソースの一部を紹介します。

Ray Tracing in One Weekend(英語)

Ray Tracing: The Next Week(英語)

Ray Tracing: The Rest of Your Life(英語)

Unity GPU ライトマッパーは、純粋な拡散ライトマッパーです。これは、光とマテリアルのインタラクションを大幅に単純化してくれるほか、ファイアフライとノイズを減衰させるのにも役立ちます。もっとも、収束率を高めるためにできることはまだたくさんあります。以下に私たちが使用している手法をいくつか紹介します。

ロシアンルーレット

各反射で、蓄積されたアルベドに基づいてパスを確率的に削除します。このことについては、Eric Veach 氏の論文(67 ページ)でわかりやすく説明されています。

環境の多重重点的サンプリング(MIS)

分散が大きい HDR 環境では出力にかなりのノイズが生じる可能性があり、望ましい結果を得るためには膨大なサンプル数が必要になります。したがって、まずは分析を行い、重要な領域を特定し、適宜サンプリングを実行して環境を評価するためのサンプリング手法を、組み合わせて適用します。環境のサンプリング以外でも使えるこのアプローチは、一般に「多重重点的サンプリング」として知られており、最初に提唱されたのは Eric Veach 氏による論文(ページ 252)でした。これはグルノーブルの Unity Labs とのコラボレーションの一環です。

多数の光

各反射で、直接光を確率的に 1 つ選択します。そして、空間のグリッド構造によってサーフェスに影響を及ぼす光の数を制限します。これは AMD とのコラボレーションで実現したものです。光選択のサンプリングは品質にとって重要なので、光に関する多数の問題について現在さらなる調査を進めています。

GPU ライトマッパーと HD レンダーパイプライン(HDRP)によってレンダリングされた Unity ロンドンオフィス。1 つのシーンに光が多数使用されている。

ノイズ除去

ノイズは、パストレーサーからの出力でトレーニングされた AI デノイザーを使用して除去しています。詳細については、Jesper Mortensen による Unity GDC 2019 でのプレゼンテーションを参照してください。

まとめ

GPU ライトマッパーでインタラクティブなライトマッピングを提供するために、データ駆動型のパイプラインを用いたり、GPU 本来の性能を最大限に引き出したり、効率的なアルゴリズムを利用したりしていることについて説明してきました。GPU ライトマッパーは現在開発中で、継続的に改良しています。

ぜひ皆さんのご意見をお聞かせください。

ライティングチーム

追記:コペンハーゲンでライティング開発者を募集しています。このブログ記事の内容に興味をお持ちで、新しい課題に挑戦したいとお考えの方はお問い合わせください。

2019年5月20日 カテゴリ: テクノロジー | 15 分 で読めます

Is this article helpful for you?

Thank you for your feedback!