游戏蛮牛学习群(纯技术交流,不闲聊):539178957
游戏蛮牛 手机端
开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:2282

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 1926|回复: 16

[慕容小匹夫] 聊聊Unity的Gamma校正以及线性工作流

[复制链接]  [移动端链接]
排名
2075
昨日变化

53

主题

296

帖子

3043

积分

Rank: 9Rank: 9Rank: 9

UID
44527
好友
61
蛮牛币
2614
威望
0
注册时间
2014-9-12
在线时间
577 小时
最后登录
2018-8-2

专栏作家活力之星认证开发者

发表于 2018-5-9 08:16:14 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本帖最后由 慕容小匹夫 于 2018-5-9 08:19 编辑

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
-EOF-
最后打个广告,欢迎支持我的书《Unity 3D脚本编程》
https://item.jd.com/12035114.html


[size=0em]​

更多图片 小图 大图
组图打开中,请稍候......

回复

使用道具 举报

7日久生情
1784/5000
排名
2450
昨日变化
7

43

主题

739

帖子

1784

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
219600
好友
7
蛮牛币
2383
威望
0
注册时间
2017-4-27
在线时间
468 小时
最后登录
2018-8-14
发表于 2018-5-9 09:06:35 | 显示全部楼层
感谢分享

回复

使用道具 举报

7日久生情
3136/5000
排名
2243
昨日变化
15

0

主题

2098

帖子

3136

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
219676
好友
1
蛮牛币
2437
威望
0
注册时间
2017-7-12
在线时间
462 小时
最后登录
2018-8-14

活力之星

发表于 2018-5-9 09:45:28 | 显示全部楼层
谢谢分享

回复

使用道具 举报

7日久生情
2601/5000
排名
2731
昨日变化
5

0

主题

1847

帖子

2601

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
185339
好友
0
蛮牛币
3781
威望
0
注册时间
2016-11-20
在线时间
266 小时
最后登录
2018-8-14
发表于 2018-5-9 10:09:54 | 显示全部楼层

回复

使用道具 举报

5熟悉之中
704/1000
排名
4056
昨日变化
26

0

主题

152

帖子

704

积分

Rank: 5Rank: 5

UID
254155
好友
0
蛮牛币
921
威望
0
注册时间
2017-11-13
在线时间
218 小时
最后登录
2018-8-14
发表于 2018-5-9 15:24:20 | 显示全部楼层
现在正在阅读 脚本编程 中。
[发帖际遇]: 秩亦 乐于助人,奖励 2 蛮牛币. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

5熟悉之中
515/1000
排名
5585
昨日变化
1

2

主题

140

帖子

515

积分

Rank: 5Rank: 5

UID
252889
好友
1
蛮牛币
1809
威望
0
注册时间
2017-11-6
在线时间
145 小时
最后登录
2018-8-14
QQ
发表于 2018-5-10 08:16:13 | 显示全部楼层
感谢分享

回复

使用道具 举报

2初来乍到
109/150
排名
12864
昨日变化
3

0

主题

11

帖子

109

积分

Rank: 2Rank: 2

UID
143591
好友
0
蛮牛币
236
威望
0
注册时间
2016-3-27
在线时间
36 小时
最后登录
2018-7-31
QQ
发表于 2018-5-10 14:18:03 | 显示全部楼层
感谢分享

回复

使用道具 举报

排名
4271
昨日变化
3

5

主题

122

帖子

726

积分

Rank: 9Rank: 9Rank: 9

UID
2289
好友
2
蛮牛币
664
威望
0
注册时间
2013-8-20
在线时间
229 小时
最后登录
2018-7-26

专栏作家社区QQ达人

发表于 2018-5-10 14:35:03 | 显示全部楼层
mark下

回复

使用道具 举报

4四处流浪
480/500
排名
6845
昨日变化
2

0

主题

163

帖子

480

积分

Rank: 4

UID
243107
好友
0
蛮牛币
634
威望
0
注册时间
2017-9-13
在线时间
143 小时
最后登录
2018-7-27
发表于 2018-5-10 16:39:25 | 显示全部楼层

回复

使用道具 举报

6蛮牛粉丝
1287/1500
排名
2266
昨日变化
9

0

主题

348

帖子

1287

积分

Rank: 6Rank: 6Rank: 6

UID
136635
好友
0
蛮牛币
1301
威望
0
注册时间
2016-2-15
在线时间
369 小时
最后登录
2018-8-14
发表于 2018-5-11 09:30:48 | 显示全部楼层
学习了,谢谢

回复

使用道具 举报

5熟悉之中
837/1000
排名
5367
昨日变化
4

0

主题

433

帖子

837

积分

Rank: 5Rank: 5

UID
146677
好友
9
蛮牛币
2720
威望
0
注册时间
2016-4-25
在线时间
164 小时
最后登录
2018-8-9
QQ
发表于 2018-5-11 11:34:07 | 显示全部楼层
支持楼主,谢谢分享

回复 支持 反对

使用道具 举报

7日久生情
1884/5000
排名
1386
昨日变化
1

0

主题

589

帖子

1884

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
164723
好友
0
蛮牛币
3518
威望
0
注册时间
2016-8-29
在线时间
469 小时
最后登录
2018-7-31
发表于 2018-5-14 09:19:38 | 显示全部楼层

回复

使用道具 举报

3偶尔光临
258/300
排名
7540
昨日变化
5

0

主题

57

帖子

258

积分

Rank: 3Rank: 3Rank: 3

UID
270137
好友
2
蛮牛币
585
威望
0
注册时间
2018-3-1
在线时间
49 小时
最后登录
2018-6-25
发表于 2018-5-14 10:47:19 | 显示全部楼层

回复

使用道具 举报

3偶尔光临
268/300
排名
9164
昨日变化
4

0

主题

80

帖子

268

积分

Rank: 3Rank: 3Rank: 3

UID
272501
好友
0
蛮牛币
205
威望
0
注册时间
2018-3-15
在线时间
74 小时
最后登录
2018-8-12
发表于 2018-5-15 10:45:49 | 显示全部楼层
很好的资源~

回复

使用道具 举报

6蛮牛粉丝
1479/1500
排名
25282
昨日变化
12

3

主题

736

帖子

1479

积分

Rank: 6Rank: 6Rank: 6

UID
63377
好友
0
蛮牛币
14
威望
0
注册时间
2014-12-24
在线时间
727 小时
最后登录
2018-8-14
发表于 2018-5-15 12:08:07 | 显示全部楼层
很不错哦平衡水倒海翻江王嘉尔
[发帖际遇]: 商城新_e88uA 发帖时在路边捡到 2 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表