lucille の方向性

by syoyo

長期取材旅行では、数多くのレンダラ野郎と出会いました。

やはり「レンダラ野郎同士は惹かれあう」というのは本当だったのか…

とりあえず、世の中では結構なひとたちが(オフラインの)レンダラ野郎を欲している
ことがわかりました。

レンダラを書いていれば、とりあえず 10 年は食いっぱぐれる心配はなさそうです。

レンダラ野郎たちからのご助言をいただき、
これからの lucille の方向性を考えてまとめてみました。

レンダラ側
– モーションブラー
– アンチエイリアシング(マルチサンプリング)
– ERPT などの大域照明
– メモリに入らないようなシーン(やテクスチャ)を扱えるようにするためのメモリ管理機構(MMU)
– 空間データ構造
シェーダ側
– 任意の Du, Dv 関数の実装

モーションブラー

モーションブラーは、特にプロダクションにおいては必須の機能です。
やはりモーションブラーがないと使い物にならないとのこと(プロダクションにおいては)
レイトレベースのレンダラで分散レイトレなどではなく、効率よくモーションブラー
を実現する手法はまだまだ open な問題だと思います。

たとえば 1 フレーム内で画面の端から端まで移動する物体でも
きれいに効率的にモーションブラーがかかるなど。

なにかひらめかないかなぁ…

アンチエイリアシング(マルチサンプリング)

これはとくにピクセルジャギーを減らすためのアンチエイリアシング(マルチ/スーパーサンプリング)と、
シェーダのアンチエイリアシングになります。
やはりプロダクションのものはよく出来ているだけあって、物体のふちなどの
ジャギーもまったく見えません。

結局のところ、シェーダアンチエイリアシングを除くと、
一次レイがヒットする(つまりカメラから直接見える)物体のふちの
ジャギーが一番目に付く要素であるので、これらのアンチエイリアシングには、
一次処理はスキャンラインなどでジオメトリックにアンチエイリアシングする
のが結果画像が一番きれいに出そうです。

もちろんスキャンライン + 2 次反射以降はレイトレというハイブリッドではなく、
できれば純粋なレイトレで一次レイもうまくアンチエイリアシングできないものかな
と思ったりもしますが、8×8 のsupersample/jitter sampling でも物体のふちの部分に見た目で
ジャギーが見えてしまうくらいなので、A-buffer やポリゴンのプレフィルタリングの
ほうが正確でコストがかからずに処理できそうに思えています。

ERPT などの大域照明

個人的には、そろそろフォトンマップ + final gather はやめて、
ERPT や MLT などのように純粋な
パストレーシングベースの大域照明をやりたいと思っています。
フォトンマップ系はすでに多くの実装がありますし、
もちろん(パラメータがうまく調整されていれば)見た目の品質が非常に高いのですが、
レンダラ側の実装者からすると、
density estimation +  final gather というのはあまりエレガントではなく、
けっこう、既存のほかのレンダリング機能(たとえばアンチエイリアシングなど)
との組み合わせが面倒になってしまうという問題があります。

最近は ERPT もそうですが、Importance Resampling も提案されてきていますので、
よりシンプルかつ効率的な大域照明のサンプリング手法が見つかりそうな感じがします。
もちろん、ノイズはまだまだ消せそうにありませんが。

「一次レイはスキャンラインのハイブリッド方法がいい」
と言っているのでこれとちょっと矛盾する点ができてしまいますけどね。

メモリ管理機構(MMU)

えてしてシーン作成者は、レンダリング時のメモリ使用量のことなど考えずに
シーンを作ります。テクスチャも馬鹿でかいです。とくに 2k とかの画面サイズになると、
テクスチャ一枚でもすんげー詳細で容量が大きくなります。
ジオメトリ、テクスチャともに、 OS のスワップファイルのような感じで、
処理する分だけメモリに読み込み、必要がなくなったら HDD に退避、
というようにして一度にメモリに入りきらないようなシーンでもレンダリングできるように
していかなくてはなりません。

こちらは OS や Linux の教科書に書いてあるような単純なもので十分でしょう。
ちょっと懸念するのは、メモリにアクセスする場合に、
常にアドレス変換 & キャッシュのチェックをしなければならなくなるという
ことでしょうか。

mental ray は、64 bit 化でレンダリング速度が 100 倍! と
謳っているようですが、64 bit 化で単純に演算速度がそんなに早くなるわけ
ないので、ここらへんのメモリ管理機構をソフトウェアでやっていたのを、
64 bit アドレッシングなのでする必要がなくなったことによるパフォーマンスアップが
主たる要因のような気がします。

空間データ構造

レイトレの空間データ構造をどうするかは、
主に現在グリッド(ハッシュ)教と kd-tree 教が激しいバトルを繰り広げているそうです。

Arnold やとあるプロダクションのインハウスレンダラでは、
グリッドハッシュ法を用いているとのことです。
Arnold では、グリッドのサイズは 500^3 くらいで分割しているとのこと。

kd-tree は構築などにおける精度的な問題があるとのことで、
結構グリッド(グリッドハッシュ)が用いられているようです。

平原などの巨大なシーンなどは、実制作では分割してレンダリングし、
コンポジットで結合することで、巨大な平原にとっても密で小さな物体が
ぽつんと存在するという、ひとつのセルに膨大なプリミティブが集中してしまう
グリッドの問題を回避しているとのことです。

lucille は一様グリッドですが、前々からハッシュベースのグリッドを試してみたいと
思っていました。今回はそのよい後押しになりそうです。

とはいえ、グリッド教の中にも、kd-tree よさそげ、という声も聞かれます。
特に新興のレンダラには kd-tree 教が多いようです。

任意の Du, Dv 関数の実装

RenderMan シェーディング言語には Du(), Dv(), ( Deriv() ) という、
任意の引数の微分を求めることができる関数があります。

RenderMan シェーディング言語の仕様において、 Du(), Dv() の微分関数を実装できるか
がシェーダ実装のアーキティクチャ設計におけるキモといえるでしょう。
それ以外は力技でだいたい出来るとのこと。

一番良い解は、シェーダの実行をスタックマシンで行い、
REYES 的なマイクロポリゴンベースの
レンダラアーキティクチャにすることです。

もっとも困難な条件(アーキティクチャ)は、シェーダの実行はネイティブコード(DLL)で行い、
レイトレをベースにしているレンダラアーキティクチャです。lucille がこれにあたります。

Du(), Dv() の実装が困難な理由は、この微分関数が任意の関数を引数に取る可能性が
あるからです。たとえば、

Du(x * x + normalize(I)b)

という関数の微分値を取ることができたりするのです。

レイトレは基本的にポイントサンプリングですから、
微分値を求めることは困難です。

レイ微分や 4 本のレイを同時に飛ばす(SIMD)などを
行えば微分値を取ることも出来ますが、
たとえばシェーダをネイティブコードで実行するアーキティクチャの場合、
Du() が呼ばれた時点で微分を計算するために

“どうやって”隣のレイトレのサンプル点におけるシェーダの情報を取得するのか?

という問題があります。

スタックマシンアーキティクチャであれば、
Du のインストラクションが呼ばれた時点での
情報を保持しておいてシェーダ実行を停止、
次のサンプルのシェーディングを行うようにすることで
解決させることができるでしょう。

レイトレ + ネイティブコードのアーキの場合、
現在ではたぶん二つの”解”というものを教えていただきました。
ヒントは pthread の実装および kilauea の spot エンジンです。
とはいえ、特に前者においてはネイティブコードのスタックを
退避させるという、あまりエレガントな解決方法ではないようです。
これについては、なにかしらのエレガントな手法を発見できれば、
きっと論文レベルのものになることでしょう。 
 

…と、結局まとめてみると、レンダラをプロダクションレベルで使うには、
どのような機能が必要か、ということになってしまいました。

とはいえ、プロダクションでの大規模、大容量、安定、高品質、そしてなおかつ
レンダリング時間が短い(1 フレーム 2, 3 時間が望ましいらしい)という要求は、
ある意味レンダラへの究極のストレステストともいえますので、
これがレイトレ + シェーダのアーキティクチャのレンダラで
クリアできるとなると、きっとすばらしい道が開けていけるのではないか
と思っています。

レンダラは突き詰めると奥が深いですね。
やることはいっぱいあって尽きることがありません。

個々最近はあまり昔ほど lucille 自体の実装も進めることができていませんが、
とはいえバージョン 1 の完成が 198 年後の予定ですので、
マイペースで続けていきたいと思っています。

Advertisements