浅析Unity中的Enlighten与混合光照
0x00 前言
在Unity的5.6版本之前的5.x中,主要使用了Geomerics公司的Enlighten【1】来提供实时全局照明以及烘焙全局照明,在5.6之后Unity引入了新的Lightmapper——Progressive来提供烘焙全局照明并且提供了更多的混合光照模式,但是Enlighten仍然是Unity中全局照明的主要提供者。
所以,本文就来聊聊Unity5.6以及Unity2017中和Enlighten、混合光照相关的话题吧。
0x01 直接光和间接光
大家都知道在Unity中,我们可以在场景中布置方向光、点光、聚光等类型的光源。但如果只有这些光,则场景内只会受到直接光的影响,而所谓的直接光简单理解就是从光源发出的直接影响物体的光。如果只考虑直接光的影响,则会缺乏很多光影细节,导致视觉效果很“平”。而间接光则描述了光子在物体表面之间的反弹,能够用来增加细节以及真实感。
例如上图中,位于天花板灯直射光线之外的区域缺乏光照效果。表现为四壁上没有明暗细节,相反此时直接光范围之外都是均匀的黑色,而整个空间同样也显得十分平。
而增加了间接光之后进行渲染,可以看到光被物体表面反弹,彩色光从一个表面转移到另一个表面。表现为红色墙壁和绿色墙壁(在右侧和红墙相对,没在画面内)的颜色反映到了场景中的其他表面上,四壁也不再是均匀的黑色,而有了层次感。
0x02 全局照明和渲染方程
正如前文所说,在Unity中使用直接光是无法模拟光子在物体表面反弹的效果的,因此Unity使用了Enlighten所提供的全局照明(Global illumination)来提供间接光效果。
所谓的全局照明(缩写为GI)是3D计算机图形中使用的一组算法的通用名称,旨在为3D场景添加更逼真的照明。这样的算法不仅考虑直接来自光源的光(直接光),而且还考虑随后来自相同源的光线被场景中的其他表面反射的情况,即间接光。
而全局照明可以用一个称为渲染方程【2】的复杂方程来描述:
渲染方程定义了光线是如何离开表面上某个点的。但是各位也可以看到,这个积分方程太复杂以至于无法快速的计算结果。因此在具体的实现上,往往采用了一些近似的方法。
Unity中所使用的Enlighten采用的近似方法是辐射度算法,即假设存在一组有限的静态元素和仅有漫射光传输来简化计算。上面的渲染方程就可以简化为下面这样:
这样,我们就可以将积分计算变为计算求其他元素的辐射度之和。
其中Bi指的是在i点最终的光,Le是i点本身的光,而两个点之前有多少比例的光会被反弹,则有“form factor”来定义,即公式中的Fij,Lj是J点的光。而pi(输入法问题,先写成p)则是一个和i的材质属性相关的参数。
这样,这个方程就比较好理解了,同时计算相比之前的渲染方程也简单了很多。
而有了简化的方程之后,Enlighten在计算全局光照时,会将场景分组到便于并行处理的system中。接下来,每个system被切割成离散的cluster。
在计算光照时,Enlighten将场景视为一组cluster。而场景内的cluster则会以一种层级关联的结构来组织,并且会对其映射的静态几何体的albedo进行采样,之后在Light Transport阶段计算cluster之间的关系以使得光在cluster之间进行传递。
这样看上去全局照明方案已经完美的解决了间接光的问题,但是预计算实时GI的代价仍然很高,针对移动平台这样相对比较低端的设备,就需要有更加优化的解决方案。所以将静态对象的光照信息烘焙到lightmap上,同时保持对动态对象使用实时光就变成了一个不错的方案。而这就是我们下文中要说的混合光照。
0x03 混合光照
使用混合光照和直接使用烘焙的lightmap有什么区别呢?简单来说,选择“混合”烘焙模式,会将标记为静态的GameObject受到的来自混合光源的光照信息保存为Lightmap。 然而,与标记为“烘焙”的灯光不同,混合光源也会为场景中的非静态(动态)GameObject提供实时的直接光照。
在Unity5.x的版本中,光源就已经有了混合模式的选择。但是直到Unity5.6版本,混合模式又变成了4种子模式。而到了Unity2017版本,则变成了3种子模式——shadowmask的两种模式的设置被放到了Quality Setting内的Shadow下。如下图所示,分别来自Unity5.5、Unity5.6以及Unity2017.3三个版本的Lighting窗口截图。
可以看到Unity5.6之后的版本,混合光照的模式增加了Baked Indirect、Distance Shadowmask、Shadowmask、Subtractive这些子模式(2017版本后Distance Shadowmask的设置放到了Quality Setting中)。
首先我们可以先来看看Subtractive模式,因为Subtractive模式也是5.6之前Unity所使用的混合光照模式。
例如在Unity5.5中,我们如果将光源设置为mixed来开启混合光照,和在5.6之后选择Subtractive模式开启混合光照是一样的。
那么现在就可以安心的使用混合光照了吗?
我们可以来看看一个在这种模式下常见的问题:
即使用点光开启mixed模式时,左下角的动态对象cube并没有产生由点光产生的实时阴影。
这个听上去并不符合预期,因为mixed光照模式下对动态对象显然要提供实时的光照效果,也就是预期应该是像下图这样产生点光造成的阴影。
那么这是为什么呢?这其实是因为在5.6之前的混合光照,以及5.6之后使用subtractive模式的混合光照只能在"只有"一个方向光(Directional light)的时候才能正常工作。
subtractive模式的另一个问题就是烘焙的结果中没有高光,这是因为这种模式不仅仅烘焙了间接光,而且它还会烘焙直接光,因此也就无法提供高光效果了。所以可以看到在上面的截图中,并没有高光的效果。
正是由于这种混合光照模式的不足,所以Unity5.6之后又引入了更多的混合光照模式,以满足大家的需求。
但是,作为开销最小的一种混合光照模式,subtractive模式仍然有被保留的必要。在一些对性能要求高过对效果的要求时,subtractive模式仍然不失为一个不错的选择。
0x04 混合光照新模式-Shadowmask
Unity的新的混合光照机制的一个重要功能便是对Shadowmask【3】的支持了。混合光照开启shadowmask模式的情况下,Unity会额外生成一套shadowmask贴图,它每个纹素的4个通道可以分别用来保存4盏不同的光源。
如图,这张shadowmask保存了2盏光源的信息,一个主方向光和一个点光。
Shadowmask的主要功能本质上十分容易理解,它解決了之前subtractive模式下无法完成的事情。Shadowmask不仅仅可以处理方向光,而且还可以正确的处理点光和聚光在混合光照条件下的表现。
可以看到左下角的动态对象cube产生了由mixed模式下的点光产生的实时阴影。
同时,由于没有对直接光进行烘焙,因此能够在运行时提供正确的高光效果。
而有些朋友如果打开Mixed Lighting的子mode选择菜单的话,在5.6版本中还会看到另一个和shadowmask类似的选项——Distance Shadowmask。
那么Distance Shadowmask和Shadowmask又有什么区别呢?
这就和阴影距离(Shadow Distance)以及混合光照下阴影是如何产生的有关了。
我们在Edit->Project Settings->Quality设置中可以找到Shadow Distance的设置,在实时光模式下,只有在Shadow Distance范围内的物体才会有阴影的影响。
而在混合光照-Shadowmask模式下,静态物体和静态物体之间的阴影是通过shadowmask贴图来产生的。而对于静态物体接收的动态物体产生的阴影,则只有在shadow distance范围内动态物体才会产生实时的shadow map阴影。而动态物体之间的阴影,同样需要在Shadow Distance范围内来产生。至于动态物体如果要接收静态物体的阴影,则需要借助light probe的帮助。
如上图所示,在静态的墙和地面之间,由shadowmask提供了阴影。
至于那三个动态物体cube,在没有light probe的情况下,左下角的cube无法接受到静态墙体的阴影。而右边的两个cube之间则有实时的shadow map阴影,并且它们也在静态墙壁上投下了实时的shadow map阴影。
而如果使用Distance Shadowmask模式的话,各位猜猜哪一种阴影产生的方式会发生变化呢?
答案其实很简单,首先肯定和Shadow Distance相关,其次肯定是和实时的shadow map和烘焙好的shadowmask之间的切换相关。嗯,答案呼之欲出——在distance shadowmask模式下,处于shadow distance范围内的静态物体也会实时的产生shadow map阴影,这一点和动态物体一样,因此无论是静态物体之间还是静态物体和动态物体之间都会产生实时的shadow map阴影。而一旦超出了shadow distance的范围,则静态物体的阴影会从shadowmask中获取。
如上图所示,可以看到静态的墙和地面之间的阴影边缘锐利了,不像之前从Shadowmask获取的阴影,由于分辨率的原因可能会更加模糊,此时已经是实时的Shadow Map阴影了,因此边缘更加清晰锐利。而且左下角的cube已经可以接受到静态墙体的实时阴影了。
不过正如我刚才提到的,Distance Shadowmask和Shadow Distance以及两种阴影产生的方式有关,那么就有可能会出现一种瑕疵~即在Shadow Distance的内外,会有两种不同的阴影,而Shadowmask产生的阴影由于分辨率的原因往往更加模糊,例如下面这样:
可以看到墙壁产生了两种不同的阴影。因此这个问题各位在开发过程中可能也要关注一下。
另外,Distance Shadowmask和Shadowmask相比,性能上的开销显然要更大一些,这也不难想象,毕竟还有很多静态物体也会产生实时阴影嘛。那么我们可以简单的对比一下两者在Draw Call上的开销:
可以看到,右侧的Distance Shadowmask的drawcall开销要远远高于shadowmask。因此,在移动设备上采用shadowmask而不是开销更高的Distance Shadowmask对于追求性能的情况更加适用。当然,Unity2017之后,Distance Shadowmask和Shadowmask的设置已经移到了Quality Setting中了,这意味着我们可以在运行时动态的切换这两种模式了。
void OnTriggerEnter(Collider other)
{
if(QualitySettings.shadowmaskMode == ShadowmaskMode.Shadowmask)
{
QualitySettings.shadowmaskMode = ShadowmaskMode.DistanceShadowmask;
}else
{
QualitySettings.shadowmaskMode = ShadowmaskMode.Shadowmask;
}
}
当然,无论是Distance Shadowmask还是Shadowmask,它们都可以在Shadow Distance之外提供来自Shadowmask的阴影,虽然较之实时阴影要模糊一些。因此,对于需要远方也要产生阴影效果的项目,Shadowmask模式是一个很好的选择。
0x05 混合光照新模式-Baked Indirect
下面还剩下最后一个混合光照模式——Baked Indirect【4】。事实上这个混合模式的名字就已经十分直白了——它只烘焙间接光,其他的全部是实时的。Shadowmask贴图?不存在的。
因此我们有了实时光、实时阴影等等。所以,在Shadow Distance的范围之内和实时光下效果一样,阴影是实时的shadow map,并且在shadow distance之外不会有阴影产生——哪怕是分辨率比较低的模糊阴影也没有。
可以看到在Shadow Distance范围内的墙的阴影十分清晰,但是在范围之外则已经没有阴影了。同时,这种混合模式的开销也很大,因为它只烘焙了间接光,其他的全部是实时的。
0x06 总结
ok,让我们对这几种混合模式做一个小的总结吧~可以简单的归纳为下面这个表格【5】。
Ref
【1】https://www.siliconstudio.co.jp/middleware/enlighten/en/
【2】https://en.wikipedia.org/wiki/Rendering_equation
【3】https://docs.unity3d.com/Manual/LightMode-Mixed-BakedIndirect.html
【4】https://docs.unity3d.com/Manual/LightMode-Mixed-Shadowmask.html
【5】https://docs.google.com/spreadsheets/d/18R663xpccuyRns1kOvGAiqj0qU9QFMbbXyrOCQMsU5w/edit#gid=1748986211