Unity Shader入门精要读书笔记(9)
Unity的渲染路径
- 渲染路径决定了光照是如何应用在Unity Shader中的,我们需要为每个Pass指明他使用的渲染路径,只有为Shader正确的选择和设置了渲染路径,我们才能正确的计算光照。
- 大多数情况下,一个项目使用一种渲染路径,我们也可以在摄像机中设置来实现多种渲染路径。
- 如果当前显卡不支持所选渲染路径,那么Unity会自动使用更低一级的渲染路径。
- 渲染路径是我们和 Unity的底层渲染引擎的一次重要的沟通。
前向渲染路径
-
每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。
-
对于每个逐像素光源,我们都需要一次完整的渲染流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源的影响,那么要渲染整个场景一共需要N*M个 Pass。可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。
-
Unity中处理前向渲染路径中处理光照的3种方式:
(1)逐顶点处理
(2)逐像素处理
(3)球谐函数(SH)处理 -
光源的渲染模式指的是该光源是否是重要的。
-
在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity使用的判断规则如下:
(1)场景中最亮的平行光是按像素处理的。
(2)Not Important的光源是按照逐顶点或SH处理的。
(3)Important的光源是按照像素处理的
(4)如果根据以上规则得到的逐像素光源数量小于Quality Setting 中的逐像素光源数量(PixelLight Count),会有更多的光源以逐像素的方式进行渲染。
顶点照明渲染路径
- 顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时也是得到的效果最差的一种类型,它不支持那些逐像素才能得到的效果,例如阴影、法线映射、高精度的高光反射等。使用逐顶点的方式计算光照。
- 顶点照明渲染路径通常在一个 Pass 中就可以完成对物体的渲染。在这个 Pass中,我们会计算我们关心的所有光源对该物体的照明,并且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径,并且具有最广泛的硬件支持(但是游戏机上并不支持这种路径)。
- 在 Unity中,我们可以在一个顶点照明的Pass 中最多访问到8个逐顶点光源。如果影响该物体的光源数目小于8,那么数组中剩下的光源颜色会设置成黑色。
延迟渲染路径
- 前向渲染的问题是:当场景中包含大量实时光源时,前向渲染的性能会急速下降。
- 除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲区也被统称为G缓冲(G-buffer)。G缓冲区存储了我们所关心的表面(通常指的是离摄像机最近的表面)的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。
- 延迟渲染主要包含了两个Pass。在第一个Pass中,我们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是通过深度缓冲技术来实现,当发现一个片元是可见的,我们就把它的相关信息存储到G缓冲区中。然后,在第二个Pass 中,我们利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫反射系数等,进行真正的光照计算。
- 延迟渲染使用的Pass 数目通常就是两个,这跟场景中包含的光源数目是没有关系的。换句话说,延迟渲染的效率不依赖于场景的复杂度,而是和我们使用的屏幕空间的大小有关。这是因为,我们需要的信息都存储在缓冲区中,而这些缓冲区可以理解成是一张张2D图像,我们的计算实际上就是在这些图像空间中进行的。
- 对于延迟渲染路径来说,它最适合在场景中光源数目很多、如果使用前向渲染会造成性能瓶颈的情况下使用。而且,延迟渲染路径中的每个光源都可以按逐像素的方式处理。但是,延迟渲染也有一些缺点。
(1)不支持真正的抗锯齿功能
(2)不能处理半透明物体
(3)对显卡有一定要求MRT、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。
Unity的光源类型
- Unity一共支持4种光源类型:平行光、点光源、聚光灯和面光源。
(1)平行光:照亮的范围没有限制,通常作为太阳出现。没有一个唯一的位置,可以放在场景中的任意位置,几何属性只有方向,平行光到场景中的所有点的方向都是一样的,这也是平行光名字的由来。平行光没有一个具体的位置,因此也不会衰减。
(2)点光源:点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光。
球体的半径可以由面板中的Range属性来调整,也可以在Scene视图中直接拖拉点光源的线框(如球体上的黄色控制点)来修改它的属性。点光源是有位置属性的,它是由点光源的Transform组件中的Position属性定义的。对于方向属性,我们需要用点光源的位置减去某点的位置来得到它到该点的方向。而点光源的颜色和强度可以在 Light组件面板中调整。同时,点光源也是会衰减的,随着物体逐渐远离点光源,它接收到的光照强度也会逐渐减小。点光源球心处的光照强度最强,球体边界处的最弱,值为0。其中间的衰减值可以由一个函数定义。
(3)聚光灯:它的照亮空间同样是有限的,但不再是简单的球体,而是由空间中的一块锥形区域定义的。聚光灯可以用于表示由一个特定位置出发、向特定方向延伸的光。
这块锥形区域的半径由面板中的Range属性决定,而锥体的张开角度由Spot Angle属性决定。我们同样也可以在Scene视图中直接拖拉聚光灯的线框(如中间的黄色控制点以及四周的黄色控制点)来修改它的属性。聚光灯的位置同样是由Transform组件中的Position属性定义的。对于方向属性,我们需要用聚光灯的位置减去某点的位置来得到它到该点的方向。聚光灯的衰减也是随着物体逐渐远离点光源而逐渐减小,在锥形的顶点处光照强度最强,在锥形的边界处强度为0。其中间的衰减值可以由一个函数定义,这个函数相对于点光源衰减计算公式要更加复杂,因为我们需要判断一个点是否在锥体的范围内。 - 光源的5个属性:位置、方向、颜色、强度以及衰减。
Unity的光照衰减
- 使用纹理查找计算衰减的弊端:
(1)需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度。
(2)不直观,不方便,无法使用其他的数学公式进行计算 - 用于光照衰减的纹理:Unity在内部使用一张名为_LightTexture0 的纹理来计算光源衰减。需要注意的是,如果我们对该光源使用了 cookie,那么衰减查找纹理是_LightTextureBO.
- 为了对LightTexture0纹理采样得到给定点到该光源的衰减值,我们首先需要得到该点在光源空间中的位置,这是通过_LightMatrix0变换矩阵得到的。
Unity的阴影
- 当一个光源发射的一条光线遇到一个不透明物体时,这条光线就不可以再继续照亮其他物体(这里不考虑光线反射)。因此,这个物体就会向它旁边的物体投射阴影,那些阴影区域的产生是因为光线无法到达这些区域。
- 在实时渲染中,我们最常使用的是一种名为Shadow Map 的技术。这种技术理解起来非常简单,它会首先把摄像机的位置放在与光源重合的位置上,那么场景中该光源的阴影区域就是那些摄像机看不到的地方。
- 在前向渲染路径中,如果场景中最重要的平行光开启了阴影,Unity就会为该光源计算它的阴影映射纹理(shadowmap)。这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置(深度信息)。
- Unity选择使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是 LightMode标签被设置为ShadowCaster的 Pass。这个 Pass 的渲染目标不是帧缓存,而是阴影映射纹理(或深度纹理)。
(1)如果我们想要一个物体接收来自其他物体的阴影,就必须在Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照结果相乘来产生阴影效果。
(2)如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在 Unity中,这个过程是通过为该物体执行LightMode为 ShadowCaster的 Pass来实现的。如果使用了屏幕空间的投影映射技术,Unity还会使用这个Pass产生一张摄像机的深度纹理。