Bootstrap

Unity Gamma & Linear Color Space

转载文章,出自http://www.manew.com/thread-105872-1-1.html,作者 alphatt


Gamma & Linear Color Space

一、真实?感觉?     1、你相信你的眼睛吗
(蓝黑or白金?)
(A和B的颜色一样吗?)
2、你以为你以为的就是你以为的吗
(中度灰,127?186?)
二、Gamma Color Space     1、什么是亮度
    人类视觉对亮度感知能力并不是呈现线性关系的,人类的眼睛在在感受纯白(亮度100%)与纯黑(亮度0%)后,对于中等亮度(亮度50%)的实际感知并不是中灰(PS中128度灰)所带来的亮度,而当人眼接受到18%左右的亮度的光源后,就能感觉到这是中等亮度了。 那么人类对于对亮度的感知响应称为亮度。
    2、什么是Gamma和Gamma矫正
    传统的CRT显示器是通过电压的强度来进行屏幕亮度的显示的,而CRT显示器显示的亮度跟施加的电压并不是呈线性的关系的,是呈现一种幂律响应的方式:在显示器的表面处产生的亮度与提高到2.5倍的施加电压大致成比例,该功率函数的指数的数值通常被称为γ(Gamma)。
这是一个实际CRT传输功能的图形
    为了实现亮度的正确再现,同时,如上面所说(什么是亮度),人类对于亮度的感知是非线性的,所以必须补偿该非线性。(这里的非线性指两个,人类对亮度的感知的非线性和CRT显示器的对电压的非线性的响应关系)
那么对于Gamma矫正的原因,网上也是众说纷纭,有的人认为仅仅是因为老式计算机(CRT)的的历史遗留 问题,这个说法主要来自Gong大,还要一个党派认为这仅仅是因为人眼的视觉感官所决定的,跟CRT没半点关系,这个说法主要来自韩世麟,那么最终也有人结合这两派提出自己的观点,她认为主要跟人眼就关,CRT只是个巧合,CandyCat。
        1)CRT历史遗留问题党
    开发gamma编码是用来抵消阴极射线管(CRT)显示器的输入和输出特性。电子枪的电流,也就是光的亮度,与输入的正极电压的变化是非线性的。通过gamma压缩来改变输入信号抵消了这个非线性,因此输出图像就能有预期的亮度。来自维基百科
如果单纯考虑历史原因,CRT的输入输出问题,那么计算就很简单了,如下图

那么我们所看到的图像,就有以下几种情况了:

按照Gong大的意思,这个原因完全与人眼无关,但是这里我是质疑的,当然下面也有人留言进行了反驳。
        2)人眼至上党             人眼对自然亮度的感知是非线性的(韦伯定律)
对于你看到的跟实际的情况不符,韩世麟用了一条韦伯定律来大概说明了下这种现象,在wiki上韦伯定律是这么解释的:

韦伯定律:在同类刺激之下,其差异阈限的大小是随着标准刺激强弱而成一定比例关系的,K=ΔI/I K为常数。 
费希纳定律:在绝对阈限之上,主观的感觉强度与刺激强度的改变,两者间呈对数的关系,亦即,刺激强度如果按几何级数增加,而引起的感觉强度却只按算术级数增加。

    这条定律套用到前面所说的亮度的关系就是说,在一间黑屋子里,开一盏灯,你立刻就能感觉到亮度的巨大变化,如果在这种状态下想获得成倍的亮度变化的感觉,理论上你再开一盏灯就可以,但实际不是,可能要开两盏灯你才能感受到这个屋子里的亮度变亮了一倍,也就是说,你想获得成倍亮度变化的体验,所开的灯的数量并不是呈线性比的,而是一个指数函数的关系。

    这张图说明,在亮度呈现线性变化的情况下,人眼的感觉的变化并不是呈现线性的,下面黑色区域的变化,人眼能很容易的分辨,而上面白色区域的变化,人眼就很难分辨了,为了加强这种感觉,戴眼镜的同学可以把眼睛摘掉,或者远距离观察。
            大自然的灰阶是无限的,而记录展示的灰阶是有限的
    好了,既然上面说到,人眼观察到的这个世界的线性亮度变化,实际感觉是非线性的,那么怎么利用这点呢,前面说到了因为CRT显示器的原因,还有一个非常非常重要的原因,就是大自然有无穷无尽的色彩,亮度,需要将这些无穷无尽的色彩全都装进屏幕里,显然是不太可能的,那么就需要一个映射关系,正常的计算机显示的图片都是8bit的位图,那么8bit的位图所能产生的色彩是256^3,而灰阶只有可怜的256个。
在摄像机等记录影响的设备进行记录画面的时候,如果不矫正的话,按照线性的去记录,理论上没啥问题,实际上牵扯到我们刚刚讲了人眼的特性,这就很有问题了。请看下图

    下面的灰阶图表示了正确的线性灰阶过度关系,但是上图才是我们人眼真正感知到的灰阶过度关系,如果按照下面的状态去记录,那么人眼就会感觉到暗部细节偏少,在人眼的感官里,白色是占了很大一部分的,然而人眼对亮度不那么敏感,所以造成了很大的浪费。
    因此,我们就需要用到Gamma矫正来纠正这种关系,说白了就是要让人感到爽。
因此我们就有了pow(color, 1/2.2)这个gamma encoding操作,通过这个公式,先让一张图变亮,来让暗部细节更丰富(暗部细节得到更多的色阶),最后再通过displaygamma输出到屏幕上,达到还原的目的。
    这里补充一个小知识:伽马校正的公式是:V(out) = V(in) ^ r. 0 < r < 1 时,该 r 称为 encoding gamma,该过程称为 gamacompression; 1< r, 该 r 称为 decoding gamma,该过程称为gamaexpansion。具体表现如下图。
好了,上面说了那么多,通过这张图基本就可以完全理解了
  • 绿色的曲线是人眼的感光度的曲线,也就是对光线敏感程度的一条曲线,从图中蓝色的线可以看出,当亮度达到0.218左右的时候,人眼就已经感觉到这是中度亮度了。
  • 为了配合这种特性,必须要用相反的一条曲线进行校正,也就是图中蓝色的曲线,可以看到,当进行这样一个过程之后,可以以用0.729的亮度来表示0~0.5的黑色区域,而那些人眼不敏感的白色区域只占到了0.271,这样就达到了资源节省的目的。


    那么来说说,那个CRT是怎么回事,当年CRT的display Gamma是2.5,这个数值接近于人眼的感光的幂率1/0.45=2.2,于是当时的人们很惊讶,我X,真是太巧了,显示器不需要做额外的工作就能正确显示我们之前根据人眼的特性矫正过的图片了,太棒了!这就造成了一个小问题,0.45*2.5最终的结果是1.125,并非是1,但是也没人管这事啊,直到后来,科技大佬们终于管起了这事:

sRGB (标准红绿蓝)是惠普和微软 1996年合作创建的用于显示器,打印机和互联网的RGB色彩空间 ,随后由IEC标准化为IEC 61966-2-1:1999。 [1]它通常用作不包含任何颜色空间信息的图像的“默认”颜色空间,特别是如果图像存储为8位整数。

    SRGB的display gamma的值为2.2,这样配合之前根据人眼推算出来的gamma值为0.45,最终的gamma值就为1了。

    当然,1这个值也不是绝对的,只是在大部分的情况下比较合适,如果在黑暗的电影院里,1.5是比较好的选择,而在比较明亮的办公室里,1.125是比较好的选择,这就像手机上的自动调节光照一样,阳光很好的室外,会自动调亮屏幕,夜晚躺在床上看小说,屏幕自动调暗。

    那么对于Gamma和Gamma矫正以及人眼什么的,可以用一下几句话来总结:
韩世麟:灰阶有限的前提下,因为人眼对自然的非线性感知特性,我们才需要Gamma校正。
渣渣:gamma校正存在的本质原因是:是受限于有限存储空间及渲染带宽,需要在整个图像的流转各级转换中尽可能保留暗部细节,以满足人眼对暗部敏感的需求。
疾风齿轮:gamma值就是对动态范围内亮度的非线性存储/还原算法。

Gamma encoding of images is used to optimize the usage of bits when encoding an image, or bandwidth used to transport an image, by taking advantage of the non-linear manner in which humans perceive light and color.[1] The human perception of brightness, under common illumination conditions (not pitch black nor blindingly bright), follows an approximate power function (note: no relation to the Gamma function), with greater sensitivity to relative differences between darker tones than between lighter ones, consistent with the Stevens' power law for brightness perception. If images are not gamma-encoded, they allocate too many bits or too much bandwidth to highlights that humans cannot differentiate, and too few bits or too little bandwidth to shadow values that humans are sensitive to and would require more bits/bandwidth to maintain the same visual quality.[1][2] Gamma encoding of floating-point images is not required (and may be counterproductive), because the floating-point format already provides a piecewise linear approximation of a logarithmic curve.[3]
Although gamma encoding was developed originally to compensate for the input–output characteristic of cathode ray tube (CRT) displays, that is not its main purpose or advantage in modern systems. In CRT displays, the light intensity varies nonlinearly with the electron-gun voltage. Altering the input signal by gamma compression can cancel this nonlinearity, such that the output picture has the intended luminance. However, the gamma characteristics of the display device do not play a factor in the gamma encoding of images and video—they need gamma encoding to maximize the visual quality of the signal, regardless of the gamma characteristics of the display device.[1][2] The similarity of CRT physics to the inverse of gamma encoding needed for video transmission was a combination of luck and engineering, which simplified the electronics in early television sets.[4]
图像的伽玛编码用于通过利用人类感知光和颜色的非线性方式来优化编码图像时使用的比特,或者用于传输图像的带宽。 [1]在普通照明条件下(不是黑色或黑暗),人类对亮度的感知遵循近似的功率函数 (注意:与伽马函数无关),对较暗的色调之间的相对差异的敏感性比较轻一些,与史蒂文生的亮度感知的幂律一致。 如果图像没有伽玛编码,它们会分配太多的位或太多的带宽,以突显人类无法区分,太少的位或太少的带宽影响人类敏感的值,并且需要更多的位/带宽来维护相同的视觉质量。 [1] [2] 浮点图像的伽马编码不是必需的(并且可能适得其反),因为浮点格式已经提供了对数曲线的分段线性近似。 [3]
虽然伽玛编码最初是为了补偿阴极射线管 (CRT)显示器的输入输出特性而开发的,但这不是现代系统的主要目的或优点。 在CRT显示器中,光强度随电子枪电压而非线性变化。 通过伽玛压缩来改变输入信号可以消除这种非线性,使得输出图像具有预期的亮度。 然而,不管显示装置的伽马特性如何,显示装置的伽马特性不会影响图像和视频的伽马编码,因此它们需要伽马编码以使信号的视觉质量最大化。 [1] [2] CRT物理学与视频传输所需伽马编码的相反性是运气与工程的结合,简化了早期电视机中的电子设备。 [4]

    对于CRT党和人眼党,我后来又仔细阅读了下wiki的说明,wiki的意思是,虽然早期gamma的存在是为了补充阴极射线管(CRT),但是这不是现代系统主要的目的,我猜测这里说的现代系统应该就是微软联合惠普等科技大佬指定sRGB标准之后的时间,所以对于业界的Gamma&Gamma矫正就不做深究了,下面主要讲讲中的Color Space。



三、Unity3d中的Gamma和Linear Color Space
    在unity中除了选择渲染路径外,在“颜色空间”也是很重要的。颜色空间决定了在灯光计算中混合颜色或从纹理中读取值时,统一所使用的计算方式。这可能会对游戏的画面真实性产生比较大的影响,在以前Linear Color Space不支持移动平台,但是在Unity 5.5中已经加入了对和iOS的线性渲染。通过线性渲染,你可以确定你的输入输入,输出和计算都是在正确的颜色空间中完成的。最终图像的亮度将随着场景中的光的数量增长而呈现线性增长。
    在Android上,线性渲染需要OpenGL ES3  API,市面上61.1%的Android设备都满足。在iOS上,线性渲染需要Metal API,市面上71.1%的IOS设备都能满足。
    根据最新的消息,Unity2017.2已经开始支持WebGL2.0的线性色彩空间。目前市面上支持的浏览器是Chrome和Firefox浏览器。
    首先明确一点,在Shader中计算颜色一定是在线性空间进行的。使用unity在Gamma空间中创建的纹理可以在线性空间颜色空间中被正确的表现出来。
    所有的8位图片都是经过gamma矫正的,但是shader计算是要在线性空间下的,所以当你选择线性空间的时候,为了克服gamma空间下贴图计算不准确的问题,可以让unity使用RGB采样而不是sRGB采样,具体设置如下:

  • Gamma Color Space workflow
    虽然线性工作的计算结果更准确,但确实还是需要存在一个gamma工作流,因为以前线性工作流并不能在移动平台使用,现在即使支持移动平台,但为了更大的兼容性,还是需要gamma工作流。
    当引擎被设置成了gamma color space之后,渲染管道就会使用它们所存储的伽马色空间中的所有颜色和纹理——纹理在使用着色器时,不会从它们中移除伽马校正。此时不管是是否勾选上面提到的sRGB的选项,这张纹理最终都会得到一个正确的颜色值,原因是即使这些值是在伽马空间中,Unity编辑器的着色计算仍然把它们的输入看作是线性空间的。为了确保最终结果正确,编辑器在将着色器输出写入framebuffer时进行了调整,并没有对最终结果应用伽马校正。
  • Linear Color Space workflow
    使用线性工作流相比gamma工作流能够获得更真实的渲染。不管你时在线性空间下还是gamma空间下创建的贴图都是可以在线性空间下工作的。Gamma空间下创建的贴图在线性空间下被提交给着色器前可以移除gamma 矫正。

  • 线性贴图

    当设置了Linear Space,引擎默认你的忒图是在gamma color space中的,unity使用你GPU的sRGB标准来在两者间进行交叉使用,如果你的贴图本身是在linear空间下制作的,你需要忽略sRGB采样。

  • Gamma贴图

    从伽马色彩空间到线性色彩空间的过程,这种转换是隐式应用的,因为Unity编辑器已经将这些值转换为浮点数,然后将它们作为常量传递给GPU。当采样纹理时,GPU会自动移除伽马校正,将结果转换为线性空间。这些输入然后被传递给Shader,在正常情况下,在线性空间中进行照明计算。当将生成的值写入framebuffer时,它要么是直接更正,要么是在线性空间中保留,以便稍后进行伽马校正——这取决于当前的呈现配置。例如,在高动态范围(HDR)中,渲染结果被保留在线性空间中,而伽马则稍后修正。
根据上面的解释,其实可以进行如下的总结:

    当选择Gamma Space时:就是一个“负负得正”的过程,加载贴图(此时贴图是校正了的1/gamma),采样,计算,再写到缓冲区,显示在屏幕上,也不会对输出像素进行任何处理,这意味着输出的像素会经过显示器的display gamma转换后得到非预期的亮度,通常表现为整个场景会比较昏暗。

当选择Linear Space时:Unity会使用线性计算光照纹理,引擎会创建支持sRGB的缓冲区,替换普通的缓冲区。同时会默认的对纹理进行一次反gamma矫正(就是进行一次pow(x,2.2)),这个过程就是unity在线性空间下对于gamma空间下贴图的转换处理的过程。如果此时开启了混合,在此种缓冲区进行Alpha混合时,先将缓冲区的颜色G(x)^Gamma,变换回线性变化,再进行混合,而后再对得到的结果G(x)^(1/Gamma)到缓冲区,这样保证Alpha混合时结果也是正确的.此外,如果应用了HDR,创建了浮点缓冲区,写入和读取浮点缓冲区时都没做特殊处理,里面的内容都是线性变化的,在将浮点缓冲区写入带sRGB的缓冲区时再一次性进行G(x)^(1/Gamma)。
    sRGB模式是在近代的GPU上才有的东西。如果不支持sRGB,我们就需要自己在shader中进行伽马校正。对非线性输入纹理的校正通常代码如下:

float3 diffuseCol = pow(tex 2D( diffTex, texCoord ), 2.2 );  

最后输出颜色为了配合显示器的displaygamma一定要进行如下计算:

fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);
return fragColor;

最后通过一个实例,来看看在unity中使用 Linear Color Space:
  • 首先在unity中设置成linear空间,Edit -> Project Settings -> Player -> Other Settings
  • 然后再PS里面制作一张128度灰的贴图(这里可以制作两张,一张是8bit的128度灰,还有一张是32bit的128度灰)导入Unity
  • 新建一个不接受光照的Shader,最终颜色呈两倍输出

  • 新建一个Sphere和一个材质,然后先把8bit的图贴在球上,观察球的颜色,再把引擎的颜色空间切换成Gamma空间,你会得到如下图所示的结果:

很神奇,对不对,Linear空间下的颜色表现竟然是错的!,反而gamma是对的,原因在与,这张图默认进入引擎是被标记了sRGB采样的,那么在线性空间下,就会发生这样的计算(pow(0.5, 2.2) * 2, 1/2.2)最终颜色值结果只有0.68处理办法就是去除sRGB采样:
  • 而对于32位编辑的纹理,不管是linear还是gamma空间,不管是否勾选srgb采样都能获得最正确的结果。

  • Linear 和 Gamma 的区别、
    当使用线性呈现时,光照方程的输入值与伽马空间中的值不同。这意味着不同的结果取决于颜色空间。例如,光线突出的表面有不同的响应曲线,而图像效果的表现则不同。

  • 光的衰减度

        距离和普通光照的衰减有两种不同的地方:


    • 当在线性模式下渲染时,额外的伽马矫正使得光的半径显得更大。
    • 灯光的边缘也显得更加清晰。这更正确地表现了在表面上光照的衰减。

  • 光强度的响应


    当你使用伽玛渲染时,提供给一个着色器的颜色和纹理已经有了伽马校正。当它们被用在一个着色器上时,高亮度的颜色实际上比线性照明的亮度要高。这意味着当光强度增加时,表面会以非线性的方式变得更亮。这就导致了很多地方的照明过于明亮。整个场景的表现就会变得非常糟糕。当你使用线性渲染时,当光强度增加时,表面的响应仍然是线性的。这就产生了更现实的表面阴影和更漂亮的表面效果。具体可以看上面的图。
  • Linear 和 Gamma 混合

    当混合到帧缓冲器中时,混合发生在帧缓冲区的颜色空间中。
    当你使用伽玛空间渲染时,非线性的颜色会混合在一起使用。这在数学计算上不是一种正确的混合颜色的方法,甚至有可能得到意想不到的结果,但是这是在一些图形硬件上混合的唯一方法。
    当你使用线性空间渲染时,混合会出现在线性色彩空间中,这在数学计算上是正确的并且能得到比较精确的结果。
  • 内置转换代码


四、参考资料
https:// unity3d.com/cn/learn/tutorials/topics/graphics/choosing-color-space

https://www.zhihu.com/question/43574033?sort=created

http://blog.csdn.net/zhaoguanghui2012/article/details/54016868

https://blogs.unity3d.com/cn/2016/12/07/linear-rendering-support-on- android-and-ios

https://docs.unity3d.com/Manual/LinearLighting.html?_ga=2.149938495.806633104.1499837304-570944340.1493705545

http://blog.csdn.net/candycat1992/article/details/46228771

http://blog.csdn.net/golden_shadow/article/details/40183561

https://docs.unity3d.com/Manual/LinearRendering-LinearOrGammaWorkflow.html

https://docs.unity3d.com/Manual/LinearLighting.html

https://docs.unity3d.com/Manual/LinearRendering-GammaTextures.html

https://docs.unity3d.com/Manual/LinearRendering-LinearTextures.html

https://en.wikipedia.org/wiki/SRGB

https://www.w3.org/Graphics/Color/sRGB.html

http://www.klayge.org/2011/02/26/gamma%E7%9A%84%E4%BC%A0%E8%AF%B4/

https://forum.unity3d.com/threads/bug-with-bypass-srgb-sampling.282469/

http://qiankanglai.me/2014/12/24/gamma-correction/

https://www.zhihu.com/question/27467127

https://zhuanlan.zhihu.com/p/26644788

https://www.slideshare.net/ozlael/hable-john-uncharted2-hdr-lighting?from_action=save

http://www.poynton.com/notes/colour_and_gamma/GammaFAQ.html
By,Alpha 2017-07-21
转载请注明作者出处
;