レイトレーシング(14): 光沢面にトライ
これまでは物体表面での反射として完全拡散反射と完全鏡面反射のみ扱ってきた。ただ現実世界はほとんどが光沢のある表面(光沢面とする)なので、リアルな画像を生成するには光沢面のサポートが必須である。とくにぼやけたハイライトは3DCGの醍醐味(?)である。今回はそれを実装してみる。
0. Progressive Photon Mapping法に特化
その前に・・・
前回記事では、従来のPhoton Mapping法を用いたレイトレーシングプログラムに対し「オプションとして」Progressive Photon Mapping法(以下PPM法)による画質向上の機能を追加した。しかし作ってみると、このPPM法はこれまでのプログラムに比べ、かなり容易に表現力の向上が可能になるとわかった。たとえば「ちゃんとしたアンチエリアシング」や今回取り組む光沢面などである。
逆に、PPMでないと光沢面などの効果をシンプルに実装できないことから、今後はPPMを主体とすることにした。これにより、PPMを使うかどうかといったフラグや場合わけは排除し、PPMに特化した実装に変更する。結果として、実装はシンプルながらPPMの繰り返し回数を増やすほど高品質な画像が生成できるようになると期待する。
1. 光沢面の考え方
光沢面は完全拡散面と完全鏡面の中間となる表面状態である。
光子が物体表面に当たった時に、完全にランダム(拡散反射)でも完全に一方向(鏡面反射)でもなく、ある程度の確率で鏡面反射方向の周囲に分散して反射する。どの程度の確率でどれほど分散するかは表面の粗さ(
ここで「ハイライト」について言及しておきたい。昔からあるベタな3DCG画像では、床平面上に球体が置かれ、例えば球の右上ぐらいに明るいハイライトがぼんやりとした白い円で表現されている。このハイライトが画像のリアルさをぐっと引き上げる。昔ながらの手法ではこのハイライトを視線、光源方向などから計算で擬似的に求めていたわけだ。しかし実際のハイライトは「光源の映り込み」である。周りの物体が反射して映り込むのとなんら変わらない。ゆえにこれまでの記事の作例では球の上部に四角いエリアライトがそのまま映り込んでいる(完全鏡面反射だから)。
ではぼんやりしたハイライトはどうやってできるのかというと、物体表面がざらついているために、表面の微小なところでは、ある部分は光源が映り込み、ある部分は映り込まないといったことが起こっていて、それらを総合すると光源がぼんやり映ることになるからだ。
であれば、擬似的に計算でハイライトを「作る」のではなく、光源の映り込みという単純な処理でハイライトを表現できるようにしたい。PPMを使えばそれがうまくできそうだ。
2. 微小平面の法線
光沢面をサポートするために、光子/視線が反射する方向を
- 交点から鏡面反射方向のベクトルを中心としてその周囲に広がった方向を生成する
- 交点の微小平面法線をランダムに生成し、それを基に反射方向を算出する
今回は2で実装することにした。理由は以下のとおり。
- 1の場合ランダムに方向を生成すると、入射方向寄りの方向が生成できない(わけではないが面倒に思った)(図2.2-a)
- 微小平面法線を使えば、反射ベクトルだけでなく屈折ベクトルも作り出せる(図2.2-b)
- マイクロファセット理論との相性がいいのではないか(と勝手に推測)
では実際の作り方だ。
- まず交点の法線ベクトル
と光子/視線の入射ベクトルn から、e と直交する交点平面上のベクトルn ,u を外積を使って求める。(図2.3)v
2. 次にランダムな方向ベクトルを作るため、2つの一様乱数
(参考: Realistic Image Synthesis - BRDFs and Direct Lighting - のp.5)
- 直交ベクトルと各係数から微小平面の法線ベクトル
を求める。n'
これで法線ベクトル
さて問題は、上記のような変換ができ、かつ
この式の意図は次の通り。
-
の変化に対しroughness の変化が逆(増えると減る)なので、揃えるためにm のような形にしたが、微調整のため1-roughness を使った。\sqrt{roughness} -
としたのは「見た目になだらかな変化」となるような冪指数を試した結果。好み。10^6 -
は無理だが、\infty が1-\sqrt{roughness} なら非常に大きな数字になってほしいので指数関数を使った。1
ただしこの式ではどんな
これで微小平面法線(
統計学的にもよい結果が得られる、と期待しよう。
3. 実装と作例
まず物体の材質を表すパラメータにpowerGlossy
として追加する。レンダリングのたびにこの計算を行うのを避けるためである。この冪指数を計算している関数がdensityPower
である。
densityPower :: Double -> Double
densityPower rough = 1.0 / (10.0 ** pw + 1.0)
where
pw = 6.0 * (1.0 - sqrt rough)
次に、ランダムな方向ベクトルを生成するdistributedNormal
である。2つの乱数xi1
, xi2
を生成したあと、上記式に基づき係数x
,y
,z
を求め、最後にまとめて法線ベクトルnvec'
にしている。
distributedNormal :: Direction3 -> Double -> IO Direction3
distributedNormal nvec pow = do
xi1 <- MT.randomIO :: IO Double -- horizontal
xi2 <- MT.randomIO :: IO Double -- virtical
let
phi = 2.0 * pi * xi2
uvec0 = normalize $ nvec <*> (Vector3 0.00424 1.0 0.00764)
uvec = case uvec0 of
Just v -> v
Nothing -> fromJust $ normalize $ nvec <*> (Vector3 1.0 0.00424 0.00764)
vvec = uvec <*> nvec
xi1' = xi1 ** pow
rt = sqrt (1.0 - xi1' * xi1')
x = cos(phi) * rt
y = xi1'
z = sin(phi) * rt
nvec' = x *> uvec + y *> nvec + z *> vvec -- n'の生成
case normalize nvec' of
Just v -> return v
Nothing -> return ex3
あとはこれまで単純に交点法線を使っていたところを置き換えてやれば良い。
traceRay :: Screen -> Bool -> V.Vector Object -> V.Vector Light -> Int
-> PhotonMap -> Double -> Material -> Ray -> IO Radiance
traceRay !scr !uc !objs !lgts !l !pmap !radius !m0 !r@(_, vvec)
| l >= max_trace = return radiance0
| otherwise = do
case (calcIntersection r objs) of
Nothing -> return radiance0
Just (t, p, n, m, io) -> do -- n: 交点の法線ベクトル
: (中略)
nvec' <- if rough sf == 0.0
then return n
else distributedNormal n (powerGlossy sf)
let
(rdir, cos1) = specularReflection nvec' vvec --n'を使って反射ベクトルを求める
: (後略)
光沢面の作例を次に示す(図3.1-a,b,c)。それぞれ黄色いプラスチック、金、黄色みのガラスである。各球は左上から
他の作例も示そう。
(図3.2-a, b)
4. まとめ
今回は光沢面を扱えるよう機能拡張した。作例からもわかるようにぼんやりしたハイライトがそれなりの品質で表現できるようになったと思う。特に金属ではとても本物らしくみえる。
一方、この手のハイライトは鏡面反射BRDFとして研究されており、Cook-Torranceモデルがしばしば使われているそうだ(下式)。(物理ベースレンダリングを柔らかく説明してみる(4))
式中の
Discussion