OpenGL(十八)Gamma校正 色域 与 HDR

通常来说,在不同设备上看到的颜色是不同的。其中最常提及的概念是高动态光照渲染(High-Dynamic Range,简称 HDR )。它可以使图像在亮度的表现上更丰富。这篇文章讨论设备颜色和校正的相关概念。

眼前的黑不是黑

人眼对亮度的敏感程度不是线性的,因此我们我们更容易看到亮的区域。


上图中左侧是线性渐变的图,右侧是我们实际看到的。在显示器上,输入线性信号就会显示出右图的效果。为了解决这个问题,在显示时会加入一个颜色校正,来保证图片看起来正确,这就是 Gamma Correction,具体说来,这个算法是 pow(color, 1/2.2)。所有的图片和视频都被编码到伽马空间中。可以这样理解,你所看到的一切都不是真的。

伽马校正

通过pow(color, 1/2.2)这个Gamma-encode操作,可以使灰度部分得到更多的色值,得到一张比较“亮”的图片并存储起来。这张图片虽然暗部细节都保留下来了,但是不能直接看这张“亮”的图片,那只是一个中间产物而已。我们需要显示器做一个pow(color, 2.2)的Gamma-decode操作把它压暗。

OpenGL(十八)Gamma校正 色域 与 HDR_第1张图片

从上图可以看出当gamma值为2.2时,曲线会上拱,输出值大于实际值,也就使得暗部获得更多的色彩。至于2.2是怎么来的,我只能说是经验数字,没什么科学依据。因此这个值也可以根据自己的实际体验进行调整,不过工业标准就是2.2哦。

虽然这个方案比较完美,但有了伽马校正之后,事情并没有变得简单。因为只有取到线性色值,光线计算才能在正确的线性空间(linear space)中进行,最终显示效果又需要应经过伽马校正。

sRGB

为了规范显示的颜色。微软联合HP、三菱、爱普生等厂商联合开发的sRGB(standard Red GreenBlue)通用色彩标准,绝大多数的数码图像采集设备厂商都已经全线支持sRGB标准,如:数码相机、数码摄像机、扫描仪、显示器等都能看到sRGB的选项。而且几乎所有的打印、投影等成像设备也都支持了sRGB标准。有了这套标准,从理论上保证了各种影音设备上经过校正的颜色显示效果相同。它的校正方式如下图所示:

OpenGL(十八)Gamma校正 色域 与 HDR_第2张图片

其中蓝线为经过Gamma-encode的色值,红线为sRGB的值。

Unity中的Gamma

对于PC平台上的程序可以直接设置颜色空间

OpenGL(十八)Gamma校正 色域 与 HDR_第3张图片

但移动平台并没有,所以意义并不大。而对于默认的Gamma模式,渲染时shader中读到的颜色和贴图已经应用了gamma-encode。所以它会比实际更亮一些。这也使得模型和场景有种褪色的感觉。对比图如下:

解决它的办法,是在shader中进行Gamma校正:

//input
float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 );  
//output
fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);
return fragColor;

但这也不是万全之策。当混合时,需要用到framebuffer,这时取到的已经是encode的色值。那么两种颜色空间的值(线性和非线性),就会在一起混合。虽然这不是正确的做法,但也只能将就了。

另外也可以通过PostEffect来统一处理颜色校正。这能避免在每个shader中都加入Gamma校正。但其实它是在错误的计算结果上进行修正,并没从根本上还原色彩,而且还产生了性能消耗,因此效果也需要衡量。

由于手机硬件只支持Gamma格式的资源,所以需要使用Gamma工作流。在这种模式下,图和颜色都是Gamma-encode之后的,而引擎将其作为线性输入来处理。为了保证一个合理的输出结果,编辑器在输出结果时也不加Gamma-encode。(错上加错,能是对?我也是醉了,可能是因为图形学第一定律吧)

另一方面,在Unity中还可以直接使用Linear space图元,只要勾选Texture面板中的Bypass sRGB Sample即可。

OpenGL(十八)Gamma校正 色域 与 HDR_第4张图片

这样设置后,在线性模式中可以防止其被重复解码。在Gamma模式中,没找到相关记载,我觉得要不就在输出时会对其进行一次encode,要不压根就没什么用。

对于LightMap都是在线性空间下计算,以Gamma color存储的,可以不用担心移动端上色值的问题,记得选中TextureType为Lightmap即可。

最后你要是问Android、IOS支持不支持线性空间?官方表示:

On Android, linear rendering requires at least OpenGL ES 3.0 graphics API and Android 4.3.
On iOS, linear rendering requires the Metal graphics API.

所以基本GG。而且Android机器屏幕也不全是sRGB的,比如华为Mate9就是NTSC。而且有些是1600w色,有些是1670w色,有些厂商偷偷摸摸在后面改个颜色偏向。我觉得只能通过动态调整PostEffect中的Gamma来达到相同的显示效果。

你说的白是什么白

归根到底,出现伽马校正的原因是存储颜色时灰阶色值不够,24位色图片每个通道只有2^8个色阶,总共只能显示2^24种颜色。如果能很精细的映射灰度,就不需要伽马校正。拿白色来说,如果必须用8位色值,白色只能保存为(255,255,255)。但如果用16位浮点数,白色就可以保存为(255.0,255.0,255.0)。这时就可以精确的映射灰度。另一方面,由于有了足够的色值,也可以表现出更亮的白色,具体说就是允许rgb中的值超过1,这就是高动态光照渲染。

HDR

这项技术的核心就在于使用了float16存储颜色数据。在OpenGL中使用

glTexImage2D(GL_TEXTURE_2D, 0, dataType, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL);

即可增加图片的存储精度。

它的优点在于:

  • 强光部分颜色不会损失
  • bloom和glow效果支持更好
  • Reduction of banding in low frequency lighting areas

劣势在于:

  • 浮点数渲染会更慢,并且需要更多显存。
  • 不支持硬件抗锯齿。
  • 不是所有硬件都支持。

Unity中的配置

通过在Camera上勾选可以开启:

总结

总的说来,如果感觉褪色严重,就需要进行Gamma校正。在制作流程中,如果希望规避,就改对应的shader,在这个过程中注意性能消耗与视觉效果的性价比。如果性能允许,就打开HDR。如果要使用bloom或glow,也要打开哦,反正都要费性能了,就不差这点显存啦。

参考文献:

  • https://docs.unity3d.com/Manual/LinearLighting.html
  • https://docs.unity3d.com/Manual/HDR.html
  • https://www.zhihu.com/question/27467127/answer/111973548
  • https://www.zhihu.com/question/20602284
  • http://blog.csdn.net/candycat1992/article/details/46228771

你可能感兴趣的:(OpenGL,OpenGL,从入门到起飞)