随着各大计算平台的算力稳步增长,特别是GPU技术的不断进化,原先可望而不可及的技术比如实时光线追踪技术开始逐步走入玩家的视野。一些先锋厂商甚至已经超出Demo的范畴,开始正式推出支持实时光追的游戏。
不过目前的实时光追技术还只能在配备了最新Nvidia RTX 20系列显卡的PC机上才能实现(前一代Nvidia 10系列显卡,比如GeForce 1080,1070,甚至1060也可以用软件实现实时光追,但是总体效果不佳)。
大多数玩家所在的移动平台上,目前并没有实时光追技术被正式推出。虽然有厂商如Imagination Technologies推出了移动端实时光追的架构设计和演示Demo(可查看Imagination官网:https://www.imgtec.com/powervr-ray-tracing/),但是这些技术要在主流手机厂商如华为,苹果,三星,小米等那里得到实际应用,还有一段不短的路程要走。
因此我们今天来聊一下一项“老”技术:光照贴图烘焙。虽然实时光追非常诱人非常好,目前来说要想实现好的光照效果,我们还是要依赖光照贴图烘焙技术来实现。
本文将以问答的方式来讲解Unity 2019.4版本中的Progressive Lightmapper(Unity 2018等之前版本中的Enlighten已在2019.4中正式弃用)。之所以选择问答的形式原因如下:
大家阅读时可以看完一个问题的解答随时停下来。
便于大家事后查找相关内容。
什么是光照贴图(Lightmap)?为什么要用光照贴图?
场景中的光照信息大致可以分成两类:直接光照和间接光照。
如果没有间接光照,那么整个场景就没有真实性可言。但是间接光照的实时计算在目前的硬件条件下,只有支持实时光线追踪的硬件才能实现,比如Nvidia的RTX系列显卡。在普通的计算设备上,特别是移动端设备上目前还没有实时光线追踪的解决方案出现。因此我们必须依赖预先计算好的光照贴图来提供这些间接光照信息。
光照贴图本质上就是一张或者多张应用在场景模型上的贴图。它们包含的是通过光照贴图烘焙方式进行预计算所获得的间接光照,阴影等信息(可以在烘焙时选择只烘焙间接光照,不烘焙阴影)。使用光照贴图可以避免在游戏运行时进行实时的光照和阴影计算,提高游戏的运行性能,特别适合用于性能较弱的计算平台比如移动平台上。
要在Unity中预计算(或者说烘焙)获得光照贴图,我们使用的是Unity官方研发的Progressive Lightmapper(渐进式光照贴图烘焙)。在Unity 2018版本之前,Unity中集成的光照烘焙模块是一套第三方解决方案Enlighten。由于第三方厂商Geomerics不再维护Englighten,所以从Unity 2019版本开始Enlighten被标记为弃用状态(Deprecated)。Unity 2021.1版本开始将完全移除Englighten模块。
ProgressiveLightmapper基于AMD的Radeon Rays技术(https://gpuopen.com/radeon-rays/)开发。RadeonRays是一套支持跨平台的ray intersection库(如大家对Radeon Rays技术感兴趣,可以参考网址:https://gpuopen.com/radeon-rays/)。
渐进式光照贴图烘焙,对场景中模型的要求是什么?
使用渐进式光照烘焙对模型有以下几个要求:
1. 模型上不能有重叠的UV。
2. UV之间要有足够的间距以避免“渗色”现象的发生。
3. 因为光照贴图只能烘焙静态物体,所以我们要把需要参与烘焙的物体标记为Static。
以下为具体说明:1. 模型上不能有重叠的UV。我们可以尝试使用Unity的ImportSettings窗口中的Generate Lightmap UVs来生成第二套UV(记得在勾选复选框以后点击Apply按钮):
但是通常建议大家在建模软件中制作第二套UV,因为光照贴图烘焙用的UV与普通纹理贴图的UV有所不同。
Unity中并没有内置的模型UV查看功能,这里介绍一个Unity资源商店中的小工具UV Inspector,如下图所示:
下面两张图是在Unity中使用UV Inspector的界面。在Scene窗口选中模型以后,UV Inspector界面就会显示当前模型包含的所有UV。左侧为第一套UV用于纹理贴图;右侧为第二套UV用于光照贴图烘焙:
2. UV之间要有足够的间距以避免“渗色”现象的发生。“渗色”的发生因为两块UV之间的间隔不足,导致一块UV上的颜色“渗透”到了相邻的UV上。
3. 因为光照贴图只能烘焙静态物体,所以我们要把需要参与烘焙的物体标记为Static,如下图所示:
如上图所示,我们习惯上直接在Game Object上勾选右上角的Static复选框,然后把层级中所有物体标记为Static。不过对于光照贴图烘焙有意义的两项是Contribute GI和Reflection Probe Static,因此你也可以只勾选这两项。
上图中的Contribute GI(贡献全局光照)选项和Mesh Renderer中的Contribute Global Illumination(贡献全局光照)是联动的。如果我们勾选右上角的Contribute GI,Mesh Renderer组件中的Contribute Global Illumination也会被勾选,Receive GlobalIllumination(接受全局光照)则会被设置为Lightmaps,意指当前Game Object会使用光照贴图获取间接光照。
我们也可以把Receive Global Illumination设置成Light Probes,这时间接光照信息就会来自相关的光照探针(Light Probes)。
渐进式光照贴图烘焙对硬件的要求是什么?支持Unity的哪些渲染管线?
硬件要求:
1. 至少需要一块支持OpenCL 1.2的显卡
2. 至少2GB的显存
3. CPU支持SSE4.1指令
支持的渲染管线:
1. 内置渲染管线(Built-in Render Pipeline):支持Baked Indirect,Subtractive和Shadowmask光照模式。
2. 通用渲染管线(Universal Render Pipeline,简称URP):支持Baked Indirect,Subtractive和Shadowmask光照模式。
3. 高清渲染管线(High Definition Render Pipeline,简称HDRP):支持Baked Indirect和Shadowmask光照模式。
因为用于烘焙测试的两个场景都是基于HDRP来制作的,所以接下去的讲解我们将会围绕HDRP的光照烘焙模块来进行。
渐进式光照贴图烘焙出来的是什么?
光照贴图烘焙出来的是光照贴图(Lightmaps),光照探针(Light Probes)和反射探针(Reflection Probes)。
按照不同的Lighting Mode(光照模式),光照贴图烘焙出来的结果是不同的。
HDRP下Lighting窗口的光照模式支持Baked Indirect和Shadowmask两种(内置渲染管线和通用渲染管线请参考文档)。
1. Baked Indirect模式:
如果场景中的灯光模式设置为Mixed,那么这些灯光会给场景提供直接光照,间接光照信息则被烘焙到光照贴图和光照探针中。此选项适合中高端的平台,比如PC,主机。
2. Shadowmask模式:
如果场景中的灯光模式设置为Mixed,灯光会给场景提供直接光照,间接光照烘焙到光照贴图和光照探针中。Shadowmask和光照探针遮挡信息会被烘焙到阴影信息中。你可以在Project Settings > Quality窗口设置Shadowmask的模式(Shadowmask或者Distance Shadowmask两种模式可选)。此选项在游戏运行时比Baked Indirect模式性能更好,因为光照贴图中已经预先烘焙了阴影信息。
3. Subtractive模式:(在内置和通用渲染管线中支持)
场景中的直接光照,间接光照和阴影信息都会烘焙到光照贴图中。适合对性能敏感的平台比如移动端平台。
如果场景中光源设置为Mixed模式,在三种光照模式下动态和静态物体的行为可参考以下列表:
注:上述总结自Unity文档:https://docs.unity.cn/2019.4/Documentation/Manual/lighting-mode.html
以上对比中的Shadow Distance在三种不同渲染管线中设置的方式是不一样的,以下为设置详解。
(1)内置渲染管线:
打开Edit > Project Settings > Quality> Shadows > Shadow Distance。
(2)URP通用渲染管线:
打开Edit > Project Settings > Graphics中选择UniversalRenderPipelineAsset进行设置。
(3)HDRP高清渲染管线:
通过在场景中使用的Volume进行设置。
界面操作(HDRP示例):打开Window > Rendering > Lighting Settings窗口,在Mixed Lighting区域勾选Baked Global Illumination复选框,然后在Lighting Mode中选择光照模式:
注:HDRP中不支持Subtractive光照模式
渐进式光照贴图烘焙的CPU版本和GPU版本有什么区别?
CPU和GPU两个版本所用的底层技术相同,唯一的区别是:CPU版本使用CPU和内存进行计算;GPU版本使用显卡和显存进行计算。
1. 如果使用CPU版本进行烘焙,影响烘焙效率的是CPU的速度和内存的大小。
2. 如果使用GPU版本进行烘焙,影响烘焙效率的则是显卡的速度和显存的大小。
界面操作(HDRP示例):打开Window > Rendering > Lighting Settings窗口,在Lightmapping Settings区域可以选择使用哪个版本的ProgressiveLightmapper:
Lighting界面上那一堆参数理解一下?
选择好Lighting Mode和Lightmapper,我们来看一下具体的烘焙参数。确保打开Window > Rendering > Lighting Settings窗口。
在HDRP中进行光照烘焙时可以为整个场景指定一个用于烘焙的天空盒作为环境光,如下图所示:
我们可以在这里使用当前HDRP场景中使用的天空盒设置,也可以使用不同的天空盒设置。当然,如果你使用相同的天空盒,那么烘焙所得的光照贴图将会拥有与当前场景一样的环境光。
内置和通用渲染管线的环境光设置参数:
注:上图中的设置不在此详细讲解,可以参考中文文档中的具体描述:https://docs.unity.cn/cn/current/Manual/GlobalIllumination.html
接着我们来看具体的烘焙参数。如下图所示,光照贴图烘焙窗口中Lightmapping Settings区域的这些参数在UI上目前是放在一起的,这么多密集摆放的参数对于新用户来说一眼看上去会有不知所措的感觉,下文会一一进行介绍:
1 Prioritize View:
启用此选项,如果Scene窗口打开,系统会逐步烘焙Scene窗口看到的画面,然后再继续烘焙Scene画面之外的场景区域。
如果你在Scene窗口移动场景中物体,改变物体和灯光属性或者改变Scene窗口画面等操作,烘焙会及时调整,快速逐步烘焙改变后的画面。
通过这个方式,我们可以快速预览Scene窗口当前画面的间接光照信息,及时做出相应修改,加快迭代速度,而不必像之前使用Enlighten时需要等待烘焙全部完成以后才能看到结果。
在使用此选项时记得打开Auto Generate(自动生成)复选框。
2 采样设置相关:
此区域的设置跟烘焙时所用的采样方式和采样数值相关。
Multiple ImportanceSampling:(默认是禁用状态)这是针对环境光采样的设置。如果开启,可以缩短光照贴图的生成时间,但是在场景中某些较暗的地方会产生明显的噪点。
Direct Samples:用于设置从每一个纹素(Texel)射出的采样路径数(针对直接光照)。数值越大效果越好,烘焙时间也越长。
Indirect Samples:用于设置从每一个纹素(Texel)射出的采样路径数(针对间接光照)。数值越大效果越好,烘焙时间也越长。针对户外场景,指导数值为100。室内场景(包含自发光物体),可以按需增加采样路径数直到看到效果。
Environment Samples:针对环境光的采样数。数值越大效果越好,烘焙时间也越长。默认数值为500。
Light Probe SampleMultiplier:如要使用此功能,必须在Project Settings > Editor> Graphics中禁用Use legacy Light Probe sample counts,如下图所示:
此数值会被用于分别乘以Direct Samples,Indirect Samples和Environment Samples这三个数值。这三个数值会被应用于LightProbes采样。数值越大效果越好,烘焙时间也越长。
Bounces:此数值用于控制计算光子弹射时的反弹次数,一般2次可以满足普通场景的需求。
3 降噪设置相关:
Filtering区域的设置用于光照贴图的降噪操作。降噪操作本质上是一个针对已经烘焙好的光照贴图做后处理的过程。
如果启用Filtering功能,系统会在把光照贴图的Direct,Indirect和AmbientOcclusion这三部分信息结合之前,分别为这三个部分应用降噪算法。
我们可以选择Auto(自动)或者Advanced(高级)两种方式。
自动:Progressive Lightmapper会自动选择一个当前机器支持的降噪算法应用到光照贴图上(因为规则是固定的,所以具体规则请参考Unity文档)。
高级:可以为Direct,Indirect和Ambient Occlusion分别选择降噪算法(Denoiser)或者降噪滤镜(Filter)。如果你有支持Nvidia Optix降噪算法的GPU,可以选择Optix;如果有支持RadeonPro降噪算法的GPU,可以选择RadeonPro;在任何情况下,都可以选择基于CPU的降噪算法OpenImageDenoise。
除了使用Optix,Radeon Pro和OpenImage Denoiser这一类具备AI功能的降噪算法之外,我们也可以进一步使用降噪滤镜,比如以下配置:
这里的Guassian(高斯)滤镜会在降噪算法之后在光照贴图上做进一步的模糊处理,以减少光照贴图中的噪点。
注:内置渲染管线和通用渲染管线还支持A-Trous滤镜。此滤镜会在尽量减少光照贴图中噪点的同时降低模糊的效果,因此通常比高斯滤镜的效果更好。
4 光照贴图分辨率和大小相关:
这里涉及三个参数:Lightmap Resolution,Lightmap Padding和Lightmap Size。
Lightmap Resolution(光照贴图分辨率):数值单位为texelsper unit(每单位面积的纹素)。
Texel(纹素)有别于Pixel(像素)。像素是图片的基本单位,如果我们在图片编辑软件中把图片放大到足够大,可以看到这些图片由许多正方形的像素组成,所以像素是屏幕空间的概念。纹素则是纹理贴图的基本单位,纹理贴图是应用于模型上的,所以并不是屏幕空间的概念。
在模型被绘制到屏幕上时,纹素会被转换成屏幕上的像素展现出来。我们可以通过网络上找到的这张图来理解像素和纹素之间的对应关系:
像素和纹素最大的区别是:像素其实就是图片数据;但是纹素可以代表很多类型的数据,它可以是纹理贴图,也可以是用于计算阴影的深度图。
光照贴图本质上是纹理贴图,因此Progressive Lightmapper在这里用纹素而不是像素来代表光照贴图的分辨率。
Lightmap Padding(光照贴图间距):数值单位为texel(纹素),默认值为2。烘焙好的光照贴图中包含很多Charts。这些Charts可以理解成对应模型上包含烘焙光照信息的UV色块。在游戏运行时,这些色块会与模型网格进行映射,完成最终效果的计算(在模型原先的纹理上叠加烘焙的光照信息)。但是这些“色块”之间必须保持一定的距离才能确保模型上一个部位的颜色不会“渗色”到另一个部位。
Lightmap Size(光照贴图大小):数值单位为像素,默认值为1024。根据Lightmap Resolution和Lightmap Padding的参数设定,烘焙出来的光照贴图数量会相应的变化。这里的大小其实代表的是每张光照贴图的最大尺寸。按照实际需求,即使设置了2048,某些光照贴图的尺寸也有可能是1024或者512。
接下去我们用一组测试数据来说明上述三个参数的关系(使用的项目是上面的夜间场景):
从上述测试数值可以总结如下:
Lightmap Resolution越大,烘焙时间越长,光照贴图越精细。
Lightmap Padding越大,光照贴图的数量越多,但是可能可以减少提示UV重叠的模型数量。
Lightmap Size越大,光照贴图的数量越少,光照贴图越精细。
以下截图是配置-6的烘焙参数和结果:
从上图中可以看到,在提高了Lightmap Resolution,增加了Lightmap Padding数值和Lightmap Size数值以后,从原先的119个UV重叠的物体下降到了94个UV重叠的物体。
那么UV重叠到底是什么意思?是因为我们在制作模型解UV的时候没有做到UV之间保持足够距离吗?
出现这个黄色警告信息的原因有以下几种(我们也列出了可能的解决方法):
模型上用于光照烘焙的UV确实存在重叠:在Console界面我们可以看到警告UV重叠的信息中包含了具体哪个模型有这个问题。我们可以具体看一下这些模型的UV(这是用于光照贴图烘焙的第二套UV。如果没有这套用于光照烘焙的UV,我们需要手动生成或者用Unity的模型导入界面来生成这套UV)。如果模型的原始UV确实存在重叠,我们可以通过外部建模工具来修复。
模型上用于光照烘焙的UV不存在重叠:如果看下来其实所有模型的原始UV都不存在问题,在实际烘焙好光照贴图的场景中也看不出有什么“渗色”的情况,我们可以忽略这个警告。当然我们可以尝试提高Lightmap Resolution和增加Lightmap Padding的方式来提高光照贴图的精度,从而减少出现在警告中提示UV重叠的物体数量。不过要注意这会影响烘焙所需的时长,以及增加的光照贴图的大小。
5 Compress Lightmaps:
默认启用,对光照贴图进行压缩操作。虽然压缩过的光照贴图可以减少内存占用,但是也会导致光照贴图质量下降。
6 Ambient Occlusion相关:
环境光遮蔽用于为场景中的某些区域比如裂缝,孔洞,墙面的交界处,或者任何两个物体相邻的区域添加类似于阴影的效果。它会让这些地方变得比其他地方更暗一些。此处的设置会把这些环境光遮蔽信息烘焙入光照贴图中。
在HDRP中,我们可以在Volume中设置Ambient Occlusion,不过那是针对当前摄像机看到的区域来计算的基于屏幕空间的实时环境光遮蔽,属于实时计算的范畴。Volume中设置的实时环境光遮蔽效果,可以通过Window > RenderPipeline > Render Pipeline Debug窗口,把Lightings设置的Full Screen Debug Mode设置为SSAO(Screen Space Ambient Occlusion),就能看到基于屏幕空间的环境光遮蔽效果(红色箭头区域为部分AO效果):
Max Distance(最大距离):为了计算出一个物体是否被另一个物体挡住从而算出相应的环境光遮蔽效果,系统需要使用射线来做侦测之用。此参数用于控制射线的长度。射线的长度越长,光照贴图中由环境光遮蔽产生的阴影区域越多,反之越少(只有相邻很近的物体之间才会有环境光遮蔽产生的阴影)。如果设置为0,意味着此射线为无限长;默认数值为1。因为数值0代表射线长度无限长,所以画面中的环境光遮蔽明显暗很多。
Indirect Contribution(间接光贡献):数值在0到10之间,默认数值为1。此参数用于控制间接光强度对环境光遮蔽的影响。下面三张图比较了不同Indirect Contribution数值对画面整体的环境光遮蔽造成的影响,可以看到IndirectContribution越大,环境光遮蔽越暗(红色线条标出的区域最明显)。
Direct Contribution(直接光贡献):数值在0到10之间,默认数值为0。此参数用于控制直接光强度对环境光遮蔽的影响。
7 Directional Mode(方向模式):
Directional:此模式下会生成第二套光照贴图,专门用于保存入射光的主要方向信息。使用法线贴图的材质可以利用这张光照贴图上的方向信息,在计算法线贴图时加入光照贴图中保存的全局光照信息。不过此模式下生成的光照贴图通常比Non-Directional模式下生成的光照贴图大一倍。(此模式下生成的光照贴图无法在SM2.0和GLES2.0硬件上解码使用。在这些硬件上会回退到Non-Directional模式)。
Non-Directional:禁止烘焙时生成第二套用于保存入射光主要方向信息的光照贴图。
8 Indirect Intensity和Albedo Boost:
Indirect Intensity(间接光强度):用于控制光照贴图中保存的间接光强度。数值限定在0到5之间。默认数值为1。数值大于1会增强间接光强度,小于1会减弱间接光强度。
Albedo Boost(反射率增强):用于控制物体表面之间光子弹射的数量。默认数值为1。数值限定在1到10之间。数值越大,物体表面的反射率越趋向于白色。
9 Lightmap Parameters:
用于控制各项烘焙相关的参数。你可以使用预设的参数也可以自行创建参数并保存下来以便复用。
除了在烘焙窗口可以全局指定这些预设的参数,你也可以为场景中参与烘焙的模型的Mesh Renderer组件单独指定预设的参数,示例如下图所示:
不同显卡对GPU版本的烘焙效率有什么影响?
为了让大家更好地理解不同显卡配置和显存大小条件下,对GPU版本光照贴图烘焙效率的影响,我们专门配置了三台机器做烘焙测试。三台机器的配置信息如下:
感谢Nvidia中国和攀升为这次测试提供相关测试机器(IPASON D 系列设计师电脑,官网:http://ipason.com/)。
以下是不同配置下6组光照烘焙参数配置所用的烘焙时长。
1 办公室场景烘焙测试
2 斯蓬扎场景夜间场景烘焙测试
总结:用于测试的三块显卡Nvidia GeForce 2060s,2070s和2080Ti的区别是它们的CUDA核心和显存大小。可以看到无论是什么样的参数配置:CUDA核心越多,显存越大,烘焙时间越短。所以我们可以说:不差钱的直接上2080Ti,毕竟烘焙这块可以给你节省很多迭代时间;入门级可以考虑2060s,因为性能也不差。
相同场景使用CPU烘焙需要多长时间?
使用斯蓬扎夜间场景,烘焙参数和烘焙时长如下。与GPU版本相比,CPU版本所需的烘焙时间明显偏长。
可能大家会问:既然GPU版本那么快,为什么还要CPU版本?
答案是:进行光照贴图烘焙时,GPU版本使用的是显存。就目前的显卡来说,显存总是有限的,我们也无法像添加内存那样可以自行添加(内存也相对便宜很多)。如果当前场景在烘焙时所需的显存空间超出了当前显卡具备的显存大小,那么GPU版本就会停止工作。这时我们就需要一个后退的方法,那就是CPU版本来救场了:在烘焙过程中,如果Unity发现显存耗尽,Unity会把GPU版本自动切换到CPU版本。
为什么GPU版本开始烘焙以后,有时候会自动切换成CPU版本?
GPU版本自动切换到CPU版本的原因是当前系统的可用显存不足,GPU版本无法继续进行正常的烘焙操作。
从GPU版本到CPU版本的切换会发生在准备烘焙阶段。在Unity编辑器的Console窗口可能会出现两段黄色的警报信息(第一段必出),示例图如下:
OpenCL报错:后退到CPU光照烘焙。后面一段的意思就是显存不足了。
(可能出现)这一段是说降噪处理失败。请尝试警用降噪处理或者降低光照贴图大小。
我们在使用GPU烘焙的时候,需要注意的是:等待Prepare Baking…这个阶段结束,开始Baking的时候看是否会切换到CPU版本,如下图所示:
如果看到Baking…[ETA: xx:xx:xx],观察到没有切换到CPU版本的话,你可以放心之后会继续用GPU版本进行烘焙了。否则如果这时候你离座去干个别的事情,可能回来一看烘焙时间翻了10倍:因为自动切换到CPU版本。
如何避免GPU烘焙自动切换成CPU烘焙?
因为场景中参与烘焙的资源量大小是不一样的,所以完全避免切换是不可能的。
通过前面不同型号的GPU烘焙测试,可以知道确保能够在场景中使用GPU烘焙的前提条件是当前系统可用显存的大小。因此能否使用GPU烘焙就看我们的系统能否省出足够的显存给渐进式光照烘焙这个模块用。以下是一些节省系统显存的方法:
1. 如下图所示,通过顶部菜单Edit > Project Settings打开项目设置界面,在烘焙开始之前将Texture Quality调整为Eighth Res,意思是在Scene窗口和Game窗口只使用纹理贴图的1/8尺寸进行显示。(默认为Full Res:意思为使用完整尺寸的纹理贴图进行显示)。烘焙结束之后调整回Full Res。具体设置界面如下图所示:
2. 在烘焙过程中如果不需要查看渐进式的烘焙过程,可以隐藏Scene窗口和Game窗口,比如像下图一样将Project Settings窗口覆盖在最上层:
3. 将场景切分成多个小场景,使用多场景的方式进行加载。这样可以针对各个小场景进行烘焙。
结语
渐进式光照贴图烘焙系统为我们提供了快速迭代的工作流和整个烘焙时长的预估,完全改变了之前使用Enlighten系统时对所需烘焙时长完全靠猜,以及光照贴图烘焙效果要等到烘焙完成以后才能看到的问题。这大大加快了光照烘焙的速度,也让灯光美术师有了更多的自由度获得自己想要的效果。
不过要想用好这个系统,除了需要深入了解每个参数背后的意义,使用不同的场景(户外,室内,或者像斯蓬扎这种既有室内部分也有户外部分的场景)针对不同的参数做各种测试是非常关键的。通过这些测试练习,我们可以增加对这些参数以及这些参数之间如何配合的直观感受,有助于我们日常工作中在面对不同类型的场景时,能够在保证速度的情况下,烘焙出高质量的光照贴图。