聊聊Unity的Gamma校正以及线性工作流
0x00 前言的前言
这篇小文其实是在清明节前后起的头,不过后来一度搁笔。一直到这周末才又想起来起的这个头还没有写完,所以还是直接用一个月前的开头,再将过程和结尾补齐。
0x01 前言
结束了在南方一周的出差,清明时节回到了刚好下过雪并且和南方有20多度温差的北京之后,终于有时间来写点文字了。这篇小文,我主要想来聊一聊在使用Unity时和gamma校正相关的话题。事实上关于Gamma校正的来源历史以及理论知识已经有很多相关的文章了,比如龚大的《gamma的传说》、Nvidia的Gpu Gems的文章等等。所以我在理论知识上只是稍作着墨,主要还是要来聊聊Unity中的Gamma校正的相关内容。
0x02 显示器和gamma校正
关于gamma校正来源的说法很多,具体可以参考龚大的《gamma的传说》的显示器说以及乐乐的《我理解的伽马校正》中所提到的人眼视觉特点说。两者都有道理,并且客观上这两个说法发生了有趣的巧合,最后达到了一个还不错的效果。
简单来说,过去的CRT显示器存在一个特点,即屏幕上显示的颜色对于传递而来的原始值并不是线性的(非线性) ,在这里非线性意味着以一个比率增加某个颜色分量,并不会导致显示器屏幕上的光强度增加相同的比例。举一个例子,假如我们将一个颜色的红色分量变成之前的两倍,显示器的屏幕所显示的红光并不会变成之前的两倍。
事实上CRT显示器的输入和输出之间的关系近似于一个指数关系,而这个指数便是我们常常听到的gamma。典型的gamma范围在2.0到2.4之间,一般该值常常以2.2作为折中。虽然后来的LCD并不存在这个特点,但是为了保证兼容,也选择了和当年CRT一样的非线性特性。

上图中的红色实心线是在gamma = 2.2的情况下,显示器实际显示色彩强度的方式。 这一部分是由显示器的特性导致的。所以如果图片不做任何处理,经过pow(2.2)的操作之后显然会变得更暗,所以gamma校正就显得很有必要了。而gamma校正要做的事情也十分简单,即通过pow(1/2.2)将颜色强度提高,也就是上方的红色虚线,这样经过显示器时就会将显示器的pow(2.2)抵消掉。
同时,人眼对暗部的变化更加敏感,而对亮部变化其实不是很敏感。这可以以摄像作为一个例子,使用摄像机时,摄像机会把进入到镜头内的光线亮度编码成图像中的像素。
例如人们看到下面这张图,会自然而然的认为中间的地方即灰度为0.5的地方。

事实上,摄像机“看到的”光线亮度如下图,上图中间部分的灰度其实只是在0.2左右。

所以在只有8bit的情况下,没有必要在亮部浪费过多,这样就可以表现更多暗部的细节变化,所以实际亮度只有0.2经过gamma校正后实际被编码成了0.5的像素值。
当然,我个人认为显示器的特性是gamma校正出现的主要原因,而人眼对暗部的敏感而出现的gamma校正则更像是为了适应显示器这种特性而为的一种编码策略的“优化”。
0x03 硬件实现
sRGB 颜色空间是一个可以直接用来在显示器上显示的非线性颜色空间。
以OpenGL作为图形库为例,Unity实现Gamma校正以及Linear workflow借助了OpenGL的 texture_sRGB 以及 framebuffer_sRGB 相关拓展,事实上是一种硬件层面的实现。
EXT_texture_sRGB会对texture做pow2.2的gamma校正,将输入从sRGB空间转换到线性空间;而framebuffer_sRGB如果开启,并且输出的目标是sRGB颜色空间,则硬件会将结果再做一次pow0.45(为了方便,下文使用0.45代替1/2.2)的gamma校正,将结果从线性空间转换到sRGB颜色空间。这样保证输入的是正确的数据,并且中间的计算是线性的过程,最后的结果再转移到sRGB空间来中和显示器的输出,这样就能保证一个正确的光照计算结果。
下面我们可以通过RenderDoc来分别分析一下gamma workflow和linear workflow在安卓手机上的渲染流水线。
不过我在使用RenderDoc的目前正式版本(v1.0 - 6 Mar, 2018)时遇到了一些小问题,即我无法正常的通过RenderDoc启动我的App,总是会报下图中的错误。

这其实是这个版本的一个bug,相关issue可以参考(https://github.com/baldurk/renderdoc/issues/903),解决方案的话就是不使用这个正式版,而是下载latest nightly build。
ok,回到正题。大家都知道,利用renderdoc我们可以很方便的查看渲染流水线的各种数据以及各种资源的参数等等,所以首先我们来看看在gamma空间下的整个工作流。
首先我在工程中倒入两张一样的图片,分别叫做gamma和linear,在gamma空间下一个勾选了导入设置中的sRGB,另一个则不勾选,并且都是用了ETC2的压缩格式。


可以看到两张图片并没有什么变化。下面我们来抓一帧来看一看两者在OpenGL中的纹理格式:


两者的格式都是GL_COMPRESSED_RGB8_ETC2。有趣吧,可以看到Unity设置了Gamma空间后,图片导入时无论是否选择勾选sRGB的结果都是一样的。
之后我们再来看一看FrameBuffer的情况:

没有什么意外,同样是我们常见且习惯的格式——GL_RGBA8。
这样整个gamma workflow的过程中没有涉及到所谓的gamma校正,整个过程和上一节中描述的一样——导入了经过处理的图片,最后再经过显示器的处理中和——传统且充满了巧合与错误。
接下来,我们将整个工程切换到Linear空间。同样,两张一样的图片一个勾选sRGB,另一个不勾选,并且同样是用了ETC2的压缩格式。


这次就更有趣了,我们可以看到勾选了sRGB的图片变暗了,而没有勾选的则仍然保持原样。并且,勾选sRGB的图片在下面的信息中显示是sRGB——它被作为一张sRGB纹理来看待,需要进行gamma校正;而另一张,则显示的是Linear——它被当作一张Linear纹理来看待,不需要经过gamma校正。
所以勾选了sRGB的纹理变的更暗了,这是因为经过了pow(2.2)的gamma校正处理。
下面我们来抓一帧来看一看在linear workflow下两者在OpenGL中的纹理格式:

可以看到,勾选了sRGB选项的Texture在OpenGL中的格式为GL_COMPRESSED_SRGB8_ETC2,即硬件会对其作一次Pow2.2的gamma校正,将它转化到线性空间中。

而没有勾选sRGB选项的Texture在OpenGL中的格式仍然是GL_COMPRESSED_RGB8_ETC2,所以硬件不会对它进行pow2.2的gamma校正操作,所以针对真正的线性空间图片不要勾选sRGB选项也就是这个原理——否则的话,颜色会比正确的结果更暗、数据也会错误。
不过有意思的还在后面,即framebuffer的格式。下面我们就来看一看framebuffer的抓帧结果:

framebuffer的格式为GL_SRGB8_ALPHA8,即此时保存的结果经过了pow0.45的gamma校正,从线性空间转换到了sRGB空间——这当然是合理的,因为它要中和最后显示器的gamma校正——但是,有一件事情这时会变的比较棘手......
0x03 透明混合
是的,这件事情就是透明混合的问题。由于透明混合是一个线性的过程,因此在混合中作为Dst的那一方的framebuffer的数据就要是线性空间的了。
所以此时混合的操作事实上会先将framebuffer的内容从sRGB空间再次pow2.2转换到线性空间,和src进行混合,再将混合后的结果pow0.45转换回sRGB空间保存到framebuffer中。
是不是有点乱?
我们来写一下公式,代入一个数据就明白了:
ret = (srcColor^2.2 * srcAlpha + dstColor^2.2 * (1 - srcAlpha) ) ^(1/2.2)
ok,这时我们假设src的color值的g分量为1,alpha为0.2;dst的color值的g分量为0。则计算结果为:
0.481156505。
但是在我们的传统的认知下,或者是在Gamma workflow的情况下,这次混合的结果是什么呢?我们来写一下混合公式:
ret = srcColor * srcAlpha + dstColor * (1 - srcAlpha)
代入同样的数据,计算的结果为:
0.2。
两个差别颇大的结果,而如果混合的次数越多,则结果的差别也会更大。
事实上这个问题的处理目前也并没有一个特别十全十美的解决方案,目前常见的几种做法大概包括以下几种方案:
- 使用gamma 1.0来制作资源,即在线性空间中制作资源。
- 自己在ui的shader中对alpha进行pow(2.2)的操作,但是这个只是稍微修复问题,并没有真正的解决问题。
- 仍然使用gamma space的workflow,但是涉及光照的shader自己来做pow 2.2和pow 0.45的校正。这样的话ui还在gamma space,而光照计算在linear space。
当然这里只是抛砖引玉,希望有更好解决方案的朋友能够在此多多分享一下经验。
Ref
http://www.klayge.org/2011/02/26/gamma的传说/
https://developer.nvidia.com/gpugems/GPUGems3
https://blog.csdn.net/candycat1992/article/details/46228771
https://renderdoc.org
https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB.txt
https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt
聊聊Unity的Gamma校正以及线性工作流的更多相关文章
- Gamma校正与线性工作流
1 Gamma校正是什么?8位亮度值x(0-1)经过x^0.45的一个提亮过程. 2 为什么需要Gamma校正 人的眼睛是以非线性方式感知亮度,在自然界中,人感觉到的一半亮度其实只有全部能量的0.2, ...
- 【转】关于LWF——线性工作流
1.什么是LWF? LWF全称Linear Workflow,中文翻译为线性工作流.“工作流”在这里可以当作工作流程来理解.LWF就是一种通过调整图像Gamma值,来使得图像得到线性化显示的技术流程. ...
- 浅谈unity中gamma空间和线性空间
转载请标明出处:http://www.cnblogs.com/zblade/ 一.概述 很久没有写文章了,今天写一篇对gamma空间和线性空间的个人理解总结,在查阅和学习了各个资料后,算是一个个人笔记 ...
- Gamma校正与线性空间
基础知识部分 为了方便理解,首先会对(Luminance)的相关概念做一个简单介绍.如果已经了解就跳到后面吧. 我们用Radiant energy(辐射能量)来描述光照的能量,单位是焦耳(J),因为光 ...
- 图像处理之gamma校正
1 gamma校正背景 在电视和图形监视器中,显像管发生的电子束及其生成的图像亮度并不是随显像管的输入电压线性变化,电子流与输入电压相比是按照指数曲线变化的,输入电压的指数要大于电子束的指数.这说明暗 ...
- gamma校正
1 gamma校正背景 在电视和图形监视器中,显像管发生的电子束及其生成的图像亮度并不是随显像管的输入电压线性变化,电子流与输入电压相比是按照指数曲线变化的,输入电压的指数要大于电子束的指数.这说明暗 ...
- OpenGL核心技术之Gamma校正
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你/2.2次幂.Gamma校正后的暗红色就会成为(0.5,0.0 ...
- gamma校正原理
http://blog.csdn.net/u013286409/article/details/50239377 目录(?)[-] 图2中左图为原图,中图为gamma = 1/2.2在校正结果,原 ...
- Gamma校正及其OpenCV实现
參考:[1]http://www.cambridgeincolour.com/tutorials/gamma-correction.htm [2]http://en.wikipedia.org/wik ...
随机推荐
- 深入浅出Java Dom4j读取XML
在以前自己使用的xml较少,只是了解其很强大,现在可算是在DRP中,真正的开始使用它了,以前只是简单的理解xml,xml即可扩展标记语言,简单的使用,具体是什么?怎么用?还是一直让自己期待的. 首先来 ...
- HDFS HA: 高可靠性分布式存储系统解决方案的历史演进
1. HDFS 简介 HDFS,为Hadoop这个分布式计算框架提供高性能.高可靠.高可扩展的存储服务.HDFS的系统架构是典型的主/从架构,早期的架构包括一个主节点NameNode和多个从节点Da ...
- eclipse代码恢复(开发程序代码恢复)
如果误操作,让本地代码丢失了不用怕,Eclipse local history可以恢复. 误删除文件后,直接ctrl+z可以恢复. 拉去代码覆盖了本地,也可以一个一个或者整体进行恢复:http://b ...
- matlab函数interp2及其c++代码
最近将一个matlab程序转为c++,途中遇到interp2这个家伙,我是左查右查,发现网上没有人总结这个玩意,于是我来初探一下,还是别有洞天的,嘿嘿. 1.关于interp2 Vq = interp ...
- [sersync+rsync] centos6.5 远程文件同步部署记录
针对本地文件的修改,自动同步到远程文件夹,远程备份很方面.研究了下大家的主流同步方案一般是 rsync+inotify和rsync+sersync, 我这里使用sersync的方案,当然大部分都是参照 ...
- 软考之路--从生活着手,看PV如何操作
PV操作,是软考当中一个很重要的考点,一听到这个名词,顿时赶脚高大上有么有,在软考的历年试题中,也不乏PV操作的身影,老师也对PV操作进行了一次讲课,那时年少,听得稀里糊涂,也不是很理解,在小编的理解 ...
- Web Service进阶(六)SOAPBinding绑定方式异常 is not found. Have you run APT to generate them
当在类中填充相应方法时,提示如下错误: 出现以上错误的原因就是在注解中没有添加@SOAPBinding(style=SOAPBinding.Style.RPC)这句话.估计也与JDK的版本相关,这方面 ...
- Java创建柱状图及饼状图
Java创建图表其实还是很方便的,但是要引入相关的jar包.如下 jfreechart.jar jcommon,jar gnujaxp.jar 其中最主要的是jfreechart.jar. 下面就让我 ...
- 04_查看Android内存使用情况
创建项目 Android清单文件 <?xml version="1.0" encoding="utf-8"?> <manifest xm ...
- ORACLE收集统计信息
1. 理解什么是统计信息 优化器统计信息就是一个更加详细描述数据库和数据库对象的集合,这些统计信息被用于查询优化器,让其为每条SQL语句选择最佳的执行计划.优化器统计信息包括: · ...