伽马校正(Gamma Correction)与sRGB

article/2025/7/27 19:13:19

文章目录

  • 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纹理错误
让美术人员在线性空间中进行纹理制作可以解决这一问题, 但是伽马校正的内部逻辑并不一定是所有美术人员都需要理解的知识, 并且在 sRGB 空间进行创作显然更加容易. 所以一般情况下我们都会由程序来对纹理进行重校. 重校可以在 shader 中进行, 读取出纹理中的颜色之后对其进行 pow 2.2 的校正. 也可以通过图形 API 由硬件进行处理, 比如 OpenGL 中可以在创建纹理的时候将其指定为 GL_SRGB 或 GL_SRGB_ALPHA.

sRGB 纹理重校和伽马校正一样, 使用时需要注意应用的对象, 并不是所有的纹理都是在 sRGB 空间制作的. 诸如 diffuse 贴图这类为物体上色的纹理几乎都是在 sRGB 空间的, 相反像法线贴图这种提供光照参数的纹理则几乎都是在线性空间的. 将法线贴图配置为 sRGB 纹理反而会导致渲染错误.


正确的点光衰减

在真实世界中, 光源产生的亮度与距离的平方成反比, 也就是

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

http://chatgpt.dhexx.cn/article/fvZaAkJ7.shtml

相关文章

图形学基础——伽马校正

百人计划学习视频连接:【技术美术百人计划】图形 2.6 伽马校正 颜色空间 具体内容在之前的文章有讲:色彩空间介绍 sRGB:微软在1996年发布的通用颜色标准DCI-P3:数字电影播放的颜色标准Rec-709、PAL:电视行业的颜色标…

伽马校正

注:本文为博主参考书籍和他人文章并加上自己的理解所编,作为学习笔记使用并将其分享出去供大家学习。若涉及到引用您的文章内容请评论区告知!如有错误欢迎指正! 一、伽马校正简介 射伽马校正是图像预处理阶段经常使用的一个非…

可执行文件的装载,进程和线程,运行时库的入口函数(第六章)

目录 第1步.创建一个独立的虚拟内存空间 第2步.读取可执行文件头,建立虚拟空间和可执行文件之间的映射关系 第3步.将cpu的指令寄存器设置成可执行文件的入口地址,启动运行。 程序开始执行,发生页错误。 随着程序的执行,不停…

疯狂python讲义学习笔记——前十章完结

#第一章&#xff1a;绪论 #单行注释多行注释""" 多行注释 """#dir列出指定类或模块的属性与方法&#xff0c;help查看某个函数或方法的帮助文档 print(dir(str)) print(help(str.title))#第二章&#xff1a;数据类型 a5 print(type(a))#<clas…

嵌入式代码学习心得记录

一、C语言学习心得记录 函数递归 编写顺序 终结条件输入下一级递归参数,调用下一级递归函数.当前递归函数的操作代码,在下一级递归函数执行完成后执行的操作代码. #include <stdio.h> #include <string.h>void revert(char *s, int len) {// 终结条件if(len <…

python 字节流分段_一文掌握CTF中Python全部考点

声明&#xff1a;Tide安全团队原创文章&#xff0c;转载请声明出处&#xff01;文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用&#xff0c;任何人不得将其用于非法用途以及盈利等目的&#xff0c;否则后果自行承担&#xff01; 前 言 一次偶然的机会&#xff0c…

Python课程学习笔记 下

笔记目录 Python 学习笔记 上面向对象和异常面向对象基本理论定义类根据类实例化对象属性相关方法相关类的补充属性补充方法相关补充描述器python对象的生命周期内存管理机制面向对象的三大特性面向对象应当遵循的原则 错误和异常错误和异常的概念常见的系统异常和系统异常类继…

从点击APP图标到首页展示的加载过程

转载链接在这里 iOS 系统架构 Mac系统是基于Unix内核的图形化操作系统&#xff0c;Mac OS 和 iOS 系统架构的对比分析发现&#xff0c;Mac OS和iOS的系统架构层次只有最上面一层不同&#xff0c;Mac是Cocoa框架&#xff0c;而iOS是Cocoa Touch框架&#xff0c;其余的架构层次都…

python os模块

10.3 os模块 os就是“operating system”的缩写&#xff0c;顾名思义&#xff0c;os模块提供的就是各种 Python 程序与操作系统进行交互的接口。通过使用os模块&#xff0c;一方面可以方便地与操作系统进行交互&#xff0c;另一方面页可以极大增强代码的可移植性。如果该模块中…

Python:异常处理,模块和包,正则表达式

1&#xff0c;异常处理机制 Python的异常处理机制可以让程序具有极好的容错性&#xff0c;让程序更加健壮。当程序运行出现意外情况时&#xff0c;系统会自动生成一个Error对象来通知程序&#xff0c;从而实现将“业务实现代码”和“错误处理代码”分离&#xff0c;提供更好的可…

操作系统面试题总结

进程的常见状态&#xff1f;以及各种状态之间的转换条件? 创建状态&#xff1a;进程在创建时需要申请一个空白PCB&#xff0c;向其中填写控制和管理进程的信息&#xff0c;完成资源分配。如果创建工作无法完成&#xff0c;比如资源无法满足&#xff0c;就无法被调度运行&#…

Python--多线程与并行

一、线程和进程介绍 1.1、进程基本概念 进程&#xff08;Process&#xff09;&#xff0c;是计算机中已运行程序的实体&#xff0c;曾经是分时系统的基本运作单位。在面向进程设计的系统&#xff08;如早期的Unix、Linux2.4及更早的版本&#xff09;中&#xff0c;进程是程序…

【Python学习】--pythonf笔记

系列文章目录 文章目录 系列文章目录前言一、Python代码规范&#xff08;编码、代码格式&#xff09;1. 编码2. 代码格式3. import 语句4. 空格 二、Python代码注释&#xff08;块注释、行注释、文档注释&#xff09;1. 块注释2. 行注释3. 建议4. 文档注释 三、Python命名规范&…

进程和信号

进程和信号 一、进程初识 1、程序的开始和结束 开始&#xff1a;编译链接时的引导代码。操作系统下的应用程序其实在main执行前也需要先执行一段引导代码才能去执行main。在程序链接时由链接器将编译器中事先准备好的引导代码给链接进去与我们的代码一起组成最终的可执行程序…

什么!学Python多进程的你还不知道multiprocessing模块?该充电了>_(Python编程 | 系统编程 | 并行系统工具 | multiprocessing模块)

文章目录 multiprocessing模块基本操作&#xff1a;进程和锁关于实现和用法的规则 *IPC*工具&#xff1a;管道、共享内存和队列管道共享内存和全局对象队列和子类 启动独立程序其他更多 multiprocessing模块 Python标准库中的multiprocessing模块允许脚本通过与threading模块非…

一文入门Python基础

Python基础 python中的输出函数 print()函数 可以输出的内容 数字字符串含有运算符的表达式(会返回表达式计算的结果) 内容输出的目的地 显示器文件 # 将数据输入文件中&#xff0c;注意点——所指的盘必须存在——使用filefp fp open(路径,模式) print(hello,file fp) fp.…

深入理解Linux进程管理(1.0)

学习方法论 写作原则 标题括号中的数字代表完成度与完善度 0.0-1.0 代表完成度&#xff0c;1.1-1.5 代表完善度 0.0 &#xff1a;还没开始写 0.1 &#xff1a;写了一个简介 0.3 &#xff1a;写了一小部分内容 0.5 &#xff1a;写了一半内容 0.9 &#xff1a;还有个别内容没写 1…

一、Linux系统编程:进程基础

1 进程基础 1.1 概念 定义 程序在计算机上的一次执行过程&#xff0c;执行中的程序。本质 1、程序在地址空间中按照代码逻辑控制流执行 2、资源分配最小单位 进程是一个抽象概念 1.2 进程与程序 区别 进程程序动态静态有生命周期指令集合只能对应一个程序可以对应多个进程…

YARN源码解析之NodeManager中的ContainerExecutor

在NodeManager中&#xff0c;有三种运行Container的方式&#xff0c;它们分别是: DefaultContainerExecutorLinuxContainerExecutorDockerContainerExecutor 从它们的名字中&#xff0c;我们就能看得出来&#xff0c;默认情况下&#xff0c;一定使用的是DefaultContainerExec…

linux进程状态怎么手动切换,二十六、Linux 进程与信号---system 函数 和进程状态切换...

26.1 system 函数 26.1.1 函数说明 system(执行shell 命令) 相关函数 fork&#xff0c;execve&#xff0c;waitpid&#xff0c;popen #include int system(const char * string); 函数功能&#xff1a;简化 exec 函数 函数说明 system()会调用 fork() 产生子进程&#xff0c;由…