游戏场景的灯光无疑非常重要的,其不仅起到照亮场景的作用,好的灯光更能渲染气氛,使游戏效果更加逼真。然而又不能滥用灯光,因为灯光,阴影等会消耗大量的性能,造成游戏的卡顿,内存消耗太大等问题。因此掌握Unity中灯光,活用灯光非常关键。
对现实生活中光线的反射,折射,衍射等特性的模拟,一直以来都是计算机图形学的重要研究方向。在漫长的发展过程中,出现过很多很多计算方案,总体有这几种:
- 直接模拟光线从光源发出到最终被物体吸收的正向过程,也就是GI(Global Illumination);
- 不直接模拟光线,而是反向搜集物体表面特定点的受光强度来模拟现实照明效果,也就是FG(Final Gathering);
- 完全不考虑光线的行为,单纯基于“物体上与其它物体越接近的区域,受到反射光线的照明越弱”这一现象来模拟现实照明(的一部分)效果,即AO(Ambient Occlusion);
- 将场景光照结果完全烘焙到模拟贴图上,从而假冒显示光照效果,即Lightmap。
前两种方式计算量非常大,往往需要很长时间进行渲染,很难应用在游戏设计领域,在游戏设计领域,光照贴图技术依旧是主流方式。
一. Unity中的灯光,阴影和渲染路径
1. 灯光类型
在Unity中灯光属于直接照明(Direct Lighting),灯光本质都是空物体加上灯光组件。直接照明可以产生阴影,但光线不会反射,折射,但可以穿透半透明材质物体。
在Unity中的灯光有:平行光,点光源,探照灯,面积光;此外,还可以创建两种探针(Probe):光照探针组(Light Probe Group,保存光照信息让物体有更真实的光照效果)和反射探针(Reflection Probe,在探头位置生成反射贴图,主要让场景有更好的反射效果)。
- Directional Lighting 平行光
平行光通常用来作为阳光,当我们新建场景也会在场景中默认放置一个平行光源,平行光不会衰减。
因为所有灯光都是使用同一个灯光组件,不同的是灯光类型不同,所以下面只介绍每种灯光的特殊属性。
对于使用灯光组件,这里有几点需要注意的:
- Indirect Mutiplier间接光照强度倍乘,意味着如果大于1,每次反射的光线将会更亮,若小于1,每次反射光线变暗,不要光线反射则设为0;
- Mode中对应Realtime(实时光源), Mixed(混合光源), Baked(烘焙光源);
- Cookies是将指定图片的阿尔法(alpha)通道作为一个遮罩,是光线在不同的地方有不同的亮度。如果灯光是聚光灯或方向光,为一个2D纹理;若为点光源,则必须为立方图(Cubemap);
- Render Mode中有Auto, Important, Not Important。当为Important时,灯光将精细渲染,当为Not Important时,灯光将以最快速度渲染,Auto则让系统自动分配渲染精细度。
- Point Light 点光源
点光源模拟一个小灯泡向四周发出光线的效果,其亮度在其照亮范围内随距离增加而衰减;
特殊属性:
Range: 光线射出的范围,超过这个范围则不会照亮物体;
- Spotlight 探照灯
探照灯模拟一个点光源仅沿着一个圆锥体方向发出光线的效果,在其照亮范围内亮度随着距离增加而衰减。
特殊属性:
Spot Angle: 灯光射出的张角范围。
- Area Light 面积光
面积光模拟一个较大的发光表面对周围环境的照明效果,通常面积光亮度衰减得很快,阴影比较柔和。Unity中面积光仅在烘焙光照贴图时有效。
特殊属性:
Width: 面积光的宽度。
Height: 面积光的高度。
2. 阴影类型
在Unity的灯光中可以为该灯光产生的阴影设置不同的阴影类型,分为:无阴影(NoShadows),硬阴影(HardShadows),软阴影(SoftShadows,阴影模糊效果)。其本质都是使用阴影贴图模拟的阴影效果,而不是真实光照形成的暗色区域。
三种阴影模式,无阴影就是没有阴影,硬阴影边缘清晰,而软阴影边缘柔和,更加贴近现实生活中的阴影,同时计算消耗也会更大。灯光组件上的阴影参数会随着灯光照明模式(Mode)参数不同更改,下面详细说明每个属性的意义:
- Baked Shadow Angle: 烘焙阴影的角度,当照明模式中包含烘焙时有效;
- Realtime Shadows Strength: 实时阴影强度,该值越大阴影越黑;
- Resolution: 阴影贴图分辨率,高精度贴图阴影会更加逼真,同时也更消耗性能和增加内存。
- Bias: 阴影偏移,增大这个值可以让阴影底部(和产生阴影游戏物体接合处)远离其母物体,可以使用这个属性让阴影显示更加自然,避免阴影贴图盖住游戏物体受光部分;
- Normal Bias: 法线偏移,增大这个值阴影会向阴影面的法线方向(即阴影突出来的部分)收缩;
- Near Plane: 阴影近裁切平面,与摄像机距离小于这个距离的场景物体不会产生阴影,一般在0.1附近,或者是光源照明范围的百分之一;
- Cookie: 相当于给灯光加一个遮罩,用来模拟一些阴影效果,比如贴上网格图模拟窗户栅格效果;
- Cookie Size: 调整Cookie贴图大小;
在属性设置中,Resolution的参数除了使用几个定好参数,也可以使用Use Quality Setting来在Quality面板中定义的。通过Edit->Project Settings->Quality打开Quality面板,在这里可以针对不同质量等级设定参数。
Quality面板才是各种渲染设置的最终决定者。不光只是用来设置阴影质量,渲染质量等也是在这儿进行设置。在这里只介绍其中的阴影设置:
- Shadow: 决定使用哪种类型的阴影;
- Shadow Resolution: 设置阴影不同的渲染分辨率。对应的就是灯光组件中阴影模块下的Resolution参数;
- Shadow Projection: 有两种方法定向光投射阴影。Close Fit适合渲染更高分辨率的阴影,但是相机移动会有轻微的摆动。Stable Fit适合渲染较低分辨率的阴影,不会因相机移动轻微摆动;
- Shadow Distance: 离相机可见的阴影最大距离,超出的阴影将不会被渲染;
- Shadowmask Mode: 提供两个选项,Shadowmask和Distance Shadowmask,这里先附上一张盗来的图,图中的英文不准确:
这里的发射者指的是阴影的产生者,而接收者则是阴影覆盖的区域(可能是平面也可能是其它物体),是否在影子距离之内则是由Shadow Distance来决定,light probes表示使用光照探针搜集光照信息才能产生阴影,shadowmask则是阴影贴图。由此可得,Distance Shadowmask要比Shadowmask更加耗费性能,因为它更想使用实时阴影。
- Shadow Near Plane Offset: 靠近平面的阴影偏移(暂时不明白有什么用);
- Shadow Cascades: 设置阴影级联数,可选为0,2,4;
- Cascade Splits: 不同级联对应的场景区域的划分比例;
3. 渲染路径(Rendering Path)
Unity提供两种渲染路径:Forward和Deferred。
- Forward
在Forward渲染路径下,每个物体被光源渲染成一个“通道”,物体受到越多灯光光照,渲染次数越多。在灯光较少的情况下,Forward方式渲染速度非常快,处理透明贴图也非常快,还可以使用“多重取样抗锯齿(MSAA)”等这样的硬件处理技术;但会随着灯光的增多渲染速度迅速下降,不适用于多灯光照明场景。
- Deferred
Deferred渲染路径其渲染时间不会随着灯光增多而提高,但是会随着整体受光照影响区域的扩大而提高(若整个场景都已经被照亮,再增加灯光设置复杂度不会进一步影响渲染速度了)。新版本的Unity默认渲染路径是Deferred渲染。
二. 天空盒,间接照明和烘焙
除了上面提高的直接照明外,还有间接照明:
- 环境光(Ambient Light),特指来自天空的漫反射。Unity中可以继承“天空盒”的颜色作为环境光颜色,也可以自行指定环境光颜色;
- 反射光,特指天空漫反射之外的所有环境漫反射。Unity中使用光照贴图或灯光探针来模拟;
- 自发光物体。Unity中自发光物体本身的亮度仅使用颜色来模拟,自发光物体对于环境的影响则通过光照贴图或灯光探针模拟。
1. 设置天空盒
当我们进入到生成的默认Unity场景里,会看到一个默认的天空盒,无论我们往什么地方移动周边的景色都没什么变化,彷佛广阔无边。实际上,是因为天空盒跟随着开发者相机移动的,故感觉天空盒广无尽头。
天空盒如同其名,就是一个正方形的稍微大一点的盒子,且贴图是在盒子内部,形成一个全景视图。因此,天空盒的六个面的贴图必须要求是无缝贴图,这样从任何方向看都是一副连续的画面。
要设置一个天空盒,首先要创建一个材质Material,材质的Shader处有三种选择:
将自定义完成的天空盒在Window->Lighting->Settings中应用就完成了(下面和环境光一起讲)。
环境光(Environment Lighting)的来源(Source)有三种:Skybox, Color, Gradient。
- 当为Skybox时表示环境光来源为天空盒,提供属性选项为:
- Intensity Mutiplier: 强度乘数,控制天空盒场景光在场景中游戏物体上的亮度;
- 当为Color时表示为环境光来源于指定的一个颜色,提供属性选项为:
- Ambient Color: 为环境光指定一个特定的颜色;
- 当为Gradient时表示环境光照来源于一个从地平线到穹顶的颜色渐变,提供属性选项为:
- Sky Color: 指定从天空发出的光的颜色;
- Equator Color: 指定从场景边缘发出的光的颜色;
- Ambient Mode: 指定从地面发出的光的颜色。下面为效果图,应该比较直观:
环境反射(Environment Reflections)的来源(Source)也有两种:
- 当为Skybox时表示指定天空盒作为反射效果,提供属性选项为:
- Resolution: 控制将场景中天空盒用作反射效果的分辨率;
- 当为Custom时表示指定自定义的立方体贴图作为反射效果,提供属性选项为:
- Cubemap: 需要提供一个CubeMap用作反射效果;
2. Lighting设置补完
Mixed Lighting设置:
- Baked Global Illumination: 是否让光照类型为混合(Mixed)和烘焙(Baked)的灯光使用全局光照烘焙。
- Lighting Mode: 混合类型灯光的照明模式,决定了混合灯光下游戏物体的阴影。提供Baked Indirect, Shadowmask, Subtractive三种模式:
- Baked Indirect: 烘焙间接模式,混合类型灯光提供实时的直接光源,但间接光被烘焙到光照贴图和光照探针上,它为所有的静态和动态物体提供实时阴影,只有间接照明的路线是预定义的(比较消耗性能);
- Shadowmask: 阴影遮罩模式,需要配合Quality面板中Shadows块的Shadowmask Mode来使用的(参考上面的表格)。同样是提供实时直接光源并烘焙间接光源,但是其阴影产生机制则是和Quality面板的Shadowmask模式参数相关。间接照明和直接遮挡的路线是预计算的,使用这种方式烘焙光照贴图,注意不能随便改变光源的位置,否则可能会出现叠影的问题;
- Subtractive: 这种模式下,混合灯光的直接光照和间接光照都用来烘焙静态物体的光照贴图(含阴影)了,而动态物体的阴影则是实时采用场景中最亮的光源投影到静态物体上。所有光路都是预先计算好的,通常只有一个光源(最低性能消耗,故常用于构建低端移动设备)。
此时会额外提供一个属性:Realtime Shadow Color,实时阴影颜色,实时阴影最黑的点将会被定义为这个颜色。
Lightingmapping Settings: 当Mixed Lighting的Baked Global Illumination为false时,该模块许多功能会被禁用。
-
Lightmapper: 指定使用哪种计算方法来计算光照贴图,提供Enlighten和Progressive。Enlighten: 这是一种常用的烘焙计算方法;
Progressive: 一种新版的烘焙计算方法,它可以先烘焙摄像机可见区域,再烘焙其它区域,所以预览较快,其提供属性选项有:
- Prioritize View: 烘焙的先后顺序,勾选后先烘焙摄像机视锥体里的启用的元素;
- Direct Samples: 直射光的采样数量,更高的采样值会增加光照贴图的质量;
- Indirect Samples: 间接光的采样数量,同上;
- Bounces: 间接光的反射次数(和环境光不同的是这个是用于光照贴图计算的);
- Filtering: 配置在烘焙光照贴图时对噪音的处理:
None: 无; Auto: 自动; Advanced: 高级模式,提供Direct Radius, Indirect Radius和Ambient Occlusion Radius,三个选项都是对特定的贴图有值越大会增加模糊但是会减少噪音的效果。
-
Indirect Resolution: 用来指定间接灯光照明计算的每单位有多少纹素,纹素越大,光照细节越高;
-
Lightmap Resolution: 全局光照产生的光照贴图每单位有多少纹素,通常设置为Indirect Resolution的10倍左右;
-
Lightmap Padding: 修正两个物体在光照贴图中的纹素间隔,以避免颜色渗透;
-
Lightmap Size: 指定生成的光照贴图大小,光照贴图为正方形贴图,边长最大4096纹素;
-
Compress Lightmaps: 是否压缩光照贴图,压缩的纹理可能会带来不需要的纹理效果;
-
Ambient Occlusion: 允许控制环境遮挡的表面亮度(计算像素亮度和场景中附近物体之间的关系,以确定何时阻止特定像素接收附近环境光),较高的值表示遮挡区和全亮区之间较大对比度,仅适用于由GI系统计算的间接照明;
- Max Distance: 设置从一个物体发出的射线射程,以确定对象是否被遮挡。较大的值会生成较长的射线,并为光照贴图提供更多阴影,较小的值则生成较短的射线,这样只有两个物体非常靠近才会产生阴影。0表示投射无限长射线。
- Indirect Contribution: 调整间接照明中环境遮挡的对比度,值越大物体产生的光照贴图间接光强度越大。
- Direct Contribution: 调整直接照明中环境遮挡的对比度,值越大物体产生的光照贴图直接光强度越大。
-
Final Gather: 启用最终聚集时,会对最后一次GI光线反射后的光照结果再以与烘焙的光照贴图相同的分辨率进行一次FG计算。这将增加光照贴图的质量但增加烘焙时间。
- Ray Count: 从每个收集点发出的射线数量;
- Denoising: 将去噪过滤应用于聚集输出;
-
Directional Mode: 提供Directional和Non-Directoinal两个选项,Non-directional为平面漫反射,只有一个光照贴图存储关于表面发出光的信息。在Directional模式下,会生成第二个光照贴图来存储入射光的信息,因此需要两倍的额外光照贴图数据存储空间,shader可以根据这些入射光信息更好的表现。但是定向光照贴图无法在SM2.0或使用GLES2.0时使用,它们将回退到非定向光照贴图。
-
Indirect Intensity: 间接光照强度,控制实时存储的间接光照和烘焙的间接光照贴图的亮度,默认1。
-
Albedo Boost: 反射率提高,控制场景中的材质反射率来控制表面间反射的光量,增加此值将以靠近白色的反射率进行间接光照计算,默认1贴合实际物理现象。
-
Lightmap Parameters: 设置详细的光照贴图参数(有几个默认值,也可以创建新的参数)。
Other Setting:
- Fog: 添加场景雾效;
- Color: 雾效颜色;
- Mode: 控制随着和摄像机距离的增加,雾效的积累方式,提供的选项有Linear, Exponential(指数), Exponential Squared(指数平方)
- Start: 距离摄像机多远开始产生雾;
- End: 距离摄像机多远产生的雾完全遮住场景中的物体;
- Halo Texture: 光晕贴图,为场景中带有光晕的光源指定一个光晕纹理贴图;
- Halo Strength: 光晕强度,控制光源周围光晕效果的可见度;
- Flare Fade Speed: 耀斑淡出速度,控制光源耀斑在最初出现到淡出的时间;
- Flare Strength: 耀斑强度,控制光源产生的镜头耀斑可见度;
- Spot Cookie: 指定探照灯的默认Cookie纹理;
Debug Setting:
- Light Probe Visualization: 光照探针形象化,其属性有:
- Only Probes Used By Selection, All Probes No Cells, All Probes With Cells, None
- Display Weights: 选中时,Unity从用于有效选择的光探针绘制一条直线到用于插值的四面体位置。这是调试探针值和放置位置的一种方法;
- Display Occlusion: 选中时,若混合照明模式为Distance Shadowmask或Shadowmask,Unity会显示光线探测器的遮挡数据;
3. 烘焙光照贴图
上面的参数设定部分其实已经涉及到很多光照贴图方面的内容了,下面来讲讲具体如何进行光照贴图的烘焙(Baking)。光照贴图(Lightmap),其实就是使用贴图来模拟全局照明的效果。
光照贴图需要将所有参与的场景物体的UV重新排列组合成互不重叠且尽量少形变的方形结构,再把光照信息烘焙到一张或几张较大尺寸的贴图中。当所有场景物体放在同一个贴图时,那么一个多边形面片上的光照信息精度就受限于该多边形面片所对应的UV在贴图中所占据的面积大小。出于场景优化考虑,我们当然希望将有限的光照贴图面积尽量多的分配给更需要的物体,因此可以修改不同场景物体的MeshRenderer组件里的所占光照贴图比例的参数。
当我们把上面Lighting面板中的参数设置完后(或者直接使用默认参数),就可以点击Generate Lighting按钮烘焙光照贴图了。烘焙好的贴图会被储存在场景文件所在目录下与场景文件同名的子目录中,因此烘焙光照贴图前需要保存场景。(如果选择了Auto Generate,则不会在同名子目录中保存光照贴图,需要手动烘焙)。接着就可以在Lighting的Global maps选项卡查看照明系统系统正在使用的实际纹理,包括强度光照贴图,阴影遮罩和方向性贴图;在Object maps预览当前选中的游戏物体的以烘焙贴图的预览。
三. 光照探针和反射探针的使用
1. 反射探针(Reflection Probe)
天空盒的信息不可能包含所有场景信息,在很多时候,对象从天空盒收集的反射信息可能会被遮蔽,向室内对象或隧道中。要准确反射这些对象,就需要用到反射探针。**反射探针从它们的位置对周围取样并把结果写到cubemap上,并让周围经过的物体得到环境的反射镜像。**反射探针越少越好。
- Type: 类型选择,有三种模式提供:
- Baked: 用于烘焙模式,当进行光照贴图烘焙时,生成对应的反射贴图;此模式下,只有标记为“Reflection Probe Static"的对象才会被反射探头取样。
- Custom: 可以自定义一个cubemap来指定反射贴图,也可以点击下方新增的Bake按钮手动生成一个静态的cubemap,提供的属性:Dynamic Objects – 若被启用动态物体也将被烘焙到cubemap上;Cubemap指定反射的cubemap;
- Realtime: 实时生成反射贴图,会对所有可见的物体取样,和实施光照一样非常消耗性能,一般用于镜子效果(启用还需要再QualitySetting里勾选支持realtime reflection probes);提供属性:
- Refresh Mode: 刷新模式。On Awake,在启用是时刷新一次;Every Frame,每帧根据TimeSlicing刷新;Via Scripting,通过脚本刷新Unity将不会自动刷新。
- TimeSlicing:反射贴图的刷新频率,通过数帧刷新一次的形式来降低对帧率的影响;提供属性:AllFacesAtOnce – 6个面一次性刷新,9帧执行一次;IndividualFaces – 每个面单独刷新,刷新频率9+6为14帧一次;NoTimeSlicing – 每一帧执行,消耗最大。
- Runtime settings: 这些设置将在游戏物体和cubemap渲染的时候使用:
- Importance: 影响一个MeshRenderer中的多个ReflectionProbe的Weight的自动混合比例。当一个物体处在多个ReflectionProbe中时,首先考虑每个ReflectionProbe的Importance值,之后在此基础上考虑每个ReflectionProbe与该物体之间的分别交叉体积的大小。
- Intensity: 强度,用于设置生成的cubemap的明暗程度。
- Box Projection: 一般情况下,反射cubemap是被假设从无限远处投射来的图像,不同角度都能看到反射图且改变距离反射物体的远近反射图不会发生变化,但这种情况不适用于室内;而开启Box Projection,则是创建一个投射有限距离内的物体,当物体在探针内部的位置发生变化时,反射图案尺寸也会发生变化。ProbeSize及ProbeOrigin会影响ReflectionProbe映射图案的效果,在Graphics Settings -> Tier Settings里面可以统一进行开关。
- Blend Distance: 两个反射探针的混合区域,只有在延迟的反射探针上才可以设置;
- Box Size: 反射探针的box大小;
- Box Offset: 反射探针的box偏移值;
- Cubemap capture settings: 正方体贴图捕捉设置(这一块和摄像机组件的设置非常像,实际上就是使用摄像机将周围的景象摄下来成cubemap):
- Resolution: 反射探针生成的cubeMap的分辨率大小,分辨率越大呈现的反射图像越清晰;
- HDR: 允许使用HDR;
- Shadow Distance: 反射阴影距离,
- Clear Flags: 清除标记。即在渲染时采用什么作为底图。
- Skybox: 天空盒,默认模式。这样将使用天空盒来填充cubemap的空白区域,若没有天空盒,则使用Background色;
- Solid Color: 纯色。将使用选定的纯色来填充cubemap的空白区域;
- Background: 调整cubemap的背景颜色;
- Culling Mask: 剔除层遮罩。决定哪些层要渲染,哪些不渲染,提高效率;
- Use Occlusion Culling: 启用遮挡剔除;
- Clipping Planes: Near: 近切面;Far: 远切面。
使用反射探针产生的cubemap的游戏物体,其MeshRenderer需启用反射探针和一个着色器来支持。
2. 光照探针组(Light Probe Group)
烘焙的产生的光照贴图只能用于静态游戏物体,那怎么解决动态物体的光照信息问题呢?为了让动态物体也能够获得某个地点的光照信息,就需要将这些光照信息记录下来,并在运行时能快速读取和使用。
光照探针组就是为此设计的,通过在场景中放置采样点捕捉各个方向的光线信息,将这些记录的光照信息通过球谐函数编码处理后保存为文件,这些信息占用的存储空间很少且运行时解码速度很快。场景中的Shader可以使用这些信息来模拟物体表面的光照。
使用光照探针,需要注意的是不能将所有探头放在一个平面上,必须构成立体形状,这样是为了能够从物体的头部高度或更高的空中接收光照信息。为了提高光照探头的效率,应该在光照变化大的地方多放置探头,不大的地方隔较远距离放置探头。
光照探针运行耗费性能速度也快,在照明小的,凸起的物体上有很好的效果,但是,因为探针是采样计算光照信息,因此对复杂的照明效果很难表现;且每个位置只用一个球模拟,对大模型的光照效果可能不会太好。一个场景中有多个Light Probe Group时,Unity在运行时会自动将这些Group合并,并移除位置重复的Probe。光照探针的具体使用可以参阅这篇文章。
后记
这一篇文章断断续续写了一两周,查询了许多资料,也拜读其它许多前辈的博客,但是即便如此,对于Unity中灯光方面,自我感觉还是存在许多的疑惑。也许需要真正在实践中尝试后,再来重读这篇文章才会有新的启发吧。
这里要特别感谢这几位大神的博客,可以说我的很多内容都是参阅他们的资料才写出来的,感兴趣的读者可以去阅读一下原文:
https://blog.csdn.net/weixin_42304838/article/details/82495272
https://www.jianshu.com/p/7594b044e6dc