這回的目標是在tonemapping中模擬相機一部分特性,這次的參考資料是來自寒霜引擎。在實作之前,我們首先得搞清楚pixel內到底儲存的是甚麼東西。
Render target中每個pixel代表底片中的一小塊區域,寒霜引擎在pixel中儲存的光線單位是使用illuminance而不是irradiance。illuminance是光度單位,每種光度單位都可以對應到一個輻射量單位,光度單位之間的關係可以直接套用輻射量單位之間的關係,先前已有說明因此不再詳述。
Radiant energy | J (焦耳) |
Luminous energy |
lm·s |
Radiant flux |
J/s or W (瓦特) |
Luminous flux |
lm (流明 Lumen) |
Radiant intensity |
W/sr |
Luminous intensity |
lm/sr or cd (燭光 Candela) |
Irradiance |
W/m2 |
Illuminance |
lm/m2 or lx (勒克斯 Lux) |
Radiance |
W/(m2·sr) | Luminance |
lm/(m2·sr) |
▲輻射量單位與光度單位對應表
光度單位大都以我的名字開頭 (毆飛
寒霜選擇illuminance有數個理由,首先現實中的光源通常都使用光度單位,美術也使用光度單位。若render target採用irradiance,引擎多了一個轉換功夫,美術除了為每個光源提供流明之外,還必須提供發光效率,增加美術的負擔。
發光效率為100%時,luminous flux與radiant flux的比值為683,此數值可被引擎用來作光度與輻射量單位間的換算。因為人眼對不同波長有不同的敏感度,所以即使兩種光源消耗相同能量,兩者發出的不同光譜有可能造成發光效率的不同。由下表可知不同光源之間效率相差數倍或數百倍,無法為了省功夫而全部採用同一個預設值。
燭光 | 0.04% |
白熾燈 | 2%~5% |
LED燈 | 0.66~22% |
螢光燈 | 8%~15% |
太陽 | 13.6% |
▲常見光源之發光效率,取自wiki
要得到發光效率除了用光譜計算,就只能靠商品上的標示了。雖然偶爾還是能在購物網站上找到有標示的商品,不過還不如引擎內全面改用光度單位來的省事。
▲某購物網站的商品頁面只教你怎麼辨識,但不提供數據... (怒
說到購物網站,燈泡的顏色通常只會標示色溫,而不是RGB。色溫即黑體輻射體處於某溫度時所發出的顏色,例如色溫2700K代表黑體輻射體的溫度2700K時所發出的顏色。為了得知燈泡的顏色必須實作色溫轉換RGB,我的實作參考了這個網站。簡單來說(?)就是利用普朗克黑體輻射定律,從溫度求出黑體輻射體所發出的光譜,再從光譜求出人眼所看到的顏色。
光線的發出和儲存都搞定了,接著該搞定曝光。這部分簡單許多,計算可以直接套用公式,公式與其推導過程可見wiki或寒霜文件,寒霜使用的方法是Saturation-based speed。光線經過曝光計算後,大部分會被壓縮到0~1區間。在這裡應該再要加上一道查表步驟,模擬底片的特性,不過我還沒找到作法,只能先切掉大於1的部分,當作過度曝光。最後一步是做gamma校正(RGB轉sRGB),因為gamma曲線其實不是很平滑,寒霜推薦轉換sRGB的演算法採用精確版本,而非以往的近似版本,接近黑色的顏色才會比較準確。
因為沒有準備光度單位的environment map,因此測試用場景只在角落放了四個點光源:
▲光圈 f/1.4, 快門 0.1s, ISO400, 色溫5000K, 1500流明
▲加上color grading以模擬底片特性
該做的還有很多,找到更好的底片特性模擬法、用ray-tracing驗證光線演算法是否正確、生出IBL用的cube map,如果Vulkan出的話還要開始移植。說到Vulkan,Nvidia似乎差不多準備好了,還不出嗎?