《UnityShader 入门精要》更复杂的光照
代码&示例图见:zaizai77/Shader-Learn: 实现一些书里讲到的shader
到了这里就开启了书里的中级篇,之后会讲解 Unity 中的渲染路径,如何计算光照衰减和阴影,如何使用高级纹理和动画等一系列进阶内容
Unity 中的渲染路径
在Unity里,渲染路径(Rendering Path)决定了光照是如何应用到Unity Shader中的。我们需要为每个Pass指定它使用的渲染路径
在Unity 5.0版本之前,主要有3种:前向渲染路径(Forward Rendering Path)、延迟渲染路径(Deferred Rendering Path)和顶点照明渲染路径(Vertex Lit Rendering Path)。
设置Rendering path
摄像机组件的Rendering Path中的设置可以覆盖Project Settings中的设置
如果当前的显卡并不支持所选择的渲染路径,Unity会自动使用更低一级的渲染路径。例如,如果一个GPU不支持延迟渲染,那么Unity就会使用前向渲染。
Pass {
Tags { "LightMode" = "ForwardBase" }
前向渲染路径
前向渲染路径的原理
每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:一个是颜色缓冲区,一个是深度缓冲区。我们利用深度缓冲来决定一个片元是否可见,如果可见就更新颜色缓冲区中的颜色值。伪代码描述:
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计算一个逐像素光源的光照结果,然后在帧缓冲中把这些光照结果混合起来得到最终的颜色值。假设,场景中有N个物体,每个物体受M个光源的影响,那么要渲染整个场景一共需要N*M个Pass。可以看出,如果有大量逐像素光照,那么需要执行的Pass数目也会很大。因此,渲染引擎通常会限制每个物体的逐像素光照的数目。
Unity中的前向渲染
一个Pass不仅仅可以用来计算逐像素光照,它也可以用来计算逐顶点等其他光照。这取决于光照计算所处流水线阶段以及计算时使用的数学模型。当我们渲染一个物体时,Unity会计算哪些光源照亮了它,以及这些光源照亮该物体的方式。
在Unity中,前向渲染路径有3种处理光照(即照亮物体)的方式:逐顶点处理、逐像素处理,球谐函数(Spherical Harmonics, SH)处理。而决定一个光源使用哪种处理模式取决于它的类型和渲染模式。光源类型指的是该光源是平行光还是其他类型的光源,而光源的渲染模式指的是该光源是否是重要的(Important)。
在前向渲染中,当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(例如,距离该物体的远近、光源强度等)对这些光源进行一个重要度排序。
- 场景中最亮的平行光总是按逐像素处理的。
- 渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理。
- 渲染模式被设置成Important的光源,会按逐像素处理。
- 如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),会有更多的光源以逐像素的方式进行渲染。
光照会在Unity Shader的Pass中进行计算。前向渲染有两种Pass:
- Base Pass
- Additional Pass
- #pragma multi_compile_fwdbase和#pragma multi_compile_fwdadd,只有分别为Bass Pass和Additional Pass使用这两个编译指令,得到一些正确的光照变量,例如光照衰减值等
- Base Pass旁边的注释给出了Base Pass中支持的一些光照特性。例如在Base Pass中,我们可以访问光照纹理(lightmap)。
- Base Pass中渲染的平行光默认是支持阴影的(如果开启了光源的阴影功能),而Additional Pass中渲染的光源在默认情况下是没有阴影效果的,即便我们在它的Light组件中设置了有阴影的Shadow Type。但我们可以在Additional Pass中使用 #pragma multi_compile_fwdadd_fullshadows代替#pragma multi_compile_fwdadd编译指令,为点光源和聚光灯开启阴影效果,但这需要Unity在内部使用更多的Shader变种。
- 环境光和自发光也是在Base Pass中计算的。这是因为,对于一个物体来说,环境光和自发光我们只希望计算一次即可,而如果我们在Additional Pass中计算这两种光照,就会造成叠加多次环境光和自发光,这不是我们想要的。
- 在Additional Pass的渲染设置中,我们还开启和设置了混合模式。这是因为,我们希望每个Additional Pass可以与上一次的光照结果在帧缓存中进行叠加,从而得到最终的有多个光照的渲染效果。如果我们没有开启和设置混合模式,那么Additional Pass的渲染结果会覆盖掉之前的渲染结果,看起来就好像该物体只受该光源的影响。通常情况下,我们选择的混合模式是Blend One One。
- 对于前向渲染来说,一个Unity Shader通常会定义一个Base Pass(Base Pass也可以定义多次,例如需要双面渲染等情况)以及一个Additional Pass。一个Base Pass仅会执行一次(定义了多个Base Pass的情况除外),而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用,即每个逐像素光源会执行一次Additional Pass。
(要记得东西也太多了吧。。。。)
内置的光照变量和函数
顶点照明渲染路径
顶点照明渲染路径是对硬件配置要求最少、运算性能最高,但同时得到效果最差的一种类型。它仅支持逐顶点的光源计算,不支持逐像素,所以无法得到:阴影、法线映射、高精度的高光反射等效果,它是前向渲染的一个子集。
Unity 中的顶点照明渲染
顶点照明渲染路径通常在一个Pass中就可以完成对物体的渲染,在这个Pass中,我们会计算关心的光源对物体的照明,且是按照逐顶点处理的。
它是Unity中最快速的渲染路径,且具有最广泛的硬件支持(游戏机上不支持这种路径)。Unity 5以后被作为一个遗留的渲染路径,未来的版本中可能会被移除。
可访问的内置变量和函数
在Unity中一个顶点照明的Pass中最多可以访问8个逐顶点光源。
顶点照明渲染路径中可以使用的内置变量:
如果影响该物体的光源数目小于8,则数组中剩余的光源颜色会被设置成黑色。有些变量我们同样可以在前向渲染路径中使用,如:unity_LightColor,但这些变量的数组的维度和数值在不同渲染路径下的值是不同的。
顶点照明渲染路径中可以使用的内置函数:
延迟渲染路径
当场景中包含大量实时光源,使用前向渲染会造成性能急剧下降。每个光源都会执行一个Pass,重复地渲染一个物体会存在很多相同的重复计算。
在这样的环境下,本是一种更古老的渲染方法的延迟渲染,又流行了起来。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还使用了额外的缓冲区——G缓冲(Geometry Buffer,G-buffer)。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数目通常是2个,与场景中包含的光源数目没有关系,和屏幕的大小有关。
延迟渲染适合在光源数目众多、使用前向渲染造成性能瓶颈的情况下使用,每个光源都可以按逐像素处理。但它也有缺点:
- 不支持真正的抗锯齿功能;
- 不能处理半透明物体;
- 对显卡有要求,必须支持MRT(Multiple Render Targets)、Shader Mode 3.0及以上、深度渲染纹理以及双面的模板缓冲。
延迟渲染需要两个Pass:
- 第一个Pass用于渲染G缓冲
把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中对每个物体来说,这个Pass仅执行一次。
- 第二个Pass用于计算真正的光照模型
使用上一个Pass中渲染的数据来计算最终的颜色光照颜色,再存储到帧缓冲中。
这些变量都可以在UnityDeferred Library.cginc文件中找到它们的声明。
更加深入的东西就直接看官方文档吧,太深入了,写了也是白写,记不住
Unity - Manual: Introduction to rendering paths in the Built-In Render Pipeline
↑ 4种渲染路径(前向渲染路径、延迟渲染路径、遗留的延迟渲染路径和顶点照明渲染路径)的详细比较,包括它们的特性比较(是否支持逐像素光照、半透明物体、实时阴影等)、性能比较以及平台支持
Unity 的光源类型
之前的例子中用且仅用了一个平行光光源,之后在本节中,我们将会学习如何在Unity中处理点光源(point light)和聚光灯(spot light)。
Unity一共支持4种光源类型:平行光、点光源、聚光灯和面光源(area light)面光源仅在烘焙时才可发挥作用,所以这里不讨论
光源类型有什么影响
最常使用的光源属性有:光源的位置、方向(到某点的方向)、颜色、强度以及衰减(到某点的衰减,与点到光源的距离有关)。
平行光
它的几何定义是最简单的。平行光可以照亮的范围是没有限制的,它通常是作为太阳这样的角色在场景中出现的,它没有一个唯一的位置,也就是说,它可以放在场景中的任意位置,它的几何属性只有方向,光照强度不会随着距离而发生改变。
点光源
点光源的照亮空间则是有限的,它是由空间中的一个球体定义的。点光源可以表示由一个点发出的、向所有方向延伸的光