本篇参考B站视频 “技术美术百人计划”·霜狼_may ;
《Shader入门精要》·冯乐乐女神著;
https://www.zhihu.com/question/27467127/answer/111973548
https://www.jianshu.com/p/9a91e6ad0d38
本篇主要用于自我复习,如有疑问或发现有什么错误,请多指教~
本篇内容主要包括伽马校正,韦伯定律,CRT,线性工作流,Unity颜色空间。
1.色彩空间
在之前的文章里有具体讲过色彩空间相关的东西,我们先简单回顾下,色彩空间是怎么定义的?
2.传递函数
已知在颜色空间下三原色的值,要显示在电子设备上换成视频信号,就需要用到传递函数;
一个传递函数包含两部分:
Gamma校正是什么?
传递函数其实就是伽马校正所使用的函数,我们一般看到的是被简化的幂函数
Gamma校正简单来讲就是对线性三色值和非线性视频信号间进行编码与解码的操作。
Gamma校正做了啥?
如上面两张图,例如我们在相机捕捉到自然界中一些线性的光信号再转存到电脑中可以为电脑识别的视频信号时,就需要进行编码操作;
而当我们用电脑去展现这个照片或视频时就需要将其从非线性的视频信号欢原为线性的光信号,就需要解码操作,最后变成我们所看到的样子。
下图中是视频中的具体举例,使用的是SRGB色彩空间,它的Gamma值约为2.2。最开始编码操作时的0.45通过1/2.2计算而得;
为啥要搞Gamma校正?
非线性转换目的主要是为了优化存储空间与带宽,传递函数能更好地帮我们利用编码空间,我们现在通用的格式还是8位,等以后如32位时,有足够颜色空间能利用时,或许就不需要伽马编码的工作。之前的文章也有说过,这里就截个图;
人的感觉具有相对性——Weber定律,其公式为ΔI/I=K;
Gamma校正稍微总结下:
1.与Gamma校正的关系
在SRGB被提出的1996年前,早期的图像显示器都是CRT(阴极射线显像管),人们发现这种设备亮度与电压不成线性,而是Gamma约2.2的幂值关系,是巧合的进行校正。
2.阴极射线管相关介绍
是一种真空器件,利用电磁场产生高速的,经过聚焦的电子束,偏转到屏幕的不同位置轰击屏幕表面的荧光材料而产生可见图形。
3.CRT与转换函数
上图中左图是Gamma编码曲线也是人眼对物理光强的响应曲线,右边则是早期CRT,电压与屏幕亮度之间关系的映射曲线;
以视觉中灰色为例,在物理光强为0.218左右时,经过编码后变成0.5,在经过CRT的校正后,从0.5回到0.218,还原真实物理光强;
4.什么是中灰值
通过实验我们能发现,人眼的视觉感官机具迷惑性,由于同时对比度无法判断出视场中目标物的绝对亮度,它对亮度主观影响与背景亮度有一定关系。那么我们的中灰就并不可能为一个固定的具体数值,全凭视觉感受。
1.何为线性工作流?
在生产的各个环节,正确使用Gamma编码及解码,使最终的到的颜色数据与最开始输入的物理数据一致;如,若是使用Gamma空间的贴图,在传给着色器前需要从Gamma空间转到线性空间;
2.不规范产生的问题
若是在着色阶段未使用线性空间下的颜色进行计算,结果可能如下图所示
再就是目前支持线性空间的平台
Unity硬件特性支持,主要由两个硬件特性支持:SRGB Frame Buffer,SRGB Sampler;
4.Substance Painter到unity过程
5.PS到Unity
关于PS和Unity中的混合:
在上面我们有讲到现在我们通用的格式为单通道8bit,而当图像存储空间提升时,或许不需要再使用Gamma变换。比如每个通道都是32位的hdr(High Dynamic Range),高动态范围相反的是低动态范围(LDR)。
1.动态范围是啥
简而言之,它指的是最高与最低亮度值间的比值。
现实中,场景的最亮和最暗范围非常大,超出显示器能显示范围256阶灰阶(单通道精度8位,灰度为2的8次方)。
2.HDR的好处
HDR使用远高于8精度的32位来记录亮度信息,可以表示超过0-1内的亮度,从而更精准反映现实光照环境。虽然最后要转回显示设备使用的LDR(通过色调映射方式控制转换),但是计算过程却真实可信;
贴图是来自Quixel里混合得到的,所以勾选sRGB选项,上面的第一张是Gamma空间下的,第二张的则是线性空间下的,第三张是去掉勾选sRGB的;
可以看到除去HDR外都有收到影响;
2.利用手动power方法进行伽马校正
结果如下图,Gamma空间下前两排和上面一样,第三排是经过处理的;
3.利用Unity在Unity CG.cgnic中封装的函数
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))
}
主要用的还是GammaToLinearSpace 和LinearToGammaSpace 函数;
采样贴图时使用GammaToLinearSpace,然后最后输出用LinearToGammaSpace;
关键代码如下:
float3 col=GammaToLinearSpace(tex2D(_MainTex, i.uv));
col=LinearToGammaSpace(col);