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

眼前的黑不是黑

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



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


伽马校正

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


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

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

sRGB

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

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

Unity中的Gamma

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



但移动平台并没有,所以意义并不大。而对于默认的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即可。

这样设置后,在线性模式中可以防止其被重复解码。在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

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信