伽馬校正小記
Gamma 這個事情好煩,我總是記不清楚,每次都要重新查資料,和矩陣運算一樣討厭。
所以在這裡簡單記錄一下,這樣下次可以查的快一些。圖像文件(png,jpg等)中的數據:sRGB
↓ Shader中讀取顏色值:進行Gamma展開 (變得更暗)Shader運算中的數據:Linear↓ 把顏色值寫到FrameBuffer:進行Gamma壓縮 (變得更亮)
最終渲染結果的數據:sRGB ↓ 顯示到顯示器上進入人眼的數據:Linear記住:同一個認知意義上的顏色值,當用sRGB空間表示時,要比Linear空間表示時更亮。比如,亮度為0.5的灰色,我們預期應該是白色亮度的一半。在sRGB空間表示時,卻是0.73。
伽馬校正,是個啥?
早期的CRT顯示屏輸入電壓輸出亮度的關係並不是線性的。比如輸入0.5的電壓,則會輸出大約0.2的亮度,要想獲得0.5的亮度,則必須輸入0.73。總體上輸入電壓與輸出亮度遵循著這樣的一個換算關係:
這裡面這個γ就是我們說的伽馬值。實際上,通常的顯示器都是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。如果拿上文講的概念進行對應的話,則有:
伽馬壓縮 Linear to sRGB伽馬展開 sRGB to Linear那為什麼說經過伽馬壓縮這種說法是不準確的呢?因為 Linear to sRGB 這個過程不能簡單的用一個 就解決了。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 GemsUncharted 2: HDR LightingGamma correction - WikipediasRGB - Wikipedia
sRGB Approximations for HLSL推薦閱讀:
※使用Unity2017開發MTV:一個Timeline和Cinemacine的實踐
※《流放之路》、觸發器、編程
※如何打造細緻的2D捏臉系統
※指上互動教你怎麼去避免棋牌遊戲的同質化
※如何製作並發行你夢想的遊戲,避免在過程中狗帶!