颜色管线精粹
前言
接触图形学中颜色相关的概念许久了,色域,LDR,HDR,ToneMapping,伽马空间,线性空间,sRGB,ACES等名词也都算耳熟能详,但一直以来都是零散细碎的知识点,从来没系统整理过。
个人一直关注UE的发展和官方油管频道,恰巧看到了一个颜色管线的视频,算是事无巨细的详解了上面每一个概念和他们之间的关系,遂有此文,与诸君共赏。
今天我们要讨论发生在引擎和渲染过程中的颜色管线以及特定的颜色转换
线性渲染
我们先想象一个简化版的渲染管线,一个纹理输入,在线性空间下渲染,经过后处理,最后显示在窗口中(或者存储到一个文件中)
我们先从中间看起,即Linear Rendering in RGB,既然是渲染,我们首先应该关注的就是颜色值
颜色值代表光量,可以是范围很大的值,也可以是范围很小的值
并且他们是线性
的,这意味着如果我们将颜色值翻倍,我们得到的光量也是翻倍的
他们用于引擎的工作环境和运行时环境的颜色空间中
在更加深入之前,我们先了解一下颜色的基础知识
颜色基础知识
一般我们在谈论颜色空间的时候,同时涵盖了颜色编码
和颜色空间
这两个概念,我们先来看颜色空间
颜色空间
实际上除了RGB之外还有很多别的颜色空间,但是他们都可以表示同样的颜色值(也都是三个值),并且有公式可以用于他们之间的值转换
既然我们目前在讨论渲染,那我们就用RGB进行讲解
首先我们可以把RGB颜色空间想像成R,G,B三个互相垂直的坐标轴组成的坐标系,当他们的范围都是0-1时,得到的Cube如图所示
但是RGB颜色规范也有很多,并不一定是三个互相垂直的坐标轴,例如Rec709(sRGB),Rec2020,P3D65,AdobeRGB等
可以看到Rec709的红绿蓝不再互相垂直,并且表示的颜色范围有限
接下来让我们学习如何比较这两个颜色空间的值
我们知道,图片和显示器可以是不同的颜色空间
一个Rec709的显示器,一个Rec709的图片,图片将会被显示的很好,因为两者颜色空间一致,颜色值可以一一映射
但一个Rec709的显示器显示一个Rec2020的图片就会导致图片饱和度过低,因为一部分颜色映射失败了,Rec2020被设计为显示到具有充分饱和度的显示器上,而不是Rec709
颜色编码
颜色空间告诉我们什么是红色,绿色,蓝色,而颜色编码则是我们存储这些值的方式
颜色编码在图片(PNG,JPEG,MP4,EXR等),信号(HDMI)和显示器上都有应用
图片是一种float编码,很适合保存线性数据,但在信号方面,信号并不是一个高位深的数据,所以有另一种编码方式
事实上从信号到显示器有一系列不同编码(甚至是Camera Log)
说了这么久的光量,是时候形象的展示下了
蓝色部分是理想的输入输出存储函数,而黑色线条则是光电传输函数,之所以是0.218,是因为
而人眼对于暗部变化最敏感,基于此的编码方式则为sRGB编码,好巧不巧,我们前面的颜色空间
中也有sRGB颜色空间(Rec709),需要注意区分两者概念
提到上面的图,就不得不提及伽马矫正(伽马编码)这一概念了
图像的伽玛编码用于通过利用人类感知光和颜色的非线性方式来优化对图像进行编码时的比特使用或用于传输图像的带宽。 [ 1 ]在常见的照明条件下(既不是漆黑也不是耀眼的明亮),人类对亮度(明度)的感知遵循近似幂函数(与伽玛函数无关),对较暗色调之间的相对差异具有更大的敏感性比较浅的色调之间,符合亮度感知的史蒂文斯幂定律。如果图像不是伽马编码的,它们会分配太多的比特或太多的带宽来处理人类无法区分的高光,而分配太少的比特或太少的带宽来处理人类敏感的阴影值,并且需要更多的比特/带宽来维持相同的视觉质量。 [ 2 ] [ 1 ] [ 3 ]浮点图像的伽玛编码不是必需的(并且可能会适得其反),因为浮点格式已经提供了对数曲线的分段线性近似。 [ 4 ]
尽管伽马编码最初是为了补偿阴极射线管(CRT) 显示器的亮度特性而开发的,但这并不是它在现代系统中的主要目的或优势。在 CRT 显示器中,光强度随电子枪电压呈非线性变化。通过伽玛压缩改变输入信号可以消除这种非线性,从而使输出图像具有预期的亮度。然而,显示设备的伽马特性在图像和视频的伽马编码中不起影响。他们需要伽玛编码来最大限度地提高信号的视觉质量,而不管显示设备的伽玛特性如何。 [ 1 ] [ 3 ] CRT 物理原理与视频传输所需的伽马编码逆的相似性是巧合和工程的结合,这简化了早期电视机中的电子设备。 [ 5 ]
- 显示器的输入颜色值和输出亮度之间呈指数函数关系,指数值称为Gamma,一般为2.2
- 引入Gamma值的目的是适应人眼对亮度的不均匀感受,牺牲亮部细节来保留更多暗部细节。2.2的Gamma值来源于CRT显示器的电压-亮度响应曲线。
- 为保证相片在屏幕上的显示效果与现实一致,相机会在生成图片时对记录的亮度做一次0.45次方的处理,0.45次方来源于2.2的倒数
- 在生成时做过一次0.45次方处理的图片,位于Gamma空间,使用它们时需要手动做一次2.2次方,转换到线性空间,以获取物理正确的亮度/反射率等数值
- 如果不使用PBR流程,完全可以不关心颜色空间的问题,因为看上去差别不大
- 要使用PBR流程,则需要将Gamma空间制作的图片都转换到线性空间下使用
- 只有“颜色图”才需要转换颜色空间,法线图等“数据图”不需要转换
- 在线性空间下计算光照后,写入Framebuffer前需要做0.45次方操作转回Gamma空间
- “Gamma校正”一词特指从线性空间转到Gamma空间的操作,也就是0.45次方,2.2次方的理论上是他的逆操作,不过实际使用中这个名词基本是混着用的
总之,既是历史原因也是人眼感知需要,伽马矫正这一概念一直被延续到现在
引擎颜色空间
引擎默认是sRGB(Rec709)的颜色空间,注意,是颜色空间,而不是颜色编码,在这里他是线性的
之所以额外说明Rec709是因为他本身和sRGB颜色空间等价,而还有一种颜色编码是sRGB,需要做区分,我们约定,当谈论sRGB时,多是指sRGB的颜色编码,而不是颜色空间
一张纹理会有自己的颜色空间,自己的颜色编码,显示器也有自己的颜色空间,颜色编码,所以一定存在转换,这就是游戏引擎的工作,我们先来看纹理输入
颜色转换-纹理
纹理导入成UAsset时并不会改变颜色值,GPU最后使用的资产时Platform Format的文件,UAsset的功能就是规范化原始纹理的导入设置,例如一个10位深的纹理导入后会变成16位深,这时候并未改变颜色值,也没有颜色转换,但会让我们更方便的进行操作
那么颜色转换发生在什么时候呢,答案是UAsset到Platform Format这步,这一步的转换基于在Texture Editor中的参数
Mipmap的生成也在这一步
这样就达成了引擎中所有的数据都在引擎的颜色空间中工作的目的
如果勾选了sRGB,则会以sRGB编码做一次转换(即2.2次幂运算,Unity同理),用以矫正到正确的颜色值
颜色转换-后处理阶段
颜色值可以是HDR的,是线性值,同工作颜色空间
对一些效果感兴趣的可以自己搜索了解下
实际上Tonemapper不是一步,而是很多步骤集合在一起的(尽管我们常说的Tonemapper是一次Color Lut图采样计算)
实际上一些相机后处理功能和Tonemapper类似,所以可以有取舍的关闭打开
需要注意,Tonemapping需要先于Gamma校正进行,Gamma校正应该作为所有后处理的最后一步
引用
https://www.youtube.com/watch?v=II_rnWU7Uq8&t=2s
https://dev.epicgames.com/community/learning/knowledge-base/BLEJ/unreal-engine-color-pipeline-basics
https://zhuanlan.zhihu.com/p/66558476
https://zhuanlan.zhihu.com/p/609569101
https://zhuanlan.zhihu.com/p/720067122
https://kinematicsoup.com/news/2016/6/15/gamma-and-linear-space-what-they-are-how-they-differ