在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/2012/08/srgb-approximations-for-hlsl.html?m=1
    return sRGB * (sRGB * (sRGB * 0.305306011h + 0.682171111h) + 0.012522878h);

    // 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(0.h, 0.h, 0.h));
    // An almost-perfect approximation from http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
    return max(1.055h * pow(linRGB, 0.416666667h) - 0.055h, 0.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 == 0;
#endif
}

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

在稍微修改了Standard Shader中對於輸入貼圖採樣值轉換到線性空間,並在最終輸出結果時進行gamma轉換後,顯示的結果基本和Substance一致,修改片段如下,針對輸入:
1、處理輸入反射率貼圖(albedo)的採樣值

half3 diffColor = DiffuseAndSpecularFromMetallic1 (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
FragmentCommonData o = (FragmentCommonData)0;
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, 1);

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章