本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:
- 书本中句子照抄 + 个人批注
- 项目源码
- 一堆新手会犯的错误
- 潜在的太监断更,有始无终
我的GitHub仓库
总之适用于同样开始学习Shader的同学们进行有取舍的参考。
文章目录
- 更复杂的光照
- Unity的渲染路径
- 前向渲染路径
- Unity中的前向渲染
- 顶点照明渲染路径
- 延迟渲染路径
- 延迟渲染原理
- Untiy中的延迟渲染
- 可访问的内置变量和函数
更复杂的光照
在之前的章节中,我们学习了关于基础光照计算的Shader,之前的场景中往往只有一个光源且光源类型是平行光。但在实际开发过程中,我们往往需要处理更多数目、类型更复杂的光源。本章将会学习实现更复杂的光照,并且还可以得到阴影。
在学习这些之前,我们有必要知道Unity是如何处理这些光源的。当我们在场景里放置了各种类型的光源后,Unity的底层渲染引擎是如何让我们在Shader中访问它们的?
Unity的渲染路径
在Unity中,渲染路径(Rendering Path) 决定了光照是如何应用到UnityShader中的。因此,如果要和光源打交道,则需要为每个Pass指定它们的渲染路径。
新版后的Rendering Path在Open Editor里找到
Unity支持多种渲染路径,在2018中可以看到支持一下四种:
分别为前向渲染路径(Forward Rendering Path)、延迟渲染路径(Deferred Rendering Path)和顶点照明渲染路径(Vertex Lit Rendering Path) (其中Legacy Deferred指的是旧版中遗留的延迟渲染路径,用于兼容旧版程序的,顶点照明渲染路径的UntiyShader已经被抛弃,但仍然兼容)
大多数情况下,一个项目只用一种渲染路径,因此我们可以为整个项目设置渲染时的渲染路径。但有时我们也希望使用多种渲染路径,通常我们会在摄像机设置中对渲染路径进行设置:
使用setting代表使用项目默认设置的渲染路径。如果当前显卡不支持该渲染路径,则会自动启用更低一级的渲染路径,例如不支持延迟渲染时会自动启用前向渲染。
完成了渲染路径的设置之后,我们就能在Pass中使用标签来指定Pass使用的渲染路径了。不同的渲染路径可能会包含多种标签设置,例如之前shader中的标签:
Tags{"LightMode" = "ForwardBase"}
标签指明了使用前向渲染路径中的ForwardBase路径,而前线渲染还有一种路径叫做ForwardAdd ,如下表所示:
虽然现在看不懂这些标签的含义,只需明白使用渲染路径标签设置,我们为Pass指定了渲染路径,如果没有指定,则可能一些光照变量不会被正确赋值,可能计算出错误的效果。(例如之前说过ForwardBase会指定_LightColor0,但如果光源较多则可能出错)
前向渲染路径
前向渲染路径是最常用的一种渲染路径。每进行依次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息,一个是颜色缓冲区,一个是深度缓冲区。
深度缓冲判断一个图元是否可见(深度测试),如果可见则更新颜色缓冲区的颜色值。下列伪代码描述了前向渲染的大致过程:
我们会逐图元,逐片元,在每个片元着色器中逐像素(或者逐顶点)地进行光照计算。对于每个逐像素光照都需要进行一次上述的完整渲染过程,如果一个物体在多个逐像素光源影响的范围内,就需要执行多个逐像素Pass,然后在帧缓冲中将光照结果混合起来得到最终的颜色值。
如果场景中又N个物体,而每个物体受M个光源影响,那么场景渲染就需要N*M个Pass。为了避免过多的渲染Pass,渲染引擎通常会限制每个物体的逐像素光照的数目。
Unity中的前向渲染
一个pass不仅仅可以用来计算逐像素光照,也可以计算逐顶点光照。这取决于光照计算的流水线阶段以及计算时使用的数学模型。
当渲染物体时,unity会计算哪些光源照亮了它以及这些光源照亮物体的方式。
在Unity中,前向渲染路径有3种处理渲染方式:逐顶点处理,逐像素处理,球谐函数(Spherical Harmonics ,SH)处理 。
决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指光源是平行光还是点光源等其他类型的光源,而光源的渲染模式需要指定是否为重要的光源。如果是Important的光源,那么Unity就会对这个光源渲染Pass进行逐像素处理。越重要的当然需要越精细。
在前向渲染时,当我们渲染也给物体,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如距离物体的远近,光源强度等)来对光源进行重要度排序,越重要的渲染越精细。
根据排序,一定数量的光源会按照逐像素方式处理,最多4个光源按照逐顶点方式处理,其余的使用SH处理。Unity的判断规则如下:
- 场景中最亮的平行光总是逐像素的
- 渲染模式是Not Important的光源,会逐顶点或者SH处理
- 渲染模式是Important的光源会逐像素处理
- 如果根据以上规则得到的逐像素处理的光源数量小于Quality Setting中的逐像素光源数量,那么会有更多的光源以逐像素进行渲染
光照计算都是在Pass中进行的。在前向渲染中又Base Pass 和Additional Pass两种。这两种Pass进行的标签和渲染设置如图所示:
- 上图中提到的例如**#pragma multi_compile_fwdbase** 和 #pragma multi_compile_fwdbase 这样的预编译指令,可以为Pass生成对应的Shader variant(变体,变种),用于处理不同条件下的逻辑渲染,例如是否使用LightMap,使用哪种光源类型等。只有设置正确的预编译指令,我们才能在相关Pass中得到一些正确的光照变量。
- 对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(当然也可以定义多次,例如需要双面渲染时)以及一个Additional Pass。Base Pass通常用于计算一个最重要的逐像素光源,该Pass只执行一次,而Additional Pass会计算多种光源的逐像素光照,每个光源都会执行一次该Pass。
- Base Pass中渲染的平行光默认支持阴影,而Additional Pass中渲染的光源在默认情况下是没有阴影效果的,即使我们在它的Light组件中设置了Shadow Type。我们可以使用
#pragma multi_compile_fwdadd_fullshadows
来替代,从而为点光源和聚光灯开启阴影效果,但需要在Unity内部使用更多的shader variants - 环境光和自发光也是在BasePass中计算的,因为对于一个物体来说我们只希望计算一次环境光和自发光,而如果我们想要在Additional Pass中计算这两种光照就会造成叠加多次环境光和自发光。
- 在Additional Pass的渲染设置中,我们还开启和设置了混合模式,因为Additional Pass会计算多次,我们希望每个Pass可以在帧缓存中与上一次的光照结果进行叠加,从而得到最终有多个光照叠加的混合效果。如果不开启Blend就会导致缓存被覆盖,通常情况下我们选择混合模式Blend One One 。
顶点照明渲染路径
顶点照明渲染路径使用逐顶点的方式来计算光照,对硬件配置要求最少,运算性能最高,得到的效果最差。且不支持逐像素渲染才能得到的效果,例如阴影、法线映射、高精度的高光反射等。
实际上,顶点照明渲染路径仅仅只是前向渲染路径的一个子集。也就是说,所有可以在顶点照明渲染路径中实现的功能都可以在前向渲染路径中完成。且顶点照明渲染路径只能使用逐顶点相关的变量,不可使用逐像素照明变量。
所以顶点照明渲染完全可以被逐像素照明渲染代替。虽然文中花了一些笔墨来介绍,但是暂且先不深入了解了。
延迟渲染路径
前向渲染的问题是:当场景中包含大量实时光照时,前向渲染的性能会急剧下降。例如某个区域放置了多个光源,而这些光源相互影响的区域又互相重叠,就会导致重叠区域中的每个物体的逐像素平行光照结果都要计算多次Pass。从而导致这个物体被重复渲染,很多计算实际上是多余的。
延迟渲染是一种古老的渲染方法,由于前向渲染可能造成的问题而被再次使用。除了前向渲染使用的颜色缓冲和深度缓冲之外,延迟渲染还会利用额外的缓冲区,这些缓冲区被称为G-buffer,(G即Germetry)。G缓冲区包含了几何体的表面的其他信息(通常是离摄像机最近的表面),例如该表面的法线、位置、用于光照计算的材质属性等。
延迟渲染原理
延迟渲染主要包含了两个Pass,第一个Pass中我们不进行任何光照计算,而是仅仅计算哪些片元是可见的,这主要是通过深度缓冲技术来实现的。若一个片元是可见的,我们就把它的相关信息存储在G缓冲区中。然后,在第二个Pass中,我们利用G缓冲区的各个片元信息,例如表面法线、视角方向、漫反射系数等进行真正的光照计算。
可以看出延迟渲染使用的Pass数量通常是两个,和场景中光源的数量无关。因此延迟渲染的效率不依赖于场景的复杂度,而和我们使用的屏幕空间的大小有关,因为所有的信息都存储在缓冲区中,缓冲区可以视为一张张的2D图形,所有计算都是在图像空间中进行的。
Untiy中的延迟渲染
在Untiy中有两种延迟渲染路径,新版的和遗留的。如果游戏中使用了大量的实时光照,那么我们可能希望选择延迟渲染路径,但这种路径需要一定的硬件支持。
旧版的延迟渲染路径不支持基于物理的Standrad Shader。延迟渲染的使用情况是:场景中光源较多、使用前向渲染会导致性能瓶颈的情况下(例如实时光照)下使用。但是延迟渲染也存在一些缺点:
- 不支持真正的抗锯齿
- 不能处理半透明物体
- 对显卡有一定要求,显卡必须支持MRT,Shader Model 3.0及以上,深度渲染纹理以及双面的模板缓冲
当使用延迟渲染时,Unity要求我们提供两个Pass:
- 第一个Pass用于渲染G缓冲,在这个Pass中,我们会将物体的漫反射颜色,高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中,对于每个物体来说,该Pass只会执行一次
- 第二个Pass用于计算真正的光照模型,这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。
如图所示是《彩六围攻》中使用的GBuffer缓冲区作为一个示例,RT指的是渲染目标(Render Targets) ,不仅仅是颜色值,甚至速度值,所有数据都能被存放到GBuffer中。
在Unity的GBuffer中,一般会包含以下几个渲染目标(Unity似乎只保存颜色值,因此被称为渲染纹理):
- RT0:格式ARGB32,RGB通道用于存储漫反射颜色Diffuse,A通道没有使用
- RT1:格式ARGB32,RGB通道存储高光反射颜色Specular。A通道用于存储高光反射的指数部分Gloss
- RT2:格式ARGB2101010,RGB通道用于存储法线,A通道没有使用
- RT3:格式ARGB32(非HDR)或ARGBHalf(HDR),用于存储自发光+lightMap +反射探针(reflection probes)
当在第二个Pass中计算光照时,默认仅可使用Unity内置的Standrad光照模型,若想要使用其他光照模型,就需要替换掉原有的Internal-DeferredShading.shader文件。(URP的延迟渲染好像还在开发中)
可访问的内置变量和函数
Unity官方文档中给出了四种渲染路径的比较。总的来说,我们需要根据游戏发布的目标平台来选择渲染路径。如果当前显卡不支持所选渲染路径,那么Unity会自动使用比其低一级的渲染路径。