現在、BBSに書き込みキーを設けています。「書き込みキー」欄に、「MYOMOTO」をカギカッコは抜いて、 半角、小文字で 打ち込んで書き込みをしてください。 このキーワードは時々変更されますが、その都度こちらにて報告します。
予告通り、今日はおいしそうなグミキャンディの作り方を考えてみました……なんかグミっぽくねぇなあ?(結論)
しつこくsubsurface scatteringネタなのですが、媒質に入っていった光を追いかけるのは意外と楽しいものでして。光を追いかけるとかカッコよくないですか?フォトンチェイサーとか、ヤバイ。銀河を駆ける賞金稼ぎみたいですね。
冒頭から異様なテンションですが、ともかく今回は光を追いかけてみました。美味しそうなグミキャンディの定義として、HARIBO GOLDBÄRENを用意したのですが、コーディングに入る前に全部食べてしまったしもう一袋買うようなお小遣いも無いのでただの角丸立方体になってしまいました。美味しそうなグミキャンディをつくりたいのであって、実際に美味しいと全部食べてしまうのでダメなのです。
さてさて、そんな僕にとっては最も重要ですが皆さんにとってはどうでもいい話は置いておくとして、今回のデモは一体何をしているのかを説明しましょう。まず、いきなりですが、半透明な物体がなぜ半透明なのか、考えてみましょう。宝石でもいいですし、色素の入った寒天や下敷きやセロファンといった物でもいいです。これらの物質は基本的に無色(工業用の合成サファイアなどは無色です)ですが、鉄イオンや食紅などの不純物が入っており、不純物が特定の波長の光を吸収・散乱してそれ以外の波長の光は透過させるため半透明に見えるというわけです。
ですから、媒質の内部で起こる光の吸収・散乱を追いかけるとリアルな半透明の表現が出来そうです。古典的な半透明の表現ってα値を指定してポリゴンを描くときに元から描いてあった色と混ぜるだけでしたが、この方法ではお世辞にもリアルな半透明ではないと感じた事は誰しも経験しているでしょう。少なくとも、α値は全体で一律ではなくて分厚い部分は濃くなるはずですし、薄い部分は薄くなるでしょう。まあ、半透明で表現される人体などは大概幽霊のソレですから、プレイヤーから「幽霊がリアルではない」と言われても「キミは幽霊を見たことがあるの!?」と返せば終わりですし、多少は、ね。
さて、そういうわけですから、媒質の中に入った光が媒質から出ていくまでどれだけの道のりを旅したのか求めます。長くとどまったならば濃い色が付き、すぐ出て行ったならば薄い色になるはずです。今回は、媒質内での光の総走行距離rに対して、以下のように色を計算することにしました。
\[ \alpha = 1 - e^{-rk} \\ color = \alpha * pigment + (1-\alpha)*bgcolor\\\\ k : 濃度についての定数 \\ pigment : 色素の色 \\ bgcolor : 背景色 \\ \]1種類の散乱のみを扱った場合、散乱が無指向性ならαブレンドフォグと同じ式になると以前の独りごちで書いた通りですから、これはこれでれっきとしたsubsurface scatteringと主張できそうです。簡単でしょ?ともかく、厳密に考えると不純物の種類によってモデル自体を変える必要があるので、今回はこういうモデルということで吸収は考慮せずに通してみます。
さて次に、媒質内での光の総走行距離rを求める方法です。媒質による屈折を考えない場合はシンプルで、以下のように媒質の表面で止まったレイをさらに直進させて、媒質を出て行った時の点と媒質に入る時の点の距離を求めるだけです。とはいっても、実装に当たっては物質の内部に居るときは物質の表面までの距離をマイナスで返してくれるよう内部・外部ともなるべく正確な距離を返す距離関数を選択する必要があります。
これではなんだか芸が無いので今回は媒質による屈折を考えてみました。以下のような感じで、媒質に一度入ったレイは出ていく時に臨界角以下の入射角でないと反射して媒質の内部に戻って旅をし続けます。やがて媒質を出ていくと、出て行った先の景色をbgcolorとして目に届けるのです。正確に言うと出て行った先の景色から放たれた光が逆の道順を辿って目に飛び込んでいます。このように、屈折・反射が原因で表面から見た物体の色は複雑な模様ができるのです。今回は濃い砂糖水の屈折率ということでおおまかに1.5で計算してみました。レイが空気→砂糖水へと侵入していく時にGLSLのrefract関数に渡す屈折率は1.5で指定したくなるのですが、先に述べたように実際の光の道筋は視点から飛ばすレイと逆方向なので、屈折率の逆数の0.67ぐらいを指定する事で光の道筋を逆順に辿ります。ところで、現実の物体は表面に凹凸があるのでこんなにきれいに反射・屈折を起こさないですから、反射率・透過率といったパラメータで「それっぽさ」を補足する(2022-0720追記 この説明は誤りです)のですが、再帰が書けないし面倒くさいので今回の作例では媒質表面での光の挙動は全反射か全透過かのどちらかにしています。
2022-0729 追記。光の道筋とカメラから飛ばしたレイが逆方向なのはその通りですが、それを理由として屈折率を逆数で指定するわけではありません。GLSLのrefract関数の第三引数etaは、視線であろうと光線であろうと、「発射元の媒質の屈折率 / 発射先の媒質の屈折率」で与えられます。また、反射率・透過率は表面の凹凸の関与もありますが、滑らかな面であってもフレネルの法則に述べられる通りの反射率・透過率を示します。この文書を書いた当時は理解が全く足りておりませんでした。
媒質内でいくら反射しても出てこない場合は仕方ないので濃度1.0として近似してしまいます。何回の反射まで追うかを変えると出来上がる絵も変わってきますね。
左から1、2、4、8、16回の反射を追っています。解像度にもよりますが2回もやれば十分、4回以上は違いが良く分からないという感じでしょうか。また、媒質内を光が屈折して出て行った回数によって色調を変えると↓みたいな感じになります。
こういうのもescape-time fractalって言うんでしょうか?わかりません。オマケとして、RGBで屈折率を変えても(凄く重くなりますが)面白い絵になります。
いまいち伝わりづらいですが口のなかに虹が広がりそうです。というか石油の味とかがしそうです。
こんな感じで一粒で何回か美味しいグミキャンディが出来ました。今回みたいに簡単なプリミティブしか使わないんだったらレイマーチングじゃなくてレイトレにした方が計算コストも低いので良かったでしょうね。反射・屈折を真面目に追いかけてもおいしそうな物は出来ないような気がしますし、こういうのを真面目にやろうとするとコースティクスの表現をしたくなるのでパストレースをしたくなるのですが、subsurface scattering自体は美味しそうな物体の描画には大いに役立つはずです。
Hayase Taku(SANDMAN)