DirectXの話 第161回
頂点単位のライトベイク
19/05/12 up
今回はライトベイクをやってみました。
Diffuseのみ、頂点単位なので結果としてはイマイチ感ありますが、何もないよりだいぶマシかなと。
いつもどおりサンプルはGitHubに置いてあります。
Sample013が対象のサンプルです。
Diffuseライトベイクの実現静的なライトベイクは現在のリアルタイムレンダリングでは基本中の基本となっています。
多くの場合はライトマップと呼ばれるテクスチャにライティング結果をオフラインでレンダリングしておき、リアルタイムレンダリング時にテクスチャをサンプリングする手法です。
これとは別に頂点単位でライトベイクし、頂点バッファとしてカラー情報を渡す手法もあります。
今回実装したのは後者の方です。
ライトマップはテクスチャ解像度を高くすればそのままクオリティを高くしやすいという利点がありますし、多くの場合で頂点単位のライトベイクより高クオリティになりやすいです。
しかし、通常のテクスチャを貼り付けるUV座標とは別にユニークなUV座標が必要ですし、大きすぎるシーンではライトマップのテクスチャ枚数が増えてしまったり、解像度が高ければサンプリング時間がかかったりなどの速度面での不安があります。
これに対して頂点単位のライトベイクの場合は頂点情報としてレンダリングに渡すことが出来ます。頂点数にもよりますが、ライトマップよりデータサイズを小さく、処理を高速にしやすいです。
欠点は頂点数が少ないとポリゴンの割り方がわかりやすくなってしまったり、だからといって頂点数を増やしてしまうとその他のレンダリング、例えばシャドウマップやZ Pre Passが重くなったりします。
また、LODモデルにも対応する場合は別途の頂点情報が必要になるなどの問題もあります。
現在のライトベイク関係ではこのあたりがネックになるためにあまり使われないのかもしれません。
とはいえ、ライトマップよりも実現しやすい手法ですので、まずライトマップを実装する前に頂点ベイクを実装してみようと思った次第です。
実装について
私のサンプル用ライブラリのglTFメッシュクラスはサブメッシュごとに頂点情報を持っています。
そのため、まずはサブメッシュごとにカラー用の頂点バッファを生成します。
このための構造体は VertexLightColor です。
頂点カラー用のバッファは精度を考えてRGB32ビットフォーマットを使っています。
シェーダコードは vertex_bake.lib.hlsl にまとまっています。
基本的にはSample011で使用しているパストレーサーとほぼ同じですが、RayGenerationShaderの最初に飛ばすレイは始点を頂点座標、方向を頂点法線を中心としたランダムな半球方向としています。
DispathcRays() でレイトレース開始する場合、通常は画面サイズで行うわけですが、今回は出力バッファが画像ではなく頂点バッファですので1次元のみ使用しています。
DispatchRays() では最大3次元でレイトレースが可能ですが、1次元でいいので Width に頂点数を与えています。Height と Depth は1です。
当然、シェーダコードで使用しているのも DispatchRaysIndex().x のみです。
あと、シャドウについてはSample012と同様にレイトレースで行っています。
こちらは画面ピクセル単位での計算なので、カメラを動かした場合は更新されます。
その他のパラメータは頂点ベイクについてのパラメータとなるので、変更すると頂点ベイクのレイトレースが最初から行われます。
さすがにバウンス回数を増やすと重くなりますが、512フレームで終了するのでそれ以降はカメラ動かすだけなら更新されません。
速度についてですが、バウンス回数が少なければそれほど重くない印象です。
流石にリアルタイムで使用するには厳しい速度ですが、開発時にリアルタイムに更新するように使うのはありじゃないでしょうか?
ただ、1フレームで全てを更新するやり方ではなく、一部の頂点のみを更新することで開発時に簡易ライトベイカーとして使用できるような気がします。
特に現在は1フレームで処理していますが、非同期コンピュートで複数フレームに渡って処理するのもありじゃないかなぁと。
実際、UE4のレイトレにはその方向での実装に期待している声もありますので、開発イテレーションを高速にするためにDXRを使うのはかなりいい手法じゃないでしょうか?
追記
思いっきりプログラムがバグっていたので修正しました。
結果として、絵がだいぶ変わってます。
何がおかしかったかというと、このSponzaさんはレンダリングする際に20倍してるので、ライトベイクする際にも各頂点を20倍しなければならなかったわけです。
ええ、まあ、やり忘れてました。
つまり、今までのライトベイクは見た目に対して1/20のサイズのミニチュアの頂点座標に対して行われてたわけです。
この修正によってちゃんと赤いカーテンの上の部分とかに赤い間接光が出たりするようになりました。
多分これでプログラムはあってるはずです。
で、ちょっと問題なのが床。直接光が当たっている場所以外は真っ黒です。
原因はやっぱり頂点単位だから。
この床面の頂点は以下のようになってます。
頂点が部屋の端の方にしか存在しないわけで、ここまで光はほとんど届かないわけです。
結果として真っ暗になってしまうので、頂点単位でベイクするならある程度の頂点分割は必要になります。
特に重要な場所なんかは予め頂点を細かく割っておく必要もあったりするので、モデラーの負担が高くなってしまいますね。
まあ、今回はサンプルなので分割したりはしてませんが、実装しようと思っている方は注意が必要でしょう。