Unity 更复杂的光照

Unity 的渲染路径

在Unity里,渲染路径决定了光照是如何应用到Unity Shader中的。因此,如果要和光源打交道,我们需要为每一个Pass指定她使用的渲染路径,只有这样才能让Unity知道想要那种的渲染路径,我们吧光源和后处理的光照都放在这些数据里,就可以访问了。也就是说,我们只有为Shader正确地选择和设置需要了需要的渲染路径,该Shader的光照计算才能被正确的执行。

Unity支持多种类型的渲染路径主要有三种:向前渲染路径,延迟渲染路径,和顶点照明渲染路径。之后的版本中顶点渲染路径已经被Unity抛弃;

大多数情况下,一个项目只使用一种渲染路径,因此我们可以为整个项目设置渲染时的渲染路径。

但是有的时候,我们希望可以使用多个渲染路径,如A摄像机使用向前渲染路径,而B摄像机的使用延迟渲染路径,那么我们可以在每一个摄像机的渲染路径设置中设置该相机使用的渲染路径,用来覆盖Project Setting中的设置。

我们可以在每个Pass中使用标签来指定该Pass使用的渲染路径。这是通过设置Pass的LightMode标签实现的。不同类型的渲染路径可能会包含多种标签设置。

 

如果一个Pass没有指定任何渲染路径一些光照变量可能不会被正确赋值,我们计算出的效果也就很可能是错误的。

向前渲染路径

向前渲染路径是传统的渲染方式,也是我们最常用的一种渲染路径。首先会概括向前渲染路径的原理,然后再给出unity对于向前渲染路径的实现细节要求,最后给出Unity Shader中那些内置变量用于向前渲染路径的。

原理:

每进行依稀完整的向前渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲区来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。

对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程,如果一个物体在多个逐像素光源影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光源结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受到M个光源的影响,那么要渲染整个场景一共需要M*M个Pass。可以看出来如果有大量逐像素光照,那么需要执行pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照数目。

Unity中的向前渲染

事实上,一个Pass不仅仅可以用来计算逐像素光照,它也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当我们渲染一个物体时,Unity会计算那些光源照亮了它,以及这些光源照亮该物体的方式。

在Unity中,向前渲染路径有3种处理光照的方式:逐顶点处理,逐像素处理,球谐函数处理。而决定一个光源使用那种处理模式取决于他的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否重要的。

在向前渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体影响程度对这些光源进行一个重要度排序。其中一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。

Unity使用的判断规则如下。

  1. 场景中最亮的平行光总是按逐像素处理的。
  2. 渲染模式被设置成Not Important的光源,会被逐顶点或者SH处理。
  3. 渲染模式被设置成Important的光源,会被按逐像素处理。
  4. 如果根据以上规则得到逐像素光源数量小于Quality Setting中的逐像素光源数量,会有更多的光源以逐像素的方式进行渲染。

向前渲染有两种Pass:Base Pass和Additional Pass。通常来说,这两种Pass进行的标签和渲染设置以及常规的光照计算如图:

 

顶点照明渲染路径

顶点照明渲染路径是对硬件配置要求最少,运算性能最高,但同时也是得到的效果最差的一种类型,它不支持那些逐像素才能得到的效果,例如阴影,法线映射,高精度的高光反射等。实际上,她仅仅是向前渲染路径的一个子集,也就是说,所有可能在顶点照明渲染路径中实现的功能都可以在以前的光照路径中完成。就如他的名字一样,顶点照明路径渲染路径只是使用了逐顶点的方式来计算光照,并没有什么神奇的地方,实际上,我们在上面的前向渲染路径中可以计算一些逐顶点的光源。但如果选择使用顶点照明渲染路径,那么Unity会只填充那些逐顶点相关的光源变量,意味着我们不可以使用一些逐像素光照变量。

 

延迟渲染路径

前向渲染的问题是:当场景中包含大量时事光源时,前向渲染的性能会急速下降。延迟渲染是一种古老的渲染方法,由于向前渲染可能造成瓶颈问题,最近几年又流行起来。除了向前渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲区也被统称为G缓冲,其中G是英文Geometry的缩写。G缓冲区存储了我们所关心的表面的其他信息,例如表面法线,位置,用于光照计算的材质属性等。

延迟渲染的而原理

延迟渲染主要包含两个Pass。在第一个Pass中我们不进行任光照计算,而是仅仅计算那些片元是可见的,这主要四号通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把他的相关信息储存到G缓冲区中。然后在第二个Pass中,我们利用G缓冲区的哥哥片元信息,例如表面法线,视角方向,漫反射系数等,进行真正的光照计算。

可以看出,延迟渲染使用的Pass数目通常就是两个,这跟场景中包含的光源数目是没有关系的。换句话说,延迟渲染的效率不依赖场景的复杂度,而是和我们使用的屏幕空间的大小有关。这是因为,我们需要的信息存储在缓冲区中,而这些缓冲区可以理解成一张张2D图像,我们的计算实际上就是这些图像空间中进行的。

Unity中的延迟渲染

对于延迟渲染路径来说,他最适合在场景中光源数目很多,如果使用前向渲染造成性能瓶颈的情况下使用。而且,延迟渲染路径中的每一个光源都可以按逐像素的方式。但是,延迟渲染也有一些缺点。

  1. 不支持真正的抗锯齿功能
  2. 不能处理半透明物体
  3. 对显卡有一定的要求

当使用延迟渲染时,Unity需要提供两个Pass。

第一个Pass用于渲染G缓冲。在这个Pass中,我们会吧物体的漫反射颜色,高光反射颜色,平滑度,法线,自发光和深度等信息渲染到屏幕空间的G缓冲区。对于每个物体来说,这个Pass仅仅执行一次。

第二个Pass用于计算真正的光照模型。这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再储存到帧缓冲中。

默认的G缓冲区包含以下几个渲染纹理。

RT0:格式手机ARGB32,RGB通道用于存储漫反射颜色,A通道没有被使用。

RT1:格式是ARGB32,RGB通道用于存储高光反射颜色,A通道用于存储高光反射的指数部分。

RT2:格式是ARGB2101010,RGB通道用于存储法线,A通道没有被使用。

RT3:格式是ARGB32,用于存储发光+lightmap+反射探针。

深度缓冲和模板缓冲。

当在第二个Pass中计算光照时,默认情况下仅可以使用Unity内置的Standard光照模型。如果我们想要使用其他的光照模型,就需要换掉原有的Internal-DeferredShading.shader文件。

可访问的内置变量和函数

Unity的光源类型

Unity中一共支持4种光源:平行光,点光源,聚光灯,面积光。面积光仅在烘焙的时候才会发挥作用。

我们来看一下光源的类型不同会给Shader带来那些影响,我们可以考虑shader中使用了光源的那些属性。最长使用的光源属性有光源的位置,方向,颜色,强度以及衰减这5个属性。而这些属性和他们的几何定义息息相关。

平行光

对于我们使用的平行光来说,他的几何意义是最简单的。平行光可以照亮的范围是没有限制的,他通常是作为太阳的那样的角色出现在场景中的。

平行光简单是是因为他没有一个唯一的位置,也就是说,他可以放在场景中的任意位置。他的几何属性只有方向,我们可以调整平行光的Transform组件中的Rorarion属性来改变他的光源方向,而且平行光到场景中的所有的方向都是一样的。由于平行光没有一个具体的位置,因此也没有衰减的概念,也就是说光照强度不会随着距离而改变。

点光源

点光源的照亮空间是有限的,他是由空间中的一个球体定义的。点光源可以表示由一个点发出的,向所有方向延伸的光。

球体的半径可以由面板中的Range属性来调整,也可以在Scene试图中直接拖拉点光源的线框来修改她的属性。点光源是有位置属性的,他是由点光源的Transform组件中的Position属性定义的。对于方向属性,我们需要用点光源的位置减去某点的位置来得到的他到该点的方向。而点光源的颜色强度可以在Lihgt组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离光源,他接受到光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。中间的衰减值可以由一个函数定义。

聚光灯

聚光灯是这三种光源总最复杂的一种。她的照亮空间同样是有限的,而不再是简单的球体,而是由空间中的一块锥形区域定义的。聚光灯可以用于表示由一个特定位置出发,特定方向延伸的光。

这块锥形区域的半径U由面板中的Range属性决定,而锥形的张开角度由Spot Angle熟悉决定。我们同样也可以在Scene试图中直接拖拉聚光灯的线框来修改他的属性。聚光灯等位置同样是由Transform组件中的Position属性定义的。对于方向属性,我们需要用聚光灯的位置去减去某点的位置来得到该点的方向。聚光灯的衰减也是随着物体逐渐远离点光源而逐渐减小,在锥形顶点出光照强度最强,在锥形边界强度为0。其中间的衰减值可以由一个函数定义,这个函数相对于点光源衰减计算公式要更加复杂,因为我们需要判断一个点是否在椎体的范围内。

Unity的光照衰减

我们提到Unity使用一张纹理作为查找表来在片元着色器中计算逐像素光照的衰减,这样的好处在于,计算衰减不依赖于数学公式的复杂性,我们只要使用一个参数值去纹理中采样即可。但是使用纹理查找来计算衰减也有一些弊端。

需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度。

不直观,同时也不方便,因此一旦把数据存储到查找表中,我们就无法使用其他数学公式来计算衰减。

但由于这种方法可以在一定程度上提升性能,而且得到的效果在大部分情况下都是良好的,因此Unity默认就是使用这种纹理查找的方式来计算逐像素的点光源和聚光灯的衰减的。

使用数学公式计算衰减

尽管纹理采样的方法可以减少计算衰减的复杂度,但有时我们希望可以在代码中利用公式来计算光照衰减。

可惜的是,Unity没有在文档中给出内置衰减计算的相关说明。尽管我们依然可以在片元着色器中利用一些数学公式来计算衰减,但由于我们无法在Shader中通过内置变量得到光源的范围,聚光灯的朝向,张开角度等信息,因此得到的效果往往在有些时候不尽人意,尤其在物体离开光源的照明范围时突变。当然我们可以利用脚本将光源的相关信息传递给Shader,但这样的灵活性很低。我们只能期待未来了。

Unity的阴影

阴影是如何实现的?当一个光源的光线遇到不透明物体时,这条光线就不能在照亮其他的物体,因此这个物体就会向他旁边的物体投射阴影。

在实时渲染中,我们最常使用的一种名为Shadow Map的技术。这种技术理解起来非常简单,他会首先把摄像机的位置放在与光源重合的位置上,那么场景中该光源的阴影区域就是那些相机看不到的地方。Unity就是使用这种技术。

一个物体接受来自其他物体的阴影,以及它向其他物体投射阴影是两个过程。

如果我们想要一个物体接受来自其他物体的阴影,就必须在Shader中对阴影映射纹理进行采样,把采样结果和最后的光照结果相乘来产生阴影效果。

如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在Unity中,这个过程是通过该物体执行LightMode为ShadoeCaster的pass来实现的。如果使用了屏幕空间的投影映射技术,Unity还会使用这个Pass产生一张摄像机的深度纹理。

猜你喜欢

转载自blog.csdn.net/f402455894/article/details/123108393
今日推荐