美術前上個禮拜找我問光照圖總是烘焙過暗的問題,一時興起我在 Gamma 和 Linear 兩個顏色空間切換了下,發現一個 Shader 明暗不同,另一個 毫無變化,於是激發了我去研究下在 Unity 里使用 Linear 的細節。(雖然最后發現美術的問題跟這個不太有關系)
這里不想在過多冗長的去復述關於所有 Gamma 校正的歷史和概念,假設已經閱讀過相關資料,比如以下(包括但不限於):
https://www.cnblogs.com/murongxiaopifu/p/9001314.html
https://blog.csdn.net/candycat1992/article/details/46228771
https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch24.html
https://www.qiankanglai.me/2014/12/24/gamma-correction/
https://docs.unity3d.com/Manual/LinearLighting.html
這里僅僅討論下 Unity 平台下 Linear 空間下渲染出現的和預期不一致的錯誤(姑且先叫錯誤,着實還不明白的地方),和所理解然后預期的結果不一致。測試場景也很簡單,制作三張紅色貼圖A, B 和 C,其中 A 和 B 一樣:(127,0,0),C 為(187,0,0),其中187=((127/255)^0.45)*255。A 導入選項為 Linear,不勾選 sRGB,B 勾選 sRGB,C勾選 sRGB。
A B C
可以發現,A 保持顏色不變,B 和 C 的顏色都變暗,都被做了 Gamma 校正 2.2,所以更暗;尤其是 C,原本 187 的顏色被矯正后,正好變成127,這是符合預期的。
接下來在場景中創建三個材質,使用 Shader:Unit/Texture 來顯示貼圖本身顏色,貼圖分別使用 A, B, C;然后創建一個 Sphere 分別使用這三個材質在 Linear 空間下渲染查看顏色。顯示效果如下:
A B C
然而預期結果為:球 A 和 C 均為 (127,0,0),B 為(55,0,0)。根據:A 貼圖為 Linear,采樣后不會被硬件 Gamma 2.2 而維持原樣,Shader 計算輸出到 ColorBuffer 時 Gamma 0.45 提亮,最終輸出到顯示器再 Gamma 2.2,最終輸出顏色(127,0,0);B 貼圖為 sRGB,采樣后被硬件 Gamma 2.2 為 ((127/255)^2.2)*255=55,然后 Shader 中計算輸出 ColorBuffer 時 Gamma 0.45 變回到127,最終被顯示器 Gamma 2.2,輸出顏色(55,0,0);
以上可以看到實際渲染結果和預期結果的差異:實際渲染結果都被“提亮“了,都額外的被多增加了一個 Gamma 0.45。可是至少 Linear 的圖片是什么顏色就應該輸出什么顏色。於是將以上三種情況分別在手機上使用 RenderDoc 抓幀分析下。
場景 A:
以上分別顯示了 A 場景中,圖片的格式,以及紋理緩存的格式:
Red127_Linear 格式:GL_COMPRESSED_RGB8_ETC2,顏色為(127,0,0);
Color Buffer 格式:GL_SRGB8_ALPHA8,顏色為(187,0,0),
由於寫入 ColorBuffer 會做 Gamma 0.45 的校正,所以 ColorBuffer 這個顏色是正確的,配合顯示器的 Gamma 2.2 校正,應該正好為(127,0,0)。可是實際上顯示器輸出的就是 ColorBuffer 的內容(187,0,0),像是沒有經過 Gamma 2.2 校正;抑或是因為 sRGB 格式的 ColorBuffer 再次被 Gamma 0.45,然后再被顯示器 Gamma 2.2,否則如何解釋呢?暫時不明白。
那么再看下場景 B:
Red127_sRGB 格式:GL_COMPRESSED_SRGB_ETC2 顏色為(55,0,0),因為 sRGB 圖片做了 Gamma 2.2 校正;
ColorBuffer 格式:GL_SRGB8_ALPHA8,顏色為(127,0,0),
所以當 Color 輸出到顯示器做了 Gamma 2.2 校正后,實際顏色應該為 (55,0,0),然而貌似也少了這一步,跟情況 A 一樣。
最后來看下場景 C:
Red187_sRGB 格式:GL_COMPRESSED_SRGB_ETC2 顏色為(127,0,0),因為 sRGB 圖片做了 Gamma 2.2 校正;
ColorBuffer 格式:GL_SRGB8_ALPHA8,顏色為(187,0,0),
最后 ColorBuffer 輸出經過顯示器的 Gamma 2.2 矯正后應該輸出顏色 (127,0,0),然而實際卻是(187,0,0),同場景 A 和 B 的情況一模一樣。
以上測試使用 Unity 2018.1.2f1。
綜上,以上三種結果和過程分析都指向一個事實:Unity 在 Linear 顏色空間下直接將 sRGB 格式的 ColorBuffer 輸出到了顯示器,缺少了 Gamma 2.2 這一步,使得顏色都變亮了,無法得出預期的結果,或許有什么地方理解的還不到位,也或者我缺少了過程中的某個設置,目前還不得而知,如果有哪位大牛知道,還請指點下。
【本文固定地址:https://www.cnblogs.com/yaukey/p/unity_linear_color_space_unexpected_results.html】