“為什么我渲染出來的場景,總是感覺和真實世界不像呢?”
游戲從業者或多或少都聽過Linear、Gamma、sRGB和伽馬校正這些術語,互聯網上也有很多科普的資料,但是它們似乎又都沒有講很"清楚"。
游戲界(特別是中小團隊)很容易忽略這些概念造成的影響。長遠來看,作為游戲從業者的你應該理解這些術語的含義,理解它們的本質聯系,理解選擇Linear或Gamma 空間帶來的工作流變化。
本文將會簡單介紹Gamma、Linear、sRGB和伽馬校正的概念。接着通過實例解析統一到線性空間的步驟,最后介紹如何在Unity中實施相應的工作流。
什么是Linear、Gamma、sRGB和伽馬校正?
在物理世界中,如果光的強度增加一倍,那么亮度也會增加一倍,這是線性關系。
而歷史上最早的顯示器(陰極射線管)顯示圖像的時候,電壓增加一倍,亮度並不跟着增加一倍。即輸出亮度和電壓並不是成線性關系的,而是呈亮度增加量等於電壓增加量的2.2次冪的非線性關系:
2.2也叫做該顯示器的Gamma值,現代顯示器的Gamma值也都大約是2.2。
這種關系意味着當電壓線性變化時,相對於真實世界來說,亮度的變化在暗處變換較慢,暗占據的數據范圍更廣,顏色整體會偏暗。
如圖,直線代表物理世界的線性空間(Linear Space),下曲線是顯示器輸出的Gamma2.2空間(Gamma Space)。
橫坐標表示電壓,縱坐標表示亮度
好了,正常情況下,人眼看物理世界感知到了正常的亮度。而如果顯示器輸出一個顏色后再被你看到,即相當於走了一次Gamma2.2曲線的調整,這下子顏色就變暗了。如果我們在顯示器輸出之前,做一個操作把顯示器的Gamma2.2影響平衡掉,那就和人眼直接觀察物理世界一樣了!這個平衡的操作就叫做伽馬校正。
在數學上,伽馬校正是一個約0.45的冪運算(和上面的2.2次冪互為逆運算):
左(Gamma0.45) 中(Gamma2.2) 右(線性物理空間)
經過0.45冪運算,再由顯示器經過2.2次冪輸出,最后的顏色就和實際物理空間的一致了。
最后,什么是sRGB呢?1996年,微軟和惠普一起開發了一種標准sRGB色彩空間。這種標准得到許多業界廠商的支持。sRGB對應的是Gamma0.45所在的空間。
為什么sRGB在Gamma0.45空間?
假設你用數碼相機拍一張照片,你看了看照相機屏幕上顯示的結果和物理世界是一樣的。可是照相機要怎么保存這張圖片,使得它在所有顯示器上都一樣呢? 可別忘了所有顯示器都帶Gamma2.2。反推一下,那照片只能保存在Gamma0.45空間,經過顯示器的Gamma2.2調整后,才和你現在看到的一樣。換句話說,sRGB格式相當於對物理空間的顏色做了一次伽馬校正。
還有另外一種解釋,和人眼對暗的感知更加敏感的事實有關。
如圖,在真實世界中(下方),如果光的強度從0.0逐步增加到1.0,那么亮度應該是線性增加的。但是對於人眼來說(上方),感知到的亮度變化卻不是線性的,而是在暗的地方有更多的細節。換句話說,我們應該用更大的數據范圍來存暗色,用較小的數據范圍來存亮色。這就是sRGB格式做的,定義在Gamma0.45空間。而且還有一個好處就是,由於顯示器自帶Gamma2.2,所以我們不需要額外操作顯示器就能顯示回正確的顏色。
以上內容,看完后還是不懂也沒關系,在繼續之前你可以先死記住以下幾個知識點:
- 顯示器的輸出在Gamma2.2空間。
- 伽馬校正會將顏色轉換到Gamma0.45空間。
- 伽馬校正和顯示器輸出平衡之后,結果就是Gamma1.0的線性空間。
- sRGB對應Gamma0.45空間。
統一到線性空間
現在假設你對上文的概念有一定認識了,我們來講重點吧。
在Gamma 或 Linear空間的渲染結果是不同的,從表現上說,在Gamma Space中渲染會偏暗,在Linear Space中渲染會更接近物理世界,更真實:
左(Gamma Space),右(Linear Space)
為什么Linear Space更真實?
你可以這么想,物理世界中的顏色和光照規律都是在線性空間描述的對吧?(光強度增加了一倍,亮度也增加一倍)。 而計算機圖形學是物理世界視覺的數學模型,Shader中顏色插值、光照的計算自然也是在線性空間描述的。如果你用一個非線性空間的輸入,又在線性空間中計算,那結果就會有一點“不自然”。
換句話說,如果所有的輸入,計算,輸出,都能統一在線性空間中,那么結果是最真實的,玩家會說這個游戲畫質很強很真實。事實上因為計算這一步已經是在線性空間描述的了,所以只要保證輸入輸出是在線性空間就行了。
所以為什么你的游戲畫面不真實呢?因為你可能對此混亂了,你的輸入或輸出在Gamma Space,又沒搞清楚每個紋理應該在什么Space,甚至也不知道有沒用伽馬校正,渲染結果怎么會真實呢?
現在假設我們的目標是獲得最真實的渲染,因此需要統一渲染過程在線性空間,怎么做呢?
注:統一在Linear空間是最真實的,但不代表不統一就是錯的。一般來說,如果是畫質要求高的作品(如3A)等,那么都是統一的。沒這方面要求的則未必是統一的,還有一些項目追求非真實的渲染,它們也未必需要統一。
統一到線性空間的過程是看起來是這樣的,用圖中橙色的框表示(現在看不懂圖沒關系,跟着后面的步驟來一步步看):
我們從橙色框的左上角出發。
第一步,輸入的紋理如果是sRGB(Gamma0.45),那我們要進行一個操作轉換到線性空間。這個操作叫做Remove Gamma Correction,在數學上是一個2.2的冪運算 。如果輸入不是sRGB,而是已經在線性空間的紋理了呢?那就可以跳過Remove Gamma Correction了。
注:美術輸出資源時都是在sRGB空間的,但Normal Map等其他電腦計算出來的紋理則一般在線性空間,即Linear Texture。詳見后文!
第二步,現在輸入已經在線性空間了,那么進行Shader中光照、插值等計算后就是比較真實的結果了(上文解釋了哦~),如果不對sRGB進行Remove Gamma Correction直接就進入Shader計算,那算出來的就會不自然,就像前面那兩張球的光照結果一樣。
第三步,Shader計算完成后,需要進行Gamma Correction,從線性空間變換到Gamma0.45空間,在數學上是一個約為0.45的冪運算 。如果不進行Gamma Correction輸出會怎么樣?那顯示器就會將顏色從線性空間轉換到Gamma2.2空間,接着再被你看到,結果會更暗。
第四步,經過了前面的Gamma Correction,顯示器輸出在了線性空間,這就和人眼看物理世界的過程是一樣的了!
我們再舉個例子,我們取sRGB紋理里面的一個像素,假設其值為0.73。那么在統一線性空間的過程中,它的值是怎么變化的?
第一步,0.73(上曲線) * [Remove Gamma Correction] = 0.5(直線)。( )
第二步,0.5(直線) * [Shader] = 0.5(直線)(假設我們的Shader啥也不干保持顏色不變)
第三步,0.5(直線) * [Gamma Correction] = 0.73(上曲線)。( )
第四步,0.73(上曲線) * [顯示器] = 0.5(直線)。( )
如果不進行Gamma Correction,就會變暗,因為第三步不存在了,第四步就會變成:
0.5(直線) * [顯示器] = 0.218(下曲線)。( )
再對照上面的圖琢磨琢磨?
Unity中的Color Space
我們回到Unity,在ProjectSetting中,你可以選擇Gamma 或 Linear作為Color Space:
這兩者有什么區別呢?
如果選擇了Gamma,那Unity不會對輸入和輸出做任何處理,換句話說,Remove Gamma Correction 、Gamma Correction都不會發生,除非你自己手動實現。
如果選了Linear,那么就是上文提到的統一線性空間的流程了。對於sRGB紋理,Unity在進行紋理采樣之前會自動進行Remove Gamma Correction,對於Linear紋理則沒有這一步。而在輸出前,Unity會自動進行Gamma Correction再讓顯示器輸出。
怎么告訴Unity紋理是sRGB還是Linear呢?對於特定用途的紋理,你可以直接設置他們所屬的類型:如Normal Map、Light Map等都是Linear,設置好類型Unity自己會處理他們。
還有一些紋理不是上面的任何類型,但又已經在線性空間了(比如說Mask紋理、噪聲圖),那你需要取消sRGB這個選項讓它跳過Remove Gamma Correction過程:
到底什么紋理應該是sRGB,什么是Linear?
關於這一點,我個人有一個理解:所有需要人眼參與被創作出來的紋理,都應是sRGB(如美術畫出來的圖)。所有通過計算機計算出來的紋理(如噪聲,Mask,LightMap)都應是Linear。
這很好解釋,人眼看東西才需要考慮顯示特性和校正的問題。而對計算機來說不需要,在計算機看來只是普通數據,自然直接選擇Linear是最好的。
除了紋理外,在Linear Space下,Shaderlab中的顏色輸入也會被認為是sRGB顏色,會自動進行Gamma Correction Removed。
有時候你可能需要想讓一個Float變量也進行Gamma Correction Removed,那么就需要在ShaderLab中使用[Gamma]前綴:
[Gamma]_Metallic("Metallic",Range(0,1))=0
如上面的代碼,來自官方的Standard Shader源代碼,其中的_Metallic這一項就帶了[Gamma]前綴,表示在Lienar Space下Unity要將其認為在sRGB空間,進行Gamma Correction Removed。
擴展:為什么官方源代碼中_Metallic項需要加[Gamma]?這和底層的光照計算中考慮能量守恆的部分有關,Metallic代表了物體的“金屬度”,如果值越大則反射(高光)越強,漫反射會越弱。在實際的計算中,這個強弱的計算和Color Space有關,所以需要加上[Gamma]項。
雖然Linear是最真實的,但是Gamma畢竟少了中間處理,渲染開銷會更低,效率會更高。上文也說過不真實不代表是錯的,畢竟圖形學第一定律:如果它看上去是對的,那么它就是對的。
注:在Android上,Linear只在OpenGL ES 3.0和Android 4.3以上支持,iOS則只有Metal才支持。
在早期移動端上不支持Linear Space流程,所以需要考慮更多。不過隨着現在手機游戲的發展,越來越多追求真實的項目出現,很多項目都選擇直接在Linear Space下工作。
一旦確定好Color Space,那么就需要渲染工程師、技術美術和美術商量和統一好工作流了。在中小團隊或項目中,這些概念很容易被忽略,導致工作流混亂,渲染效果不盡人意。現在你懂了嗎?