今時のカメラはシャッタボタンを押したら、勝手にフォーカスを合わせてパシャリと写真を撮ってくれます。一方で、今でも、「デジタル一眼」の様な、ちょっと凝った写真を撮るための機械では、手動でピント合わせを行う、と言う手順が、シャッタボタンを押す前に発生します。
レイトレーシングではどうでしょうか?基本的に、レイトレーシングは、理想的な「ピンホールカメラ」のエミュレートになっているので、ピント合わせの必要がありません。
理想的なピンホールカメラでは、全ての距離にピントが合うので、ピント合わせを実施する必要が無いのです。実物のピンホールカメラでは、光が通り抜ける場所が「点」(針であけた穴、ピンホール)のため、とっても暗くなります。これを補うために露光時間を延ばしたり、晴れた日に明るい屋外で撮影したりして、光量を補うことになります。
実物のカメラはどうなっているかと言うと、「絞り」と言う物が存在し、絞りを開くと明るく、速いシャッタ速度で撮影が可能になります。絞りを狭めると暗くなるので、シャッタ速度を遅くして光量を稼ぐ必要がでてきます。ボロが出ますので、写真の話はこの辺にして、専門のレイトレーシングへ戻します。
レイトレーシングは理想的なピンホールカメラなので、全ての距離にピントが合ってしまうのですが、実物の写真の様に、被写体だけピントを合わせて、その手前や奥にある物のピントをブレさせるような効果を出したい時もあります。例によって(?)パスレイトレーシング(分散レイトレーシング)で実現できますので、確かめてみましょう。
通常、視点は「点」です。大きさを持ちません。従って、全ての距離にフォーカスが合うようになっています。ここで、視点に大きさeye_rを持たせてみます。
視点が大きさを持ったことで、スクリーン上の同じピクセルを通してみた場合でも、視点の端と端から見える景色が異なっていることが分かります。この作用によって、ピントが合っていない深度の物体はぼやけて見えることになります。同じ図で視点の大きさが大きいほど、ピントがずれた場所のボケ方が大きくなる理屈もわかるでしょうか?視点の大きさが0となれば、まさに視点が「点」となって、全ての深度にピントが合うようになります。
図の中で、一か所だけ、ピントが合っている場所があります。それは、スクリーン上の点です。スクリーンの位置を決めているのは、view命令の参照点座標(refx,refy,refz)です。
view wid/2, refx,refy,refz, topx,topy,topz, eyex,eyey,eyez[, eye_r] ;
参照点座標でピントの合う距離(焦点距離)、視点の大きさeye_rで被写界深度(ピントが合っていると認識できる深度の幅)をコントロールできます。
ピントは参照点に立てるスクリーン上で合うようになっているので、参照点座標を変えると、ピントの合う距離を調整することができます。一方、スクリーンの大きさ(画面横幅の1/2)はview命令のパラメータで決まっているので、単純に参照点座標だけを変えると、視野(見える範囲)も変わってしまいます(下図)。
参照点を変化した時に、視野(見える範囲)が変わらないようにスクリーンのサイズを相似変換してやる必要があります。
そうすることで、ピントの合う距離だけを変化させることができます。単純な相似変換なので、計算は簡単で、最初に指定した視点から参照点までの距離を1として、ピントを合わせる距離までの相対位置をref_dとする視野面の定義手続きを作りました。
procedure viewr wid_h,
top.x,top.y,top.z,
ref.x,ref.y,ref.z,
eye.x,eye.y,eye.z,
eye_r, ref_d
{
// 視点とスクリーンを変えずに、参照点までの距離ref_dを
// 変える視野面定義。
// eye_r:視点の大きさ(半径)
var ref2.x,ref2.y,ref2.z;
ref2.x=eye.x + (ref.x - eye.x)*ref_d;
ref2.y=eye.y + (ref.y - eye.y)*ref_d;
ref2.z=eye.z + (ref.z - eye.z)*ref_d;
view wid_h*ref_d,
top.x,top.y,top.z,
ref2.x,ref2.y,ref2.z,
eye.x,eye.y,eye.z,
eye_r;
}
最初に申し上げた通り、通常のレイトレーシングは、ピンホールカメラのエミュレートですので、普通にレイトレーシングすると、全ての距離にピントが合った画像しか得られません。折角、視点の大きさeye_rというパラメータを導入していても、です。視点が大きさを持つことによるフォーカスのブレ(ピントのボケ)を表現するには、計算上の視点を指定したeye_rの範囲内にランダムに分散させて、何回かレイトレーシングを実施し、その平均を取ることで初めて効果を発揮します。
cond命令を使って、パスレイトレーシングを指示するように指定しておきます。
cond <pix>,<sub>, <raylv>,<shdlv>,<dislv> ;
最後のパラメータ、<dislv>が直接的なパス数(分散数)を設定するパラメータです。1画素当たり、<dislv>に設定した回数だけランダムな視線を発生させ、その平均を取ることで、今回の場合は「視点が大きさを持つことによるフォーカスのブレ」を計算します。他にも、以下の様なケースでランダムなベクタを発生させ、様々な効果を発現させることが可能です。
No. | 分散 | 得られる効果 |
1 | 大きさを持つ視点上にランダムに視点を生成 | フォーカスのブレ |
2 | 反射ベクタをランダムに分散 | 金属表面の様な鈍い映り込み |
3 | 屈折(透過)ベクタをランダムに分散 | 曇りガラスの様な半透明体 |
4 | 大きさを持つ光線源へ向けたシャドウフィーラをランダムに分散 | 柔らかな半影 |
5 | ピクセル(画素)内に視線がランダムに分散 | アンチエイリアシング |
実は、<dislv>単体で制御しようとすると、空間的なエイリアシングが目に付いたりするので、これを低減するために、サブサンプリング数<sub>に2ないし3程度の値を設定するようにしています。この場合、計算時間は、以下の様になります。
いずれにせよ、ランダムな要素が入ってくるので、ある程度、<dislv>には大きな値を設定しないと、完成画像がザラザラしたノイジィな画像になってしまいます。と言って、「どこまで大きくしたら十分」という線も引けないため、どこまで行っても際限なく計算時間がかかるという、実に困ったチャンでもあります。
完成したデータと動画です。
Xi-TRACEデータ:focus.xitr
最初にオブジェの立体的な形をご覧いただくためのアニメーションが入っています。円筒の側面に小さな球体がらせんを描くように並べられており、こちら側の口から向こう側を覗きこむような視野を形成しています。最初、一番手前の球体にフォーカスが合った状態で始まって、次第に奥の球体へフォーカスが移動していきます。奥の球にピントが合うと、逆に手前の球のピントはずれてぼやけて見えます。奥まで行ったら、また手前に戻ってきて……、これを何回か繰り返しています。