在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。因此,我们只有为Shader正确地选择和设置了需要的渲染路径,该shader的光照计算才可以被正确执行。
unity中的渲染路径:
大多数情况下,一个项目只会使用一种渲染路径。
前向渲染路径是传统的渲染方式,也是我们最常用的一种渲染路径。
向前渲染路径原理:
在进行一次完整的向前渲染过程中,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:颜色缓冲区和深度缓冲区。利用深度缓冲来确定一个片元是否是可见的,如果可见就更新颜色缓冲区中的颜色值。
Pass {
for (each primitive in this model) {
for (each fragment covered by this primitive) {
if (failed in depth test) {
// 如果没有通过深度测试,说明该片元是不可见的
discard;
} else {
// 如果该片元可见
// 就迚行光照计算
float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
// 更新帧缓冲
writeFrameBuffer(fragment, color);
}
}
}
}
对于每个逐像素光源,我们都需要进行上面一次完整的渲染流程。
如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。
Unity中的向前渲染:
Unity中向前渲染路径的3种处理光照的方式:
逐顶点处理、逐像素处理和球谐函数(Spherical Harmonics,SH)处理。
而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。
光照类型:指该光源是平行光还是其它类型光源
光源的渲染模式:是否是Important
在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。其中,一定数目的光源会按逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源可以按SH方式处理。
向前渲染中的两种Pass ,在Pass中进行光照计算。
对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以定义多次,例如需要双面渲染等情况)以及一个Additional Pass。
一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外),而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。
顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时也是得到的效果最差的一种类型,它不支持那些逐像素才能得到的效果,例如阴影、法线映射、高精度的高光反射等。实际上,它仅仅是前向渲染路径的一个子集,也就是说,所有可以在顶点照明渲染路径中实现的功能都可以在前向渲染路径中完成。
如果选择使用顶点照明渲染路径,那么Unity会只填充那些逐顶点相关的光源变量,意味着我们不可以使用一些逐像素光照变量。
Unity中的顶点照明渲染:
顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染。在这个Pass中,我们会计算我们关心的所有光源对该物体的照明,并且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径,并且具有最广泛的硬件支持(但是游戏机上并不支持这种路径)。
可访问的内置变量和函数:
在Unity中,我们可以在一个顶点照明的Pass中最多访问到8个逐顶点光源。
顶点照明渲染路径中可以使用的内置变量
顶点照明渲染路径中可以使用的内置函数
延迟渲染是一种更古老的渲染方法,但由于上述前向渲染可能造成的瓶颈问题,近几年又流行起来。
除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,这些缓冲区也被统称为G缓冲(G-buffer),其中G是英文Geometry的缩写。G缓冲区存储了我们所关心的表面(通常指的是离摄像机最近的表面)的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。
延迟渲染的原理:
延迟渲染主要包含了两个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图像,我们的计算实际上就是在这些图像空间中进行的。
Unity中的延迟渲染:
Unity有两种延迟渲染路径,一种是遗留的延迟渲染路径,即Unity 5之前使用的延迟渲染路径,而另一种是Unity5.x中使用的延迟渲染路径。如果游戏中使用了大量的实时光照,那么我们可能希望选择延迟渲染路径,但这种路径需要一定的硬件支持。
对于延迟渲染路径来说,它最适合在场景中光源数目很多、如果使用前向渲染会造成性能瓶颈的情况下使用。而且,延迟渲染路径中的每个光源都可以按逐像素的方式处理。
延迟渲染的缺点:
Unity要求我们提供两个Pass。
(1)第一个Pass用于渲染G缓冲。在这个Pass中,我们会把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中。对于每个物体来说,这个Pass仅会执行一次。
(2)第二个Pass用于计算真正的光照模型。这个Pass会使用上一个Pass中渲染的数据来计算最终的光照颜色,再存储到帧缓冲中。
默认的G缓冲区(注意,不同Unity版本的渲染纹理存储内容会有所不同)包含了以下几个渲染纹理(Render Texture, RT)。
深度缓冲和模板缓冲。
当在第二个Pass中计算光照时,默认情况下仅可以使用Unity内置的Standard光照模型,如果我们想要使用其他的光照模型,就需要替换掉原有的Internal-DeferredShading.shader文件。
可访问的内置变量和函数:
这些变量都可以在UnityDeferred Library.cginc文件中找到它们的声明。
总体来说,我们需要根据游戏发布的目标平台来选择渲染路径。如果当前显卡不支持所选渲染路径,那么Unity会自动使用比其低一级的渲染路径。