關於Color Space是Gamma還是Linear的一些問題

這個問題源自於我們的UI發現自己在FGUI下製作的東西,在Unity中顯示的效果不對。例如90%透明度的黑底圖片導出到Unity中的效果非常的透,可能只有70%左右的效果。

然後我們絞盡腦汁的找了半天不同,才發現是由於我們工程Color Space設置成爲Linear的問題。而且相對的UGUI也存在這樣的問題,我們做個簡單的測試,在Gamma環境下,UGUI Image爲黑色90%透明度的遮擋效果如下(爲了方便後面要講到的一些內容,我順便取了下圖中幾個顏色顯示出來的RGB的值):

然後我們打開Edit-Project Settings-Player-Other Settings,將Color Space由Gamma轉爲Linear,效果如下:

      

這就很神奇了。

於是就查了下有關Gamma和Linear的資料,看看如何能夠解決這樣的問題,由於網上相關資料很多,內容也很多,也防止自己以後忘記了要重新找,這裏都先貼幾個大佬發的文章。

問題源自於人眼對光照強度的敏感度是非線性的,對於這個問題,有個舉例我覺得很好,就是在一個黑暗的房間中,若放上一根蠟燭,我們的眼睛能明顯的感覺到變化。假如每個蠟燭的光照強度都一樣,但是當在一個房間中有100個蠟燭的時候,再放上一根,我們是基本感覺不到變化的。所以人眼對亮部的識別特別差,對暗部的識別高一些。

聊聊Unity的Gamma校正以及線性工作流

Unite 2018 | 淺談伽瑪和線性顏色空間

在文章中有個公式可以很好的解決我們的疑惑,就是爲什麼變得更透明瞭。

在shader的混合模式爲Blend SrcAlpha OneMinusSrcAlpha時,Gamma的計算公式爲:

ret = srcColor * srcAlpha + dstColor * (1 - srcAlpha)

套用我們上面的例子srcColor爲黑色的遮罩,即RGB=0,0,0,srcAlpha=0.9,dstColor爲白色的底,即RBG=1,1,1,套入公式中:

0 * 0.9 + 1 * (1 - 0.9) = 0.1

但是在Linear下,計算公式爲:

ret = (srcColor^2.2 * srcAlpha + dstColor^2.2 * (1 - srcAlpha) ) ^(1/2.2)

套入公式後,結果變爲0.35,相比0.1要更白,所以看上去感覺更透明瞭。

(0 ^ 2.2 * 0.9 + 1 ^ 2.2 * (1 - 0.9)) ^ 0.45 = 0.35

紫色底,黑色透明遮罩的效果大家可自行計算一下,兩個值也是近似最終效果的。

同時由於這個公式涉及到了srcColor和dstColor,所以在Shader中也是比較無解(文章中也提到了幾個解決方案)。目前的做法就是加深srcColor的srcAlpha值來進行優化,例如

//hlsl語法
#if !defined(UNITY_COLORSPACE_GAMMA) && (UNITY_VERSION >= 550)
    if(v.color.r * v.color.g * v.color.b != 1)
        o.color.a = PositivePow(v.color.a, 0.4545454545);
#endif

 

注:在Shader中也有一些函數可以幫助我們在Gamma和Linear之間轉換,例如

UnityCG.cginc

// Legacy for compatibility with existing shaders
inline bool IsGammaSpace()
{
    #ifdef UNITY_COLORSPACE_GAMMA
        return true;
    #else
        return false;
    #endif
}

inline float GammaToLinearSpaceExact (float value)
{
    if (value <= 0.04045F)
        return value / 12.92F;
    else if (value < 1.0F)
        return pow((value + 0.055F)/1.055F, 2.4F);
    else
        return pow(value, 2.2F);
}

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 float LinearToGammaSpaceExact (float value)
{
    if (value <= 0.0F)
        return 0.0F;
    else if (value <= 0.0031308F)
        return 12.92F * value;
    else if (value < 1.0F)
        return 1.055F * pow(value, 0.4166667F) - 0.055F;
    else
        return pow(value, 0.45454545F);
}

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));
}

Color.hlsl

// Gamma22
......

real3 Gamma22ToLinear(real3 c)
{
    return PositivePow(c.rgb, real3(2.2, 2.2, 2.2));
}

real4 Gamma22ToLinear(real4 c)
{
    return real4(Gamma22ToLinear(c.rgb), c.a);
}

......

real3 LinearToGamma22(real3 c)
{
    return PositivePow(c.rgb, real3(0.454545454545455, 0.454545454545455, 0.454545454545455));
}

real4 LinearToGamma22(real4 c)
{
    return real4(LinearToGamma22(c.rgb), c.a);
}

// sRGB
......

real3 SRGBToLinear(real3 c)
{
    real3 linearRGBLo  = c / 12.92;
    real3 linearRGBHi  = PositivePow((c + 0.055) / 1.055, real3(2.4, 2.4, 2.4));
    real3 linearRGB    = (c <= 0.04045) ? linearRGBLo : linearRGBHi;
    return linearRGB;
}

real4 SRGBToLinear(real4 c)
{
    return real4(SRGBToLinear(c.rgb), c.a);
}

......

real3 LinearToSRGB(real3 c)
{
    real3 sRGBLo = c * 12.92;
    real3 sRGBHi = (PositivePow(c, real3(1.0/2.4, 1.0/2.4, 1.0/2.4)) * 1.055) - 0.055;
    real3 sRGB   = (c <= 0.0031308) ? sRGBLo : sRGBHi;
    return sRGB;
}

real4 LinearToSRGB(real4 c)
{
    return real4(LinearToSRGB(c.rgb), c.a);
}

後續瞭解的更多的時候繼續補充。

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