天天看點

在Unity的Gamma顔色空間下使用Standard Shader的總結

起因

今天在使用自己修改的Standard Shader調PBR效果的時候,發現項目中使用Substance導出的貼圖應用到Unity中要明顯比原本軟體中亮,整體有一種發灰的感覺,而之前使用空工程的時候效果和Substance一緻。一開始以為是shader有修改,反複檢查shader和貼圖都完全一樣,天空盒和環境光方向光設定也一樣,但最終效果卻不一樣。

檢視Substance導出到Unity的文檔https://support.allegorithmic.com/documentation/display/SPDOC/Unity+5,發現其中的第一個步驟為”In the Player project settings, set the Color Space to Linear”,即需要将項目的顔色空間切換到線性空間,檢視項目的Color Space設定是Gamma,嘗試切換到Linear空間則效果正常。

原因探究

關于Linear和Gamma空間的差別,馮樂樂的文章總結的很好:

http://blog.csdn.net/candycat1992/article/details/46228771,

在Unity中如果使用Gamma顔色空間且不做任何手工處理時,所有計算過程是在Gamma空間下的,即使用了非線性的值當做線性值進行計算,這個計算過程無疑是錯誤的,在較為簡單的經典光照模型中,美術可以通過手動調節Unity材質上的材質,達到想要的效果(通過錯誤的參數和錯誤的計算流程)。而這樣不經校正的輸入貼圖顔色值,如果使用在PBR中,這個就會偏差很大,而PBR材質中并不具備足夠的參數來手動調節結果到正常的值(計算過程較為複雜),是以在PBR中一定要處理Gamma校正才能保證最終的結果可以接受。

那是否可以簡單的将項目Color Space設定為Linear呢?在PC平台上這樣基本就能解決問題,但移動端就會有一定的相容性問題。根據Unity官方文檔https://docs.unity3d.com/Manual/LinearRendering-GammaTextures.html對于線性空間的描述:

Linear supported platforms Linear rendering is not supported on allplatforms. The build targets that support the feature are:

- Windows,Mac OS X and Linux (Standalone)

- Xbox One

- PlayStation 4

- Android

- iOS

- WebGL

There is no fallback to gamma when linear rendering is notsupported by the device. In this situation, the Player quits. You can check the active color space from a script by looking at QualitySettings.activeColorSpace. On Android, linear renderingrequires at least OpenGL ES 3.0 graphics API and Android 4.3. On iOS, linear rendering requires the Metal graphics API. On WebGL, linear rendering requires at least WebGL 2.0 graphics API.

即Android裝置需要支援OpenGL ES 3.0和至少Android 4.3的系統,IOS需要Metal graphics API(系統至少為IOS 8),這一部分機器的比重有多大呢?

根據Unity自己的調查https://blogs.unity3d.com/cn/2016/12/07/linear-rendering-support-on-android-and-ios/:

With Unity 5.5, linear rendering is now available on Android and iOS. On Android, linear rendering requires OpenGL ES 3 graphics API which represents 61.1% of the Android devices. On iOS, linear rendering requires Metal graphics API which represents 71.1% of the iOS devices.

即目前還有38.9%的Android裝置和28.9%的IOS裝置無法使用Linear顔色空間,是否要相容這部分使用者,需要每個項目自己斟酌。對于我們自己項目而言,是需要相容到這部分使用者的,是以就需要在項目顔色空間設定為gamma的情況下,自己在shader中對輸入的貼圖顔色進行校正轉化。

操作步驟

在shader中進行輸入貼圖采樣值進行轉換時,有兩個問題需要注意:

  • 需要確定輸入的貼圖是Gamma Encoded的,即經過了Gamma編碼。目前大部分的美術工具如果輸出時不經過特别的設定,預設都是經過了Gamma編碼(這樣就可以直接在顯示器上顯示),但也有某些特殊情況下貼圖直接是線上性空間下的,對于這部分貼圖就不需要進行轉換,轉換後的結果反而是錯誤的。
  • 隻有内容為顔色的貼圖需要進行轉換,法線貼圖和通道控制圖則不需要。對于内容為數值的貼圖來說,這部分貼圖在美術工具中導出的結果就是線性的,即不需要進行Gamma轉化就可以直接使用。

Unity在UnityCG.cginc頭檔案中提供了GammaToLinearSpace和LinearToGammaSpace進行兩個空間的轉化,其中的算法是近似算法,效率還比較高,其中注釋指出了對應近似算法的介紹:

inline half3 GammaToLinearSpace (half3 sRGB)
{
    // Approximate version from http://chilliant.blogspot.com.au///srgb-approximations-for-hlsl.html?m=
    return sRGB * (sRGB * (sRGB * h + h) + h);

    // Precise version, useful for debugging.
    //return half3(GammaToLinearSpaceExact(sRGB.r), GammaToLinearSpaceExact(sRGB.g), GammaToLinearSpaceExact(sRGB.b));
}
           
inline half3 LinearToGammaSpace (half3 linRGB)
{
    linRGB = max(linRGB, half3(h, h, h));
    // An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
    return max(h * pow(linRGB, h) - h, h);

    // Exact version, useful for debugging.
    //return half3(LinearToGammaSpaceExact(linRGB.r), LinearToGammaSpaceExact(linRGB.g), LinearToGammaSpaceExact(linRGB.b));
}
           

同時還提供了一個判斷函數判斷目前項目是否在Gamma顔色空間下:

inline bool IsGammaSpace()
{
#if defined(UNITY_NO_LINEAR_COLORSPACE)
    return true;
#else
    // unity_ColorSpaceLuminance.w == 1 when in Linear space, otherwise == 0
    return unity_ColorSpaceLuminance.w == ;
#endif
}
           

根據這個函數的傳回值,可以選擇針對貼圖的采樣值進行處理或者不處理。

在稍微修改了Standard Shader中對于輸入貼圖采樣值轉換到線性空間,并在最終輸出結果時進行gamma轉換後,顯示的結果基本和Substance一緻,修改片段如下,針對輸入:

1、處理輸入反射率貼圖(albedo)的采樣值

half3 diffColor = DiffuseAndSpecularFromMetallic1 (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
FragmentCommonData o = (FragmentCommonData);
o.diffColor = GammaToLinearSpace(diffColor);
o.specColor = GammaToLinearSpace(specColor);
           

2、處理環境貼圖(簡介光照)的采樣值

UnityGI gi = UnityGlobalIlluminationMAD (d, occlusion, s.normalWorld, g);
gi.indirect.diffuse = GammaToLinearSpace(gi.indirect.diffuse);
gi.indirect.specular = GammaToLinearSpace(gi.indirect.specular);
return gi;
           

針對輸出:

half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, oneMinusRoughness);
color *= light.color * nl;
color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm);
color = LinearToGammaSpace1(color);
return half4(color, );
           

這樣就基本完成Standard Shader在Gamma顔色空間下的修改使用。