【笔记】【百人计划】图形2.6 伽马校正

Gamma矫正

image-20220730122434969

传递函数

  • OETF
    • 光转电传递函数 ,负责把场景线性光转到非线性视频信号值
  • EOTF
    • 电转光传递函数,负责把非线性视频信号值转换成显示光亮度
image-20220730122710864

Gamma矫正

  • Gamma是指对线性三色值和非线性视频信号之间进行编码和解码的操作
  • 图像经过gamma编码储存在硬盘中,将获取到的物理数据做一次gamma值约为0.45(1/2.2)的映射,这样的过程叫做gamma编码(此时的图像像素比实际物理像素要更亮——线性空间)
  • 在显示图像时,需要将每个像素做一次gamma值约为2.2的矫正,使最终的结果为正确的物理数据(经过显示的gamma矫正后,之前偏亮的图像亮度降低——gamma空间)

$$
V_{out}=V_{in}^{gamma}
$$

image-20220730144948798

为什么需要gamma矫正

  • 非线性转换的目的主要是为了优化储存空间和带宽,传递函数能够更好地利用编码空间
  • 显示图像的数据都是8bit,但是人眼对暗部变化更敏感,为了充分利用带宽,那么需要使用更多空间去存储暗部值,也就是说暗部使用更高精度保存,亮部使用更低精度保存。

以这张图来说明,对于人眼来说,上面的图像变化更均匀,但实际下面的图像才是物理上亮度均匀变化的。

(上面这张的中间灰度叫做美术中灰,下面的中间灰度叫做物理中灰)

image-20220730145336723

将这两种变化进行一个映射,就是gamma曲线

image-20220730145547142

韦伯定律

  • 感觉的差别阈限随原来刺激量的变化而变化,而且表现为一定的规律性,用公式表达就是$\Delta\Phi/\Phi = C$ ,其中$\Phi$ 为原刺激量,$\Delta\Phi$ 为此时的差别阈限,C为常数,又称为韦伯率。(所受刺激越大,需要增加的刺激也要足够大才会让人感觉到明显变化,但是只适用于中等强度的刺激)

总结

  • 人眼对暗部变化更敏感
  • 我们目前使用的真彩格式RGBA32,每个通道只有8位用于记录信息,为了合理使用带宽和存储空间,需要进行非线性转换
  • 目前我们所普遍使用的sRGB颜色空间标准,他的传递函数gamma值位2.2(2.4)

CRT与gamma矫正

  • CRT
    • 早期使用的CRT显示器(阴极射线显像管),设备的亮度与电压并不成线性关系,而是gamma值约为2.2类似幂律的关系
    • 这种硬件特性与gamma矫正的需求正好是一种巧合
image-20220730150841828

人眼对于中灰的感受取决于环境

image-20220730151319961
  • 线性工作流
    • 在生产各个环境,需要正确使用gamma编码与解码,使最终得到的颜色数据与最初输入的物理数据一致
    • 如果是使用gamma空间的贴图,在传给着色器前需要从gamma空间转到线性空间
image-20220730154948879

image-20220730155223847

Unity中的颜色空间

Edit-Project Settings-Player-Other Settings下的Rendering部分,修改Color Space

image-20220730155540487

  • 选择Gamma Space,Unity不会做任何处理
  • 选择Linear Space,引擎的渲染流程在线性空间计算,理想情况下项目使用线性空间的贴图,不需要勾选sRGB,勾选sRGB的贴图会通过硬件特性采样时进行线性转换。

硬件支持

线性空间需要图形API的硬件支持,目前支持的平台

  1. Windows,Mac OS x和Linux(Standalone)
  2. Xbox One
  3. PS 4
  4. Android(Opengl ES 3.0)
  5. iOS(Metal)
  6. WebGL

硬件特性支持

主要由两个硬件特性来支持

  • sRGB Frame Buffer

    • 将Shader的计算结果输出到显示器前做Gamma矫正
    • 作为纹理被读取时会自动把储存的颜色从sRGB空间转换到线性空间
    • 调用ReadPixels()、ReadBackImage()时,会直接返回sRGB空间下的颜色
    • sRGB Frame Buffer只支持每通道8bit的格式,不支持浮点格式
    • HDR开启后会先把渲染结果绘制到浮点格式的FB中,最后绘制到sRGB FB上
  • sRGB Sampler

    • 将sRGB的贴图进行线性采样的转换

使用硬件完成sRGB贴图的线性采样和shader计算结果的gamma矫正,比起在shader里对贴图采样和计算结果的矫正要快

贴图制作导出的处理

Substance Painter

sp贴图导出时,线性的颜色经过gamma变换,颜色被提亮了,所以需要在Unity中勾选sRGB选项,让它在采样时能还原回线性值。

image-20220730160341610

Photoshop

如果使用线性空间,一般来说Photoshop可以什么都不改,导出的贴图只要在Unity中勾上sRGB就可以了。

如果调整Photoshop的gamma值为1,导出的贴图在Unity中也不需要勾选sRGB

image-20220730160653259

  • ps对颜色管理特别精确,Unity里看到的颜色要经过显示器的Gamma变换,而ps不会,ps会读取显示器的Color Profile,反向补偿回去。

  • ps中有第二个Color Profile,叫做Document Color Profile。通常默认为sRGB Color Profile,和显示器的Color Profile一致,颜色被压暗了,所以ps中看到的结果才和Unity一样。

混合

Unity中的混合是线性的(线性空间模式下),ps图层与图层之间混合时,每个上层图层都经过了gamma变换,才做了混合。需要在设置中更改选择“用灰度系数混合rgb颜色”,参数设置为1,这样图层才是直接混合的结果。

image-20220730161152441

作业

手动尝试伽马校正的几种方法

1.Unity线性空间

项目设置使用Linear空间,并且albedo贴图勾选sRGB

image-20220730165247570

2.Unity Gamma空间

在项目设置里选择gamma空间

image-20220730170411814

可以观察到输出的颜色中,高光更亮、范围更大,因为光照是线性计算的,而且直接按照gamma空间输出了;但是直接输出的颜色更暗了。

手动Gamma矫正

对于输出的radiance,我们进行gamma矫正

1
return fixed4(LinearToGammaSpace(color),1.0);

以及采样贴图时,变换到线性空间计算

1
fixed3 albedo = GammaToLinearSpace(tex2D(_Albedo, i.uv).rgb) * _Diffuse.rgb;

(理论上来说,albedo这种属性是线性空间的,但是这也关乎到贴图导出的设置,一般制作的都是sRGB的图片,所以还是需要转换一下。)

这两个封装好的函数,也可以手动地去使用2.2次方去计算。

image-20220730171629559

可以看到法线颜色球正常了,中间的我写的shader高光也压下来一些,但是这个Standard的着色器就改不了了。

所以引擎还是很方便的,我们在线性空间工作模式下,很多事情都帮我们做好了。但是需要在贴图的设置上多加注意。

  • 麻烦的地方
    • 在需要手动gamma矫正的平台上,在混合这一步会出现问题(因为在混合之前就进行gamma矫正了)
    • 所以一个解决方法是:在fs颜色输出时不进行gamma矫正,但是需要一步后处理来完成gamma矫正,也造成了一些性能损耗。

参考资料

[1] https://www.bilibili.com/video/BV1cU4y1b7UF 【技术美术百人计划】图形 2.6 伽马校正

[2] Unity Shader 入门精要 p356-363.