這篇blog是最近看Digital Images: from File to Screen後稍微整理後的簡短心得, 使用到的範例圖片也是直接從文中轉載的,主要是為了釐清gamma correction到底在做什麼。gamma correction跟gamma encoding之間的關係是什麼的一些筆記。
線性色彩空間
線性的定義 - 如果一個色彩空間是線性的,表示當一個顏色乘以某個變數(比如3),該顏色的亮度或光度也將會乘以一樣的變數( 比如 3 )。在這種情況下,線性指的是值與結果顏色的亮度之間的關係。在電腦繪圖中,渲染器處理的顏色值始終以線性色彩空間表示。如果在所有地方都能保持線性,一切都不會那麼複雜,但實際上我們需要考慮兩個因素:人類的視覺和螢幕(很不幸的是非線性的),先從人類的視覺開始講起。
人類的視覺

$$ lightness = luminance^{1/3} $$
人類能適應的光強度範圍很大,從1一直到10的10次方都能感知到。從只有一盞蠟燭的房間,到大晴天的場景,然而我們無法同時感知到這個範圍內的所有強度。比如說,在大晴天的場景上放一個蠟燭,我們就很難感受到場景上有任何差異。
我們的眼睛處理如此大範圍的照明的方式是改變其對場景整體亮度的敏感度。眼睛快速適應光的強度的能力(主要透過虹膜擴張和收縮)被稱為brightness adaptation。此外,人類的視覺對黑暗區域中的微小亮度變化比在明亮的區域時更為敏感。比如說亮度僅線性亮度 18% 的光源在眼睛看來卻大約有線性亮度的50%亮(等一下下面會看到一個灰階圖比較好理解)。人類視覺對亮度的非線性感知稱為Lightness(明度)。
螢幕與Gamma Correction

在電腦的早期時代,大多數顯示器都使用 CRT(映像管)。映像管的問題在於用於控制螢幕上每一個點的電壓和亮度之間的關係不是線性的,而是呈一個下凹的曲線(上圖紅色線)。亮度和電壓之間的這種非線性關係可以使用以下方程式表示:
$$ brightness = voltage^{\gamma} $$
上面等式中的次方項即稱為Gamma。 這是一個嚴重的問題,這會讓亮度較暗的區域的顏色,變得更暗。為了解決gamma次方問題,人們使用一個inverse gamma次方來將結果校正回原本的線性值,這個用於校正的曲線稱為gamma correction。
加上gamma correction後的等式:
$$ {brightness} = \text{image}^{\frac{1}{\gamma}} \cdot \text{voltage}^{\gamma} \rightarrow \text{linear curve} $$
數學太爛,我拿0.5這個值來驗算一下(假設線性空間下,一個RGB顏色為(0.5,0.5,0.5))
$$ \text{RGB為0.5,經過gamma correction}\rightarrow 0.5 ^{1/2.2} = 0.72974005284 $$
$$ \text{儲存的RGB值 }0.72974005284\text{ 透過映像管在gamma空間下顯示}\rightarrow 0.72974005284^{2.2} = 0.499999 \text(還原為原本的線性值0.5 $$
Gamma Encoding
現代顯示器已經不會有過去映像管因為物理限制只能顯示在gamma空間的問題,現代的顯示器可以正確地在線性空間顯示顏色,但是我們依然選擇在gamma空間顯示顏色,原因如下 :
首先要回顧到,人眼對較亮的顏色變化不敏感,對於較暗的顏色變化較為敏感,人眼對亮度的感知是非線性的。
而在電腦上,我們將每個顏色的三個通道各以8bit來儲存,這個精度的範圍就是RGB顏色的0-255,透過RGB值可以有256256256種可能的顏色組合,如果三個通道的值分別是 : 0 代表黑色,128則是灰色,255則是白色。這個關係顏色的關係是線性的,
既然人眼對於較暗的顏色較為敏感,我們將原本的線性顏色做一個gamma encoding,在這曲線下較暗的顏色可以在8bit中分配到更多的空間,而較亮的顏色以更低的精度儲存(反正人眼也無法感知到),如下圖的第二列灰階圖,相較於線性的顏色變化,人眼反而更能區別出gamma encoding後的顏色變化。

而這個gamma encoding曲線目前業界訂為1/2.2。1/2.2是為了搭配人眼的感知範圍與更有效率的利用儲存空間定義出的曲線,而這剛好與過去我們為了配合CRT螢幕所做的gamma correction一樣,所以即便現代螢幕已經可以在線性空間下顯示顏色,在顯示時還是會在gamma空間下,這是為了將gamma encoding過的顏色重新校正回線性的數值。
結論
總結來說,過去我們做gamma correction的1/2.2曲線)來搭配CRT映像管的非線性、gamma空間亮度,而現在螢幕輸出的顏色仍然是在非線性的gamma空間,是因為在電腦上儲存時我們都有使用gamma encoding (1/2.2曲線)。在顯示時在gamma空間下剛好可以把這些gamma encoding過的顏色校正回線性的。兩條一樣的曲線,使用的原因不同,但最終的結果恰巧一樣,所以人們常搞混。
sRGB顏色就是將線性空間下的顏色透過gamma encoding轉換後的顏色
一些常見問題 :
Q : 我坐在電腦螢幕前,看到的一張設為sRGB顏色空間的圖片,我觀察到的顏色是什麼空間的?
A : 是線性的,理想上是最終讓人能看到線性的顏色,不要想太多,gamma encoding就當成一個對於顏色的壓縮方式就好。
電腦上的顏色到眼睛傳播的順序是:
- 操作某軟體X,看著螢幕中的圖片(sRGB圖片1/2.2曲線) →
- 現代螢幕會自動顯示於gamma空間(2.2曲線)→
- 肉眼最終接收到還原為線性空間的顏色
Q : Unity引擎中的linear pipeline與gamma pipeline是甚麼意思?
A : 這兩個選項會影響shader的光照計算結果。
在gamma pipeline中,在shader內計算光照的時候,光線的強弱是線性的,但是輸入給shader的顏色貼圖是在sRGB空間下,一個有光照模型的shader最後會將線性的光照結果與非線性的sRGB顏色混再一起,造成不真實的顏色。
選擇linear pipeline的時候,再把sRGB貼圖傳給shader時,引擎會將貼圖的sRGB校正回線性空間,這樣貼圖顏色與線性的光照結果混和後才會是正確的顏色。
Q : 在import圖片到遊戲引擎中中時,怎麼選擇圖片是sRGB還是linear的
A : 簡單來說,如果圖片上的顏色是實際上要給使用者的眼睛看的,都是sRGB,而用來給shader做計算的則是linear。
給眼睛看的圖片 : albedo/base/diffuse texture
給shader做計算用的 : specular map/occlusion map/normal map…etc