2023年2月1日水曜日

Cascade Shadow 2

以前カスケードシャドウを作ったけど、ベースはマイクロソフトのサンプルを元に自分のライブラリに移植した。
サンプルにはオブションがいくつもあり、その中から自分が使う部分のみを残して移植した。それでも結構なコード量で、半分以上は理解ができていない状態だった。

HLSLの魔導書にもカスケードシャドウについて書かれていて必要最小限の実装になっている。
今回はこれを改造して、自分のライブラリに移植する。


分割エリアを定義


最低限の実装なので魔導書では固定値で定義していた。

カメラに設定されているNearZ(1.0f)とFarZ(10000.0f)を取り出して、中間の分割位置をパラメータで指定する実装になっている。

このままでは色んな場面に対応できないので配置するメッシュからNearZとFarZを計算して、分割位置はパーセント指定するように修正した。

まず視線(カメラからフォーカス)ベクトルを準備する。
メッシュロード時に中心と半径はメッシュデータに保持しておくようにしてあるので、それを利用して、カメラからメッシュの中心までのベクトルと視線ベクトルの内積を計算。
結果に半径を足したものがMaxFarZを超えるなら更新、半径を引いたものがMinNearZよりも小さければ更新する。
最後にカメラのNearZよりも小さければNearZは補正する。


分割エリアを描画するためのライトビュープロジェクション行列の計算


この部分がカスケードシャドウのいちばん重要な部分だと思うが、難しくてよくわからない。
・ライトカメラのプロジェクション行列(XMMatrixOrthographicLH)とビュー行列(XMMatrixLookAtLH)からライトビュープロジェクション行列を求める。
・分割した領域の視錐台の8頂点を求める。
・頂点をライトビュープロジェクション空間に変換
・各頂点の最大、最小値を求めてクロップ行列を求める。
・クロップ行列にライトビュープロジェクション行列を乗算。
・出来上がった行列をシェーダに渡す。

仕組みとしてはこの行列でシェーダ内の頂点を変換して、XYが-1~1の範囲内に収まっていれば、その分割区間内ということが判断できるらしい。


分割区間別の深度バッファを用意


魔導書では一番近い場所の深度バッファから、遠くに行くに連れ縦横半分のサイズの深度バッファを複数枚を用意していた。
これを1枚の横長に分割数分拡張した深度バッファで処理することにする。

深度バッファ

こうすれば最終的なシェーダにわたすリソースは分割数が変わっても1つ分固定になる。


影描画


魔導書のシェーダはフォワードレンダリングで、頂点シェーダで4領域分の頂点計算をして、ピクセルシェーダに渡していた。
これは気持ち頂点計算が無駄な気がした(描画対象の領域が手前だとしても奥の分まですべて頂点計算をしてしまう)のと、この方法をディファードレンダリングではできないので、ピクセルシェーダ側に頂点計算を持っていった。
その後ディファードレンダリングで実装し直してみたら、今回はあっさりうまく行った。

影なし

影あり

カスケードデバッグ


問題点


つなぎ目のアーティファクト


デバッグ表示

つなぎ目にアーティファクト

たまに一番手前と次の影の境目に点線が表示される。
深度バッファギリギリまで参照していることが原因なので、一定範囲を超えたら一段階広い深度バッファを参照するように修正した。


謎の黒点


謎の黒い点

ディファードレンダリングで描画するように修正したら、黒い点が表示されるようになった。
原因はBRDFの計算の中にあるフレネル項の部分で、pow(1-cos, 5)という部分のcosの値が1を超えている場合、値がnanになり色が黒になっていた。
cosをmin( 1, cos )にしてpowの引数がマイナスにならないように修正した。


今後の課題


・ソフトシャドウ
・つなぎ目のぼかし

魔導書のカスケードシャドウのシェーダはすごくシンプル。
先にマイクロソフトのサンプルで実装経験があったので、今回はいろいろ改造することができるようになった。
また別の機会に改良することにする。

0 件のコメント:

コメントを投稿