大家好,我是小熊猫,应邀来分享一些关于在Unity开发中的一些经验分享。
这一篇文章来比较系统的讲一下Unity中的光照系统模块,包括Realtime、Baked、Mixed、探针,以及对光照参数的一些解释。
本文内容主要讲了6个部分:
1、环境的组成;
2、直接光照和间接光照所构成的全局光照;
3、Unity灯光类型;
4、Unity灯光模式(实时、混合、烘焙)以及一些参数介绍;
5、Realtime与烘焙技术方案;
6、光照构建模式Lightmapping参数;
7、灯光探针与反射探针。
1、环境的组成
在说光照以前,我们首先了解一下环境系统的组成部分,其光源类型它分为太阳光和环境光,也就是DirectionLight和Skylight,在UE4中,有独立的Skylight组件,还有Fog的大气环境存在,那么在Unity中没有独立的Skylight组件,但是他在Lighting的环境设置中。其次Fog也有,我们暂时先不对Fog做讲解,在Unity中,环境光有两种存在方式,一种是来自天空球,一种是来自自定义颜色,两者只允许存在一种方式。
这两种方式在我们的Window-Lighting-Settings下可以看到,如下图所示:

Skybox Material:这个是我们的天空球材质,我们的场景天空其实是一个材质去实现的,最常见的它其实是一个Cubemap类型的材质球,由一个内立面的立方盒子或者半球形构成。
红色线框中的就是我们的环境光,Skybox,天空球,天空球材的反射光源来自于贴图本身,细节跟随贴图本身,所以反射细节远远高于自定义颜色,因为天空上会有各种颜色,云朵、太阳、星空、月亮、极光等等,所以使用天空球作为环境光,那么场景中的物体所反射的光线颜色也会越漂亮,越接近真实,因为他会反射环境光的细节。如果这里的环境光选择Skybox,那么环境光的颜色就是采用我们天空球材质中贴图的颜色来做为环境光源。
这里补充一点,在Unity中,环境光,与UE4中的Skylight相同,他会使场景反射指定颜色或者材质的颜色信息到场景中,使得场景中的物体被环境光线所影响,例如阴影、太阳光,如果我们太阳光是红色,环境光我们自定义为蓝色,那么此时主光源的颜色必然是紫色,而物体的阴影则是蓝色,这是因为一个物体的光影信息会受到来自太阳光和环境光的影响,如下图所示:

通过上图中我们可以看到,环境光的类型我们改成了Color,也就是自定义一个颜色,那么Color的模式,我们的物体阴影,光照面反射面全都是一种颜色,还有一种模式是Gardient,如下图所示:

在Gardient模式下,我们可以分别设置天空地面还有赤道的方向被照射的颜色,这里的EquatorColor我们引入一个地球示意图:

如上图,如果我们的光线来自于右侧或者左侧水平方向,正对太阳的面积,我们肯定是会有太阳光的颜色,假设太阳光是白色,亮部就会有白色,同时,赤道方向的颜色,如上如所示,无论从哪个方向都会照射到,所以对于太阳正对面的颜色,处于水平方向的光照,正对面的颜色应该是太阳光与EquatorColor的颜色,如下图所示:

那么为什么太阳正对面之外,比如背面是反射绿色呢,也就是EquatorColor的颜色呢,我们我们目前的场景,处于地球的照射面积,也就是如下图所示:

那么我们地球不是一个二维平面,他是一个三维空间,那么在被太阳光直射的半球内,被照射的物体背面的环境,他会显示EquatorColor的颜色,因为在被照亮的半球里,一个物体除了太阳直射的面为亮以外,其他面不可能为黑色(也就是阴暗面),因为这是个三维空间,光线并不是照射一次,所以被照亮的半球里,物体直射太阳的面,是定向光与EquatorColor的颜色,而背面没有被照射到,则是EquatorColor的颜色。
上文中引介Gardient模式来说了下环境光的颜色,还涉及到一个直接光照和简介光照的概念,也就是我们所说的全局光照,俗称GI,下面我们来说一下GI。
2、直接光照和间接光照所构成的全局光照
我们先来看一张图:

第一张图是没有光照的显示效果,中间的图,是直接光照,直接光照是什么意思呢,就是说一个物体,它接受来自一个光源的第一次照射,就叫直接光照,举个例子就是说一个物体,被太阳光第一次照射到的地方,就会亮起,也就是正对着太阳面的会亮,不对着太阳面的地方就是黑色阴暗。
间接光照的意思是说,首先我们的光线进入大气后,会被分散开成朝着很多方向照射,那么光线遇到阻挡的物体,会反弹,当反弹再次遇到阻挡物体的时候会进行二次反弹,但是,光是有衰减性,多次反弹过后,这一条光束逐渐变暗,最后消失,那么在反弹的过程中,这一条光线会逐渐照亮在经过路上的周围所能影响到的物体,如下图所示:

光线进入后,从左侧反射到地面,再由地面反射到右侧墙壁,最后反弹出去。
那么经过间接光照后,我们可以想到,没有被直接光照照射到的物体的阴暗面,自然会变得明亮起来,因为光线照射进来会不断的四周反弹,但是光存在衰减性,不断反弹的过程中能量逐渐减少,所以没有被直接光照照射到的地方,有间接光照,但是亮度也不会超过直接光照的亮度。
最后我们所看到的场景,就是直接光照加间接光照后的表现,这两种光照结合,也就是我们俗称的全局光照,简称GI,全称Global Illumination。
3、Unity灯光类型
与大部分游戏引擎相同,Unity包含了常见的几种光源,点光源、聚光灯、定向光、区域光源、环境光(Lighting设置中配置),以及包括2个特殊的对象,一个是光照探针、一个是反射探针,两种探针我们放在最后讲,先来看下几种常见的光源对象:

因为在任何一个游戏引擎里,都会存在这几种光源类型,所以就不解释都是干什么的了,当然区域光源在部分引擎中是没有的,但是可以用自发光面片模拟。
定向光主要作为太阳光使用,点光源我们可以用做一些烛光或者台灯,或者补光使用,聚光灯可以用作具有IES灯光纹理的光照,或者手电筒,壁灯,区域光源可以用来照射一定范围内的物体,但是要注意的是区域光源只能用于Baked模式,不能用作Realtime模式。那么具体这两种模式有什么区别,以及灯光里面的一些参数设置,来看下一小节。
4、Unity灯光模式(实时、混合、烘焙)以及一些参数介绍
上文中我们提到了Realtime和Baked,当然在Unity中,还有一种光照模式,叫Mixed也就是混合模式,我们接下来一个一个来讲解这三个模式的区别。三种模式的设置,我们可以选择灯光,在Mode中可以找到:

Realtime:在该模式下,光照和阴影会参与实时计算,任何的场景中无论是光影变化还是色彩变化,都会实时的显示变更后的内容。
Mixed:混合模式,在实时模式下,因为光影环境等信息会实时做出计算,所以对性能开销会有一定影响,例如在一些配置比较低的硬件上,或者手机上,因为特殊机能限制无法承受较大的性能开销,所以混合模式,会将场景中静态不动的物体,例如地面,或者墙面,烘焙成一张光照贴图,当烘焙成光照贴图后,这些物体不再参与光影计算,而场景中动态的物体,例如角色,或者一些怪物,他们会在场景里动来动去,那么只有动态的物体,才会参与实时的光影计算,因为毕竟你角色移动,影子和照射在角色身上的光影信息也是变化的。
Baked:相比Mixed混合光照更适合一些对性能要求更低的情况,这个模式下,只有静态的物体会表现正确的光影信息,如果非静态物体,那么它既不会受光照影响,也不会产生影子。
Mixed和Baked都是烘焙技术方案,同时不管是Mixed还是Baked,都可以灯光探针影响,也就是说在Baked模式下表现不了光影信息,也可以加上探针来让物体有光信息,但没什么实质性作用,只是说告诉大家探针可以让它产生光照信息。
现在一来,我们的光照其实只分为了2个类型,一个是Realtime的实时全局光照,一个是Baked和Mixed的烘焙光照,这里我先插入一个知识点,然后再对实时GI和烘焙技术做一个更深入的解释。
其实这里说的Realtime实时全局光照,它其实实际上是一种预构建光照系统,因为目前就现实来说,模拟现实世界的光线环境,并且实时的在游戏中表现,非常不现实,首先现实中的光影细节更丰富,其次越丰富的细节计算代价也越高,一边计算如此多的数据一边保证游戏满帧率运行不现实,所以这里说的Realtime实时GI,其实是一种预构建光照系统,在一定程度上的先将光照进行计算,并且存储下来信息,在游戏运行需要的时候表现这些信息,而并不是实时计算。
好了说完题外话,我们下面来讲解一下实时GI和烘焙技术。
5、Realtime与烘焙技术方案
如果要使用Realtime,首先要确保我们的主光源Mode为Realtime模式,并且Lighting-Settings选项的Realtime Lighting为勾选状态,如下图所示:


此时我们的光照已经为Realtime模式,如果使用烘焙方案,根据我上面讲过的,对游戏来说,我们应该使用Mixed混合光照,也就是说静态物体烘焙光照贴图,而动态物体,比如说游戏玩家,实时的计算光影。
那么烘焙方案,我们首先确保灯光Mode模式改为Mixed,或者你的项目需要,你可以选择Baked全静态构建,同时Lighting-Settings选项的Mixed Lighting为勾选状态,如下图所示:


然后我们要说一下,根据项目需要,或者平台需要,我们的方案,选择一种就可以,如果你的项目是移动平台,或者性能比较低的,使用烘焙方案,如果PC或者更高性能平台,使用实时光照,这里建议最好只勾选一个,也就是需要的方案,因为Realtime Lighting或者Mixed Lighting一起勾选,一样会进入处理对象,为了避免不必要的开销,哪怕很小,我们还是尽量去只勾选一个。
如果我们使用烘焙方案,在Lighting下面的选项中,会有一个Lightimapping Setting的分类参数,如下图所示:

那么第一个Lightmapper的选择,是选择我们想要烘焙的光照技术,一个是Enlighten,一个是可能以后会出的一个比较快的光照构建技术Progressive,这里我插入一个题外话,作为一个小知识点。
Enlighten的技术方案,应该算是现在最好的商用GI方案,Enlighten目前推出了可以在移动平台上实现实时GI并且开销极低的可行性,DICE的寒霜引擎就是采用了Enlighten的解决方案,国内网易的逆水寒也是引入了Enlighten,而我们Unity的Realtime GI,也是来自Enlighten的计算,烘焙方案,也是来自Enlighten的计算,不过Unity的Enlighten有点阉割的样子。
然后一直在说的烘焙,到底是在烘焙什么东西呢,我们引擎烘焙光照,其实就是烘焙光照贴图,我们先来看一下光照贴图的示意:

烘焙完成后,我们拥有3个贴图,第一个Intensity是光照信息图,这张图保存的信息是光照的颜色,第二个Directionality保存的是光照的方向,最后一个Shadowmask保存的是我们光照后受光的阴影。三个加起来,形成了我们的光照贴图。
光照贴图,是像性能妥协并且可以完成画面细节更为多的方案,烘焙,构建,就相当于渲染器在计算光照的属性,他比实时GI能表现出更多的画面细节,但是因为烘焙过后,他其实是将光影信息,烘焙成一张贴图,然后贴在了场景中物体的表面,所以带来的就是,如果此时你移动当前的物体,他的影子和光影信息是不会变的,因为这个物体的光影信息已经形成了一张贴图,贴在了物体和接受他投影物体的表面,这样带来的好处就是,在游戏运算过程中,不需要去实时计算,减少了很大一部分的性能损耗。尤其是是低端硬件平台。
6、光照构建模式Lightmapping参数
写在烘焙之前,对参与烘培的物体,必然是静态对象,那么我们选择一个场景中的对象,再Inspector窗口,右上角勾选Static:

其实我们只需要勾选Lightmap Static就可以。
既然要烘焙,必然有烘焙的很多细节参数,我们首先来看一下烘焙的三个光照模式,一种是Shadowmask,一种是Subtractive,一种是Baked Indirect,默认是Shadowmask,如下图所示:

他们依次是高低排序的,从质量上来讲,Subtractive是最廉价的烘焙模式,它带来的就是产生质量较低的光照贴图,换来比较高的运算性能,中间的Shadowmask其实是有2个,一个是Shadowmask一个是Distance Shadowmask,他是一种性能兼质量为一起的光照模式,那么Baked Indirect则是烘焙质量最高的光照模式。
Substrative:所有颜色和阴影都烘死在光照图上,运行时静态物件不参与阴影图渲染,结果是不管光源怎么变,静态物件的受光和阴影都不变。
Shadowmask:静态物件的阴影不变,但是受光情况可以随光源变化而变。
Distance Shadowmask:静态物件的阴影和受光都可以变。
Baked indirect:仅烘焙间接光照(也就是物体之间漫反射导致的表面明暗效果),其它东西(如阴影)全部在运行时计算,跟实时差不多了。
Substrative模式下,光照贴图会变成两个,如下图所示:

我们可以看到之前属于Shadowmask模式下,贴图是3个,而在Substrative模式下,贴图变成了2个,缺少了一个阴影贴图,那么阴影贴图和光照信息并在一个一个贴图里,也就是Intensity贴图中,我们之前也说过,Substrative是一种性能开销极低的光照模式,它还有一个问题就是说,像如下的场景:

物体的正确光影信息是其中主光源为淡黄色,那么物体首先会有一个来自主光源的物体影子,也就是途中黑色的那个影子,如果此时物体中间有一个点光源或者其他光源,那么物体会产生另外一个来自当前其他光源的第二个影子,但是,如果我们选择的是Substrative模式,那么他就是如下图所示的样子:

其中1和2为静态物体,他们烘焙过后,光影信息是正确的,但是3和4为动态物体,他们却没有显示来自第二盏灯光的第二个影子,这是因为在Substrative模式下,动态物体只支持来自第一盏主光源的光照,所以我们可以用Shadowmask或者Baked indirect,就可以解决这个问题。
Shadowmask拥有两个,一个是Shadowmask一个是Distance Shadowmask,他们两个的切换可以在Edit-Project Settings-Quality中,如下图所示:

这两个有什么区别呢,我们先来说一下Shadowmask,我们通过上面的光照贴图发现,Shadowmask多了一个Shadowmask的烘焙贴图,这个帖图存储的就是场景中的所有光源信息,也就是对应光照的不同阴影。
我们会发现一个问题,如下图所示:

在Shadowmask模式下,左侧和下方的2个Cube为静态物体,他们可以表现正常的光照信息,而上方和右侧的Cube,并没有表现出正常的光影信息,来解释下:
Shadowmask模式下,太阳光会影响实时的直接光照,并透过光照贴图和光照探针影响间接光照。静态物体使用Shadowmask为自己产生阴影。动态物体根据Shadow Distance设定所产生的实时阴影贴图来接收阴影。动态物体从光照探针中接收静态物体的阴影。Shadowmask模式适合用于那些只需为场景中角色或道具增加实时阴影的情况。
所以处于Shadowmask模式下的动态物体阴影是实时计算的,对于动态物体的阴影细节正确表现,一个我们可以用环境光来影响,也可以用光照探针来影响,如下图所示分别为环境光和光照探针影响后的动态物体阴影表现:


但是Shadowmask模式下,静态物体的影子,并不会影响动态物体,因为静态物体的影子烘焙到了被投射的地面上,所以在如下图中会产生不正确的阴影:

如图所示的场景中,较高的墙面为静态物体,光影信息被烘焙到贴图上,而箭头指向的Cube为动态物体,根据此时的场景,当动态物体到了墙体的阴影处,Cube也会被阴影影响,变暗,但是这里并没有变暗,原因上面讲过了,那么此时我们有2个方法可以解决,一种就是Distance Shadowmask,一种是光照探针,光照探针我们后面详细的讲一下,先说一下Distance Shadowmask,这个可以在Edit-Project Settings-Quality中Shadowmask Mode改变。
Distance Shadowmask模式下,就变成了,静态构建后的物体,可以实时向动态物体投射来自于静态物体的影子,如下图所示,改成Distance Shadowmask后,我们发现光影表现正确:

Baked indirect模式下,只会烘焙物体的间接光照,其他所有的信息,影子,光全部会实时处理,举个例子,在Shadowmask模式下,我们烘培过的光影信息,会有一个Shadowmask的贴图,其次,当你烘焙过后,你的阴影不会根据你的距离做衰减,如下图所示:

当你距离物体较远,影子依然会存在,但是如果使用Baked indirect,影子实时参与计算,所以它只会接受来自Shadow Distance的距离参数影响,如果距离参数改小,那么同样的距离,影子不会显示:

Shadow Distance的参数位于Edit-Project Settings-Quality下:

这里我们要注意一下,Baked indirect和Distance Shadowmask的动静态物体阴影都会改变。上面讲过他们的区别,这里再引用上文中提到的内容:
Substrative:所有颜色和阴影都烘死在光照图上,运行时静态物件不参与阴影图渲染,结果是不管光源怎么变,静态物件的受光和阴影都不变。
Shadowmask:静态物件的阴影不变,但是受光情况可以随光源变化而变。
Distance Shadowmask:静态物件的阴影和受光都可以变。
Baked indirect:仅烘焙间接光照(也就是物体之间漫反射导致的表面明暗效果),其它东西(如阴影)全部在运行时计算,跟实时差不多了。
Lightmapping参数的调整,也就直接影响着光照的烘焙质量,阴影质量等等。一方面和我们的物体光照分辨率有关,另一方面和Lightmapping有关,我们先来看一下物体的光照分辨率尺寸,选中场景中的物体:

红色线框,表示我们物体的光照贴图尺寸,通过调整这个值,可以控制该物体在Lightmap中所占的纹理大小。比如调整成0.1,则此物体的光照纹理就只有原来的十分之一,而调整成0,则此物体虽然可以产生阴影,但是不接收任何光照信息。
其次物体的烘焙,需要正确的光照UV,也就是2U,我们可以让它自动生成光照UV,也可以再三维软件中做二套UV,自动创建UV通过再Assets中,选择我们的模型,右侧Inspector选项中勾选创建UV,如下图所示:

这时会自动给当前物体创建一个光照UV,但是光照UV的划分越合理,光影表现越正确,自动创建UV不是最好的解决方案。
我们在看下Lightmapping设置:

红线中我们之前说过了Lightmapper的光照烘培技术,Indirect Resolution是间接光照质量,Lightmap Resolution是光照贴图质量,这两个合在一起讲,他们都是用来控制烘培光照的质量,间接光照质量,指的是物体受到的二次光照质量,直接影响的就是物体的受暗面表现,阴影表现,光照贴图质量,指的是在Unity中每个单位长度的像素,它的分辨率。数值越高,每单位像素越高,精度也就越大,构建效果也就越高,换来的则是烘焙时间的增加。
Lightmap Padding这个平时用到的地方不是很多,他是用来调整如果构建光照贴图有一些细微上的错位,然后使用这个参数来强制偏移光照贴图来对位。
Lightmap Size则对应的就是光照贴图尺寸了,最小32最高4096,这个之影响于接受阴影投射的物体,比如地形。更高的光照贴图尺寸会带来比较清晰的光影信息,但是这个不是绝对的,因为不是说开到4096就一定会获得更好的光照贴图,他还和我们之前说过的,光照贴图质量,以及物体2U等等都有联系,同时越小的光照贴图尺寸,会被拆分成多个光照贴图,如下图所示:

这是因为太小的贴图尺寸,已经存不下场景中的光影数据,所以他会分段存储,同时又因为光照贴图尺寸降低,那么光照贴图的大小也会降低,过高的光照贴图尺寸会带来很大的性能开销。
还记得我们之前解释光照贴图中,说过这个光照贴图中有一个叫做Directionality的方向贴图,它是干什么的呢,在这个贴图中,带有光照的方向性,它主要作用于被照射物体表面的坑洼痕迹,例如带有法线的物体的正确表面光照纹理,当然这个也是可以关闭的,关闭后,性能开销会降低一些,比如说移动平台上可以使用,如果不需要烘焙Directionality贴图,可以Directional Mode中,改成Non-Directional。
还有几个参数我们来看一下,一个是Final Gather,这个参数,主要用来做最终效果的表现,也就是说勾选后,它会开启世界中光线和物体之间的真实交互,会更真实的模拟在真实世界中光的传播和对象的光影之间互相交互的结果。这里引入一个英文文章来解释一下:
https://www.pluralsight.com/blog/film-games/understanding-final-gather
其他的一些我们以后会结合实际烘焙的例子再来讲解,例如像烘焙AO啊什么的。
写在最后,我们发现烘焙的时候每次修改场景,右下角都会有一个蓝色的读条,这个是因为我们勾选了自动构建光照,如下图所示:

当我们勾选Auto Generate后,处于烘焙模式下的光照系统,每次检测到场景有变更,都会再次去烘焙光照贴图,如果我们不需要的话,可以取消勾选,等最终拼接完成或者想要看下效果,点击右侧的按钮构建一下即可。
7、灯光探针与反射探针
最后一小节,我们来讲一下灯光探针和反射探针,之前在处理阴影时,还记不记得下面这张图:

处于静态物体阴影面,Cube也应该是阴暗的,因为受到了墙体的影子,之前是使用了Distance Shadowmask,那么我们现在使用探针来解决这个问题,探针,也就是每个点可以获取当前空间的光照信息,而多个探针形成了一个网状体,处于网状体的模型,会被距离他最近的4个探针所影响,这4个探针会将周围光影信息作用于当前模型。
我们先来添加一个灯光探针,如下图所示:

创建好后,我们可以看到场景中有一个四个点组成的区域:

我们将灯光探针,覆盖到场景的四周,注意不要让探针的地步陷入地面以下,因为地面以下是不受光影响,这样带来的效果就是,物体如果使用了靠近它最近的探针,如果这个探针在地面以下不受光,那么物体这一面一定会黑,调整后如下图所示:

但是这个时候我们发现Cube还是没有显示正确的光照,我们选中小球,看到了如下图所示:

我们的小球受到来自4个灯光探针的影响,但是我们发现4个小球的受光影面积,都在太阳的照射范围下,而没有阴影处,所以我们的灯光探针,除了要捕获光照面,还需要捕获阴暗面,我们需要添加灯光探针,在选中灯光探针后,位于Inspector面板,点击Edit light probes,选中其中一个探针点,复制或者Ctrl+D即可复制出一个或者多个探针,我们调整一下:

注意我们为了更多的细节,明和暗的地方,上图中我都加入了探针点,最后效果我们选择小球,可以看到它收到了来自阴影面的探针影响:

那么处于影子和照射中间,会受到阴影面和光照面的一起影响:


同时,灯光探针,也会给与动态物体正确的光影信息,如下所示:

红背后,红色地面光会反射到Cube一侧,另一侧则反射绿色光,但是如果此时Cube为移动物体,就会丢失反射信息:

我们此时可以使用灯光探针来捕获当前探针点周围像素信息,来采集当前范围内正确的环境光线,通过下面Gif可以清楚的看到前面因为加入了探针,所以会影响着Cube的反射环境光,而Gif后半截取消了光照探针,所以Cube没有任何反射光线:

反射探针,其实也有一点点类似,它是捕获以它为中心四周的环境反射到物体身上,众所周知,如果我们默认情况下的反射材质,它要么就是反射我们来自天空球的反射,要么就是我们自定义的Cubemap,而反射探针,它可以是使我们的物体实时反射周围的信息。
首先我们创建一个纯反射的材质,赋予场景中一个球体上,同样的我们创建一个绿色的材质赋予一个Cube,如下图所示:

现在的反射球,来自于天空盒子的反射,我们可以在这里改变反射的类型:

可以使用天空球,或者我们自定义的颜色,但是这里我们需要让它来实时反射当前场景,所以我们创建一个反射探针:

然反射探针和我们的球体捆绑,坐标归零,并且形成父子关系:

此时我们需要改变一些反射探针的参数,选中反射探针,我们可以看到在Inspector面板下,有两个比较关键的参数:

Type默认为Baked,也就是说,只有烘焙光照后,反射物体才会反射周围环境的情况,这里改成Realtime,也就是实时的捕获环境,并且作用于被反射的物体表面。
Refresh Mode是刷新模式,也就是说当前更新反射是每帧更新,还是根据脚本。
实时反射所带来的性能开销是巨大的,这同样也包括材质球的复杂程度,同时材质球的复杂程度也会影响烘焙光照的构建时间。
此时我们看一下更改后的球体表面反射情况:
