[光線追蹤] 07 -- 物體光源 & 物體的內(nèi)外部
物體光源
物體光源當(dāng)然就是會(huì)發(fā)光的物體,? 參考上一篇專欄里的非物體光源,? 物體光源應(yīng)該也要實(shí)現(xiàn) get_direction 和 render_light 兩個(gè)方法.? 同時(shí)為了實(shí)現(xiàn) MC 積分,? 物體處應(yīng)該實(shí)現(xiàn)物體表面的均勻采樣和面積計(jì)算,? 在獲得采樣時(shí),? 可以順便獲取采樣點(diǎn)上的信息 (比如法線和紋理坐標(biāo)等),??所以實(shí)際上如果提供光線來源的點(diǎn),? get_sample 方法應(yīng)該構(gòu)建一個(gè)完整的 HitRecord.? 為此對(duì) Sphere 類新增相應(yīng)的方法:

光線來源已經(jīng)儲(chǔ)存在 rec.ray.point 上.
然后可以實(shí)現(xiàn)物體光源 ObjectLight 類,? 因?yàn)樵阡秩竟饩€時(shí),? get_direction 和 render_light 必須是同一個(gè)點(diǎn),? 所以 ObjectLight 里需要儲(chǔ)存下 Object 上的采樣.? 又因?yàn)?get_direction 比 render_light 更早調(diào)用,? 所以可以在 get_direction 里拿到 Object 的采樣,? 那么 ObjectLight 類為:?

可以看到在 ObjectLight 里調(diào)用了材質(zhì)類的 render_emissive 方法,? 為此,? 需要實(shí)現(xiàn)一個(gè)自發(fā)光材質(zhì) Emissive 類.? 因?yàn)橹罢f過,? 這里假設(shè)光源不會(huì)反射顏色,? 所以 Emissive 類實(shí)現(xiàn)起來比其他材質(zhì)里簡(jiǎn)單非常多:

有了自發(fā)光材質(zhì)后,? Object 內(nèi)判斷是否為光源的方法可以實(shí)現(xiàn)為:



代碼到這里就完成了(?),? 又是愉快的排列組合時(shí)間:

可以看到光源亮度是高達(dá)不可思議的 200,? 理由跟之前討論的點(diǎn)光源一樣.? 下面是渲染結(jié)果:

結(jié)果是出來了,? 但是噪點(diǎn)多得不合理.? 實(shí)際上這個(gè)問題是出在這一行里:

這行是測(cè)試在 ray 前方路程 0 ~ t 內(nèi)是否有物體,? 當(dāng)返回 false 為光源到渲染表面沒有物體阻擋.? 但根據(jù) hit_anything 的實(shí)現(xiàn),? "物體"是包含光源本身的,? 并且由于浮點(diǎn)數(shù)誤差,? 實(shí)際測(cè)試路程可能為 -ε ~ t(1+ε).? 為了解決這個(gè)問題,? 應(yīng)該把實(shí)際測(cè)試的路程適當(dāng)?shù)亟財(cái)嘁稽c(diǎn):? 路程的左端已經(jīng)在 Sphere::hit 內(nèi)使用 1e-8 代替 0 解決了,? 那么路程右端可以改為 t * (1 - 1e-8),? 即

修改后的渲染結(jié)果為

可以觀察到噪點(diǎn)已經(jīng)大大減少了
題外話:? 在做第二張渲染圖的時(shí)候發(fā)現(xiàn)噪點(diǎn)仍然比 julia 實(shí)現(xiàn)的渲染多得多,? de 了兩個(gè)小時(shí) bug 發(fā)現(xiàn)是打亂采樣集的鍋.? 把自己實(shí)現(xiàn)的打亂換為 std::shuffle 之后噪點(diǎn)大大減少,? 但仍然比 julia 的多.? 這時(shí)候我就非常迷惑了,? 本來打亂采樣集的目的是減少不同采樣集之間的索引相關(guān)性,? 但是這為什么會(huì)增加噪點(diǎn)呢?? 另外 julia 實(shí)現(xiàn)的里面也有打亂采樣集,? 但為什么 julia 的噪點(diǎn)就沒有增加呢?? 已經(jīng)完全沒有頭豬了,? 摸了.
另外,? 球體光源在渲染圖里已經(jīng)變?yōu)榧儼滓黄?? 這是因?yàn)檫@里使用 Clamp01 處理溢色.? 使用 MapTo01 處理的話仍有一個(gè)問題,? 如下圖所示

在渲染圖里,? 光源的邊界不能很好地過渡到黑色背景里 (即類似抗鋸齒效果).? 產(chǎn)生這個(gè)的原因是因?yàn)楫?dāng)光線與光源碰撞直接返回光源的顏色值,? 而光源的顏色值是比渲染的其他地方高很多的,? 從而造成光源本身的顏色直接取代了同一個(gè)像素里的其他顏色.? 解決起來也是很簡(jiǎn)單的,? 只要在將光源直接返回的顏色使用 MaxTo01 處理就行,? 于是可以重寫 RayTracer 的方法:




物體的內(nèi)外部
在數(shù)學(xué)里物體的"內(nèi)部"是有準(zhǔn)確定義的,? 在描述"內(nèi)部"之前先來看一下表面和法線.
數(shù)學(xué)上定義"表面"為空間上所有符合??的點(diǎn)集,? 也就是說表面為隱函數(shù) f 的等值面.? 在表面上某點(diǎn) p 的法線定義為?
,? 亦即法線與隱函數(shù)的梯度平行并且同向.
計(jì)算機(jī)圖形學(xué)里常說"法線從內(nèi)部指向外部",? 那么結(jié)合數(shù)學(xué)上對(duì)法線的定義可以得出,? 物體的內(nèi)部即是隱函數(shù) f 值為負(fù)數(shù)的區(qū)域,? 外部是 f 為正的區(qū)域,? 并且兩個(gè)區(qū)域之間被物體表面相隔.
但是實(shí)際上,? 不只是計(jì)算機(jī)圖形,? 部分現(xiàn)實(shí)物體都不會(huì)嚴(yán)格地把空間分為兩個(gè)區(qū)域 (比如說三角形和紙張).? 沒有嚴(yán)格分隔內(nèi)部與外部的面被稱為開放表面,? 在渲染開放表面時(shí)會(huì)產(chǎn)生一定問題,? 下面先簡(jiǎn)單實(shí)現(xiàn)一個(gè)開放表面然后進(jìn)行渲染.
比較簡(jiǎn)單的開放表面就是圓面了,? 圓面是無限平面的子集,? 所以需要定義無限平面:? 由平面上的任意一點(diǎn)和其法線定義得到:?.? 那么圓可以定義為在無限平面上距離圓心一定距離內(nèi)的子集:
,? 為了模型簡(jiǎn)潔,? 可以把圓心限制在平面上.? 為了計(jì)算光線與平面的相交,? 把光線方程代入平面方程得到:??
,? 整理得
.? 那么圓面實(shí)現(xiàn)如下:

然后創(chuàng)建一個(gè)圓面, 光源, 圓面, 光源交替排列的場(chǎng)景,? 相機(jī)在兩個(gè)圓面之間:?

在這個(gè)場(chǎng)景里,? 兩個(gè)圓面的法線都為 x 軸正方向,? 渲染結(jié)果如下:

可以看到右邊的圓面錯(cuò)誤地渲染了右邊的光源而不是左邊的,? 并且如果把右邊的光源取消,? 右邊的圓面甚至無法渲染.? 從無限平面處的定義不難知道,? 現(xiàn)在可見的右邊圓面屬于"內(nèi)部",? 這個(gè)例子展示了光追不能正確地渲染物體"內(nèi)部".
解決方法也是很簡(jiǎn)單,? 既然不能正確渲染內(nèi)部,? 那沒有內(nèi)部不就能正確渲染了.? 經(jīng)過剛剛的講述可以知道,? 物體的內(nèi)外是由法線方向決定的,? 當(dāng)光線照射到物體外部時(shí)必定與法線法線相對(duì),? 即?,? 如下圖所示:

類似地,? 光線從物體內(nèi)部照射到表面時(shí)結(jié)果則相反.? 那么只要當(dāng)光線從物體內(nèi)部照射到表面時(shí)人為地 (本來程序就是人寫的就是了) 把法線翻轉(zhuǎn),? 就可以確保內(nèi)部渲染變?yōu)檎_的外部渲染.? 在?World::hit_object 里增加法線翻轉(zhuǎn):

修改后再次運(yùn)行渲染的結(jié)果:

渲染就已經(jīng)正確了.

上面講到內(nèi)外部的問題同樣會(huì)發(fā)生在光源上,? 為了展示例子,? 下面對(duì)圓面增加光源相光的方法:

實(shí)際上,? 因?yàn)?this->normal 在光追內(nèi)應(yīng)該是常量,? 所以 LocalCoord(rec.normal) 也應(yīng)該是常量,? 并且因?yàn)?LocalCoord 的計(jì)算量比較大,? 所以實(shí)際上最好可以在光追前就把這個(gè)東西計(jì)算好,? 但這里為了可讀性就沒這么做了.? 話說回來了,? 下面是需要進(jìn)行渲染的場(chǎng)景:? 一個(gè)大圓面作為地板,? 一個(gè)圓面光源垂直放在地板上方


可以看到光源只在"外部"有光照,? 而在"內(nèi)部"沒有.
把 ObjectLight 復(fù)制一份重命名為 DualSideObjectLight,? 跟 World:hit_object 里面類似,? 重寫?DualSideObjectLight::render_light 為

把 scenes5 里的 ObjectLight 替換為 DualSideObjectLight,? 渲染結(jié)果為:

這里仍然保留 ObjectLight 原行為是因?yàn)樵诓糠止庠蠢?? 討論"內(nèi)部"光照是沒必要的 (封閉物體, 比如球體),? 并且可以減少計(jì)算量,? 而且現(xiàn)實(shí)里也有很多單面光源.

摸了.? 下一篇專欄可以來說一下各種奇形怪狀的相機(jī).
項(xiàng)目倉(cāng)庫(kù):https://github.com/nyasyamorina/nyasRT
扣扣澀弔圖群:?274767696