这个问题源自于我们的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个蜡烛的时候,再放上一根,我们是基本感觉不到变化的。所以人眼对亮部的识别特别差,对暗部的识别高一些。
在文章中有个公式可以很好的解决我们的疑惑,就是为什么变得更透明了。
在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);
}
后续了解的更多的时候继续补充。