大家好,又见面了,我是你们的朋友全栈君。
文章目录
- CRT显示器与人眼视觉
- 非线性显示与渲染
- 伽马校正
- sRGB 纹理
- 正确的点光衰减
- 补充
- 参考资料
CRT显示器与人眼视觉
过去, 大多数监视器是阴极射线管显示器(CRT). 这些监视器有一个物理特性就是两倍的输入电压产生的不是两倍的亮度. 输入电压产生约为输入电压的 2.2 次幂的亮度. 这本质上是一个问题, 但是由于一个神奇的巧合, CRT显示器的这一特性被保留了下来.
这个神奇的巧合就是: 人类的视觉系统进化出了一个特性, 黑暗环境下的辨识能力要强于明亮环境, 这可能有助于我们及时发现黑暗中隐藏的危险. 如下图所示, 第一行表示的人眼感受到的亮度, 第二行表示实际的物理亮度. 物理亮度基于光子数量, 是线性的, 而感知亮度基于人的感觉是非线性的.
通过观察可以看到物理亮度在我们眼中会显得暗部细节缺失而亮部细节过剩. 人眼对物理亮度的感知和 CRT 显示器显示亮度对电压的感知很接近, CRT亮度是电压的2.2次幂而人眼的观察亮度相当于物理亮度的2次幂, 因此CRT这个缺陷正好能满足人的需要, 后面的硬件也都保留了这一非线性特性.
2.2 这一数字就是所谓的伽马(Gamma), 也叫灰度系数, 各种显示设备会有各自的伽马值, 矫正使用的伽马值取决于显示器, 但是现代系统基本上都统一使用 2.2.
非线性显示与渲染
显示器的非线性特性让亮度在我们眼中看起来更好, 但是在渲染时反而会因此导致问题. 我们的渲染计算都是在伽马值为 1 的理想线性空间进行的. 比如现在我们输入了一个暗红色的光照(0.5, 0.0, 0.0), 然后希望将亮度提升一倍变为 (1.0, 0.0, 0.0). 但是由于显示器的非线性特性, 最终显示的颜色实际是从 (0.218, 0.0, 0.0) 变成了 (1.0, 0.0, 0.0).
我们一度忽略了这一问题, 通常是美术同学将光照设置的比本来更亮一些来抵消显示器对亮度的削减. 但是这本来就是一个数学上的错误, 使用更高级的光照算法时, 这个问题会越来越明显. 在 Physically Based Rendering 兴起之后, 我们更是不可能在渲染底层存在这样的问题, 于是便产生了伽马校正技术(Gamma Correction).
伽马校正
分析上面的错误情况, 问题的原因在于我们理想的输出颜色被显示器执行了 pow2.2 的操作. 所以伽马校正的思路就是在颜色被输送到显示器之前, 我们先对其进行 pow0.454 的逆运算以抵消显示器的作用.
伽马校正可以通过图形API提供的方法由硬件执行, 也可以在像素着色器最终输出之前我们自己执行. 但无论哪种方式都需要注意区分我们当前需要的颜色是在 Gamma 1.0 的线性空间中还是在 Gamma 2.2 的 sRGB 空间中. 比如一个效果需要执行两个 pass, 第一个 pass 生成的是中间结果作为第二个 pass 的输入, 数据尚处于线性空间, 那么就不能对其进行伽马校正. 第二个 pass 生成的结果要输出到显示器, 需要进入 sRGB 空间, 所以在输出之前要对计算结果进行伽马校正.
sRGB 纹理
2.2 是大多数显示设备的平均伽马值. 每个监视器的伽马曲线都有所不同, 但是 Gamma 2.2 在大多数显示器上表现都不错. 出于这个原因, 大多数游戏会将伽马值设置为 2.2, 并且有的游戏会为玩家提供伽马值的自定义设置选项, 以适应每个监视器. 基于 Gamma 2.2 的颜色空间叫做 sRGB 颜色空间.
由于显示器总是在 sRGB 空间显示应用了伽马之后的颜色, 这导致制作纹理资源的美术同学创建的纹理都在 sRGB 空间. 在我们应用伽马校正之前, 这个纹理是可以正常使用的, 因为其制作和使用的颜色空间没有发生变化. 但是在应用伽马校正之后, 我们是把所有东西都放在线性空间中展示的, 纹理最终会错误的变亮. 这是由于在纹理制作时美术同学为了达到人眼的感知亮度相当于已经为实际需要的线性空间亮度进行了一次 pow0.454 的伽马校正, 如果此时不做处理直接使用就进行了两次伽马校正!
让美术人员在线性空间中进行纹理制作可以解决这一问题, 但是伽马校正的内部逻辑并不一定是所有美术人员都需要理解的知识, 并且在 sRGB 空间进行创作显然更加容易. 所以一般情况下我们都会由程序来对纹理进行重校. 重校可以在 shader 中进行, 读取出纹理中的颜色之后对其进行 pow 2.2 的校正. 也可以通过图形 API 由硬件进行处理, 比如 OpenGL 中可以在创建纹理的时候将其指定为 GL_SRGB 或 GL_SRGB_ALPHA.
sRGB 纹理重校和伽马校正一样, 使用时需要注意应用的对象, 并不是所有的纹理都是在 sRGB 空间制作的. 诸如 diffuse 贴图这类为物体上色的纹理几乎都是在 sRGB 空间的, 相反像法线贴图这种提供光照参数的纹理则几乎都是在线性空间的. 将法线贴图配置为 sRGB 纹理反而会导致渲染错误.
正确的点光衰减
在真实世界中, 光源产生的亮度与距离的平方成反比, 也就是
代码语言:javascript复制float attenuation = 1.0 / (distance * distance);
但是当我们在光照计算中应用这一方程的时候会发现这个衰减过于强烈. 这时如果不进行伽马校正的话, 由于显示器的伽马值, 最终的衰减实际变成了 (1.0 / distance ^ 2) ^ 2.2, 这个衰减确实太过强烈了. 在引入伽马校正之前, 通常使用亮度与距离成反比的衰减公式. 不过在显示器伽马值的作用下, 这种衰减反而变得和物理公式很接近: (1.0 / distance) ^ 2.2 = 1.0 / distance ^ 2.2
无伽马校正 | 有伽马校正 | |
---|---|---|
平方衰减 | 衰减过度 | 期望图像 |
线性衰减 | 衰减接近期望效果但是画面整体亮度错误偏暗 | 衰减不足 |
补充
在查阅相关资料的时候发现了一个精妙的方法, 此方法对人眼视觉, 显示器伽马等现象进行了直观的展现.
我们已知纯黑和纯白在显示时分别是 0 和 255. 如下图所示, 图像分为 3 列, 左右两列为黑线和白线的均匀混合, 中间一列为纯色色块. 这时让我们稍微远离屏幕, 可以发现两侧表现出来的颜色与中间列下半部分显示的颜色比较接近.
在我们朴素的线性空间认知下, 黑白均匀混合后得到颜色值应该在 128 左右. 也就是中间列上半部分的颜色值估计在 60 左右, 下半部分的颜色值应该在 128 左右. 但是事实上用取色器取色可以得知上半部分的颜色值才是 128, 下半部分的颜色值已经达到了 187. 这一现象正和我们上面研究的人眼视觉特性和显示器非线性特性有关.
左右两侧黑白混合线条的意义在于: 在屏幕上直接模拟出了一个亮度居中(0.5)的光源. 所以这张图表现出的含义如下.
参考资料
- LearnOpenGL CN: Gamma校正
- 知乎答案: 色彩校正中的 gamma 值是什么?Avatar Ye
- Gamma校正与线性空间 TraceYang
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。