9月7日(土)~9月8日(日)に猪苗代湖で開催されたレイトレ合宿7に参加しました。
自作のレンダラーでこんな画像を 60秒の制限時間 でレンダリングして4位をいただきました!
ちなみに4K解像度(3840x2160)です!
事前に本番環境で動作確認できなかったこともあり、よく見ると意図しないアーティファクトが発生しているのですが、許容レベルに収まったのはラッキーでした。
レイトレ合宿とは
レイトレ合宿は完全自作のレイトレーサーを走らせて画像の美しさを競うイベントです。
参加者はレンダラーを自作する必要がある!というだけで面白いイベントなのですが、レンダリングの制限時間が毎年どんどん短縮されているのも注目ポイントです。
第1回のレンダリング合宿では制限時間が1時間だったのですが、第7回となる今年は60秒制限でした。
この制限時間はレンダラーを起動してから画像を保存するまでの時間なので、シーンの読み込みからレンダリングをすべて含めて60秒で完了させなくてはなりません。
そのため、参加者はあらゆる手段をつかって、レンダラーの高速化に本気で取り組む必要があります。
パストレーシングの高速化のアプローチとしては、サンプリングを効率化する、BVHなどの構造をつかってシーンとの交差判定を効率化する、ノイズを軽減するためにデノイズを行う、などが挙げられます。
パストレーシングを使わないといけないルールは無いのですが、近年のレイトレ合宿ではパストレーシングが人気です。 今年のレイトレ合宿では、Stochastic Progressive Photon Mappingを実装したtabochanさん以外は全員パストレーシングだったと記憶しています。
また、複数コアのCPU・複数のGPUを利用したり、メモリのキャッシュ効率を上げてマシンスペックを最大限に活かし切るというのも、実はかなり難しい課題だったりします。私は今年は複数のGPUをうまく使えませんでした…
参加者はプロダクションレンダラーの開発者やコンピュータグラフィック分野の研究者などのプロの人から、私のように趣味でレンダラーを開発している人まで様々です。
レイトレ合宿の参加者のレベルが年々向上していて、特に今年は技術的にもアートセンスにも秀でた作品が多い中、4位と上位に食い込めて本当に嬉しかったです!
前回までのレイトレ合宿の参加レポート
ちなみに私は今年で4回目の参加になります。過去の参加レポートはこちらです。
- レイトレ合宿6 参加報告 Part2(当日編) | gam0022.net
- レイトレ合宿6 参加報告 前編(準備編) | gam0022.net
- レイトレ合宿5‽に参加して、Rustでパストレーシングを実装しました! | gam0022.net
- レイトレ合宿4!? に参加しました! - gam0022のブログ
Redflash Renderer
Redflash というGPUレンダラーを開発しました。
Redflash は NVIDIA® OptiX 6.0 上で実装したパストレーシングによる物理ベースレンダラーで、ポリゴンと レイマーチング が混在したシーンを一貫した描画ができます。
GitHubにソースコードを公開しています。
こちらはアーティファクトなしの想定のレンダリング結果です。レンダリングは30分です。クリックすると非圧縮形式の画像になります。
別視点からのレンダリング結果も紹介します。
発表資料
自作レンダラーの紹介スライドです。
レイトレ合宿の参加者にとっては常識だと思われる箇所の説明を省略してしまったので、ここから簡単に補足解説をします。
NEEとMISによるサンプリングの効率化
この2つは「パストレーシングのサンプリングを効率化する」ための非常に有名なテクニックです。
- Next Event Estimation (Direct Light Sampling)
- Multiple Importance Sampling
Next Event EstimationはよくNEEと省略されて呼ばれます。 光源が小さいシーンでは、BSDFによる重点的サンプリングだけではなかなか光源にヒットしません。 そのため、短い計算時間ではノイズだらけの結果になってしまいます。 また、BSDFの分布と光源の方向が異なる場合、むしろBSDFによる重点的サンプリングによって悪化するケースもありえます。 そこで、光源の表面上の点を明示的にサンプリングして光転送経路を生成することで、効率的なサンプリングを行うテクニックがNEEです。
Multiple Importance SamplingはよくMISと省略されて呼ばれます。 MISは複数のサンプリング戦略を組み合わせることでサンプリングの効率を向上するテクニックです。 具体的には「BSDFによる重点的サンプリング」と「NEEによるライトのサンプリング」の2つの戦略の結果を適切なウェイトで組み合わせることで、サンプリングの効率を向上します。 それぞれのサンプリング戦略が得意な部分だけウェイトを大きくすることで、分散を抑えて効率的にサンプリングができるようになります。 例えば、光源が大きくてroughnessが大きいような「BSDFによる重点的サンプリング」が得意なケースなら「BSDFによる重点的サンプリング」の重みを大きくして、 逆に光源が小さくてroughnessが小さいような「NEEによるライトのサンプリング」が得意なケースなら「NEEによるライトのサンプリング」の重みを大きくします。
NEEやMISについては、レイトレ合宿の参加者でもある @Shocker_0x15 さんが日本語で詳しく記事を書かれています。
OptiXとレイマーチングの統合
OptiXには独自のプリミティブを定義する仕組みがあるため、OptiXとレイマーチングの統合はそこまで苦労しませんでした。
IntersectionProgram
と BoundingBoxProgram
としてレイマーチングによる交差判定とAABBの定義をCUDAで実装するだけでできました。
詳細はレイトレ合宿アドベントカレンダーの記事で既に紹介しているので、気になる方は読んでみてください。
衝突判定の高速化
BVHの構築はOptiXが自動でやってくれるので、ポリゴンの衝突判定は特に高速化しませんでした。 なんとOptiX 6.0ではRTXに対応しているので、RTX 2070ではハードウェアをつかって高速化な衝突判定ができました!(が、本番環境はRTX非対応でした…)
一方でレイマーチングの衝突判定については自力で行う必要がありました。 シーン全体を1個の距離関数で表現したため、BVHなどの構造では衝突判定の高速化が難しいためです。
距離関数の軽量化
レイマーチングでは1本をレイを飛ばすごとに数百回も距離関数を評価する必要があります(今回のレンダリング結果は300回)。
レイマーチングの負荷は距離関数の複雑さに比例するので、距離関数の軽量化は効果が大きい最適化でした。
今回はMandelboxという伝統的なフラクタル図形を距離関数として用いたのですが、
メジャーなMandelboxの実装では sphereFold
という操作で分岐があったりとGPUには高負荷なものでした。
sphereFold
のどちらの分岐に入るかはMandelboxのパラメータによって決まるので、
一部のパラメータを削除したり、パラメータの範囲を狭めることで分岐を削除して処理を簡略化しました。
レイマーチングの衝突判定の精度のLOD
まず速度面では、カメラに近い部分は細部まで正確に衝突判定をする必要がありますが、遠い部分は大雑把でも問題にならないため、LODが有効でした。
品質面でもLODが必要でした。 Mandelboxの距離関数は厳密には Distance Estimator(距離推定器)と呼ばれるものです。 通常の距離関数は表面までの距離をぴったりと計算できるのに対して、 Distance Estimatorは有限のイテレーション回数では表面に漸近しても、距離0になりません。
そのため、適当な距離 eps で衝突とみなして計算を打ち切る必要があります。 また、eps を小さくすると、より細かい detail まで可視化できるのですが、 遠景まで同じ eps で処理すると高周波成分が現れて、まるでMipMap OFFのような汚い結果となります。
このようにレイマーチングの高速化と品質向上の2つの目的ために、衝突判定の精度のLODが必要でした。
LODはカメラからの距離に応じて動的に eps を決定することで実現しました。
レイマーチングではレイを漸近的に進めるため、レイが進んだ距離を必ず計算する必要があります。
このとき レイが進んだ距離 = カメラからの距離
となるため、eps は簡単に決定できます。
具体的にはレイが進んだ距離に定数を乗算したものを eps として扱うようにしました。
今回の提出シーンのように同じレイマーチングのオブジェクトの近影〜遠景がひとつのカットで混在していても、綺麗に描画できるようになりました。
また、カメラを近づけると実質無限に細部が現れるようになりました(フラクタル図形の特徴)。
1回のlaunchでなるべくたくさんサンプリングする戦略
OptiXでパストレーシングを実装する場合、通常は1回のlaunchでパストレーシングの1サンプリングを行うように実装するかと思います。
ところが、launchにも多少のオーバーヘッドがあるため、手元のPCで実験した結果では、
sample_per_launch
(1回のlaunchごとのサンプリング回数)を大きくすれば大きくするほど60秒あたりのサンプリング回数を増やすことができました。
そこで、最初の4サンプリングでマシンの性能をベンチマークして時間切れにならない最大の sample_per_launch を決定するような戦略をとりました。
Deep Learning Denoising
ディープラーニングをつかったデノイザーの性能が驚異的に良くて驚きました。
左が10spp(sample per pixel)の結果で、右がデノイズした結果です。
かなり少ないサンプリング数でも非常に綺麗にデノイズができました。 特にLucy像の拡散面の部分などは効果が絶大でした。
Deep Learning DenoisingはOptiXの標準機能を利用しただけなので、詳細については私は理解していません。
レンダリング結果とnormalとalbedoのバッファを与えてやると、綺麗にデノイズした結果を出力してくれました。
速度面でも優秀で4K解像度でも1.4秒程度でデノイズが完了しました。
まだリアルタイムレンダリングには速度的には使いづらいかもしれませんが、これまでの Bilateral Filter や Non-local Means Filter を遥かに凌駕する性能なので、改めてレンダリング技術とディープラーニングの親和性の高さを実感しました。
これからの時代はグラフィックエンジニアもディープラーニングも勉強しなくては!と思いました。
RT_BUFFER_GPU_LOCAL による最適化
Deep Learning Denoising用にalbedoとnormalのバッファを生成したところ、合計で5枚もバッファが必要になりました。
バッファの読み書きもそれなりに重たい処理なので、対策を行いました。
createBuffer の第1引数に RT_BUFFER_INPUT_OUTPUT
を指定したところ、なんと13.6%くらい高速化しました。
ディスプレイにバッファを同期するかのオプションのようでした。 ウィンドウにバッファを表示する場合はこのオプションをつけると描画結果が同期されなくなってしまいますが、 CUIモードで起動するときには同期は不要なので、このオプションを有効にすることで大幅に性能向上できました。
反省:スケジュール面が厳しすぎた
ここまでがレンダラーの紹介でした。ここからは振り返りを書こうと思います。
最大の反省点はスケジュール面が厳しすぎたことでした…
OptiXのキャッチアップを含めて約一ヶ月で開発したのですが、流石に無理なスケジュールだったと思います。
8月は仕事のプロジェクトの追い込み時期とCEDECの登壇準備が重なって、なかなかレンダラー開発の時間を捻出できず、 睡眠時間と生活を削りすぎたため、体力的にも精神的にもかなり限界でした…
そろそろ若さで無茶をカバーできない年齢になってきたので、締め切り直前になって慌てて開発するのではなく、 日頃から継続的にレンダラーを開発することが大事だろうと思います。
余談:シーン作成はUnity
時間がなくてシーン編集機能を実装できなかったので、 Unityで事前に距離関数のパラメータ調整や光源の配置を行ってシーンのイメージを固めてから、後からパラメータを自作レンダラーに移植しました。
結果的には納得できるシーンを作成できたので、作戦は成功だったと思います。
UnityのHDRPでレイマーチングを行うのには@kanetaaaaaさんのRaymarchingInHDRPを利用させていただきました。
カッコいいシーンを大量に作れたので、ついスクリーンショットをたくさん撮影してしまいました!
Unity HDRP + Raymarching by @kanetaaaaa を試してみました!
— がむ (@gam0022) August 20, 2019
カッコいいシーンが無限に作れてしまう😍
これは凄いです🙏#unity3d #raymarchinghttps://t.co/EK6JsHpTBZ pic.twitter.com/ZueP2hfzet
今後やりたいこと
シーン編集機能がほしい
現状はGUIでカメラ操作だけができます。
シーン編集に関して、上で紹介したようなUnityからパラメータを移植する方法だと最終的なルックの確認のイテレーションの高速化がしずらいので、 Redflash自体にシーン編集機能を実装したいと思っています。
距離関数を定義したCUDAファイルのホットリロード機能を実装したり、 CallableProgramをつかって距離関数を差し替え可能にしたいです。
他にも距離関数やマテリアルのパラメータをインスペクタで編集するなどは最低限欲しいなと思っています。
あとはオブジェクトの配置などをマニピュレーターでできるようにしたいですが、どうしても実装工数がかかるので、どういう感じが良いのか思案しているところです。 DCCツールから直接シーンを出力する形式だと、距離関数の扱いに困るため、なかなか難しい問題です。
リファクタリング
CallableProgramでBSDFを入れ替えられるようにしたり、ファイルを適切に分割したりして、もう少しコードをリファクタリングしたいです。
PNGのエンコード時間の短縮
PNGの保存には stb_image を使わせていただきました。
ただし、4K解像度となるとPNGの保存に1.7秒前後の時間が必要でした。
制限時間が短くなると、PNGの保存やGPUの初期化に要する時間が相対的に増えて、レンダリングに使える時間がどんどん短くなってしまいます。
そのため、PNGの保存やGPU初期化の高速化は、来年以降のレイトレ合宿では重要な課題になるだろうと予想しています。
複数GPU対応
OptiXをつかっても複数のGPUをうまく使ってくれなかったので、独自の仕組みで対応が必要のようでした。
単純な解決策として、プロセスを複数起動して最後にレンダリング結果をマージする方法が考えられますが、ちゃんと検証をしたいです。
フルスクラッチGPUレンダラー
去年まではGPUインスタンス勢は1人だけだったのですが、今年は7人(レンダラーが動かなかった人も含む)もいました。
GPU勢にも、私のようにOptiXなどのレイトレーシング用のフレームワークを使う勢と、フルスクラッチ実装勢で別れていました。
フルスクラッチ勢からは「OptiXでは作法がきっちり決められているのがなんとなく嫌だった」「GPU向けのBVH実装をしてみたかった」といった意見を聞きました。
たしかにRTXなどの登場によって交差判定がハードウェアに移りつつある今だからこそ、勉強する価値はあるのかもしれません。
感想
今年は忙しいからレイトレ合宿に参加できるか怪しいと思っていましたが、なんとかちゃんとレンダラーを提出できて良かったです。
思えば「レイマーチングとポリゴンが混在したシーンをパストレーシングしたい」というのは3年前のレイトレ合宿4のときに本当は実現したいテーマでした。
当時はレイトレ初心者だったので、ナイーブなパストレーシングで精一杯で高速化方法が分からず、 普通にレイマーチングを組み合わせたら激重になってしまい、5時間くらいかけないとまともな絵が出ない状態でした。 結局、パストレーシングを諦めて疑似手法でAOやシャドウを計算してなんとか見れる絵を提出しました…
3年間で学んだ知識でようやくやりたいことを実現できて本当に良かったです。過去の自分に勝利できました。
レイトレ合宿は自身の成長や糧となる機会を与えてくれる、とても良い合宿勉強会だなと改めて感じました。