游戏开发中的坑之二 色彩空间 (大部分内容为转载)

       在游戏引擎内制作美术的时候,特别是手游,往往会发现为什么跟原来的效果差距那么大啊,明明是一样的贴图,一样的模型,主要原因可能就出现在色彩空间上,贴图的压缩方式也会有一定影响。

       以Unity为例,当设置为Android平台后,PlayerSettings要进行如下设置,可使模型贴图在灯光下颜色可以保持正确。

这里出现了个Color Space ,选项为Linear(线性)和Gamma(伽马)

接下来分析下线性和伽马的区别

--------------------------------------------------------假装看的懂分界线--------------------------------------------

Gamma矫正

所谓伽玛校正就是对图像的伽玛曲线进行编辑,以对图像进行非线性色调编辑的方法,检出图像信号中的深色部分和浅色部分,并使两者比例增大,从而提高图像对比度效果。计算机绘图领域惯以此屏幕输出电压与对应亮度的转换关系曲线,称为伽玛曲线(Gamma Curve)。以传统CRT(Cathode Ray Tube)屏幕的特性而言,它的输入电压和显示出来的亮度关系不是线性的,而是一个类似幂律(pow-law)曲线的关系,而这个关系又恰好跟人眼对光的敏感度是相反的。这个巧合意味着,虽然CRT显示关系是非线性的,但对人类来说感知上很可能是一致的。

Gamma 校正补偿了不同输出设备存在的颜色显示差异,从而使图像在不同的监视器上呈现出相同的效果。

保存颜色信息本身矫正称为encoding gamma,显示器对颜色的矫正称为display gamma,所以一个完整的图形系统中需要两个Gamma值,两次矫正刚好在一定程度上抵消(但一般不是完全抵消)。

下图展示了Gamma矫正对颜色值的影响。

这里写图片描述

sRGB

个人电脑使用的一个标准叫sRGB,它使用的encoding gamma大约是0.45(1/2.2),这个值为了配合display gamma为2.5的设备工作的,这样两次gamma矫正后产生偏差为0.45 * 2.5 = 1.125,从而在视觉上进行了补偿。

非线性输入

大部分图像文件都进行了提前矫正,就是保存文件时已经使用了encoding gamma对像素值编码,这意味着它是非线性的,如果在shader中直接使用就是在非线性空间计算,使得结果和真实世界结果不一致。

Gamma管线(Gamma Pipeline)

在Gamma渲染管线中,所有颜色和纹理在Gamma空间被采样,在shader应用了之后前,不会对shader输入做任何处理。即使这些值是在Gamma空间中的,所有shader计算对待他们输入都当做在线性空间,此外,当把shader输出写到内存中时,没有对最终像素应用Gamma矫正。很多时候是可以接受两次偏差一定程度上相互抵消。但是这是不正确的。

两种情况:

  1. 线性输入 输入颜色值在线性空间下,而在shader中按照线性空间下的计算,这些都是正确的,但最终输出的时候也没有做任何处理(主要Gamma矫正),所以在屏幕显示时,屏幕进行了一次display gamma,这样的转换后得到非预期的亮度,通常表现为整个场景比较昏暗。

  2. 非线性输入 输入颜色值在非线性空间下(通常表现为纹理),而在shader中把该值当成是线性空间下计算的(产生了偏差),这是不正确的,在最终输出的时候也没有做任何处理,但在屏幕显示时,进行了display gamma,虽然这样产生两次偏差会在一定程度上进行抵消,但这样的抵消是不正确的。

为了直接显示时可以正确显示,大多数图像文件都进行了提前的校正,即已经使用了一个encoding gamma对像素值编码。

Gamma是非线性的,Gamma空间就是所谓的非线性空间。

线性管线(Linear Pipeline)

如果开启了线性渲染(Linear Rendering),Unity会背地里把输入纹理设置为sRGB模式,这种模式下硬件在对纹理进行采样时会自动将其转换到线性空间中;并且,也会设置一个sRGB格式的buffer,此时GPU会在shader写入color buffer前自动进行伽马校正。如果此时开启了混合(像我们之前的那样),在每次混合是,之前buffer中存储的颜色值会先重新转换回线性空间中,然后再进行混合,完成后再进行伽马校正,最后把校正后的混合结果写入color buffer中。这里需要注意,Alpha通道是不会参与伽马校正的。

sRGB模式是在近代的GPU上才有的东西。如果不支持sRGB,我们就需要自己在shader中进行伽马校正。 
对非线性输入纹理的校正通常代码如下:

float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 );

在最后输出前,对输出像素值的校正代码通常长下面这样:

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

但是,手工对输出像素进行伽马校正在使用混合的时候会出现问题。这是因为,校正后导致写入color buffer的颜色是非线性的,这样混合就发生在非线性空间中。一种解决方法时,在中间计算时不要对输出进行伽马校正,在最后进行一个屏幕后处理操作对最后的输出进行伽马校正,但很显然这会造成性能问题。 还有一些细节问题,例如在进行屏幕后处理的时候,要小心我们目前正在处理的图像到底是不是已经伽马校正后的。

总之,一切工作都是为了“保证所有的输入都转换到线性空间,并在线性空间下做各种光照计算,最后的输出(最最最最后的输出)进行伽马校正后再显示”。

Linear Rendering 和 Gamma Rendering的区别

Linear Rendering就是在shader中所有计算会在线性空间下进行,Gamma Rendering就是在shader中不进行转换到线性空间下,直接计算。然就是计算方程式不同,也就意味例如光照表面会有不同的响应曲线和图片效果,表现不相同。

  1. Light Falloff 
    光照表现一般受光源的距离和法线两个因素影响(在同等光强下)。首先当我们用Linear Rendering时,执行Gamma矫正将会使光照范围变大。第二种会使边缘模糊,分不清界限。这更准确的表现了表面光照强度下降。图片来源unity doc

  2. 表面响应强度

    随着光强的增加,非线性方式计算的表面会更亮一些。这导致了光照在表面很多地方曝光过度,而且给场景模型一个褪色(变白色了)的感觉。当你用线性渲染时,表面颜色仍然随着光照强度线性增加的,这样就使表面材质和颜色更接近现实

å¾çæ¥æºunity doc

1.RGB是RED(红),Green(绿),Blue(蓝)这三 种基色的首字母缩写。

2.“RGB色彩空间”是泛指在硬件和软件里用到的“所有颜色”

3.sRGB是RGB的一种特定类型。

4.sRGB很流行,但它的色域很有限。

5.Adobe RGB是包含sRGB与CMYK的色彩空间。

6.prophoto RGB是一种色彩更为广阔的色彩空间模式,一般用于色彩管理。

 

(一)渲染时使用的颜色空间

  • 在下刚学Unity,所以只能拿Unity举例:
  • 在Unity渲染中,存在两个颜色空间
  1. Gamma(非线性)颜色空间
  2. Linear(线性)颜色空间
  3. 现在再来看这两个颜色空间应该不陌生了吧,至少我们知道他们表达的是什么意思。
  4. Gamma颜色空间里使用的是一个进过校正(阉割)过的颜色表。
  5. Linear颜色空间里使用的是一个线性的完整的颜色表。

(二)渲染

  • 渲染是一个很复杂的过程,涉及步骤有许多,不在此展开讨论。
  • 但无论多复杂的过程,多复杂的灯光、材质等等乱七八糟的东西,它始终离不开图片,颜色。
  • 图片是通过颜色空间中的颜色创作出来的,那它就一定存在着一个问题,是否经过Gamma校正。

Linear颜色空间

  • 在Unity中,如果使用Linear颜色空间,除非对指定图片选择了ByPass sRGB(忽略sRGB,你理解为忽略Gamma校正),否则所有的图片都会默认变成sRGB格式。
  • 这样做的目的是把你在Gamma颜色空间里创作的图片里面的颜色给校正到正确的32位颜色,再参与GPU渲染计算。
  • ByPass sRGB的意思就是直接使用这个0.5,不用再校正了,除非你是在线性颜色空间里创作的图片,否则不建议这么干。
  • 然后,GPU再把结果Gamma校正一次,0.2-0.5,这样做的目的是:显示器不管你到底干了什么,他老人家是一定要在最后Gamma校正一次的(0.5-0.2)。
  • 显示器按照接受到的值进行Gamma校正(0.5校正到0.2)给你看。
  • 在这个空间里进行渲染,因为使用的是线性的颜色空间,因此你会得到非常真实的表现效果。
  • 但是,因为色彩丰富的同时,带来的各项成本,如硬件渲染能力的需求也会增加。

Gamma颜色空间

  • 在这个颜色空间内渲染,GPU不会再Gamma校正,而是直接使用存储的值参与渲染计算,因为大家使用的都是阉割版的颜色嘛。
  • 但是在这个空间里进行的渲染,因为使用的是阉割过的Gamma颜色空间,因此你会得到不符合真实表现的效果。
  • 在最终输出到显示器的时候,仍然会经过Gamma校正,将渲染后的0.5,表现成0.2给你看。
  • 因为使用了较少的颜色(相对于线性颜色空间),因此会得到比较好的渲染效率以及较低的渲染能力需求。但是图像会存在失真(偏暗,因为亮部细节表现不够)等一系列问题。

猜你喜欢

转载自blog.csdn.net/yijiankun100/article/details/81093409