Unity预计算全局实时GI(九) - Lightmap Parameters

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity预计算全局实时GI(九) - Lightmap Parameters相关的知识,希望对你有一定的参考价值。

参考技术A 由Unity提供的许多高级光照贴图控制是通过使用 Lightmap Parameters 来完成的。这些设置存储在一个Lightmap Parameters的asset中。这允许光照设置在不同场景间共享,以及在协作团队开发环境中的版本控制中共享使用。

* 方法一:打开Project视图,从Project视图左上方的Create下拉列表中点击Create > Lightmap Parameters。

* 方法二:在Project视图中按右键弹出菜单,选择Create > Lightmap Parameters。

* 方法三:依次点击编辑器顶部菜单(Asset > Create > Lightmap Parameters)。

一旦创建完毕,一个Lightmap Parameters asset可以被指定到一个静态MeshRenderer组件中。

* 在 Hierarchy窗口选择我们想要指定 Lightmap Parameters asset的G ameObject。这个 G ameObject必须被标记为静态同时有一个 MeshRenderer的组件

* 打开 Lighting 窗口 (Window>Lighting) 然后选择 Object 标签页.

* 使用 Advanced Parameters 下拉菜单去为 GameObject指定 Lightmap Parameters Asset。 Advanced Parameters下拉菜单右边的 Edit按钮可以被用做一种快捷方式,去修改指定给所选物体的 Lightmap Parameters。

相同的Lightmap Parameters asset可以被指定给多个对象物体。想要这么做,在Hierarchy窗口选择多个对象物体然后遵循以上的步骤。改变这个Lightmap Parameter asset将会影响到它指定给的所有对象物体。

和UV展开设置类似,Lightmap Parameters assets也可以被指定给一个Prefab或者应用给场景中的单个GameObject实例。将Lightmap Parameters assets应用给一个Prefab的实例将会覆盖存储在Prefab中的Lightmap Parameters asset设置。

这种方法的优势在于,适用于大多数情形下的默认Lightmap Parameters能够赋值给Prefab。当需要更多细致的控制时,在场景中可以这些默认Lightmap Parameters基础上进行修改。

在这个教程中,我们已经为每一个实例应用Lightmap Parameters assets。这是因为我们想根据每个GameObject的具体情况使用不同的Lightmap Parameters assets

配置场景范围的默认Lightmap Parameters是可能的。这些默认的Lightmap Parameters将会指定给场景中所有存在的MeshRenderer但还没有被指定一个Lightmap Parameters asset的对象物体。任何之后在场景中被创建的MeshRender将会使用默认Lightmap Parameters。当需要设置一个场景的光照时,可以通过使用这种方法减少需要手动指定的数量。

* 打开 Lighting 窗口 (Window>Lighting) 然后 Scene 标签页.

* 使用Default Parameters下拉菜单去给场景指定 Lightmap Parameters asset。

注意尽管这个Default Parameter将会被应用给所有新创建的对象物体,但是通过给对象物体制定一个Advanced Parameter仍然可以覆盖单独对象物体的Default Parameter,就像上面所描述的那样。

Unity提供了一些预置的Lightmap Parameters帮助我们快速地设置场景光照。它们是:

* Default - HighResolution

* Default - Medium

* Default - LowResolution

* Default - VeryLowResolution

指定这些Lightmap Parameters参数为其中的一种将会配置一些设置,而这些设置决定了所选对象物体的整体光照性能的开销。这些设置包括lightmap resolution,global illumination parameters,Cluster resolution,以及其它一些高级设置。当试着去平衡整个场景的光照表现性能时,这些设置是很重要的因素。

Unity存在默认参数目的是为了包含一些常用的使用情况,一般来这些默认设置对很多光照场景来说是足够了的。对于PRGI系统来说更细致的控制可以通过创建我们自己的Lightmap Parameters assets来实现。

在Unity 用户手册关于Lightmap Parameters的页面提供了Lightmap Parameters asset所有设置的整体概览,然而在本教程中,我们只关注对于光照优化最有用的设置。

Resolution

Resolution的值决定了使用了Lightmap Parameter的对象物体所需的光照贴图的分辨率。Resolution是一个乘数,与在Lighting窗口Scene标签页中的场景范围的Realtime Resolution值相乘。例如,如果场景实时分辨率设为2,这里的Resolution属性设为0.5,那所有带有这个Lightmap Parameter asset的对象都会采用每个单位1纹素来计算光照贴图纹理。

由于预计算实时全局光照只会呈现一个场景中的漫反射和间接光照,我们不需要使用传统烘焙光照贴图方法所需的分辨率。PRGI所需的光照贴图分辨率通常最多是每个单位2-3个纹素,与我们在传统烘焙的光照贴图中每个单位20-30个纹素相反。在大多数情况,尤其对于大型的户外对象物体,例如地形,可以降低很多倍。这里,一个光照贴图的分辨率降低为每个单位0.1个纹素可能会提供足够的细节表现。

需要注意,使用高分辨率光照贴图时,可能会出现使用低分辨率时不能发现的缺陷。这些阴影在最终的光照贴图纹理中可能看起来像是斑点或污迹。如果出现这种情况,可以试着调高Irradiance Budget参数来减少这种问题的出现。

Cluster Resolution

Cluster Resolution 指定预计算实时GI产生的光照贴图中的一个纹素包含集群的数量。例如,如果Cluster Resolution被设置为最大值1,那么在光照贴图中每个纹素将会有1个集群。Cluster Resolution的值设置为0.5代表在光照贴图的每个纹素只有0.5个集群。换句话说,集群的大小将会是一个光照贴图纹素的两倍。

进一步延伸这个想法,可以想象我们场景的全局实时分辨率被设置为1.我们创建一个尺寸大小为1x1x1的单位立方体,之后给这个对象物体指定一个Lightmap Parameters asset。如果我们的Lightmap Parameters asset指定分辨率为1并且Cluster Resolution也为1,我们将会看到立方体的每一侧都有一个集群。如果我们之后将我们的分辨率设置为2,结果立方体每一边的集群数量就会变为2倍(1x1),每面的集群会变成4个。

在大多数情况下,Cluster Resolution只需要我们光照纹素的一小部分。例如,参数设置为Default - HighResolution的Lightmap Parameters asset的Cluster Resolution值为0.6.

一个场景中集群太多的话会加长预计算时间,还会影响到运行时场景的全局光照。因此必须要确保增加的集群数量可以显著提高光照贴图的品质,如果减少集群数量对光照贴图并没有太大影响,那就尽可能地减少集群数量。

将分辨率与集群分辨率指定为一个比率,这意味着我们可以与场景范围的实时分辨率值建立一种相对的关联。我们可以将定义在Lighting窗口中的实时分辨率作为为整个场景的高等级的分辨率值。Lightmap Parameters之后可以对单个对象物体或者对象物体组进行细微的调控。Unity采用这种分层法可以为开发者提供更方便的全局控制,不然对每个对象都手动设置Lightmap Parameters的值会很麻烦。

Irradiance Budget

我们在前面的教程中讨论了Unity是如何通过使用集群生成一个模拟场景静态几何物体的模拟物,然后去计算PRGI的。在预计算期间,这些集群之间的关系已经被计算好,以便光照可以快速地被传遍整个层级网络。

本质上,光照贴图的一个纹素值取决于从该纹素的位置能“看到”场景集群的数量,这使我们可以在集群之间快速地计算光照反弹,以便全局光照的效果。在最终被渲染之前,这些集群被采样进光照贴图中。

Irradiance Budget决定了对集群网络进行采样时每张光照贴图纹素所使用的内存的大小。这决定了光照结果的精确度。Irradiance Budge的值越低意味着每个纹素所使用的内存越少,当记录场景视角的时候。这不但减少了内存的使用而且降低了CPU在运行时的开销,但是代价就是损失光照的保真度。Irradiance Budgets越低将会导致越低频率(模糊)的光照结果。相反,Irradiance Budget越高将会将会传递更精确的GI,但是增大了内存的使用量并且使CPU在游戏运行时产生了额外的开销。

如果我们的PRGI在游戏运行期间更新的不够快(滞后),将Irradiance Budget调低将会得到改善。这个设置比较适用于不太注意到的需要高保真度的对象物体,如很大、模糊或遥远的几何体。

Irradiance Quality

当在预计算期间PRGI光照贴图被生成时,光照贴图的每个纹素都向场景发射射线,以便反馈附近集群的可见度。然后计算纹素可以看见的每个集群的百分比。这个值定义了可见集群对光照贴图中每个纹素的光照贡献。Irradiance Quality用来指定1个纹素在这个过程中能对场景投射的射线数量。

如果对象物体与其周围的光照环境不是很匹配时可以考虑加大Irradiance Quality的值。有时会出现光照贴图的纹素出现过亮的情形,这可能是因为投射到场景中射线不足,因此导致没有检测到遮挡了光照贴图纹素的集群。相似地,如果更亮的集群没有被检测到,这里光照贴图的纹素将会出现过暗的结果。

发射更多的射线增加了解决类似问题的可能,这提高了光照贴图输出结果的准确性,同时以增加了预计算时间为代价。为了优化预计算时间,我们应该使用需要达到想要的光照结果的最小的Irradiance Quality值,请注意,这个值不会影响到游戏运行时的性能。

Backface Tolerance

从光照贴图纹素发射出来的射线,在从场景集群中收集光照信息时可能会打到几何物的背面(backface)。当生成全局光照时我们只考虑对世界可见那一面的反弹光照。从几何物体背面来的光总是被认为是无效的。这些射线打到背面对光照结果可能有潜在的危害甚至对光照结果产生破坏。通过修改 Backface Tolerance 的值可以阻止出现这种情况。

Backface Tolerance指定了必须从几何体正面来的光照百分比,以便一个纹素被认为是有效的。如果光照贴图的纹素没能通过这种测试,Unity将会使用邻近纹素的值去估算一个正确的光照结果。

调整Backface Tolerance并不会影响PRGI的优化,也不会对预计算时长有太大影响或者影响游戏运行时的性能。当增加Irradiance Budget的值后,你的光照贴图仍然存在错误的黑暗或者明亮的纹素,Backface Tolerance将会是一种解决这种情形错误的很好工具。

Modelling Tolerance

有时在场景静态物体中存在小间隙的区域会产生黑色的光晕或预期外的残像,这种情况会出现在旁边有相反几何体的区域,像是地板上的组件,如下图所示。

在这些情形下中,对象物体之间存在小的缝隙并且相对的表面之间很少甚至根本没有光线能够进入。很暗的纹素可能被位于下方的光照贴图所记录。在更低光照贴图分辨率的情况下,这些黑暗纹素的大小可能会导致它们超出遮挡物的边界并生成本不期望的黑色边缘。在光照的背景下,这些小物体产生的阴影可能是不期望的或者说被当成是污染物。

Modelling Tolerance用于忽略比Threshold值还小的细节,提高Modelling Tolerance值可以确保Unity在计算GI时忽略掉非常靠近其它物体表面的几何面,增加Modelling Tolerance值代表预计算的Light Transport阶段将忽视这些小于光照贴图纹素百分比的间隙,调低Modelling Tolerance可以确保小物体不被GI计算遗漏。

Modelling Tolerance不会影响PRGI的优化,对预计算时间和运行时性能也没什么负面影响,然而它可以被用在一些除错流程上,解决连调整Irradiance Budget或Irradiance Quality都无法改善的残影问题。

就提供的功能来说,我们已经探索了各种不同的Lightmap Parameters 设置。现在已经到了开始应用这些知识的时候了,通过创建我们自己的Lightmap Parameters assets并且在教学场景中去使用它们。

* 打开LightingTutorialStart场景.

* 在 Project 窗口下, 找到 LightmapParameters 文件夹 :Assets>LightmapParameters.

* 在Project 的坐上窗口中, 选择 Create>Lightmap Parameters.

* 将这个新建的 Lightmap Parameters asset命名为 TutorialTerrainLow

接下来,我们将要在场景中的对象物体指定这个Lightmap Parameters asset。

* 在Scene 视图, 选择离可玩区域最近的两个山形 GameObject,它们的名字都叫 MountainPeak。注意目前我们将会忽略最远的山形 GameObject。

* 通过使用 Inspector面板,确保两个 MountainPeak游戏对象物体被标记为Static。 这是由标记为 Static 的选项框所指示 。

* 打开 Lighting 窗口 (Window>Lighting) 然后选择 Scene 标签页。

* 确保开启了Auto mode. 这是由标记为Auto的选项框所指示 。

* 现在选择Object 标签页。

* 从Advanced Parameters下拉菜单中, 选择新创建的 TutorialTerrainLowLightmap Parameters asset . 这将会为选中的 GameObject 指定 Lightmap Parameters asset。

* 点击 Advanced Parameters 下拉菜单右边的 Edit 按钮,开始编辑这些设置。

第一个参数是分辨率。减少光照贴图的分辨率不但会减少预计算时间而且会提升游戏运行时的表现性能,所以我们应该在不影响光照品质的情况下,使用最低分辨率值。

对于离相机镜头很近的物体可以设置比较高的分辨率,但这里两座山距离镜头都非常远,因此本例中将该值设低一点更合适。 这些大物体表面材质的反射率相当一致。另外,从camera的角度来看,就这些对象物体的尺寸而言,它们接收的光在颜色方面变化不大。由于没有很多光照细节需要被捕捉,我们在不影响最终结果品质的情况下,减少这些对象物体光照贴图的分辨率。

* 试着慢慢地减少 TutorialTerrainLowLightmap Parameters asset的 Resolution参数,同时在 Scene视图 观察光照贴图分辨率的变化。

记住,分辨率的值是一个乘数,它与定义在Lighting窗口中Scene标签页中的场景范围的场景分辨率相乘。在我们的场景中,我们为实时分辨率设置为1.因此,如果之后我们在Lightmap Parameters中选择一个分辨率的值,那么结果光照贴图的输出将会是少于每个单位1个纹素。

两座山和其它物体距离非常远,所以它们不可能接受任何细节很高或是频率很高的光照。

这意味着我们选择一种低分辨率尽管会有一点视觉上的丢失。在我们的教学场景中,我们发现分辨率的值为0.05时,是足以捕获由这些对象物体接受的间接漫反射光照。

* 在 Project窗口 选择TutorialTerrainLowLightmap Parameters asset。

* 在 Inspector 面板, 将Resolution的值设置0.05.

接下来,我们将会观察Cluster Resolution的值。像早期描述的那样,Cluster Resolution是一个乘数,与为那个对象物体生成的任何光照贴图的纹素大小相反。当值为1时,一个单一的集群与一个光照贴图的纹素大小相同。因此低于1会比较好。记住,集群本质上是一个体素,是为了计算PRGI所生成的类似静态场景的模拟体。当这些体素的大小不足以捕捉光照颜色的变化时,我们才考虑加大这个值

这两座山的集群分辨率与光照贴图分辨率类似,也可以设为很低的值,如果将其设为场景默认的分辨率来设置集群数量,就表示可能会需要几百个小场景的集群量来覆盖这两座山。

考虑到MountainPeak对象物体有一个反射率相当一致的材质,并且这个对象物体离Camera相对较远,我们可以通过选择性地减少这个对象物体的分辨率来做出重要的节省。尽管当使用更高的分辨率时会与光照解决方案的精度不一样,对于我们场景中使用的物体来说,我们不可能注意到任何视觉上的差别。

* 集群分辨率( Cluster Resolution )对于离Camera较远的大型对象物体可以相对的低一些.

* 在Scene 视图的左上角, 使用Draw Mode下拉菜单去选择Clustering。

* 在 Project 窗口下 选择 TutorialTerrainLowLightmap Parameters asset。

* 在Inspector 面板, 减少Cluster Resolution的值. 观察值被改低后色块的大小是如何增加的。

另外,Lit Clustering绘制模式可以评估集群最终的分辨率是否足以捕获场景中细微的表现。

* 在 Scene视图的左上角,用 Draw Mode下拉菜单选择 Lit Clustering。

* 在 Project 窗口选择 T utorialTerrainLowLightmap Parameters asset。

* 在 Inspector 面板, 减少Cluster Resolution的值. 观察值被修改后视图是如何变化的 。

当调整这个值时,明确一下使用较低的Cluster Resolutions值是否会丢失一些光照细节。如果使用较高的分辨率对视觉效果没有提升,那么就应该去使用较少的集群。记住场景中的集群数量将会对预计算时间以及运行时与GI的交互产生很大影响。在这里,对于这两个MountainPeak实例,我们发现将Cluster Resolution设置为0.4是比较合适的。

* 在 Project 窗口下选择 TutorialTerrainLowLightmap Parameters asset.

* 在 Inspector 面板, 将Cluster Resolution的值设置为 0.4.

在许多情况下,降低光照贴图的分辨率以及集群的大小将会使光照性能获得很大的提升。然而,我们也可以通过修改用于计算辐射度的值:Irradiance Budget 和 Irradiance Quality 做出额外细节上的提升。

首先我们来看一下Irradiance Budget,这个值决定了为每个光照贴图纹素分配多少内存。为了游戏运行时的性能,我们应该使用最低的Irradiance Budget值,这对光照表现不会产生负面的影响。

如果预计算实时全局光照的表现结果是比较柔和的或者说是比较模糊的,这时我们可能希望增加Irradiance Budget的值。特别是场景中高对比度光照的区域没有被精确的呈现的时候。

在有两个MountainPeak 游戏对象物体的例子中,这里的光照一般是低频率的。我们不需要去捕获任何斑驳的或者高对比度的光照,这表示在对我们光照没有明显负面影响的情况下,可以降低Irradiance Budget的值,它原来的默认值是128bytes。

* 在Scene 视图的左上角, 使用 Draw Mode 下拉菜单选择Irradiance.

* 在 Project窗口中 选择TutorialTerrainLowLightmap Parameters asset 。

* 在 Inspector 面板, 减少rradiance Budget的值.

* 当我们减少这个值时,注意 MountainPeak游戏对象物体上 的光照是怎么变得稍微柔和的。

进行了一些实验后,我们将Irradiance Budget降低到64Bytes时发现对光照的品质没有产生显著的影响。

* 在 Project窗口 选择TutorialTerrainLowLightmap Parameters asset.

* 在Inspector 面板, 将Irradiance Budget的值设置为 64.

现在我们继续讲解Irradiance Quality。Irradiance Quality决定了每个集群发射的射线数量,当计算一个光照贴图纹素的值的时候。它和Irradiance Budget不一样,Irradiance Quality不会对运行时的性能产生影响,只会影响光照预计算时间。

由于发射射线数量的不足而导致丢失了小型但却很重要的集群,对于种情况增加Irradiance Quality将是很有用的。例如,对于一块几何物体有很突出颜色的效果最终没有在光照贴图上呈现出来的情况。

对于我们这两个MountainPeak 游戏对象物体来说,场景中尺寸很大的对象物体意味着我们不希望丢失附近的集群。

这表示我们可以通过在没有对光照结果造成负面影响的情况下,降低Irradiance Quality能够减少预计算时间。

* 在 Scene视图的左上角,使用 Draw Mode下拉菜单选择 Irradiance。

* 在 Project窗口 选择 theTutorialTerrainLowLightmap Parameters asset.

* 在Inspector 面板, 减少rradiance Quality的值.

* 注意随着值的减少最终的光照贴图是如何稍微变得不那么锐利。

已经测试了一些结果,我们已经发现Irradiance Quality值等于2048时,可以在GI光照的品质和预计算时间之间找到比较好的平衡。尽管丢失了一些精度,但是对于这个距离较远的对象物体的整体光照效果来说是足够了的。

* 在 Project窗口选择 TutorialTerrainLowLightmap Parameters asset.

*在Inspector 面板,将Irradiance Quality的值设置为 2048.

为距离比较远的场景配置Lightmap Parameters asset,我们现在可以应用已经学到的技术并且为LightingTutorialStart场景中剩余的静态几何物体创建Lighting Parameters assets。

我们在前面的教程中讨论过,在我们的场景中对对象物体进行分类管理是一种好的方法。基于对象物体的特性,例如尺寸大小或者离摄像机的远近程度,将场景划分为对象组将会使你更容易的进行选择,当应用Lightmap Parameters assets去微调光照的表现性能时。

当调节位于Environment游戏对象物体下面的静态对象物体时,为了指定同一个Lightmap Parameters asset,去为对象物体进行不同的分类。例如,对象物体离可玩区域是否很接近可玩区域或者离摄像机很近。在这种情况下,为整个对象物体提高分辨率可能会对光照结果产生显著的提升。对象物体是否离的比较远或者看起来是否比较模糊?这里你不太可能使用相同的分辨率由于视觉上不会看到明显好的效果。相似地,如果对象物体的材质在反射率方面没有发生大量的变化,提高分辨率可能不会产生更好的效果。

当指定Lightmap Parameters assets时过于细致是没有必要的。对于大多数场景,少数不同的变化是足够了的。许多细微的变化很难去调节,就像多次应用不同的Lightmap Parameters assets。

在我们完成了的教学场景LightingTutorialOptimal中,我们已经基于对象物体的复杂度以及所需的分辨率将场景划分为以下的归类:

* RockLow: 光滑外凸类型的石头,它们的mesh距离Camera比较远,所以只需要较低的分辨率并且减少它们 Cluster Resolution的值

* RockMedium: 光滑外凸类型的石头, 它们的mesh离可玩区域和Camera比较近,因此它们更容易被看到,由于这个原因我们将分辨率以及 Cluster Resolution的值稍微调高,同样将 Irradiance Budget 以及IrradianceQuality 的值调高。

* StructuresHigh: 适用于高分辨率以及 Cluster Resolution的重要建筑物,因此它们需要的 Irradiance Quality 和Irradiance budget的值也会更高。

* StructuresLow: 离Camera比较远的建筑物,只需要较低分辨率的值

* TerrainLow: 大型,距离比较远的mesh比如山峰,不需要高分辨率。

* TerrainPlayable: 离摄像机比较近的大型地形对象物体,需要一个 lightmap resolution 来平衡,以便Camera更近的检查,降低大地形的 Cluster Resolutions 也较为合理。

* TerrainVeryLow: 距离非常远并且部分比较模糊的地形对象物体只需要较低的分辨率以及 Cluster Resolution的值。它们的 Irradiance Quality和 Irradiance Budget的设置也同样如此。

除了使用这些自定义的Lightmap Parameters assets,在已经完成的教程场景中LightingTutorialOptimal,我们也有一些使用默认参数的结构,这些默认参数在Lighting窗口中的Scene标签页中。在这里我们使用Unity标准的值,将Lightmap Parameters设置为Default-Medium。

为场景中大多数的静态对象物体使用默认的参数,减少了需要手动指定操作的数量。为大多数对象物体将要使用的设置指定默认的参数是很有用的。为了我们的目的,Default-Medium设置结合每个单位1个纹素的实时分辨率为我们场景中的大多数物体得到了优化的结果。

当决定指定并且配置你的光照的时候将LightingTutorialOptimal场景作为指南。为场景中的剩余静态对象物体指定Lightmap Parameters应该不会花费太长的时间。记住当你为这些剩余的对象物体进行设置时,要明确将设置提高是否对结果产生实际的提升效果。在不会对视觉输出效果产生负面影响的情况下,降低设置将会是一种很好的选择。

Unity Standard shader 里面 全局光照Global Illumination(GI)

Unity Standard shader 里面 全局光照Global Illumination(GI)

Standard 粗略的来看,其实分为两个部分,一个是真正的BRDF,第二部分是UnityGI。

全局光照是在局部光照的基础上,增加考虑物体与物体之间光线交互。所以说如果局部光照系统就是由光源+待渲染物体+视点组成的话,那么全局光照系统就是由光源+各待渲染物体之间的反射光+待渲染物体+视点组成。

另外如果没有全局光照技术,这些自发光的表面并不会真的着凉周围的物体,而是它本身看起来更亮了而已。

##Unity GI

half4 fragForwardBaseInternal (VertexOutputForwardBase i)

    FRAGMENT_SETUP(s)

    UNITY_SETUP_INSTANCE_ID(i);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

    UnityLight mainLight = MainLight ();
    UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);

    half occlusion = Occlusion(i.tex.xy);
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);

    half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
    c.rgb += Emission(i.tex.xy);

    UNITY_APPLY_FOG(i.fogCoord, c.rgb);
    return OutputForward (c, s.alpha);

在UnityStandardCore.cginc里面,简单的概括 color = FragmentGI + UNITY_BRDF_PBS +Emission
GI主要在UnityGI,另外还有在UNITY_BRDF_PBS 引用到。

FragmentGI 函数 计算global illumination,返回 UnityGI

先看下 UnityGI,这个是个结构体。
在 UnityLightingCommon.cginc里面有定义

struct UnityGI

    UnityLight light;
    UnityIndirect indirect;
;

在UnityStandardCore.cginc 里面有四处定义了FragmentGI函数,最后还是下面的代码处理返回UnityGI

inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)

    UnityGIInput d;
    d.light = light;
    d.worldPos = s.posWorld;
    d.worldViewDir = -s.eyeVec;
    d.atten = atten;
    #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
        d.ambient = 0;
        d.lightmapUV = i_ambientOrLightmapUV;
    #else
        d.ambient = i_ambientOrLightmapUV.rgb;
        d.lightmapUV = 0;
    #endif

    d.probeHDR[0] = unity_SpecCube0_HDR;
    d.probeHDR[1] = unity_SpecCube1_HDR;
    #if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
      d.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
    #endif
    #ifdef UNITY_SPECCUBE_BOX_PROJECTION
      d.boxMax[0] = unity_SpecCube0_BoxMax;
      d.probePosition[0] = unity_SpecCube0_ProbePosition;
      d.boxMax[1] = unity_SpecCube1_BoxMax;
      d.boxMin[1] = unity_SpecCube1_BoxMin;
      d.probePosition[1] = unity_SpecCube1_ProbePosition;
    #endif

    if(reflections)
    
        Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.smoothness, -s.eyeVec, s.normalWorld, s.specColor);
        // Replace the reflUVW if it has been compute in Vertex shader. Note: the compiler will optimize the calcul in UnityGlossyEnvironmentSetup itself
        #if UNITY_STANDARD_SIMPLE
            g.reflUVW = s.reflUVW;
        #endif

        return UnityGlobalIllumination (d, occlusion, s.normalWorld, g);
    
    else
    
        return UnityGlobalIllumination (d, occlusion, s.normalWorld);
    


FragmentGI函数处理根据UnityGIInput,其中UnityGIInput也是结构体。开始时候对UnityGIInput进行赋值。即是灯光+世界空间顶点坐标+观察方向(视线的反方向)+衰减直接赋值即可。随后是光照贴图,在启用了静态光照贴图或者动态光照贴图的情况下,环境光为0,然后获得光照贴图的UV。否则的话,ambient直接使用VertexGIForward计算的rgb值。
后面是对反射探针的计算。
对是否反射调用合适 UnityGlobalIllumination函数。

struct UnityGIInput

    UnityLight light; // pixel light, sent from the engine

    float3 worldPos;
    half3 worldViewDir;
    half atten;
    half3 ambient;

    // interpolated lightmap UVs are passed as full float precision data to fragment shaders
    // so lightmapUV (which is used as a tmp inside of lightmap fragment shaders) should
    // also be full float precision to avoid data loss before sampling a texture.
    float4 lightmapUV; // .xy = static lightmap UV, .zw = dynamic lightmap UV

    #if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
    float4 boxMin[2];
    #endif
    #ifdef UNITY_SPECCUBE_BOX_PROJECTION
    float4 boxMax[2];
    float4 probePosition[2];
    #endif
    // HDR cubemap properties, use to decompress HDR texture
    float4 probeHDR[2];
;

UnityGlobalIllumination 真正计算GlobalIllumination的函数

在UnityGlobalIllumination.cginc文件里面有四个UnityGlobalIllumination函数,主要是下面的代码。

inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)

    UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld);
    o_gi.indirect.specular = UnityGI_IndirectSpecular(data, occlusion, glossIn);
    return o_gi;

UnityGI_Base函数

inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)

    UnityGI o_gi;
    ResetUnityGI(o_gi);

    // Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
    #if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
        half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos);
        float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz);
        float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist);
        data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist));
    #endif

    o_gi.light = data.light;
    o_gi.light.color *= data.atten;

    #if UNITY_SHOULD_SAMPLE_SH
        o_gi.indirect.diffuse = ShadeSHPerPixel (normalWorld, data.ambient, data.worldPos);
    #endif

    #if defined(LIGHTMAP_ON)
        // Baked lightmaps
        half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
        half3 bakedColor = DecodeLightmap(bakedColorTex);

        #ifdef DIRLIGHTMAP_COMBINED
            fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
            o_gi.indirect.diffuse = DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);

            #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
                ResetUnityLight(o_gi.light);
                o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
            #endif

        #else // not directional lightmap
            o_gi.indirect.diffuse = bakedColor;

            #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
                ResetUnityLight(o_gi.light);
                o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
            #endif

        #endif
    #endif

    #ifdef DYNAMICLIGHTMAP_ON
        // Dynamic lightmaps
        fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
        half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);

        #ifdef DIRLIGHTMAP_COMBINED
            half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
            o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
        #else
            o_gi.indirect.diffuse += realtimeColor;
        #endif
    #endif

    o_gi.indirect.diffuse *= occlusion;
    return o_gi;

ShadowMask阴影的衰减(烘焙阴影和实时阴影混合),SH的计算,烘焙的lightmap,平行光与非平行光lightmap,动态lightmap。最终返回UnityGI结构,该结构包含light,color,indirect.diffuse参数。
其中ShadowMask阴影遮罩是Unity5.6版本的新特性。

UnityGI_IndirectSpecular 函数 间接高光

inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)

    half3 specular;

    #ifdef UNITY_SPECCUBE_BOX_PROJECTION
        // we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into BoxProjectedCubemapDirection
        half3 originalReflUVW = glossIn.reflUVW;
        glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
    #endif

    #ifdef _GLOSSYREFLECTIONS_OFF
        specular = unity_IndirectSpecColor.rgb;
    #else
        half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
        #ifdef UNITY_SPECCUBE_BLENDING
            const float kBlendFactor = 0.99999;
            float blendLerp = data.boxMin[0].w;
            UNITY_BRANCH
            if (blendLerp < kBlendFactor)
            
                #ifdef UNITY_SPECCUBE_BOX_PROJECTION
                    glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
                #endif

                half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
                specular = lerp(env1, env0, blendLerp);
            
            else
            
                specular = env0;
            
        #else
            specular = env0;
        #endif
    #endif

    return specular * occlusion;

UnityGI_IndirectSpecular(UnityGLobalIllumination.cginc)计算间接高光,用probe相关的属性计算。
通过调用Unity_GlossyEnvironment采样Reflection Cube,计算HDR。
如果启用了Box Projection,则通过BoxProjectedCubemapDirection计算变换后的方向。

UNITY_BRDF_PBS 里面用到的GlobalIllumination

关于BRDF部分,来看下UnityStandardBRDF.cginc 里面的 BRDF1_Unity_PBS函数

    half3 color =   diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm (specColor, lh)
                    + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);

light的颜色和 diffuse,以及specular 是GlobalIllumination传进来的。

Image Based Lighting (IBL)

其中在材质上反应出周围的环境也是PBS的重要组成部分。在光照模型中一般把周围的环境当作一个大的光源来对待,不过环境光不同于实时光,而是作为间接光(indirect light)通过IBL( Image Based Lighting)来实现。这里也是优化Standard shader的一个比较重要的原因。

IBL一般通过环境光贴图(environment map)来实现。Unity用reflection probe来保存环境光贴图,通过内置变量unity_SpecCube0,unity_SpecCube1访问。
IBL就是采样两次,用粗糙度做插值。这个地方可以做些优化。

以上是关于Unity预计算全局实时GI(九) - Lightmap Parameters的主要内容,如果未能解决你的问题,请参考以下文章

Light Mode和Lighting Mode

Unity5中叹为观止的实时GI效果

关于Unity中的光照

Unity 5 中的全局光照技术详解 (转载)

Unity5-----------之GI设置简介

Unity预计算全局光照的学习(速度优化,LightProbe,LPPV)