Unity的渲染路径
第八章的渲染队列是物体的分组,渲染的时候组与组按照先后顺序进行渲染
第九章的渲染路径指的是一种渲染的方法,渲染的时候需要几个Pass,分别对一个物体渲染几次
目前Unity中内置了4种渲染路径:前向渲染路径、延迟渲染路径、遗留的延迟渲染路径、顶点渲染路径
LightMode指定渲染路径的标签
之前的练习中一直使用的就是ForwardBase渲染路径,在里面进行过逐顶点、逐片元的光照计算,当时在介绍一些计算方法时,书中就一直强调,这样计算可行是因为当前场景只有一个平行光源,现在终于明白更具体的原因了
前向渲染
最传统、最常用的渲染路径
大致原理如下:
Pass
{
// 模型的每一个渲染图元
foreach(primitive in the model)
{
// 渲染图元的每一个片元
foreach(fragment in the primitive)
{
if(falled in depth test)
{
//未通过深度测试,丢弃片元
discard(fragment);
}
else
{
// 光照计算、更新帧缓冲
float4 color = Shading(materialInfo, pos, n, lightDir, viewDir);
writeFrameBuffer(fragment, color);
}
}
}
}
在光照计算部分可以看到有lightDir这个比较特殊的参数,因为它是与光源相关的参数,而其他参数都是模型自己的参数
因此当场景中有多个光源时,就需要针对每一个光源多次执行pass,再在帧缓冲中把光照结果进行混合来得到最终的颜色值
这也是渲染引擎会限制每个物体的逐像素光照数目的原因,它需要限制执行pass的数目,来提升获得最终渲染结果的速度
Unity中处理前向渲染光照的方式
逐顶点、逐像素、球谐函数(Spherical Harmonics)
逐顶点和逐像素已经很熟悉了,而球谐函数是第一次看到,简单查了一下,是个比较大的坑。简而言之,unity中的light probe以及一些不重要的实时光源,可以用球谐光照快速的计算。球谐光照的优点是运行时的计算量与光源的数量无关,如果参数足够却可以较好的模拟实时的光照结果(简单理解spherical harmonic lighting(球谐光照))
一个光源使用哪种方式进行处理取决于光源的类型和渲染模式
- 光照类型(Type):是否是平行光(Directional,计算lightDir的方式不同)
- 渲染模式(Render Mode):是否是重要的(Important,用
逐像素
的方式进行渲染)
对于一个光源如何处理的判断:
- 场景中最亮的平行光,按照逐像素光源处理
- 渲染模式为Important的光源,按照逐像素光源处理
- 根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量,会用其他光源补充进来
- 渲染模式为Not Impotent的光源,按照逐顶点或SH处理
ForwardBase和ForwardAdd的分工
这张图很好的解释了前向渲染的两种Pass,在ForwardBase中,几章下来只有阴影没有接触到了。对于这张图,有以下几点需要注意:
- Base只会执行一次,Add会根据光源的数量执行多次
可以从它们可实现的效果、光照计算看出来原因 - 记得加上编译命令
#pragma multi_complie_fwdbase
或#pragma multi_complie_fwdadd
添加它们后,相当于添加了一些变种shader开放使用 - 对于ForwardAdd,要记得开启混合,通常会使用Blend One One
ForwardAdd需要把对物体有影响的每一个光源都执行一次,再在帧缓冲中进行混合,达到多种光照的效果;否则,会直接覆盖掉前一个光照的颜色
补充:
Unity前向渲染Base Pass中逐像素渲染的光必定为平行光
若场景中没有平行光,就会默认渲染黑色
的平行光
前向渲染中Unity的内置光照变量和函数
出现了!光源空间和光源衰减因子!
养成对方向及时归一化的好习惯~
顶点照明渲染
对硬件配置要求最低、最广泛的硬件支持、运算性能最高,但同时得到的效果也是最差的
其实是前向渲染的一个子集,它只是用了逐顶点的方法来计算光照,因此所有逐像素才能实现的效果(如高精度高光反射、纹理映射、阴影等)都无法实现
虽然在Unity5发布后,顶点照明渲染路径被当成了一个“遗留的渲染路径”,在未来版本可能会被移除,但直到当前版本(2019.4.10),官方文档中依然可以看到它,并且没有标注为Legacy,应该还可以再撑一阵子。在本次学习中对顶点照明路径暂时只做一个简单的了解
顶点照明渲染中Unity的内置光照变量和函数
顶点照明渲染路径中,最多可以访问到8个逐顶点光源(不满8个其他的设为黑色),并且可以一次性将对物体有影响的所有光源逐顶点渲染完毕,这也是它的变量是half4[8]类型的原因
延迟渲染
前向渲染缺点:对多次执行同一Pass时会进行很多重复的计算,如法线、位置、albedo等
延迟渲染是一种比前向渲染更古老的方法,但由于前向渲染可能造成的瓶颈问题,近些年又流行起来了
G缓冲 G-Buffer
延迟渲染的额外缓冲区,G代表Geometry
它存储了我们所关心的表面(通常指离摄像机近的表面)的其他信息,如表面法线、位置、用于光照计算的材质属性(漫反射颜色、高光颜色、平滑度、自发光、环境光、深度、光照纹理)等
这些属性被渲染进Gbuffer纹理,GBuffer纹理又被设定为全局shader属性,供后续shader使用
大致原理
GBufferPass //不会进行真正的光照计算,而是将光照计算需要的数据存储进GBuffer
{
foreach(primitive in the model)
{
foreach(fragment covered by the primitive)
{
if(failed in depth test)
{
discard(fragment);
}
else
{
// 将通过深度测试的片元的相关信息存入GBuffer
// lightDir和viewDir的数量根据有多少光照对物体有影响而定
writeGBuffer(materialInfo, pos, n, lightDir, viewDir);
}
}
}
}
LightingPass // 利用GBuffer中的数据进行光照计算
{
foreach(pixel in the screen)
{
if(pixel valid)
{
// 获取GBuffer中的光照计算数据
readGBuffer(pixel, materialInfo, pos, n, lightDir, viewDir);
// 光照计算
float4 color = Shading(materialInfo, pos, n, lightDir, viewDir);
// 写入颜色缓冲区
writeFrameBuffer(pixel, color);
}
}
}
GBuffer解决了前向渲染中存在的重复计算的问题,也使得延迟渲染可以将每一个光源都按照逐像素光源处理
可以看出,延迟渲染一般只需要两个Pass,和场景中有多少光源无关:
GBufferPass计算光照所需的所有数据
LightingPass利用GBufferPass计算的数据进行逐像素光照计算
从LightingPass中,也可以看到延迟渲染和前向渲染的一个明显区别:
- 延迟渲染的LightingPass是对屏幕上的每一个像素进行光照计算(只对通过了深度测试的点进行计算,也因此无法处理半透明物体)
- 前向渲染是逐模型逐光源计算
延迟渲染中的光照计算效率和场景复杂度无关,和光源影响到的像素数量成正相关
也就是说不是光源的数量或者光源照亮的物体数量主导了对性能的影响,而是光源照亮的范围(像素)
延迟渲染的缺点
- 无法处理半透明物体
原因:Pass_1不关闭深度写入、Pass_2针对屏幕空间的像素进行光照计算 - 不支持真正的抗锯齿功能
原因:GBuffer的性能限制:为什么MSAA不能用于延迟渲染 - 不支持Mesh Renderer的Receive Shadows
原因:学完阴影来补充 - culling masks的支持非常有限(32个mask必须要设置28个只能用剩下的4个这是什么神坑?)
- 不支持正交投影,如果相机是正交投影,会自动使用前向渲染
- 对显卡有一定要求
- LightingPass默认只能使用Standard模型,否则需要自行替换Internal-DeferredShading.shader文件
Unity的GBuffer
默认的GBuffer包含的渲染纹理:
- RT0,ARGB32格式:_Diffuse(RGB)
- RT1,ARGB32格式:_Specular(RGB),_Gloss(A)
- RT2,ARGB2101010格式:normal(RGB)
- RT3,ARGB2101010(非HDR)或ARGBHalf(HDR)格式,自发光 + 光照贴图 + 反射探针(Reflection Probes)
- 深度缓冲 + 模板缓冲
Unity中延迟渲染可访问的变量
补充
若要计算阴影(光源勾选了阴影时),受到这些光源影响的物体可能被渲染多次(根据会产生阴影的光源数量),极大增加计算量