伽馬校正小記

Gamma 這個事情好煩,我總是記不清楚,每次都要重新查資料,和矩陣運算一樣討厭。

所以在這裡簡單記錄一下,這樣下次可以查的快一些。

圖像文件(png,jpg等)中的數據:sRGB

↓ Shader中讀取顏色值:進行Gamma展開 V_{out} = V_{in}^{2.2} (變得更暗)

Shader運算中的數據:Linear

↓ 把顏色值寫到FrameBuffer:進行Gamma壓縮 V_{out} = V_{in}^{0.45} (變得更亮)

最終渲染結果的數據:sRGB

↓ 顯示到顯示器上

進入人眼的數據:Linear

記住:同一個認知意義上的顏色值,當用sRGB空間表示時,要比Linear空間表示時更亮。

比如,亮度為0.5的灰色,我們預期應該是白色亮度的一半。在sRGB空間表示時,卻是0.73。

伽馬校正,是個啥?

早期的CRT顯示屏輸入電壓輸出亮度的關係並不是線性的。比如輸入0.5的電壓,則會輸出大約0.2的亮度,要想獲得0.5的亮度,則必須輸入0.73。總體上輸入電壓與輸出亮度遵循著這樣的一個換算關係:

V_{out} = AV_{in}^{gamma}

這裡面這個γ就是我們說的伽馬值。

實際上,通常的顯示器都是A=1,所以公式可以簡化一些,γ值是這裡唯一的參數。

現在LCD屏雖然並不必須有這樣的特性了,但是顯示器生產商依然人為的添加了這層Gamma轉換,所以我們可以對它們一視同仁。

對顏色值施加上述公式進行變換的過程稱為伽馬校正。但是這個伽馬校正其實又是有兩個方向的,給定一個參數比如我說6,那麼當伽馬值取6和取1/6的時候是兩個互為逆運算的操作。

當γ值小於1的時候,此值稱作encoding gamma(編碼伽馬值)。

這個操作叫做gamma compression(伽馬壓縮)。

當γ值大於1的時候,此值稱作decoding gamma(解碼伽馬值)。

這個操作叫做gamma expansion(伽馬展開)。

據說一般PC顯示器的伽馬值都在2.0~2.4之間,所以大家通常就取γ=2.2來進行伽馬壓縮,取γ=0.45來進行伽馬展開。蘋果的顯示器有點特殊,它的這兩個值分別是1.8和0.55。

想要0.5亮度的顏色,需要傳給顯示器0.73,顯示器會幫你把這個亮度值進行伽馬展開,最後人眼看到的就是0.5。

這個伽馬校正的事情已經是一個標準了,以至於所有標準圖片格式保存的數據都是經過伽馬壓縮後的。你認為你拿photoshop畫了一幅畫,這裡面有個50%的灰像素,你把這圖存成了jpg,其實jpg里存的這個像素的值是0.73,不是0.5。遊戲讀紋理也一樣,一個看起來像是50%灰的紋理,其實讀出來的顏色值是0.73。如果你拿它當0.5用了,會導致很多複雜的渲染運算結果錯誤。不過Gamma正確對於渲染的重要性並不是本文的重點,有興趣的可以看參考鏈接。

伽馬編碼有一個好處。因為我們平時存顏色數據每個通道的亮度都是用8位來存儲的,8位的精度真的很低,只能表示256個級別,我們應該謹慎利用這256個級別。

人眼對於暗色的敏感程度要高於亮色,經過伽馬壓縮後,原本用來表示灰色的127/255會被編碼成186/255,也就是0~186這麼多個級別用來表示0~0.5的亮度空間,而187~255這麼多的級別用來表示0.5~1的顏色空間,重要的暗部得到了更高的存儲精度。

sRGB?什麼鬼?

Standard Red Green Blue是一個顏色空間的標準。

剛才說標準圖片格式都是經過伽馬壓縮的,這個說法不準確。應該說標準圖片格式保存的顏色數據都是sRGB顏色空間中的數據。顯示器需要的數據也是sRGB顏色空間中的數據。

和sRGB顏色空間相對的概念就是線性空間。(Linear Space)

對應的顏色值分別叫做 sRGB Color 和 Linear Color。

如果拿上文講的概念進行對應的話,則有:

伽馬壓縮 Leftrightarrow Linear to sRGB

伽馬展開 Leftrightarrow sRGB to Linear

那為什麼說經過伽馬壓縮這種說法是不準確的呢?因為 Linear to sRGB 這個過程不能簡單的用一個 V_{out} = V_{in}^{0.45} 就解決了。sRGB顏色空間的換算要比這稍微複雜一些。

在亮度接近0的時候,Linear to sRGB 的換算關係是線性的,小於1的時候gamma取2.4,大於1的時候gamma取2.3。

最後的 Linear to sRGB 換算公式是這樣的。(摘自維基百科,只包含[0,1] 區間)

  • a = 0.055

當然了,這樣計算肯定效率更低,大部分情況使用近似計算就行了。

Unity的UnityCG.cginc中有一個函數名字叫 LinearToGammaSpaceExact ,就是這一換算操作。虛幻4也有,就在GammaCorrectionCommon.usf中。

圖形API中對於sRGB的處理

其實圖形API可以幫我們進行gamma變換。

拿DirectX來舉例:在創建SRV的時候如果指定的紋理格式加_SRGB後綴的話,在進行Texture Load的時候,DirectX會自動幫我們完成gamma變換,並返回線性空間的顏色值。與此對應,如果在創建RTV的時候指定帶_SRGB後綴的格式,則寫入線性空間的顏色值,DirectX會自動轉換為sRGB值保存到Buffer中。DirectX使用的公式參考這裡 [MSDN]

參考資料

The Importance of Being Linear - GPU Gems

Uncharted 2: HDR Lighting

Gamma correction - Wikipedia

sRGB - Wikipedia

sRGB Approximations for HLSL
推薦閱讀:

使用Unity2017開發MTV:一個Timeline和Cinemacine的實踐
《流放之路》、觸發器、編程
如何打造細緻的2D捏臉系統
指上互動教你怎麼去避免棋牌遊戲的同質化
如何製作並發行你夢想的遊戲,避免在過程中狗帶!

TAG:遊戲開發 | 計算機圖形學 | 遊戲引擎 |