渲染路径决定了光照在 shader 中是如何应用的,所以在计算光源时,需要在每个 Pass 块内指定它的渲染路径,Unity 才会为我们提供正确的光照信息。

大多数情况下,一个项目只使用一种渲染路径,因此我们可以为整个项目设置渲染时的渲染路径。通过Unity的Edit->ProjectSetting->Playrt->OtherSetting->RenderingPath中选择项目所需要的渲染路径。

默认情况下,设置选择的是前向渲染路径,如图9.1所示

Unity Shader 笔记之Unity的渲染路径

如需使用多个渲染路径,如摄像机A渲染的物体使用前向渲染路径,摄像机B渲染的物体使用延迟渲染路径,我们可以在每个摄像机的渲染路径设置中设置该摄像机使用的渲染路径,以覆盖ProjectSettings中的设置,如下图9.2

Unity Shader 笔记之Unity的渲染路径

  1. Forward (前向渲染)
  2. Deferred (延迟渲染)
  3. Legacy Vertex Lit (遗留的顶点照明渲染)
  4. Legacy Deferred (遗留的延迟渲染)

下图是四种渲染路径的部分属性对比

我们需要在 shader 的每一个 Pass中指定它所使用的渲染路径,通过设置Pass的LightMode标签实现。

Unity Shader 笔记之Unity的渲染路径

如果当前显卡不支持所设置的渲染路径,则 Unity 会自动使用更低一级的渲染路径。

我们需要在 shader 的每一个 Pass中指定它所使用的渲染路径,通过设置Pass的LightMode标签实现。


Pass{
  Tags {"LightMode" = "ForwardBase"}
}

其中 ForwardBase 就是渲染路径其中一种,下表是部分 LightMode 标签支持的渲染路径设置选项

Unity Shader 笔记之Unity的渲染路径

只有正确地指定渲染路径,一些光照变量才会被正确地赋值,我们的计算结果才会正确。

前向渲染路径

1.原理

前向渲染路径是我们最常用的一种渲染路径。在进行一次完整的前向渲染时,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:颜色缓冲区和深度缓冲区。

利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。

Pass {
    for(each primitive in this model){
        if(failed in depth test){
            //如果没有通过深度测试,说明该片元是不可见的
            discard;
            }else{
                //如果该片元可见
                //就进行光照计算
                float4 color = Shadering(materialInfo, pos, normal, lightDir, viewDir);
                //更新帧缓冲
                writeFrameBuffer(fragment,color);
                }          
        }    
    }

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

2.Unity中的前向渲染

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

前向渲染路径有3种处理光照的方式:逐顶点处理、逐像素处理、球谐函数处理。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的。如果我们把一个光照的模式设置为Important,意味着我们告诉Unity“这个光源很重要,把它当成一个逐像素光源来处理”。我们可以在光源的Light组件中设置这些属性,如下图

在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity 使用的判断规则如下。

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

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

Unity Shader 笔记之Unity的渲染路径

在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。Unity 使用的判断规则如下。

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

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

Unity Shader 笔记之Unity的渲染路径

上图有几点需要说明的地方。

首先,可以发现在渲染设置中,我们除了设置了Pass 的标签外,还使用了#pragma multi_compile_fwdbase 这样的编译指令。虽然 #pragma multi_compile_fwdbase 和 #pragma multi_compile_fwdadd 在官方文档中还没有给出相关说明,但实验表明,只有分别为Base Pass 和 Addition Pass 使用这两个编译指令,我们才可以在相关的Pass 中得到一些正确的光照变量,例如光照衰减值等。

Base Pass 旁边的注释给出了Base Pass 中支持的一些光照特性。例如在Base Pass 中,我们可以访问光照纹理。

Base Pass 中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能),而Addtional Pass 中渲染的光源在默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的Shadow Type。但我们可以在Addtion Pass 中使用#pragma multi_compile_fwdadd_fullshadows 代替 #pragma multi_compile_fwdadd 编译指令,为点光源和聚光灯开启阴影效果,但这需要Unity 在内部使用更多的Shader 变种。

环境光和自发光也是在Base Pass 中计算的。这是因为,对于一个物体来说,环境光和自发光我们只希望计算一次即可,而如果我们在Addtional Pass 中计算这两种光照,就会造成多次叠加环境光和自发光,这不是我们想要的。

在Addtional Base 的渲染设置中,我们还开启和设置了混合模式。这是因为,我们希望每个Addtional Pass 可以与上次的光照结果在帧缓存中进行叠加,从而得到最终的有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Addtional Pass的渲染结果会覆盖掉之前的渲染结果,开起来就好像该物体只受该光源的影响。通常情况下,我们渲染的混合模式是Blend One One。

对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以定义多次,例如需要双面渲染等情况)以及一次Addtional Pass。一个Base Pass 仅会执行一次(定义了多个Base Pass的情况除外),而一个Addtional Pass 会根据影响该物体的其他逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Addtional Pass。

上图给出的光照计算通常情况下我们在每种Pass中进行的计算。实际上,渲染路径的设置用于告诉Unity 该Pass 在前向渲染路径中的位置,然后底层的渲染引擎会进行相关计算并填充一些内置变量(如_LightColor0等),如何使用这些内置变量进行计算完全取决于开发者的选择。例如,我们完全可以利用Unity 提供的内置变量在Base Pass 中只进行逐顶点光照;同样,我们也完全可以在Additional Pass 中按逐顶点的方式进行光照计算,不进行任何逐像素光照计算。

3.内置光照和函数

在Unity 5 中,对于前向渲染(即LightMode 为ForwardBase或forwardAdd)来说,下表给出了我们可以在Shader 中访问到的光照变量。

Unity Shader 笔记之Unity的渲染路径

我们在之前已经给出了一些可以用于前向渲染路径的函数,例如WorldSpaceLightDir、UnityWorldSpaceLightDir 和 ObjSpaceLightDir。为了完整性,我们在下表再次给出了前向渲染中使用的内置光照函数。 

Unity Shader 笔记之Unity的渲染路径

顶点照明渲染路径

1.Unity中的顶点照明渲染

顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时也是得到的效果最差的一种类型。它不支持那些逐像素才能得到的效果,如阴影,法线映射、高精度的高光反射等。

它是向前渲染路径的一个子集。所有可以在顶点照明渲染路径中实现的功能都可以在向前渲染路径中完成。

顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染。在这个Pass中,我们会计算我们关心的所有光源对该物体的照明,并且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径,并且具有最广泛的硬件支持。

2.可访问的内置变量和函数

在Unity中,一个顶点照明的Pass中最多访问到8个逐顶点光源。

如果需要渲染其中两个光源对物体的照明,可以仅使用表9.4中内置光照数据的前两个。

如果影响该物体的光源数目小于8,那么数组中剩下的光源颜色会设置成黑色。

表9.4 顶点照明渲染路径中可以使用的内置变量

Unity Shader 笔记之Unity的渲染路径

可以看到一些变量同样可以在向前渲染路径中使用,如unity_LightColor。但这些变量数组维度和数值在不同渲染路径中的值是不同的。

Unity Shader 笔记之Unity的渲染路径

 

 延迟渲染路径


1.延迟渲染原理

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

延迟渲染过程用伪代码描述如下:

Pass 1 {    
    // 第一个Pass不进行真正的光照计算    
    // 仅仅把光照计算需要的信息存储到G缓冲中
     for (each primitive in this model) {
        for (each fragment covered by this primitive) {
            if (failed in depth test) {
                // 如果没有通过深度测试,说明该片元是不可见的
                discard;
            } else {
                // 如果该片元可见
                // 就把需要的信息存储到G缓冲中
                writeGBuffer(materialInfo, pos, normal, lightDir, viewDir);
            }
        }
    }
 } 
 
 Pass 2 {
    // 利用G缓冲中的信息进行真正的光照计算
    for (each pixel in the screen) {
        if (the pixel is valid) {
            // 如果该像素是有效的
            // 读取它对应的G缓冲中的信息
            readGBuffer(pixel, materialInfo, pos, normal, lightDir, viewDir);
            // 根据读取到的信息进行光照计算
            float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
            // 更新帧缓冲
            writeFrameBuffer(pixel, color);
        }
    }
 }

 

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

2.Unity中的延迟渲染

Unity有两种延迟渲染路径,一种是遗留的延迟渲染路径,即Unity 5之前使用的延迟渲染路径,而另一种是Unity5.x中使用的延迟渲 染路径。如果游戏中使用了大量的实时光照,那么我们可能希望选择延迟渲染路径,但这种路径需要一定的硬件支持。

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

不支持真正的抗锯齿(anti-aliasing)功能。

不能处理半透明物体

对显卡有一定要求。如果要使用延迟渲染的话,显卡必须支持MRT(Multiple Render Targets)、Shader Mode 3.0及以上、深度渲 染纹理以及双面的模板缓冲。

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

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

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

默认的G缓冲区(注意,不同Unity版本的渲染纹理存储内容会有所不同)包含了以下几个渲染纹理(Render Texture,RT)。

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

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

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

RT3:格式是ARGB32(非HDR)或ARGBHalf(HDR),用于存储自发光+lightmap+反射探针(reflection probes)。

深度缓冲和模板缓冲。

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

可访问的内置变量和函数

Unity Shader 笔记之Unity的渲染路径

 

相关文章: