Unity渲染路径详解1
渲染路径概述
渲染路径可以理解为应用层信息在着色器当中接收怎样的数据输入,经过怎样的处理,最后以怎样的方式呈现在二维平面上。
不同的渲染路径要求的信息不同,计算方式不用,计算量不同,消耗便不同。针对不同的平台,不同的硬件环境,应该选择适合的渲染路径。
在Unity中不同的渲染路径对应着不同光照信息的输入,不同光照的处理方式,不同的阴影生成方式,最后不同的呈现方式等等。
在Unity当中通过为Pass定义特殊Tag和特定的编译指令来控制使用的渲染路径,获得对应的光照信息。
前向渲染、正向渲染(Forward Rendering Path)
在Unity当中使用定义LightMode为ForwardBase和ForwardAdd 两种Tag的两个Pass来实现前向渲染:
Pass{
Tags{ "LightMode" = "ForwardBase" }
}
Pass{
Tags{ "LightMode" = "ForwardAdd" }
}
在场景当中,一个物体可能会受到多个光源的影响,如果对这些光源都采取同一种光照计算方式(比如逐像素光照),那么对应的消耗可能会很高,而且一些光源本身对物体的影响并不大,所以应该根据光源的特质分类进行计算,达到可以令人满意的效果。
在前向渲染当中,采用的分类策略为:
最亮的光源采取逐像素光照;而后最多4个光源采用逐顶点光照;其余的光源采用SH光照(球谐函数光照,计算速度更快,这里暂时不做详解)
那么根据什么来对一个光源进行分类呢?主要有以下要求:
1. Render Mode设置为Not Important的光源总是采用逐顶点光照或者SH光照
2. 最亮的directional light总是逐像素光照
3. Render Mode设置为Important的光源总是采用逐像素光照
4. 如果以上分类之后逐像素光照的光源还是小于Quality Setting当中的Pixel Light Count ,则更多的光源按照逐像素光照进行处理,按照亮度排序。
将所有的光源分类之后,要在哪里进行处理呢?同样也有要求:
ForwardBase Pass处理一个逐像素directional light和所有的逐顶点/SH光源。
ForwardAdd Pass处理其余的逐像素光照。
其余实现细节
Bass Pass中一般还会实现光照贴图、环境光以及自发光效果。在这个Pass当中的directional light可以实现阴影。另外使用光照贴图的物体不会受到SH光源的影响。
如果使用OnlyDirectional Tag,那么Bass Pass将只会处理directional light、光照贴图、环境光以及自发光效果,不会处理SH光源和逐顶点光源。
Addtional Pass对每一个逐像素光源都处理一次,在这个Pass当中默认处理光源是没有阴影的,除非定义multi_compile_fwdadd_fullshadows指令。
Unity 具体代码实现
我找到一份比较有参考价值的Unity Forward Rendering 的代码实现(Unity Stander Shader内部实现),这里给到工程的链接
我们只截取和前向渲染相关的部分:
Pass
{
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]
CGPROGRAM
#pragma target 3.0
// -------------------------------------
#pragma shader_feature _NORMALMAP
#pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
#pragma shader_feature _EMISSION
#pragma shader_feature_local _METALLICGLOSSMAP
#pragma shader_feature_local _DETAIL_MULX2
#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma shader_feature_local _SPECULARHIGHLIGHTS_OFF
#pragma shader_feature_local _GLOSSYREFLECTIONS_OFF
#pragma shader_feature_local _PARALLAXMAP
#pragma multi_compile_fwdbase
#pragma multi_compile_fog
#pragma multi_compile_instancing
// Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
//#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma vertex vertBase
#pragma fragment fragBase
#include "UnityStandardCoreForward.cginc"
ENDCG
}
// ------------------------------------------------------------------
// Additive forward pass (one light per pass)
Pass
{
Name "FORWARD_DELTA"
Tags { "LightMode" = "ForwardAdd" }
Blend [_SrcBlend] One
Fog { Color (0,0,0,0) } // in additive pass fog should be black
ZWrite Off
ZTest LEqual
CGPROGRAM
#pragma target 3.0
// -------------------------------------
#pragma shader_feature _NORMALMAP
#pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
#pragma shader_feature_local _METALLICGLOSSMAP
#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma shader_feature_local _SPECULARHIGHLIGHTS_OFF
#pragma shader_feature_local _DETAIL_MULX2
#pragma shader_feature_local _PARALLAXMAP
#pragma multi_compile_fwdadd_fullshadows
#pragma multi_compile_fog
// Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
//#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma vertex vertAdd
#pragma fragment fragAdd
#include "UnityStandardCoreForward.cginc"
ENDCG
}
// ------------------------------------------------------------------
// Shadow rendering pass
Pass {
Name "ShadowCaster"
Tags { "LightMode" = "ShadowCaster" }
ZWrite On ZTest LEqual
CGPROGRAM
#pragma target 3.0
// -------------------------------------
#pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
#pragma shader_feature_local _METALLICGLOSSMAP
#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
#pragma shader_feature_local _PARALLAXMAP
#pragma multi_compile_shadowcaster
#pragma multi_compile_instancing
// Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
//#pragma multi_compile _ LOD_FADE_CROSSFADE
#pragma vertex vertShadowCaster
#pragma fragment fragShadowCaster
#include "UnityStandardShadow.cginc"
ENDCG
}
可以看到各个Pass当中除了特性关键字定义之外,顶点着色器和片段着色器都是以文件引用的方式存在的,可以在Unity编辑器文件夹里的CGIncludes文件夹里面找到这些文件,这里我们只讨论Forward Base Pass和Forward Add Pass,它们引用的都是UnityStandardCoreForward.cginc这个文件,源码为:
#ifndef UNITY_STANDARD_CORE_FORWARD_INCLUDED
#define UNITY_STANDARD_CORE_FORWARD_INCLUDED
#if defined(UNITY_NO_FULL_STANDARD_SHADER)
# define UNITY_STANDARD_SIMPLE 1
#endif
#include "UnityStandardConfig.cginc"
#if UNITY_STANDARD_SIMPLE
#include "UnityStandardCoreForwardSimple.cginc"
VertexOutputBaseSimple vertBase (VertexInput v) { return vertForwardBaseSimple(v); }
VertexOutputForwardAddSimple vertAdd (VertexInput v) { return vertForwardAddSimple(v); }
half4 fragBase (VertexOutputBaseSimple i) : SV_Target { return fragForwardBaseSimpleInternal(i); }
half4 fragAdd (VertexOutputForwardAddSimple i) : SV_Target { return fragForwardAddSimpleInternal(i); }
#else
#include "UnityStandardCore.cginc"
VertexOutputForwardBase vertBase (VertexInput v) { return vertForwardBase(v); }
VertexOutputForwardAdd vertAdd (VertexInput v) { return vertForwardAdd(v); }
half4 fragBase (VertexOutputForwardBase i) : SV_Target { return fragForwardBaseInternal(i); }
half4 fragAdd (VertexOutputForwardAdd i) : SV_Target { return fragForwardAddInternal(i); }
#endif
#endif // UNITY_STANDARD_CORE_FORWARD_INCLUDED
可以看到用宏实现了两个分支,其中UNITY_STANDARD_SIMPLE分支采用的是简化着色器,官方文档的解释为采用简化BRDF3(具体应该是光照处理的数学公式不同,如果理解错误望指点)时定义UNITY_NO_FULL_STANDARD_SHADER宏,即采用简化版本。我们这里只看下面的标准版。
四个函数明显对应着两个Pass的VS和PS,函数当中调用UnityStandardCore.cginc文件当中的另一个函数。再到UnityStandardCore.cginc文件当中找到对应的函数。
具体的函数解析涉及到的知识较为复杂,我个人还没有完全理解,不敢妄言,这里推荐找到的一篇参考文章,比较详细的解释各个函数的内部实现:【Unity】Standard Shader实现分析
参考
Unity官方文档
【Unity】Standard Shader实现分析
球谐光照(spherical harmonic lighting)解析