Forward+ Rendering

はじめに

今回の技術ブログ担当のプログラマーの小林です。再度、持ち回りがやってきました。
2度目となる今回は、Forward+ Renderingを取り上げたいと思います。

Forward+ Renderingとは

まず、Forward Renderingとは、どのようなレンダリング手法なのでしょうか。

”従来のレンダリング手法”で、光源の数に応じてジオメトリパスを走らせる必要があり、光源の数に比例してパフォーマンスが低下していく。
(Wikipedia 遅延レンダリングのメリット覧を抜粋)

と、あります。Forward Renderingは、メッシュ単位にジオメトリ、シェーディング処理を行います。そのため、メッシュ、シェイプが、m個あり、ライトがn個あるとすれば、m * n 回のライティング処理を行うことになります。

これを改善するのに、別のアプローチでレンダリングするのが、上記で引用したwikipediaにも掲載されている遅延レンダリング(Defferred Rendering)という方法です。

このレンダリングは、多数のライトを配置しても、パフォーマンス低下を抑えられる
方法のようで、UE4でもデフォルトでこのレンダリングが採用されています。
(詳しくは、他Web、書籍など多数情報が出ていますので、そちらをご覧ください。)

なれば、Forward でも、ライト数の増加させても、処理負荷を稼げるよう改良されたのが、Forward+というレンダリング方法です。(私の認識が、間違えてなければ…)

このForward+ Renderingを、私が知ったのは、2012年にAMD社から発表されたときです。その資料によれば、

視錐台を適度に分割し、その視錐台ごとにライトカリング処理を行い、影響のあるライトとのみシェーディングを行う

というものです。AMD社から発表された資料には、このレンダリングで間接光を表現されていました。当時、Leoデモを見たとき、衝撃を受けたのを覚えています。

デモを作る前に

資料には、

  • 視錐台を適度に分割、分割毎のデータを構築
  • 視錐台とのライトカリング処理(この時、交差、包含するライトのインデックスを保持していく)
  • 作成したライトインデックスリストを参照し、ライティング処理を行う

とあるので、そのように作成していきたいと思います。

実装

視錐台を適度に分割、分割毎のデータを構築


上の図はカメラ視錐台を分割したイメージです。
(図では、4×3しかしてませんが、省略してます)
青線で描いてあるのが、1つの視錘台です。ライトカリングするとき、この錘台とライト(今回は点光源なので、球)で交差判定を行います。

作成時にカメラ座標系であれば、再構築の必要もありませんし、数値も決まってきますので、作成のしやすさから、カメラ座標系の視錐台データを保持するようにしました。
なので、カリング処理時に、ライトの位置をカメラ座標系に変換してから計算する必要があります。

まず、各分割する単位に頂点を作成し、その後、カメラ位置となる座標と面情報を作成していきます。下記は、そのコードです。CPUで構築します。

通常、タイルサイズを16×16 や 32×32 などに分割するみたいなのですが、ここでは、解像度サイズ 1280 x 720 を横16、縦9分割の 80×80 pix のタイルサイズになってます。

視錐台とのライトカリング処理

ライトカリング処理は、コンピュートシェーダを使います。前段で作成した、視錐台データ、ライトデータを、UniformBlockで、バインドします。また、ライトインデックスリストの出力先となるバッファも読み書きが可能な、ShaderStorageBlockで、用意します。

今回扱うのは、点光源のみとしています。点光源の有効範囲を半径とした球と視錘台との交差判定をし、もし、交差、内包していれば、そのインデックスを出力バッファへ格納します。

作成したライトインデックスリストを参照し、ライティング処理を行う

前段で作成したライトインデックスリストをフラグメントシェーダにバインドし、その他のデータも適切にバインドします。まず、計算しようとしているフラグメントが、どの視錐台に属するのかを計算します。その後、対応する視錐台のライトインデックスを取得し、ライティング処理を行います。

点光源の距離減衰ですが、通常の距離減衰(1 / 距離^2)させると、かなり距離が離れていても、わずかながら、光が届いてしまいます。カリングした有効範囲で、確実にFallOffするように、点光源計算を変えています。

負荷

デモシーンには、64個の点光源をランダムに配置し、単純な(上下と横)移動をするようにしています。今回採用したシェーディングは、BlinnPhoneシェーディングなので、シェーディング負荷はあまりないかとも思われたのですが、そこそこ、効果が出ました。アングルによっても負荷は、変化してきますが、概ね2倍以上の負荷軽減がされています。奥行きがあるロケーションであると、やはり、ライト数が増えていくので、負荷が高くなる傾向があります。(これを解消するために、Forward++というのがあります。)

デモ動画を張り付けておきます。
途中で、ヒートマップが表示されますが、内訳は以下の通りです。

ライト数
8未満
8以上
16以上
24以上
32以上
48以上

1タイルのサイズを16×16,32×32などに変更してみると、処理負荷が変わってくるかもしれません。が、今回はやっていません。

Forward Forward+
TopView 8.9ms 1.9ms
View 10.3ms 3.5ms

ですが、想像以上に負荷が軽減されたので、手ごたえを感じています。

TopView


View

上記のデモでは、CrytekSponzaモデルデータを使用しています。
Portions of this software are included under license © 2004-2018 Crytek GmbH. All rights reserved.

終わりに

いかがだったでしょうか? 駆け足ではありますが、個人的にサンプルデモを作成したときのことを書いてみましたが、ご覧頂い方たちに何か得られるものがあれば、幸いです。