(七)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:中篇(光照探针和反射探针)

一、光照探针照明的细节

 1.1 光照探针照明概述

使用光照贴图可以大幅度提升场景渲染的真实程度,但缺点是光照贴图无法作用在非静态的物体上,所以看上去运动的物体和场景就显得恨不协调。为了解决这个问题,使用光照探针(probe lighting)技术模拟使用光照贴图的效果。光照探针大致原理:在某一光照探针的所在位置点上对光照信息进行采样,然后从该光照探针相邻的其他光照探针的位置上对光照信息进行采样,把这些采样得到的光照信息进行插值运算,便可算出这些光照探针之间某个位置的光照信息。在运行期这些插值的速度很快,可以达到实时渲染的要求。利用光照探针技术可以避免运动的物体的光照效果和整个使用静态光照贴图的场景不协调的感觉。

光照探针照明是一种能够快速而近似地模拟实际光照效果的技术。这种技术适用于类似游戏等需要实时渲染的应用场合,在这些应用中光照探针通常被应用在人物角色或者一些动态的物体上;还可以向使用了LOD技术的静态场景提供在渲染不同细节的场景时所需要的光照信息。光照探针技术在运行时的性能很高效,并且它用到的光照信息在运行之前快速地被预计算出来。

从实现技术角度来说,光照探针照明技术对照亮在3D空间中某一个指定点的光照信息在运行前的预计算阶段进行采样,然后把这些信息通过球谐函数进行编译打包存储。在游戏运行时,通过着色器程序可以把这些光照信息编码快速地重建出光照原始效果。Unity通过Light Probe组件实现光照探针照明技术的。

类似于光照贴图,光照探针也存储了场景中照明信息。不同在于光照贴图存储的是光线照射到场景物体表面的照明信息,而光探针则存储的是穿过场景中空白空间的光线信息。使用光照探针照明技术会有一些限制,例如要处理光的高频信息,球谐函数的阶数就要增大,而当提升阶数时所需要的性能耗费也会逐步提升。因此Unity在编码打包光照信息时用的函数都是低阶球谐函数,即会忽略光的一些高频信息。目前Unity使用了三阶球谐函数进行处理。

在3D空间中的一个位置点上,因为有且只使用一个球面表达式用于描述光照,所以光照探针照明技术不适合用于描述光线穿过一个很大的物体时的情况。在这种情况下光照会发生很大变动,从而无法精准地进行模拟。另一个限制就是,因为球谐函数是在一个球面上对光照信息进行编码,所以对于一个大型的有着平坦表面的物体,或者是一个有着凹面的物体,光照探针照明技术也是不适用的,如果想在一个大的物体上应用光照探针照明技术,则需要使用Light Probe Proxy Volume组件辅助实现。

光照探针可以用来存储进入探针所在点的所有照明信息,也可以只存储由场景内的其他物体表面传递过来的间接照明信息。在shader里使用方式也很灵活,即可以在顶点着色器中用来进行逐顶点光照计算,也可以在片元着色器中结合法线贴图进行逐片元光照计算,甚至可以在顶点着色器中对光探针提供的间接照明部分进行逐顶点光照计算,在片元着色器中结合法线贴图对直接照明部分进行逐像素的光照计算。

1.2 在场景中布置光照探针

光照探针组件不能直接挂接到一个游戏对象上,通常需要依赖光探针组(light probe group)组件挂接。光探针组组件可以挂在场景中任意一个游戏对象。当向场景中添加一个光探针组游戏对象时,场景中黄色小球表示的就是光探针。这些光探针可以在编辑器被编辑,也可以指定它们的位置、数量等。在场景中光探针要达到一定数量才能被正确烘焙。

最简单的光探针布局方式是将光探针排列成一个规则的3D网格样式,这样的设置方式简单高效,但是会消耗大量内存,因为每一个光探针本质上是一个球形的、记录了当前采样点周围环境的纹理图像。并且如果一片区域的照明信息都差不多,要么就没有必要使用大量光照探针。光探针一般用于照明效果突然改变的场合,如从一个较为明亮的区域进入一个较为阴暗的区域。

目前Unity3D引擎还不支持所有平面化的光探针组,即光探针不能平坦分布在一个水平面上,光探针之间在垂直方向上需要有高度差。

1.3 使用光照探针

光照探针在进行插值计算时,需要用空间中的一点来表示这个接受光线的网格位置,一般地都会使用网格包围盒的中心位置。当然也可以自定义一个接受光线的位置,方法是把场景中的一个游戏对象的Transform属性赋值给MeshRenderer组件中的Light Probe Anchor属性即可。如果一系列在外观上是连接在一起的,但实质上是相互独立的,由多个MeshRender一一对应的网格。用这些网格各自的包围盒中心点当做插值计算点时,光照效果就会在其接缝处断开,样子将显得很突兀,这种使用第三方游戏对象的Transform作为插值计算点的方式,就是解决这个问题最好方法。

1.4 光探针代理体

5.4版本后Unity增加了一个光探针代理体(light probe proxy volume,LPPV)的新功能。光探针代理体是一个“解决无法直接使用光探针技术去处理的大型动态的游戏对象的问题”的组件,如下图:

(七)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:中篇(光照探针和反射探针)_第1张图片

Bounding Box Mode属性用于设置光探针代理体所占据范围的边界,它有3个选项:

Automatic Local:默认属性,会在游戏对象自身的局部空间内计算代理体的边界,经过插值的光探针位置将在这个范围内产生。如果当前的光探针代理体组件所在的游戏对象没有同时绑定有Render子类组件,将会在这个代理体的作用范围自动生成一个默认包围盒以对应之。包围盒会涵盖当前的Renderer,以及选定了Use Proxy Volume选项的那些子游戏对象中的Renderer所占据的范围。

Automatic World:和Automatic Local的属性类似,当选用此项时,会基于世界空间计算代理体的边界。

Custom:此模式能让用户在编辑器界面上定义代理体的边界大小。此模式和Automatic Local模式类似,都是在游戏对象自身的局部空间中设置的。因此需要用户自行确保当前游戏对象及其带有Renderer组件,且启用了Use Proxy Volume选项的子游戏对象都被自定义的包围盒给包含住。

Resolution Mode属性用来指定光探针代理体中的光探针的分布密度,它有如下两个选项:

Automatic:默认选项,配合Density属性,指定了每单位长度中,在x、y、z轴上的光探针的数量计算。所以光探针具体的数量,在指定了Density属性之后,就取决于边界的大小。

Custom:自定义,用下拉菜单来设置x、y、z轴上每个光探针的个数。个数从1开始,以2的次方递增,最大到32。

要启用光探针代理组件,则需要一个挂上了Renderer子类组件,如MeshRender组件的游戏对象,并且光探针代理体组件最好和Renderer子类组件挂接在同一个游戏对象上,如果不是挂接在同一个游戏对象上,就需要在Renderer组件所在游戏对象的Inspector面板上显示地指定引用了哪个光探针代理体,如下图所示:

(七)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:中篇(光照探针和反射探针)_第2张图片

二、 反射用光探针

当使用标准着色器时,每一个材质都会有一定程度的高光镜面反射,以及对周围环境的环境映射。在硬件性能无法使用实时光线追踪的情况下,需要预先计算环境的渲染结果缓存到一个立方体贴图中,然后在运行期根据当前的视点把贴图中的内容贴到待渲染物体的表面,以产生环境映射效果。

反射探针(reflection probe)动态地产生周围环境的贴图,以产生环境映射的效果,要创建一个反射用光探针,可以通过选择GameObject|Light|Reflection Probe命令,生成一个带有ReflectionProbe组件的游戏对象。

反射探针通过渲染立方体贴图来捕获环境景象,这意味着它会对场景进行6次渲染,立方体贴图的每个面渲染一次,默认情况下,其类型设置为烘焙模式,即将ReflectionProbe组件在Inspector面板上的Type属性设置为Baked。ReflectionProbe组件的Type属性有3个可选项:

1.Baked:在编辑阶段生成一个存储了光探针周围环境景象的立方体贴图。设置为Baked类型的反射探针只能对场景中标记为Reflection Probe Static的游戏对象进行取景烘焙,一旦烘焙完成后,立方体贴图就不会发生变化,所以不会受物体位置的实时变化的影响。还可以像摄像机一样,指定反射探针的Culling Mask属性和Clipping Planes属性将不需要烘焙到贴图的游戏对象剔除。

2.Realtime:在运行时生成并更新立方体贴图,此时对场景中游戏对象进行取景生成时就不仅限于静态的了。这种比较费性能,刷新光照探针以更新贴图内容也需要较长耗时,所以可以通过设置Refresh Mode属性和Time Slicing属性以控制刷新的时机和频率。这种模式下也可以通过指定反射用光探针的Culling Mask属性和Clipping planes属性将不需要烘焙的游戏对象剔除。

3.Custom:此模式类似于Baked,在编辑阶段生成一个存储光照探针周围环境景象的立方体贴图。但如果此时Dynamic Objects复选框被选中,则没有标记为Reflection Probe Static的游戏对象也能被烘焙到贴图中。另外,此模式下也可以直接指定一张立方体贴图作为渲染用的环境景象。

当一个游戏对象横跨了多个反射用光探针时,并且绑定在此游戏对象上的Mesh Renderer组件上的Reflection Probes属性项的值不为Off时,Mesh Renderer组件会自动把游戏对象所触及的所有反射探针添加到一个它维护的内部数组中:

(七)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:中篇(光照探针和反射探针)_第3张图片

Mesh Renderer组件的Reflection Probes选项有4中使用反射探针的方法:

Off:表示不使用反射探针。

Simple:表示只使用内部数组中Weight值最大的反射探针。

Blend Probes:将启用反射探针,且游戏对象所占空间如果和多个反射探针的作用域重叠,则混合只发生在光照探针之间。这种模式适用于室内环境。如果游戏对象附近没有反射探针,渲染器将使用天空盒作为默认反射,但默认反射和反射探针之间不会混合。

Blend Probes and Skybox:将启用反射探针,且游戏对象所占空间如果和多个反射探针的作用区域重叠,则混合能发生在光照探针之间或者光探针与天空盒之间,这种模式适用于室外环境。

Reflection组件有很多属性暴露出来可供用户调整:

(七)unity自带的着色器源码剖析之——————Unity3D的全局光照和阴影:中篇(光照探针和反射探针)_第4张图片

Importance:此值将影响一个Mesh Renderer组件存储在内部数组中的多个反射探针它们各自权重值(Weight属性)的混合比例。当一个游戏对象处在多个反射探针所框定的作用域时,首先会考虑每个反射探针的Importance属性值,然后在此基础上才会考虑每个反射探针与该游戏对象之间重叠区域的体积大小。也就是说,Importance属性值的优先级高于重叠区域体积的计算。

Box Size:用来框定该反射探针的作用范围立方体的长宽高值,进入此范围的带有MeshRenderer组件的游戏对象会自动和这个反射探针产生关联。当反射探针Box Projection开启时,此Box Size属性值会影响该反射探针的立方体贴图的贴图映射效果。

Box Offset:反射探针的作用范围立方体的中心点相对于其自身transform属性中的Position值的偏移量。当选中Box Projection时,Probe Origin属性值会影响该反射探针立方体贴图的贴图映射效果。

Box Projection:一般情况下,立方体贴图上对场景景象进行反射的内容,是假设无限远的地方都能反射过来并且形成图像的,并且被反射的物体与该反射探针的距离发生改变时,贴图中的反射内容不会发生改变。这个特性一般只适用室外场景,而室内场景则要启用Box projection选项。此选项在shader model3.0及以上可用。允许反射探针仅反射场景中有限距离内的物体,且这些物体与本光照探针的距离发生变化时,反射图案的内容也同样发生变化。

ShadowDistance:此参数决定了离光照探针多远的阴影的阴影将会被反射进立方体纹理贴图中,此值的特效和Quality Settings面板中的Shadow Distance属性一样,数值越小,能反射进贴图内容中的阴影就越少,如果设置为0则完全不反射阴影到贴图内容中。

为了反射出实际环境,反射探针首先要对天空盒的立方体贴图进行采样,这个立方体贴图就是unity_SpecCube0变量,除了这个变量,unity还提供了一个给反射探针使用的数据,该反射探针使用的立方体贴图是unity_SpecCube1变量,引擎可以对这两个反射探针的数据进行混合。

你可能感兴趣的:(游戏开发,unity内置着色器源码剖析,unity,Shader)