notion image
这是侑虎科技第 263 篇原创文章,感谢作者贾伟昊供稿,欢迎转发分享,未经作者授权请勿转载。当然,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ 群:465082844)
作者简书:http://www.jianshu.com/u/5ccd10a133d9
前言
其实针对这个问题已经写了一篇很简单的填坑笔记了,但在整理本文的过程中,又想明白了一些之前没有想明白的技术问题,算是对线性空间下烘焙场景变暗的原因有了一个真正的理解,这也是整理和写博客的收益之一。
一、 问题确认
最近美术同学反馈在移动设备上场景感觉比平常开发的 PC 上要暗很多。其实之前观察到过这个现象,当时场景的烘焙效果还没完全确定,以为是不同的烘焙版本,或者设备屏幕亮度不同、色差等问题,一直没有怎么关注。
知乎有句名言——“先问是不是,再问为什么”,那么解决问题之前第一步要做的事情就是确认问题。
其实方法很简单,就是排除前面提到的不同烘焙版本、设备色差等问题,使用同一烘焙版本的场景,在移动设备上进行截图操作,然后传输到手机上在同一块屏幕下进行对比,发现场景亮度差别的确很大。
notion image
手机设备上的截图效果
notion image
PC 设备上的场景截图效果
二、 原因排查****
确认问题的确存在,而且很严重之后,我们先做一系列的排查实验来检查问题可能出现的原因。
1)首先在同一屏幕下对截图进行对比,观察是否是全屏变暗,发现使用实时光照的角色没有这个问题,因为对比的关系反而会觉得角色更亮(请忽略上面截图中的角色灰色问题,那个是因为在我的 MX4 Pro 上材质兼容性存在问题导致的),排除可能是某些错误的后处理导致全屏变暗的可能,基本确认是场景 Lightmap 相关的问题。
2)检查 Unity Editor 中切换到安卓平台,直接运行没有问题,但是如果加载 AssetBundle 包进行运行,就出现了和设备上一样的问题。推测可能和 AssetBundle 的打包相关,但是无法确认更多细节。
3)制作一个简单的 Demo,使用非 AssetBundle 的方式进行打包,放置到手机上进行截图对比,发现也存在变暗的问题,排除单纯的 AssetBundle 打包导致的问题;
4)在 Demo 中,将我们自己的材质替换为 Standard 材质,进行打包对比测试,依然有变暗的问题,排除我们项目自身材质 Bug。
5)因为我们在移动设备上使用了线性空间,因此怀疑可能是线性空间导致的,切换颜色空间进行对比实验,发现伽马空间下烘焙效果也有色差,但是没有线性空间明显。
经过这一系列的实验对照之后,我有点怀疑这是一个 Unity 的 “特性(bug)” 了。。。顺便说句题外话,我们使用的是比较新的 Unity 5.5.4f 版本,之所以从之前较为稳定的 5.3.6 版本升级上来,很重要的原因之一是我们想要在移动设备上使用线性空间。
三、 资料检索****
问题的矛头指向了烘焙,又是 Standard 材质也会存在的问题,那么猜测网络上应该有不少问题反馈和相关资料,于是照常进行一波相关资料的检索和收集。
3.1  Unity Issue Tracker
首先怀疑是否是 Unity 的官方 “特性 (Bug)”,搜索了一下 issuetracker,果然找到一个似乎相关的 Bug 汇报 BAKED LIGHTMAP IS DARKER ON ANDROID PLATFORM COMPARED TO PC PLATFORM,相关网址:https://issuetracker.unity3d.com/issues/baked-lightmap-is-darker-on-android-platform-compared-to-pc-platform,说是 Fixed in Unity 5.5.5,没提到线性空间的设置,我们 7 月份要测试也等不到 5.5.5 版本了,所以暂时先记录下,继续。
3.2  UWA 问答
UWA 的问答模块有一个帖子问过相关问题:Lightmap 在 PC 上显示正常,但是转到 Android 平台上存在色差,颜色普遍偏暗。这里引用一下回答的内容:
一般来讲,有两种情况可能会导致色偏和亮度差异。
1)Unity 烘焙的 Lightmap 是 32bit 的 HDR 图,而移动设备通常不支持 HDR 图 (32bit per channel),会按照 LDR 图(8bit per channel) 的形式进行处理,因此会出现色偏问题。因此我们建议:
在移动平台下使用 Mobile/Diffuse 材质,可载入 Standard Assets(Mobile) package 获得。如果要获得更合适的效果,需要自行修改 Lightmap 的 DecodeLightmap 函数,该函数可在 Unity.cginc 文件中找到。需要说明的是,这种方法也不能达到与 PC 端完全一致的效果。
如果需要 PC 和移动平台的显示效果一致,可以用图像编辑软体修改 Lightmap 為 LDR 格式,例如 PNG(8bit per channel)。
为了避免类似问题,请不要使用过于强烈的 Light 进行烘焙,因為 Light 的强度 (Intensity) 越高,色偏问题会越严重。若有阴影丢失时,可以尝试检查一下模型的 Lightmapindex、Lightmapscaleoffset、UV2 等影响 Lightmap 采样的一些参数。
2)另一种可能是存在过曝现象,可以尝试将 playersettings -> use direct3d 11 关闭,看问题是否解决。
这个答案给出了差异产生的原因,即 Lightmap 贴图是使用 exr 格式存储的,然后在移动设备上如果按照 LDR 进行处理产生偏差是很正常的,给出的建议我们看了下,没有对应的问题。自行修改 UnityCG.cginc 的事情前段时间帮美术干过,但是没有很好的方法可以让团队内部所有成员机器上的效果保证一致,所以一开始不是非常想采用,而且如何修改效果才能正确这个答案给得并不非常明确。
3.3 博客
接着,找到一篇《解决 Lightmap 在 PC 上与 ios 和 Android 上表现不同的问题》(http://www.ceeger.com/forum/read.php?tid=24457),使用 LogLuv 这种编码格式对 exr 文件进行重新的编码。文章中有基本的原理分析,有解决方案,还有源码,看上去很靠谱。做了一个 demo 尝试走了一遍流程,是可以解决变暗的问题,但是和 PC 上比,还会变得更亮…… 依然有偏差,不知道是否是线性空间的问题,而且更加重要的是过程太多繁琐,维护成本很高,所有的材质都需要对应修改,不理想。况且,转换之后的贴图要使用 RGBA8 的非压缩格式,一张 1024 的贴图需要占用 4-5M 左右的包体和内存空间,代价太大,不在走投无路的情况下实在不能忍。
想起不久前看过一篇知乎上的帖子:谈光照图烘焙技巧_(http://ixulin.com/2017/05/03/talk-bake-in-unity/)_,里面跨平台的部分有提到相关的问题:
推荐把没有烘焙 Lightmap 时候的颜色大体定在 0.5-0.7 左右的亮度,这样烘焙进退都可以,而且这样 Lightmap 的系数不会特别夸张的大,这样在 pc 平台和 android 基本就能保持一致。
检查了一下,做 demo 用的贴图亮度 0.63,光照亮度调整为 1,烘焙之前的颜色大约也符合 0.5-0.7 左右的亮度,不知道是否因为线性空间的问题,反正依然没有解决。但这篇文章直接提到了 UnityCG.cginc 中的源码,即 lightmap 的颜色解析过程,去看了一下。
notion image
这段代码还比较容易理解,在不同的宏控制下,使用不同的 LightmapDecode 方案,主要包括 DecodeLightmapRGBM 和 DecodeLightmapDoubleLDR 两种。
  • ***四、HDR 和 LDR****
这里扩展说明一下 HDR 和 LDR 图像的区别,注意,这里说到的 HDR 不是指 High Dynamic Range Rendering,而是仅仅指高动态范围的图像格式,当然在 HDR Rendering 中肯定要用到高动态范围的图像格式,因为和本文关系不大就不深入讨论了。
前面提到的一篇博客里也说到了,在 Unity 里,Lightmap 是以 exr 的格式来存储的,即 openEXR 格式,这是一种开放标准的高动态范围图像格式,在计算机图形学中被广泛应用。
在 LDR 的图像格式中,比如 BMP、PNG、TGA 等,一个像素的颜色值可能由 RGBA 四个通道组成,而每一个通道可以表达的范围是 0-255。也就是说 8 位就可以表示一个像素的一个通道值,RGBA 四个通道在不压缩的情况下只需要 32 位即可。而 openEXR 格式支持 16 位浮点数、32 位浮点数和 32 位整数的像素颜色值。Unity 中的 Lightmap 采用这种 HDR 格式的图像,可以表达的范围当然远比 LDR 图像的范围要大得多。
在 Unity 中,exr 格式的贴图在导入的时候会被转变为 RGBM 的格式,因为通常大家 Lightmap 的导入选项中的 TextureImporterSettings.rgbm 都是使用默认的 “Auto”,即当原始数据为 HDR 格式的时候使用 RGBM 格式的编码进行导入。RGBM 把[0, 8] 范围的值压缩成 [0, 1] 范围,并且把一个系数存储在 Alpha 通道中,最终的颜色值为 RGBA 8。
  • *****五、最终选择的解决方案******
在经过这些探究、对比和纠结之后,我决定还是采用 UWA 的建议,自己修改 UnityCG.cginc 中的源码。原因是这里是产生不同的根本所在,在不需要对美术制作流程产生任何影响的前提下,也许可以从根本上解决问题。
烘焙场景变暗的原因前文的代码和搜集的博客中已经给出了——在 PC 平台上,因为是支持 RGBM 格式的,而且我们开启了线性空间,因此 DecodeLightmap 最终走了下面代码的逻辑:
notion image
这里 decodeInstructions 的值是在外部定义的 unity_Lightmap_HDR。关于这个变量我没有从 Unity 的文档中搜索到相关的设置,只在 UnityCG.cginc 中看到一个似乎相关定义:
notion image
在移动设备上,最终会使用这样的逻辑:
notion image
也就是值直接乘以了 2.0 作为 Lightmap 的颜色值,不知道这个 2.0 是从科学的计算方法得出的,还是仅仅是一个接近的经验值。而在线性空间下,按照 RGBM 的计算方法,RGB A 8 材质最终的值。由于线性空间的亮度是线性增长的,因此 2 倍和 8 倍其实有非常大的差距,明暗差别很大也就可以理解了。当然这里我进行测试,decodeInstructions.x 的值并不是 8,而似乎是 4 或者 5 这样的值。我用 Photoshop 打开 Unity 的 Lightmap 文件,发现其中的 alpha 通道几乎全部接近纯白色或者纯黑色,只有部分是灰色的,像下图中用于测试的 Lightmap 图。
notion image
用于测试的 Lightmap 原图
notion image
对应的 Alpha 通道颜色
检查了下我们自己用的场景中的 Lightmap 贴图,无论是室外场景还是室内场景,光照贴图的 Alpha 通道都是接近纯白色或者纯黑色,部分地方是灰色,那么如果我仿照对于 RGBM 格式贴图的处理,舍弃掉对于 alpha 的乘法,结果应该只会丢失掉一些局部细节而已,不会对整体的明暗效果产生影响。于是,我选择了最终的解决方案——将 DecodeLightmapDoubleLDR 函数中的 2.0 修改为 unity_Lightmap_HDR.x 的值。
总结一下最终的解决方案:
1)修改 BuildinShader 中的 UnityCG.cginc 中的 DecodeLightmapDoubleLDR 函数为:
notion image
2)重新替换 Unity Editor 的 UnityCG.cginc 文件,删除工程中 Libery 下缓存的编译好的 Shader,即整个 ShaderCache 目录,重启 Unity 让它重新编译所有 Shader。
3)编写资源导入的后处理脚本,设置 Android 和 iOS 设备下的 Lightmap 的导入格式为 ETC2_RGB4 和 PVRTC_RGB,重新导入所有光照贴图。这里舍弃掉 alpha 通道,在安卓设备上,让之前一张 1024*1024 大小的贴图从 1.3M 变为 0.7M,意外的收获,开心~(另外我在安卓设备上尝试了 ETC2_RGBA8 的格式,结果反而全黑掉了,因为是在中间尝试的步骤,因此没有深究。这里提醒一下,如果使用同样方法修改 shader 的同学可能需要注意下光照贴图压缩格式的问题。)
4)重新生成所有相关的 AssetBundle 文件,包括 Shader、光照贴图,打包到安卓设备上,然后截图传到 PC 同屏对比,美术看完之后表示——“很完美,看上去和 PC 上的完全一样!”。
放一张修改之后在 PC 上使用安卓平台运行 AssetBundle 版本的游戏截图。
notion image
最终修复后的效果图
  • *****六、总结******
这个问题的根本原因在整理这篇文章的时候我也进行了一些思考,应该是 2.0 这个经验值是针对伽马空间下的光照贴图亮度调整的经验值,伽马空间的亮度叠加是非线性的,2.0 只能是一个接近,因此无法做到各个平台下的效果一致。在移动平台支持线性空间之后,UnityCG.cginc 并没有更新这个值在线性空间下的表现问题,导致烘焙的结果在设备上会变暗。
这个问题的解决花费了我大约两天的时间,中间进行各种资料的搜索、实验、方案对比,以及和美术进行问题的讨论和最终解决方案的效果确认。最终的解决方案虽然只修改了几行代码,但是寻找解决方案的过程却涉及到了各种知识点。工程中,解决问题的过程总是伴随着各种猜测和不解,在最后问题解决了之后,有些疑问解开了,有些疑问可能仍然没解开,比如我依然不知道 Unity 在哪里可以设置 unity_Lightmap_HDR 的值,或者说这个变量的值是由什么来决定的。最终方案的选择是基于时间成本、维护成本、最终效果一致等各个方面的考虑,我也很明确地知道这个解决方案并不一定适合所有的项目,抑或这个解决方案的背后是否还隐藏着别的坑。但是如美术看到结果时所说——“很完美,看上去和 PC 上的完全一样!” 正应了计算机图学上的那句名言:如果看上去是对的,那它就是对的。
  • *******七、问题解决后的安利:线性空间的工作流********
本文到最后,我突然理解了为什么网上那么多解决方案在建议规定贴图亮度范围,又或者如 UWA 的问答中所说的 “需要自行修改 Lightmap 的 DecodeLightmap 函数,该函数可在 Unity.cginc 文件中找到。需要说明的是,这种方法也不能达到与 PC 端完全一致的效果。”
而我们在解决这个问题之后,做到了动态光影场景效果接近最终烘焙效果,PC 预览烘焙效果几乎等于移动设备上忽略色差和亮度差别之后的效果,因为我们使用的是线性空间的工作流啊~~
之前在网易做端游项目《无尽战区》的时候,就经历过一次从伽马空间到线性空间制作方案的改变,当时已经了解到线性空间对于美术制作和最终效果的影响力。因此在了解到 Unity 5.5 开始在移动设备上原生支持线性空间的时候,我们就在 5.5.1f 版本的时候在工作室内推行了线性空间的工作流方法。由于之前角色制作已经是在用 Substance 和线性空间来做了(角色材质中模拟的线性方案),因此只在场景制作这边遇到了一些阻力,但当时为了解决烘焙效果几乎无法预览,全靠猜、重试和经验的问题,顶住压力和主美一起推行了完整的线性空间工作流。除了效果之外,这次问题的解决也让我再次体会到了线性空间的优势——虽然问题很可能是由于 Unity 官方在材质中对于设备上的结果计算没有考虑线性空间导致的,但是我们自己修复之后,可以做到设备上的效果与 PC 预览的效果基本一致,这对于美术的效果调整有很大的信心提升。虽然安卓设备上屏幕参数各种不同,但是我们可以保证在色准较好的设备上效果是稳定的。
关于线性空间的原理和优劣晚上有大把 TA、或者程序的文章在讲,包括 Unity 官方文档也有详细的说明,这里只放一张官方的对比图,有兴趣的朋友可以自己搜索。
notion image
线性空间和伽马空间对比
当然,线性空间也有代价,就是一点额外的性能消耗。Unity 5.5 版本开始支持移动设备上线性空间,需要项目组付出的代价是只支持 OpenGLES 3.0 以上的设备,对应最低的安卓系统版本是 4.3。
一方面,支持 OpenGLES 3.0 的设备占比越来越高,另外一方面,安卓模拟器貌似还都大都是只支持 2.0 的版本。这也是我们项目后面可能要面临的问题,但是有舍才有得,从项目整体的收益来看,目前使用线性空间还是利大于弊。
最后,安利最近立项的 Unity 手游项目组,可以考虑使用线性空间工作流,你们的美术会爱你的。
感谢贾伟昊的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ 群:465082844)。
也欢迎大家来积极参与 U Sparkle 开发者计划,简称 “US”,代表你和我,代表 UWA 和开发者在一起!
notion image
近期精彩回顾
notion image

▎本文由 简悦 SimpRead 转码。