SSブログ

もうちょっと真面目な光線追跡 [光線追跡エンジンを作る]

こないだ書いたようにある有名な光学設計ソフトが使えなくなった。僕は多群ズームなんか設計しないので、ほとんど使わないんだけど、仕事をする上で光線追跡して設計を評価することは必要になる。しかしその光学設計ソフトが使えていたこれまででもお客さんから「読めるデータを寄越せ」と言われたときに使っただけで、自分用にはずっと前Mathematicaで書いたのを必要になるたびごとに手を入れながら使うほうが多かった。

それはなぜかというと、汎用ソフトは僕にとって余計な機能ばっかりで痒いところに手が届きにくいのと、とくに評価結果をグラフにするときに、決まった形式でしか描けなくて一番的確な表現にならないのがもどかしいから。見慣れた人は複数枚のグラフから言いたいことを読み取れるんだろうけど、一枚に描いた方がわかりやすいのにそうならない、なんてことがある。そのプロットもビットマップしか出力できなくて見苦しい、というのもある。ようするにもともと気に入らなかった、ということなんだろうな、結局。

ところが、最近たくさん光線を飛ばさないといけない仕事ができた。Mathematicaに書いたのはマルチカーネルを意識せずに書いたので、非常に遅い。今からマルチに書き換えたとしてもせいぜいコア数倍になるだけで、1桁2桁速くするのは難しい。またどこでも実行できるわけではない。

ずっと前計画していたObjective-C版は作っただけでほったらかしになっていた。なぜかというと、Mathematicaには表示のためのグラフィクス描画機能や、数値計算のための関数が揃っていて、書き足すものは少なくてすむけど、Ojbective-Cで書いた光線追跡エンジンだけでは、その他にたくさんのものを書かないと使えないからである。

どのみち僕が使えればいいだけなので、勉強を兼ねてSwiftでやろうという気になってきた。また例によって収束するかどうかわからない「車輪の再発明」を始めようと考えはじめた...

で、どうせやるなら
  • 光線追跡の基本的な機能と、その結果を収差などで表現する機能を持つ
  • SIMDやAccelerateやMETALなんかのmacOSの数値計算に利用可能なFrameworkを使い倒す
  • Non-sequential ray traceにも対応できる構造にする
  • 当然マルチコア(マルチスレッド)対応で、かつLAN上の遊んでるマシンのCPUも追加できる
ぐらいにしたい。できあがるまえに僕の寿命が尽きるんではないか、という気もしないでもないけど、いつものことで、志は高く持ちたい。ボケ防止も兼ねて。

一方で、
  • 基本的には回折限界用で、結像系で必要になるめんどくさいモロモロはやらない
  • 偏光、薄膜、回折、ぐらいには対応するけどランダムな散乱は考慮しない
  • 屈折率分布や複屈折材料なんかには対応しない
  • 評価のみで最適化はやらない
で、ようするに僕が必要とするところだけに絞る。

Non-sequential Ray Trace

さっき書いた「Non-sequential Ray Trace」(以降、Non-SRTと書く)というのは、光線が通過する光学面の順番を決めない光線追跡で、つまり、光線の行き先にどんな面があるかまず探してから光線追跡を実行するやりかた。例えばビームスプリッタを含む光学系や、迷光解析にはNon-sequential ray traceが必要になる。

ではNon-じゃないSequential ray trace(以降、SRTと書く)はなにかというと、光線が通過する面の順番を初めから決めておくやりかたで、Non-SRTと違って「この光線がどの面に最初に到達するか」という探索をする必要がないので、計算効率は違ってくる可能性がある。普通の結像レンズなんかの設計はこれで十分なので、効率が優先される。従って、普通のレンズ設計ソフトではSRTとNon-SRTはモードが初めから分かれていることが多い。

僕の目標は、SRTとNon-SRTを分けず、初めからNon-SRTが可能なソフトウェア構造を考えておいて、SRTで十分な光学系の追跡でも計算効率の低下が最小限ですむようなものを考えたい。



Non-SRTのための構造

いきなりだけど、Non-SRTに対応するためのデータ構造を考えてみる。まず、面の形状なんかはまず置いておいて、光線追跡の対象となる光線が通るべき光学系をどう表現するか、ということを考えてみる。

光学媒質が均一かつ等方かつ定常だとする、つまり、分布屈折率媒質や複屈折媒質はなく、屈折率がナマに時間に依存したり通過する光線を吸収することで温度が上がって屈折率が変化するなどということはない、もちろん面はふにゃふにゃしていない、と言うことを前提にする。光学面は媒質を隔てるものなので、単独では存在しえず、必ず隣り合う2つの媒質の間に存在する。例えばこんな単玉レンズを考えてみる。
0810lens.png
レンズ本体の媒質をM2として、その前と後の媒質をM1、M3とする。面S1と面S2はそれぞれ媒質M1とM2、M2とM3を隔てるものだと考えられる。たとえばM1とM3の両方が空気だったら、M1=M3としてもいいし、別物だと考えてもいい。M2に入らない(S1とS2を通過しない)光線も考えられるので、M1とM3は同じものだとしてM2がM1によって囲まれているとも考えられる。その場合レンズのコバ(外周部)も面だと考えないといけない。

一方、別物だとすると、M1にある光線がM3に到達するためには必ずM2に入る必要があって、つまりその光線は必ずS1とS2を通過する必要がある。どちらとみなすかは、どう言う光線追跡をしたいか、で決めればいい。

もう少し複雑な光学系としてビームスプリッタを考えてみる。
0811bs1.png
この場合は媒質M2とM3を隔てる面がS2でビームを分割する面になっている。全体は媒質M1に囲まれていて、光線は例えば媒質M1の左側から発射されるとすると、まずS1を通過してM2に入る。そのあとS2に到達すると、光線は面で反射してM2の内部にとどまるものと、透過してM3に抜けるもののふたつに分かれて、それぞれS5とS3に到達して、M1に抜ける。

それ以外に下からM3に入ってS3とS5を通過するものとかもあり得るけど、そういう光線の通り方によらず、媒質と面との関係は変化しない。光学系は光線の影響を受けない、という定常な光学系であればあたりまえのことだけど、データ構造を考える上では便利な特性である。

ようするに何が言いたいかと言うと、均一等方定常な媒質からなる光学系はいわゆる「グラフ」とみなせる。

具体的に言えば、さっきの単玉レンズは
0810lensgraph.png
と描ける。媒質はノードであり、面はそれを繋ぐエッジである。同じようにビームスプリッタは
0811bsgraph1.png
のように描くことができる。

こんなことしてなのが面白いのか、というと、「媒質の境界が面である」というあたりまえのことを前提にすれば、面の形状や位置や姿勢などの幾何学的な詳細とは無関係に、ある媒質にある光線が次にどの媒質に入るかを具体的な追跡を始める前に決めることができる。

例えば、単玉レンズの場合に、媒質M1にいる光線は面S1を通って媒質M2に行く以外ないし、媒質M3にいる場合は面S2を通って媒質M2に入る以外ない。それ以外はすべて無限遠に向かう迷光である。媒質M2にいる光線にとってはM1に行くか、M3に行くかの二通りがありえる。これは実際に光線追跡をしてどの面をヒットするかを計算しないといけない。

この単玉レンズの例ではありえないけど、「同一面に複数回到達する」可能性を常に残しておく必要がある。これは例えばボールレンズのような1つの面だけで閉じている場合や、ライトパイプのような光学系の場合あり得る。これはNon-SRTの典型的な場合で、ある面で屈折反射した光線が、次に到達する可能性のある面として、今通ったばっかりの面も候補に入れる、ということになる(これは面の同一点で反射屈折を繰り返して抜け出せなくなることがあるので、その可能性を排除する必要がある。光線と面との交点の計算に誤差が含まれる場合、あんがい面倒な問題である)。

ただし、このままでは例えばさっきの単玉レンズのようなグラフを与えたとき
0918needsdirection.png
のAとBのような面の凹凸の方向は区別できない。

また、平面であっても、さっきのビームスプリッタのグラフでは
0918needsdirection2.png
のようなAと完全に裏返しになってるBとの区別ができない。

これは面のどちらの側がどの媒質に接しているか、と言う情報が無向グラフでは表現できないためである。最初は有向グラフで面の前後を表現しようと考えたけど、本来面の向きというのは面の属性と考えるべきなので、この「媒質と面のグラフ」に含めるのはやはりおかしいだろう。そうすると面の「幾何学」で向きを決めるしかない。光学系全体のグローバルな座標系と面のローカルな座標系との関係を面の属性として与えることにしよう。

これまで光学の光線追跡では
  • 面形状と有効領域
  • 面の次の媒質の屈折率
  • 次の面までの距離
というデータ構造を70年近く使ってきている。これはSRT、つまりある光線の次に入る面が決まっている場合にはシンプルでいい。しかしNon-SRTには不便なことはあきらかである。面の幾何学的な構造だけで「次に入る面」を決めようとすると光線の出発点と面までの距離や裏表などを判断に考慮する必要があって、面探索にそれなりの計算量が必要であるが、グラフで表しておけば、探索する必要のある面の数を限定することができる。

一本の光線を追跡したあと、その近傍の光線はたぶん一本目と同じ面を通るだろう、と期待するのは正しくて、つまりキャッシュすれば毎回全部の面を試さなくてもいい。たぶん汎用の光学設計ソフトもそうなってるんだろう。しかし幾何学だけで面を判断するとキャッシュミスのペナルティは非常に大きくなる。

さっきのビームスプリッタのグラフ表現では「多重グラフ」になっている。つまりたとえば、媒質M1からM2に入るにはS1を通る場合とS4を通る場合があり得る。この場合、どちらに入るかは、光線の始点から追跡してどちらの面に到達するかを具体的に数値計算して求める必要がある。

その代わりにたとえばこのように
0811bs2.png
ダミー面D1〜D4を用意して、単一の媒質M1をM4に分解する。ダミー面は同じ媒質を隔てているだけなのでここで屈折反射などは起こらない。しかし明らかなようにグラフは
0811bsgraph2.png
ダミー面を除いて単純グラフになる。つまり光線が存在する媒質によって入射する面を決めてしまうことができる。たとえば媒質M1に光線がある場合は入射する面はS1面だけになり、S1面にヒットしなかったら(光線はダミー面に入射するはずで)迷光になる。

ただし、完全に単純グラフに変換できない場合もあるし、またノードである媒質を増やすことになるので、いつも望ましいとは限らない。SRTであってもアパチャや面の有効範囲に入るかどうかのヒットテストをする必要がある。Non-SRTで、媒質の数を増やすよりヒットテストの回数を増やす方がSRTと効率の差が減る、という場合も十分あり得る。

また、先ほど例にあげたボールレンズやライトパイプのような場合はグラフに表せたからといって簡単にはならない。「次の面」を決定するのに面の幾何学が支配的な場合はやはり幾何学的に決定するしかない。ただ、僕がやりたいのは回折限界光学系なので、一応考慮はするけど、それが大きな問題になるわけではない。

グラフからは面がどれとどれの媒質の間の境界なのか、という情報だけが含まれることになる。この場合、グラフの情報は結局は幾何学的な関係を抽出したものに過ぎないので、グラフ表現と幾何学的な関係がコンシステントでないといけないが、グラフは幾何学とは無関係に与えることができてしまう。矛盾なしに両方を与える手段を考えないといけない。


一方、単一の媒質内の光線は直線であって、面で反射や屈折や回折が起こって進行方向や強度が変わる。1本の光線は始点が確定した折れ線(直線の連続)で、面で分割されるときに分岐が発生する。そして一度分岐した光線は合体することはない、つまり別の経路を辿って再び同じ始点と方向を持つ光線にはならない(そのような、「場」として矛盾のない光線束をBron&Wolfでは「Ray congruence」と呼んでいる)。したがって1本の光線は有向木とみなすことができる。

光線の方はグラフとみなしたからと言って当たり前すぎてそれほど面白いことは起こらないけど、光学系のほうは面探索の効率化以外にも、光学系の入力、つまりどうやって人間が書ける形式で表現できるか、ということにも利用できるはずである。これまでの面形状と媒質と距離というパラメータではさっきのビームスプリッタなんかかなり悩むんだけど、入力を例えば隣接行列として表現するということも考えられる。この場合、行と列に全ての媒質を並べて、行列要素にそれらに挟まれる面を指定する、というやりかたである。たとえばさっきのダミー面のあるビームスプリッタの場合
0811borderedmatrix.png
MathJaxがbordermatrixを書いてくれない)面の記述はグローバルな3次元内の位置と、ローカルに表現された面形状や姿勢の組み合わせだけにする。

最初のビームスプリッタのような多重グラフの場合には、面の配列を要素にするか、あるいは接続行列として
0811insidencematrix.png
と表現する。これでわかりやすくなったとはなかなか言えないけど、入力チェックはグラフとして描画すればわかりやすくなる。はず。と思う。まあ、どのくらいグラフ表現が役に立つかはわからないけど、試しにやってみよう。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。