元記事:
Blender_traces.txt(Ton Roosendaal氏作)
Blenderレイトレーサについて1. 概論これは眠っていた旧NEOGEOのレイトレーサをひっぱりだし、ほこりを払い、非常に多くの部分を改良し、レンダリングパイプラインの残りに上手く統合したものです。
Blenderにレイトレーサを(再び)追加したのは主に前回の考察が元になっています ― 遅すぎて使用に耐えない ― この事実が90年代早期には認められましたが、今はもう10年過ぎています! さらにRendermanが1年以上ほど前にこれを採用したことが、私たちがレイトレースをアニメーションレンダリングのために使用できる代替案として考慮するきっかけとなりました。
それでも、実装はまだ基本的なものにとどまり、ターゲットをBlenderに統合し、アーティスティックなコントロールを目的としてしていました。この新しいオプションはアーティストに、3Dグラフィックスの作成により多くの自由をもたらしますが、究極のリアリズムを得るには、外部のYafRayを使用するのがいい選択です。私たちはこの両方により、Blenderを90年代から脱出させ、この新しい2000年代へしっかりと根ざすことができると信じています!
Blenderでは、Render Buttons Window(F10)内の新しい"Ray"レンダリングオプションと、Lampの"Ray-shadow"もしくはMaterialの"RayMirror"の値を設定することにより利用できます。
Lampではすべてのタイプで影が有効になり、Sun・Hemiでは平行投影になります。また、今もSpot用として実行可能なShadow Buffersの隣で上手く共存しています。
最初の試みであるソフトシャドウ(エリアライト)も同様に追加されています。現在のリリースバージョンでは平面のエリアライトのみが動作し、ボリューメトリックバージョンは作業中です。ソフトシャドウレンダリングは現状では非常に遅く、さらなる改良が必要です。
OSAでレンダリングするとき、レイはBlender共通のアンチエリアス方法に従い増幅され、ずらされることになります。これにより、影の領域の縁がシャープで正しくアンチエリアスされることになります。サンプリング量が固定な(適応性がない)ため、エリアシングが形状上のMirrorとRefractionにピクセル毎に高い歪みとなって表れる可能性があります。
2. 技術資料Blenderはすでに高速で信頼性の高いスキャンラインレンダリングシステムを持っているため、レイトレーシングの追加は簡単にMaterialのオプションとして作られ、そのため、「選択的」レイトレースと呼ばれています。スキャンライン(とZバッファ化された)情報はここでは「最初のヒット」とし、そこから環境内の光もしくは色情報を集めるために任意でレイのトレースを行うことができます。
面と光線の交差判定を効率的に行えるよう、私はオクトツリーを選択しました。これは空間を再分割するシステムで、レンダリング環境全体を小さな立方体に(再帰的に)分割します。最初はひとつの立方体から、8、64、512等の立方体を得ることになります。それぞれの立方体(ノードとも呼ばれます)には立方体の中に位置するFaceへの参照が格納されます。その後、レイがヒットしたすべての立方体をトラバースすることにより、実際の近辺のFaceのみをチェックするだけですみます。
Blenderは'トップダウン'でトラバースする方法を使用しており、これはオクトツリーの分割を決められた最大値(現在は64×64×64が最大)で止めることを意味します。その後、ブレゼンハムの線描画と似たアルゴリズムでレイが通ったすべての立方体を順番にたどることができます。これは一般的にはDDA(Digital Differential Analyzer)もしくはこの場合だと3DDAとして知られています。伝統的なブレゼンハムの方式との主な違いは、このケースでは交差している
それぞれのピクセル(立方体、ノード)が見つかる必要があることです。もしあなたがこの方法で線を描画しようとしたとき、2ピクセルの幅の線として表れるでしょう。(訳注:ブレゼンハムでは線がそれぞれのピクセルを通過するとは限りません)
オクトツリーのサイズを固定した場合の主な欠点は、一つのノードに100枚(1000枚)のFaceが居座る形に簡単に陥ることです。これはそれぞれのレイについて、すべてのFaceとのレイとFaceの交差判定のコストが高くついてしまうとことを意味します。それがレンダリングサイズをできるだけ小さくする必要がある理由の一つです…(注:Materialのプロパティ"Traceable"をOFFにすることにより、Faceがオクトツリーに挿入されるのを防ぐことができます。これは影が投影されるためだけに必要な床や壁などに非常に有効です)。
この問題を解決するひとつの策として、私はそれぞれのオクトツリーのノードに別のオクトツリーのレベルを追加することを考えました。これははるかにシンプルな方法で実装されており、また、最初のBlenderエンジンの衝突判定コールのスピードアップに成功しました。私はこれを"オクトツリー値"方法と呼んでいます。
オクトツリーのノードにFaceの参照を格納する代わりに、現在はノードの実際の「アドレス」がFaceに格納されています。これにより、X、Y、Zの3つの整数値で非常に簡単に表現できるようになります。ここでは二進数の表記が使用されており、例えば「立方体のアドレス」X=2は01000000(8ビットの解像度と仮定)と表現されます。
レイがこれを通過するとき、これらの値は同様に計算できます。値のX、Y、Zの「オクトツリー値」のシンプルな二進数の比較は実際の交差判定をするとき、すばやくFaceの排除もしくは受け入れを決定するのに十分です。
現在の実装では、それぞれのオクトツリー立方体は16×16×16のオクトツリー値のグリッドでさらに分割されています。最初のテストではこの高速な排除により、80-95%の交差判定ができる可能性を見せました。
更に下記によりスピードアップされています。
- 最初のヒットチェック:もしシャドウレイが見つかっていれば、続くシャドウのチェックは以前みつかったFaceを最初にチェックします。
- 四角形を1回の交差判定コールに使用可能に(二つの三角形を処理する代わり)。
- オーバーサンプリング中の統一性の検査:レイがオクトツリー中の空ノードだけ(最初の2ノードを例外として)を訪れた場合、続く同じ先端・終端のレイは最初の2ノードのみをチェックします。多くのシーンでのテストではこれに付随して起こるエラーは1/1000以下で、すべてにおいてオーバーサンプリングが視覚化されませんでした。
3. 実装スキャンライン・Zバッファを元にしたレンダリングシステムはレイトレーサが要求するものとは全く違うアーキテクチャです。Blenderのスキャンラインシステムは、最初に各ピクセル毎ですべての可視ポリゴンを検出し、その後シェーディングの計算を行います。レイトレーサでは、可視チェックと典型的なレンダリング動作が混在し、再起的に実装されています。
Blenderのレンダラーの一番の問題点は、多くの静的かつグローバルな変数を使用していることでした。これはスキャンラインではOKですが、再起的なレイトレースに使用するのは不可能です。コードのこの部分をクリーンアップするのが作業の大部分でしたが、結果的には、レンダリング関数一式が完全に綺麗になりました。
他の恩恵の一つに、複製され完全に分離されていたBlenderの'Unified'レンダリングコードが、現在はほぼ再統合されました。現在Blenderへシェーダもしくは機能を追加すると両方のシステムが自動的に恩恵を受けます。
まだすべきことに、願わくばやがて公開されるだろうリリースのひとつが、レンダリングコードを完全にローカライズし、(例えば)UI中のプレビューレンダリングのより多くの進化や、更にマルチスレッド化ができるようにしたいと思います。
Blenderへのより洗練された'シェーダ'システムの追加、そしてこれに対するPythonによるアクセスについても、現在、実行可能な状態に近づきつつあります。
すべてのトレーシングに関するコードはblender/render/intern/source/ray.cファイルにあります。このファイルは大まかには三つのパートに分けられます。
- 初期化とオクトツリーの構築
- オクトツリーのトラバース
- レイのトレースとレンダリング
学習に便利なサンプルとして、ray_trace_shadow_rad()関数があります。これはテストのためだけに追加されたもので、「ラジオシティ」のレイトレース(別名アンビエントオクルージョン)の可能性をチェックするためです。
トレーサ設定の手順は以下の通りです。
1. Isect構造体の初期化- DDA_SHADOWもしくはDDA_MIRRORのどちらかを'mode'に設定します。前者のケースではトレーサは最初に衝突が見つかったものを返し、後者は実際の交差の見つかった部分に一番近い物を返します。
- オリジナルのFaceへのポインタの設定(自分自身の交差判定を防ぐため)。
- レイの開始と終了位置の設定(floatのベクトル)
2. 3DDA- まず、3dda()関数をIsect構造体へのポインタとともに呼び出します。
- すると、オクトツリーを使用してレイが実際にトレースされます。
- もし'true'が返れば、Isect構造体にはレンダリングを進めるための関連するすべての情報があります。
3. レンダリング一度のshade_ray()の呼び出しでレンダリングを実行します。下記の引数が必要です。
- Isect構造体へのポインタ。
- 新しい(空だが初期化されていない)ShadeInput構造体へのポインタ。ShadeInput構造体は内部レンダリングコード(rendercore.c)で必要で、shade_ray()関数によって、レンダリングを開始するための関連するすべての3D情報で埋められます。
- 新しい(空だが初期化されていない)ShadeResult構造体へのポインタ。ここには色とアルファ情報がレンダリング後に格納されます。