详解Unity 5 全局光照系统Enlighten问题(上)
Unity 5正式版面世已有一段时间,许多开发者都在使用其进行开发。在使用过程中,或多或少会碰到一些问题。今天,Unity的官方技术工程师柳振东将针对Unity 5中全新的Enlighten全局光照系统的一些问题及相应解答与大家分享。
为何Unity 5的全局光照系统易“踩坑”
Unity 5取缔了Unity 4中使用的Beast全局光照系统,采用了全新的Enlighten全局光照系统。由于这两套全局光照系统的算法不同,反映到使用上就是各种参数与操作细节发生了变化。
对于没有系统学习过计算机图形学的开发者,建议了解一下全局光照明模型。因为开发者可能对局部光照明模型(即平常所说的实时光照)比较熟悉,有时会将局部光照明模型的知识应用到全局光照明系统上,从而做了无用功。
下面将挑选一些常见问题进行分享。
为何场景烘焙出来的lightmap上有Realtime灯光的颜色
不少开发者都曾为这个问题头疼,灯光明明是Realtime的,为何会被烘焙到lightmap里面去?但在Unity 5里,这并非bug。
首先,Unity 5中新增了一种全局光照明的使用方式:Precomputed Realtime GI(预计算实时全局光照明),简称实时GI。而Bake GI则是大家在Unity 4中一直使用的lightmap烘焙方式。
实时GI与Bake GI一样,都需要预先的Bake过程。但不同的是,实时GI并不预计算场景中光线的反射信息,而是预计算场景中静态物体表面所有可能的反射光路,然后在游戏运行时结合灯光的位置、方向等信息实时计算出全局光照的结果。
这种计算机制使得预计算GI具备很大优势,使用预计算GI的灯光,其位置、方向、强度、颜色等各种信息都可以在运行时实时变化。例如,使用预计算GI可以在场景中实现非常真实的日光变化效果(全局光照明模型比实时光照效果更强)。
然而,预计算GI有一点容易让人混淆,即预计算GI需要灯光类型是Realtime。很多人会疑惑,Realtime不是实时灯光吗,怎么变成全局光照明模型的灯光了?
Unity 5的灯光属性中增加了一项,叫做Bounce Intensity。Bounce Intensity指的是全局光照中的间接光强度。可以简单地把全局光照理解为直接光照与间接光照两部分,直接光照指直接从光源射到物体上的光,间接光照指从其他物体表面反射而来的光,直接光照部分其实就是局部光照模型,也就是实时光照计算出来的结果。
在Unity 5中,Bounce Intensity的默认值是1,这意味着一盏Realtime灯光默认是使用预计算GI的,并且间接光照的强度不做改变(大于1是强制增大间接光照,在一些由室外光照射入洞穴的场合可能会用到)。而lighting窗口中的预计算GI选项也是默认开启的。
所以,如果不更改这些设置,即使看起来只使用了Realtime的灯光,但真正运行时其实是在使用全局光照系统。只要一盏灯的Bounce Intensity大于0,Unity就会认为需要使用全局光照系统。那么,即使在lighting窗口中取消勾选Precomputed Realtime GI,Unity依然会尝试使用全局光照。若还勾选着Bake GI,就会采用Bake GI这种全局光照方式,结果就是,烘焙出来的lightmap里也有那盏Bounce Intensity大于0的Realtime灯光的信息。
总结:Unity 5中灯光有个新属性Bounce Intensity,只要这个值大于0,系统就会认为需要使用预计算GI计算这个灯光。若此时Precomputed Realtime GI未被勾选,而Bake GI被勾选,Unity会把这个灯光也烘焙到lightmap中。
为何在Unity 5中动态更换lightmap没有作用
在场景中动态更换lightmap是常见需求。例如,同一个场景需要白天与黑夜两种效果,就会烘焙两张不同的lightmap,然后用脚本在运行时切换。在Unity 4中,可以通过把lightmap资源load到Texture2D中,然后赋值给一个LightmapData结构,最后赋给LightmapSettings.lightmaps来更换当前使用的lightmap。
但在Unity 5中,这种方法却不奏效。实际上,lightmap已经更换,问题在于使用lightmap的物体不知道该使用哪张lightmap,也不知道要从lightmap的哪个地方开始采样,这其实是每个Renderer上的两个参数,lightmapIndex与lightmapScaleOffset导致的。
在Unity 4中没有这个问题,是因为这些信息已被序列化进场景文件中,当场景加载进来时,这两个值就会被赋回到每个Renderer中。而在Unity 5中,由于多场景编辑的逻辑需要,lightmapIndex与lightmapScaleOffset不再被序列化到场景文件中,而是存在于一个伴随lightmap烘焙产生的新文件,Lighting Data Asset中(这个文件在初期的5.x中叫做lightmapSnapShot)。该文件与lightmap在同层目录中,并且可以在Editor中随时更换当前使用的LightingData文件。
既然LightingData文件存储着lightmapIndex与lightmapScaleOffset,只要保证LightingData文件能被最终的可执行文件使用就没问题。如果只需要更换灯光条件不同情况下的lightmap,而场景本身没有更改,那么多套lightmap对于场景中的静态物体Renderer而言只需要同一套lightmapIndex与lightmapScaleOffset信息。所以在这种情况下,要达到运行时更换lightmap的效果,只要保证lighting窗口的Ligtmaps页面中Light Data Asset里选中了正确的LightingData文件即可(很多开发者习惯性删除这个文件,从而丢失了lightmapIndex与lightmapScaleOffset信息)。
简单情况可以简单使用LightingData文件,但如果需要场景本身也有所改变该怎么办呢?例如,需要原始的场景lightmap与一个被炸弹破坏过后的场景lightmap,由于场景中的静态物体有所改变,两次烘焙会产生两个数据不同的LightingData文件,无法简单使用其中一个。可惜的是,现在Unity并不能在运行时切换LightingData文件,该文件设计之初仅作为Editor使用的资源。
对于这种情况,需要自己记录下每个Renderer上的lightmapIndex与lightmapScaleOffset信息,然后在更换lightmap时把这个信息还原回去。具体做法有很多种,基本原理都是把lightmapIndex与lightmapScaleOffset序列化起来。比如,可以给每个静态物体挂一个脚本,在里面定义一个包含Render、lightmapIndex与lightmapScaleOffset的结构,然后编写一个Save函数,用于在每次烘焙lightmap之后把lightmapIndex与lightmapScaleOffset序列化进结构中,再编写一个Load函数,用于在切换lightmap时还原每个Renderer的lightmapIndex与lightmapScaleOffset信息,这样问题就解决了。
来源:Unity官方社区