Raytracing Utility

レイトレ合宿3!!!アドベントカレンダー第八回目の記事です。


こんにちは!レイ、飛ばしてますか?

今回はユーザーにとってはあまり重要ではないけれども、レイトレーサーの開発者にとって便利な機能について基本的なものを軽く紹介していきます。「あ、そういえばアレまだ入れてないな」と気がつくことがあればうれしいです。

アサート

アサートはassert()よりも自分で作ったものに置き換えた方が良いと考えています。”_asm int 3”でもいいのですが、スタックトレース機能と、ビルド毎(Debug,Release,Releaseのアサート生きてる版)に別の動作をする機能位はあった方がいいです。

出来たアサートは事前条件を確認できるあらゆるところにしつこい位おきましょう。「ローカル座標系ではzは0以上になるはずなのに0を微妙に下回る場合がある」とか「正規化せずに渡されてくる可能性がある」など、改めて見てみると怪しいところはたくさんあるはずです。

_controlfp()

先のアサートだけだと「pdfが0になって帰ってきて静かに0除算が発生しうる」ような場合に対応することができません。割り算の前に考えられる全てにアサートを置くのも現実的ではありません。_controlfp()を設定しておけば、浮動小数周りのエラーハンドリング(INF/NANの発生、0 divなど)が可能になります。

ロギング

出力のレベルは次の三段階は最低でも用意しておくとよいと思います。

  1. 順調に進んでいることを示すログ出力
  2. 起こってほしくないことだけど起こりうること(Warning)
  3. 起こるともう何もできないこと(Error)

ロギング関数は、コンソールだけでなく、OutputDebugString、ログファイルなどにも同時に出力しておくと後で追跡するときに楽になります。

統計カウンター

レイトレーサーの内部で各種の統計量を測っておくと、実装ミスに気がついたり、何らかのコーナーケースに当たってしまったことを察知できるようになります。私は次の二種類を用意しています。

  1. 単純なカウンター。総カウントと、終了時に1秒毎にどれほどカウントアップされていたのかを最後に出力するもの。 「レイは結局何本出ていたのか」「環境マップと交差したプライマリレイはどのくらいあったのか」など。
  2. 統計カウンター。平均、最大、最小、分散などの各種統計量をカウントしておくもの。 「一回のRay-BVHでAABBの交差チェックは何回していたのか」「Adaptiveなサンプリングはどのように分布していたのか」「パスの長さの分布」など

とても深いループ内部にあるようなものが多くなるので、リリース時は完全に切るとしても出来るだけ軽く作った方がいいです。InterLocked系でやるよりも単純にスレッド毎にもたせた方がいいですし、キャッシュもキャッシュラインを丸々占有するカウンターを持っておいたほうがいいです(False Sharing対策)。

法線/深度/UV/BaseColor/AO/AABBレンダー

レンダリング方程式を解くまでには、確実に正確にやらないといけないことがたくさんあります。開発序盤だと、自作した全ての機能が信用できないので、全てを作り切ってから確認、デバッグするのは効率的ではありません。最初からインテグレーターを書くのではなく、特定の機能の動作をチェックする特別なインテグレーター(レンダー)を序盤で用意しておくとそのあとの開発が楽になります。

HDR出力

レイトレ合宿では、bmp/pngのLDR形式で出力することになっていますが、

  • 目デバッグができない。トーンマッピングがまずいのか、バグっているのか区別がつかない
  • 高輝度中/低輝度中で静かに起こっているバグに気がつかない。
  • そもそもトーンマッピングは本来はレンダーの仕事ではない

などの理由から開発中はHDRでの出力をお勧めします。

プレビュー/HDRリロード/既存ビューワーに投げる何か。

結果を確認するのに毎回出力画像を見に行くよりも、プレビュー用のウィンドウか、出力画像を自動でリロードしておく何かか、既存のビューワーに投げる機能を用意しておくと、開発が楽になります。

  • HDRイメージを表示するにはトーンマッピングの実装、そのパラメーターの調整が必要になる
  • 特定部位の拡大ができない
  • 実装が結構めんどくさい

などの理由からプレビューウィンドウよりも出力イメージを勝手に何らかのビューワーに投げる機能の方が少し分があるかなと思っています。

任意レイ追跡機能

開発を続けて行くと、大部分のパスではうまくいくのだけれど、たまに特定のパスだけ周囲に比べて異常に高い輝度を返すようなパス(firefly effect)が見つかったりします。これをデバッグするのは困難を伴います。

  • 「F5デバッグ」するには長大な時間がかかる。
  • 固定の閾値を設けて「異常値」を検出する方法では「周囲比べて異常に高い」のか「最初から輝度が高い領域」かの区別がつかない。
  • そもそも毎回同じ状況が発生するとは限らない。

一つの解決方法としては次のような方法があります。

  • 非決定的な乱数を排除する。(std::random_deviceなど)
  • ジョブ/タスクの配分を決定的なものに切り替えられるようにする。(もっとも単純なのはシングルスレッドにすること)
  • firefly effectが発生するピクセルを記録しておく
  • そのピクセルを処理する時の乱数の内部状態を出力する
  • 再度動作せせるときは乱数の内部状態を先に出力したものに合わせて、問題のピクセルから処理を始める

シーン設定ファイル自動リロード/任意領域のみレンダリング

最後はアセット調整用の機能です。 シーン設定の調整をしているときに、毎回「シーン設定書き換え → 実行 → 書き換えたアセットの部分を確認」をするのは効率が悪いです。シーン設定ファイルが更新されたら自動でそのファイルを読み直し、その中で指定された領域のみをレンダリングするような機能を入れておくと、アセットの調整が捗ります。

…これくらいでしょうか。 「こんなのもあるよ」「これないと難くない?」という意見があれば勝手にこの記事に取り入れていきます。

では楽しいレイトレーサー開発を!!!