Shaderlab Notizen 7-2 Standard Shader

五、Standard Shader中正向基础渲染通道源码

Standard Shader正向渲染基础通道(Shader Model 3.0版)的Shader源代码:

//------------------------------------【子着色器1】------------------------------------
// 此子着色器用于Shader Model 3.0
//----------------------------------------------------------------------------------------
SubShader
{
    //渲染类型设置:不透明
    Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }

    //细节层次设为:300
    LOD 300

    //--------------------------------通道1-------------------------------
    // 正向基础渲染通道(Base forward pass)
    // 处理方向光,自发光,光照贴图等 ...
    Pass
    {
        //设置通道名称
        Name "FORWARD"

        //于通道标签中设置光照模型为ForwardBase,正向渲染基础通道
        Tags { "LightMode" = "ForwardBase" }

        //混合操作:源混合乘以目标混合
        Blend [_SrcBlend] [_DstBlend]
        // 根据_ZWrite参数,设置深度写入模式开关与否
        ZWrite [_ZWrite]

        //===========开启CG着色器语言编写模块===========
        CGPROGRAM

        //着色器编译目标:Model 3.0
        #pragma target 3.0

        //编译指令:不使用GLES渲染器编译
        #pragma exclude_renderers gles

        // ---------编译指令:着色器编译多样化--------
        #pragma shader_feature _NORMALMAP
        #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
        #pragma shader_feature _EMISSION
        #pragma shader_feature _METALLICGLOSSMAP
        #pragma shader_feature ___ _DETAIL_MULX2
        #pragma shader_feature _PARALLAXMAP
//这边定义了很多的“宏”、 _NORMALMAP、_ALPHATEST_ON、_ALPHABLEND_ON、_EMISSION、_METALLICGLOSSMAP、_DETAIL_MULX2、_PARALLAXMAP,在顶点和片段着色器实现部分,可以用#ifdef _EMISSION类似的宏命令来对不同情况下的实现进行区别对待。

        //--------着色器编译多样化快捷指令------------
        //编译指令:编译正向渲染基础通道(用于正向渲染中,应用环境光照、主方向光照和顶点/球面调和光照)所需的所有变体。
        //这些变体用于处理不同的光照贴图类型、主要方向光源的阴影选项的开关与否
        #pragma multi_compile_fwdbase
        //编译指令:编译几个不同变种来处理不同类型的雾效(关闭/线性/指数/二阶指数/)
        #pragma multi_compile_fog

        //编译指令:告知编译器顶点和片段着色函数的名称
        #pragma vertex vertForwardBase
        #pragma fragment fragForwardBase
        //指明了这个pass中顶点着色函数和片段着色函数分别是名为vertForwardBase和fragForwardBase的函数

        //包含辅助CG头文件
        #include "UnityStandardCore.cginc"
        //vertForwardBase和       fragForwardBase的函数全都定义于此“UnityStandardCore.cginc”头文件中
        //===========结束CG着色器语言编写模块===========
        ENDCG
    }
……
}

5.1 顶点着色函数vertForwardBase
源码如下:

//-----------------------------------【vertForwardBase函数】---------------------------
//  用途:正向渲染基础通道的顶点着色函数
//  说明:实例化一个VertexOutputForwardBase结构体对象,并进行相应的填充
//  输入:VertexInput结构体
//  输出:VertexOutputForwardBase结构体
//  附:VertexInput结构体原型:
/*
struct VertexInput
{
    float4 vertex   : POSITION;
    half3 normal    : NORMAL;
    float2 uv0      : TEXCOORD0;
    float2 uv1      : TEXCOORD1;
    #if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
    float2 uv2      : TEXCOORD2;
    #endif
    #ifdef _TANGENT_TO_WORLD
    half4 tangent   : TANGENT;
    #endif
};
*/
//---------------------------------------------------------------------------------------------------------
VertexOutputForwardBase vertForwardBase (VertexInput v)
{
    //【1】实例化一个VertexOutputForwardBase结构体对象
    VertexOutputForwardBase o;
    //用Unity内置的宏初始化参数
    UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);

    //【2】通过物体坐标系到世界坐标系的变换矩阵乘以物体的顶点位置,得到对象在世界坐标系中的位置
    float4 posWorld = mul(_Object2World, v.vertex);

    //【3】若定义了镜面立方体投影宏,将计算得到的世界坐标系的xyz坐标作为输出参数的世界坐标值
    #if UNITY_SPECCUBE_BOX_PROJECTION
        o.posWorld = posWorld.xyz;
    #endif

    //【4】输出的顶点位置(像素位置)为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    //【5】计算纹理坐标,使用UnityStandardInput.cginc头文件中的辅助函数。
    o.tex = TexCoords(v);
    //【6】视线的方向= 对象在世界坐标系中的位置减去摄像机的世界空间位置,并进行逐顶点归一化
    o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);

    //【7】计算物体在世界空间中的法线坐标
    float3 normalWorld = UnityObjectToWorldNormal(v.normal);

    //【8】进行世界空间中的切线相关参数的计算与赋值
    //若定义了_TANGENT_TO_WORLD
    #ifdef _TANGENT_TO_WORLD
        //世界空间中的物体的法线值
        float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
        //在世界空间中为每个顶点创建切线
        float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
        //分别为3个分量赋值
        o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0];
        o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1];
        o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2];
    //否则,三个分量直接取为0,0和上面计算得到的normalWorld
    #else
        o.tangentToWorldAndParallax[0].xyz = 0;
        o.tangentToWorldAndParallax[1].xyz = 0;
        o.tangentToWorldAndParallax[2].xyz = normalWorld;
    #endif

    //【9】阴影的获取
    TRANSFER_SHADOW(o);

    //【10】进行顶点正向相关的全局光照操作
    o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);

    //【11】若定义了_PARALLAXMAP宏,则计算视差的视角方向并赋值
    #ifdef _PARALLAXMAP
        //声明一个由切线空间的基组成的3x3矩阵“rotation”
        TANGENT_SPACE_ROTATION;
        //计算视差的视角方向
        half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
        //分别将三个分量赋值给VertexOutputForwardBase结构体对象o的tangentToWorldAndParallax的三个分量
        o.tangentToWorldAndParallax[0].w = viewDirForParallax.x;
        o.tangentToWorldAndParallax[1].w = viewDirForParallax.y;
        o.tangentToWorldAndParallax[2].w = viewDirForParallax.z;
    #endif

    //【12】若定义了UNITY_OPTIMIZE_TEXCUBELOD,便计算反射光方向向量并赋值
    #if UNITY_OPTIMIZE_TEXCUBELOD
        //使用CG语言内置函数reflect计算反射光方向向量
        o.reflUVW       = reflect(o.eyeVec, normalWorld);
    #endif

    //【13】从顶点中输出雾数据
    UNITY_TRANSFER_FOG(o,o.pos);

    //【14】返回已经附好值的VertexOutputForwardBase类型的对象
    return o;
}

5.2 顶点输入结构体VertexInput
此结构体定义于UnityStandardInput.cginc头文件中,是顶点着色函数vertForwardBase的输入参数

源码如下:

//顶点输入结构体
struct VertexInput
{
    float4 vertex   : POSITION;//位置坐标
    half3 normal    : NORMAL;//法线向量
    float2 uv0      : TEXCOORD0;//一级纹理坐标
    float2 uv1      : TEXCOORD1;//二级纹理坐标
    //若DYNAMICLIGHTMAP_ON或者UNITY_PASS_META选项为开,则还定义一个三级纹理
#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
    float2 uv2      : TEXCOORD2;//三级纹理
#endif
#ifdef _TANGENT_TO_WORLD
    half4 tangent   : TANGENT;//切线向量
#endif
};

5.3顶点输出结构体VertexOutputForwardBase、
VertexOutputForwardBase结构体就是正向基础渲染通道特有的输出结构体,定义于UnityStandardCore.cginc头文件中

源码如下:

//正向渲染基础通道的输出结构体
struct VertexOutputForwardBase
{
    float4 pos                          : SV_POSITION;//像素坐标
    float4 tex                          : TEXCOORD0;//一级纹理
    half3 eyeVec                        : TEXCOORD1;//二级纹理(视线向量)
    half4 tangentToWorldAndParallax[3]  : TEXCOORD2;    //3x3为切线到世界矩阵的值,1x3为视差方向的值
    half4 ambientOrLightmapUV           : TEXCOORD5;    // 球谐函数(Spherical harmonics)或光照贴图的UV坐标
    SHADOW_COORDS(6)//阴影坐标
    UNITY_FOG_COORDS(7)//雾效坐标

    //若定义了镜面立方体投影宏,定义一个posWorld
    #if UNITY_SPECCUBE_BOX_PROJECTION
        float3 posWorld                 : TEXCOORD8;
    #endif
    //若定义了优化纹理的立方体LOD宏,还将定义如下的参数reflUVW
    #if UNITY_OPTIMIZE_TEXCUBELOD
        #if UNITY_SPECCUBE_BOX_PROJECTION
            half3 reflUVW               : TEXCOORD9;
        #else
            half3 reflUVW               : TEXCOORD8;
        #endif
    #endif
};

5.4 UNITY_INITIALIZE_OUTPUT宏
UNITY_INITIALIZE_OUTPUT(type,name) –此宏用于将给定类型的名称变量初始化为零。

5.5 _Object2World矩阵
_Object2World,Unity的内置矩阵,世界坐标系到对象坐标系的变换矩阵,简称“世界-对象矩阵”。

5.6 UNITY_MATRIX_MVP矩阵
UNITY_MATRIX_MVP为当前的模型矩阵x视图矩阵x投影矩阵,简称“模型-视图-投影矩阵”。其常用于在顶点着色函数中,通过将它和顶点位置相乘,从而可以把顶点位置从模型空间转换到裁剪空间(clip space)中。也就是通过此矩阵,将三维空间中的坐标投影到了二维窗口中。

5.7 TexCoords函数
TexCoords函数用于获取纹理坐标,定义UnityStandardInput.cginc头文件中,
源码如下:

float4 TexCoords(VertexInput v)
{
    float4 texcoord;
    texcoord.xy = TRANSFORM_TEX(v.uv0, _MainTex); // Always source from uv0
    texcoord.zw = TRANSFORM_TEX(((_UVSec == 0) ? v.uv0 : v.uv1), _DetailAlbedoMap);
    return texcoord;
}

函数实现代码中的_MainTex、_UVSec、_DetailAlbedoMap都是此头文件定义的全局的变量。
其中还涉及到了一个TRANSFORM_TEX宏,在这边也提一下,它定义于UnityCG.cginc头文件中,
源码如下:

// 按比例和偏移进行二维UV坐标的变换
#define TRANSFORM_TEX(tex,name) (tex.xy *name##_ST.xy + name##_ST.zw)

5.8 NormalizePerVertexNormal函数
此函数位于unitystandardcore.cginc头文件中
源码如下:

//--------------------------【函数NormalizePerVertexNormal】-------------------------
// 用途:归一化每顶点法线
// 说明:若满足特定条件,便归一化每顶点法线并返回,否则,直接返回原始值
// 输入:half3类型的法线坐标
// 输出:若满足判断条件,返回half3类型的、经过归一化后的法线坐标,否则返回输入的值
//-----------------------------------------------------------------------------------------------
half3 NormalizePerVertexNormal (half3 n)
{
    //满足着色目标模型的版本小于Shader Model 3.0,或者定义了UNITY_STANDARD_SIMPLE宏,返回归一化后的值
    #if (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
        return normalize(n);
    //否则,直接返回输入的参数,后续应该会进行逐像素的归一化
    #else
        return n;
    #endif
}

其中,SHADER_TARGET宏代表的值为和着色器的目标编译模型(shader model)相关的一个数值。

5.9 UnityObjectToWorldNormal函数
UnityObjectToWorldNormal是Unity内置的函数,可以将法线从模型空间变换到世界空间中,定义于UnityCG.cginc头文件中
源码如下:

//将法线从模型空间变换到世界空间
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
    // 将分量分别相乘,并进行归一化
    //Multiply by transposed inverse matrix, actually using transpose() generates badly optimized code
    return normalize(_World2Object[0].xyz * norm.x + _World2Object[1].xyz * norm.y + _World2Object[2].xyz * norm.z);
}

其中的normalize( )函数很常见,是来自CG语言中的函数,作用是归一化向量。

5.10 UnityObejctToWorldDir函数
UnityObjectToWorldDir函数用于方向值从物体空间切换到世界空间,定义于UnityCG.cginc头文件中
源码如下

//将方向值从物体空间切换到世界空间
inline float3 UnityObjectToWorldDir( in float3 dir )
{
    return normalize(mul((float3x3)_Object2World, dir));
}

5.11 CreateTangentToWorldPerVertex函数
CreateTangentToWorldPerVertex函数用于在世界空间中为每个顶点创建切线,定义于UnityStandardUtils.cginc头文件中
源码如下:

half3x3 CreateTangentToWorldPerVertex(half3 normal, half3 tangent, half tangentSign)
{
    //对于奇数负比例变换,我们需要将符号反向||For odd-negative scale transforms we need to flip the sign
    half sign = tangentSign * unity_WorldTransformParams.w;
    half3 binormal = cross(normal, tangent) * sign;
    return half3x3(tangent, binormal, normal);
}

其中的unity_WorldTransformParams是UnityShaderVariables.cginc头文件中定义的一个uniform float4型的变量,其w分量用于标定奇数负比例变换(odd-negativescale transforms),通常取值为1.0或者-1.0。

5.12 TRANSFER_SHADOW(a)宏
此宏用于进行阴影在各种空间中的转换,定义于AutoLight.cginc中。在不同的情况下,此宏代表的意义并不相同。
1 Screen space shadows(对于屏幕空间中的阴影)
对应于屏幕空间中的阴影,也就是#if defined (SHADOWS_SCREEN)
源码如下:

#if defined (SHADOWS_SCREEN)
……
#if defined(UNITY_NO_SCREENSPACE_SHADOWS)
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, v.vertex ) );

#else // not UNITY_NO_SCREENSPACE_SHADOWS

#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
……
#endif

将世界-阴影坐标乘以世界-模型坐标和物体顶点坐标的积,也就是先将物体坐标转换成世界坐标,再将世界坐标转换成阴影坐标,并将结果存放于a._ShadowCoord中。

2.Spor light shadows(对于聚光灯阴影)

对于聚光灯的阴影,也就是#if defined (SHADOWS_DEPTH)&& defined (SPOT)

 #if defined (SHADOWS_DEPTH) && defined (SPOT)
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_World2Shadow[0], mul(_Object2World,v.vertex));
……
#endif

用途就是先将物体坐标转换成世界坐标,再将世界坐标转换成阴影坐标,并将结果存放于a._ShadowCoord中。

3.Point light shadows(关于点光源阴影)

#if defined (SHADOWS_CUBE),有如下定义:
#if defined (SHADOWS_CUBE)
    #define TRANSFER_SHADOW(a) a._ShadowCoord = mul(_Object2World, v.vertex).xyz - _LightPositionRange.xyz;
……
#endif

也就是说,这种情况下的TRANSFER_SHADOW(a)宏代表语句a._ShadowCoord = mul(_Object2World, v.vertex).xyz -_LightPositionRange.xyz;
想了解此代码的含义,先要知道_LightPositionRange变量的含义。
这个变量是UnityShaderVariables.cginc头文件中定义的一个全局变量:

uniform float4 _LightPositionRange;          // xyq= pos, w = 1/range

此参数的x,y,z分量表示世界空间下光源的坐标,而w为世界空间下范围的倒数。
就是先将物体-世界矩阵乘以物体顶点坐标,得到物体的世界空间坐标,然后取坐标的xyz分量,与光源的坐标相减,并将结果赋给a._ShadowCoord。

4.Shadow off(关闭阴影)
对于关闭阴影的情况,也就是#if !defined (SHADOWS_SCREEN)&& !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE),有如下定义:

#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
    #define TRANSFER_SHADOW(a)
……
#endif

这种情况下的TRANSFER_SHADOW(a)宏代表的是空白

5.13 VertexGlForward函数
定义于UnityStandardCore.cginc头文件中
源码如下:

//顶点正向全局光照函数
inline half4 VertexGIForward(VertexInput v, float3 posWorld, half3 normalWorld)
{
    //【1】定义一个half4型的ambientOrLightmapUV变量,并将四个分量都置为0
    half4 ambientOrLightmapUV = 0;

    //【2】对ambientOrLightmapUV变量的四个分量赋值
    // 【2-1】若没有定义LIGHTMAP_OFF(关闭光照贴图)宏,也就是此情况下启用静态的光照贴图,则计算对应的光照贴图坐标
    //static lightmap
    #ifndef LIGHTMAP_OFF
        ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
        ambientOrLightmapUV.zw = 0;

    //【2-2】若定义了UNITY_SHOULD_SAMPLE_SH宏,则表示对动态的对象采样(不对静态或者动态的光照贴图采样)
    // || Sample light probe for Dynamic objects only (no static or dynamic lightmaps)
    #elif UNITY_SHOULD_SAMPLE_SH

        //【2-2-1】若定义了如下的UNITY_SAMPLE_FULL_SH_PER_PIXEL宏(即采样计算全部的每像素球面调和光照),便给ambientOrLightmapUV.rgb赋值为0
        #if UNITY_SAMPLE_FULL_SH_PER_PIXEL
            ambientOrLightmapUV.rgb = 0;

        //【2-2-2】若满足着色目标模型的版本小于Shader Model 3.0,或者定义了UNITY_STANDARD_SIMPLE宏
        //便使用球面调和函数ShadeSH9给ambientOrLightmapUV.rgb赋值
        #elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
            ambientOrLightmapUV.rgb = ShadeSH9(half4(normalWorld, 1.0));

        //【2-2-3】否则,使用三序球面调和函数ShadeSH3Order给ambientOrLightmapUV.rgb赋值
        #else
            //优化操作:光源L0、L1逐像素,光源L2逐顶点 ||  Optimization: L2 per-vertex, L0..L1 per-pixel
            ambientOrLightmapUV.rgb = ShadeSH3Order(half4(normalWorld, 1.0));
        #endif

        //【2-2-4】 从非重要的点光源中添加近似的照明 || Add approximated illumination from non-important point lights
        //若定义了如下的VERTEXLIGHT_ON宏(即开启顶点光照),便使用Shade4PointLights函数给ambientOrLightmapUV.rgb赋值,添加环境光
        #ifdef VERTEXLIGHT_ON
            //  Shade4PointLights为Unity内置的逐顶点光照处理函数,定义于unityCG.cginc头文件中
            ambientOrLightmapUV.rgb += Shade4PointLights (
                unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
                unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
                unity_4LightAtten0, posWorld, normalWorld);
        #endif
    #endif

    //【2-3】若定义了如下的VERTEXLIGHT_ONDYNAMICLIGHTMAP_ON宏(即开启动态光照贴图),则给变量的zw分量赋值
    #ifdef DYNAMICLIGHTMAP_ON
        ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
    #endif
    //【3】返回ambientOrLightmapUV变量的值
    return ambientOrLightmapUV;
}

1.unity_LightmapST变量

unity_LightmapST变量类型为float4型,定义于UnityShaderVariables.cginc头文件中,存放着光照贴图操作的参数的值

float4 unity_LightmapST

2.UNITY_SHOULD_SAMPLE_SH宏
定义于UnityCG.cginc中
源码如下:

//包含间接漫反射的动态&静态光照贴图,所以忽略掉球面调和光照 ||  Dynamic & Static lightmaps contain indirect diffuse ligthing, thus ignore SH
#define UNITY_SHOULD_SAMPLE_SH ( defined (LIGHTMAP_OFF) && defined(DYNAMICLIGHTMAP_OFF) )

就是将LIGHTMAP_OFF(关闭光照贴图)宏和DYNAMICLIGHTMAP_OFF(关闭动态光照贴图)宏的定义进行了封装

3.UNITY_SAMPLE_FULL_SH_PER_PIXEL宏

UNITY_SAMPLE_FULL_SH_PER_PIXEL宏定义于UnityStandardConfig.cginc头文件中。其实也就是一个标识符,用0标示UNITY_SAMPLE_FULL_SH_PER_PIXEL宏是否已经定义。按字面上理解,启用此宏表示我们将采样计算每像素球面调和光照,而不是默认的逐顶点计算球面调和光照并且线性插值到每像素中。
源码如下:

#ifndef UNITY_SAMPLE_FULL_SH_PER_PIXEL
#define UNITY_SAMPLE_FULL_SH_PER_PIXEL 0
#endif

4.ShadeSH9函数
ShadeSH9即球面调和函数,定义于UnityCG.cginc头文件中
源码如下:

//球面调和函数
//法线需被初始化,w=1.0 || normal should be normalized, w=1.0
half3 ShadeSH9 (half4 normal)
{
    half3 x1, x2, x3;

    //线性+常数多项式  || Linear + constant polynomial terms
    x1.r = dot(unity_SHAr,normal);
    x1.g = dot(unity_SHAg,normal);
    x1.b = dot(unity_SHAb,normal);

    //二次多项式的四个参数 || 4 of the quadratic polynomials
    half4 vB = normal.xyzz * normal.yzzx;
    x2.r = dot(unity_SHBr,vB);
    x2.g = dot(unity_SHBg,vB);
    x2.b = dot(unity_SHBb,vB);

    //最终二次多项式 ||  Final quadratic polynomial
    half vC = normal.x*normal.x - normal.y*normal.y;
    x3 = unity_SHC.rgb * vC;
    return x2 + x3 + x1;
}

5.ShadeSH3Order函数
定义于UnityCG.cginc头文件中
源码如下:

//三序球面调和函数
//法线需被初始化,w=1.0 ||  normal should be normalized, w=1.0
half3 ShadeSH3Order(half4 normal)
{
    half3 x2, x3;
    //二次多项式的四个参数 || 4 of the quadratic polynomials
    half4 vB = normal.xyzz * normal.yzzx;
    x2.r = dot(unity_SHBr,vB);
    x2.g = dot(unity_SHBg,vB);
    x2.b = dot(unity_SHBb,vB);

    //最终的二次多项式 || Final quadratic polynomial
    half vC = normal.x*normal.x - normal.y*normal.y;
    x3 = unity_SHC.rgb * vC;

    return x2 + x3;
}

6.Shade4PointLights函数

Shade4PointLights为逐顶点光照处理函数,定义于unityCG.cginc头文件中

//在正向基础渲染通道中使用,根据4个不同的点光源计算出漫反射光照参数的rgb值|| Used in ForwardBase pass: Calculates diffuse lighting from 4 point lights, with data packed in a special way.
float3 Shade4PointLights (
    float4 lightPosX, float4 lightPosY, float4 lightPosZ,
    float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
    float4 lightAttenSq,
    float3 pos, float3 normal)
{
    // 【1】将输入参数转换为光照矢量 || to light vectors
    float4 toLightX = lightPosX - pos.x;
    float4 toLightY = lightPosY - pos.y;
    float4 toLightZ = lightPosZ - pos.z;
    // 【2】计算平方的值 || squared lengths
    float4 lengthSq = 0;
    lengthSq += toLightX * toLightX;
    lengthSq += toLightY * toLightY;
    lengthSq += toLightZ * toLightZ;
    // 【3】法线方向点乘光线方向|| NdotL
    float4 ndotl = 0;
    ndotl += toLightX * normal.x;
    ndotl += toLightY * normal.y;
    ndotl += toLightZ * normal.z;
    // 【4】修正NdotL(法线方向点乘光线方向)的值 || correct NdotL
    float4 corr = rsqrt(lengthSq);
    ndotl = max (float4(0,0,0,0), ndotl * corr);
    // 【5】计算衰减系数 || attenuation
    float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
    float4 diff = ndotl * atten;
    // 【6】得到最终的颜色 || final color
    float3 col = 0;
    col += lightColor0 * diff.x;
    col += lightColor1 * diff.y;
    col += lightColor2 * diff.z;
    col += lightColor3 * diff.w;
    return col;
}

5.14 TANGENT_SPACE_ROTATION宏
TANGENT_SPACE_ROTATION宏定义于UnityCG.cginc中,作用是声明一个由切线空间的基组成的3x3矩阵
源码如下:

//声明一个由切线空间的基组成的3x3矩阵 || Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
    float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
    float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )

使用TANGENT_SPACE_ROTATION宏也就表示定义了上述代码所示的float3 类型的binormal和float3x3类型的rotation两个变量。且其中的rotation为3x3的矩阵,由切线空间的基组成。可以使用它把物体空间转换到切线空间中。

5.15 UNITY_OPTIMIZE_TEXCUBELOD宏

用0标识是否开启此功能
源码如下:

#ifndef UNITY_OPTIMIZE_TEXCUBELOD
    #define UNITY_OPTIMIZE_TEXCUBELOD 0
#endif

5.16 reflect函数

reflect函数是CG语言的内置函数。
reflect(I, N) 根据入射光方向向量I,和顶点法向量N,计算反射光方向向量。其中I 和N必须被归一化,需要特别注意的是,这个I 是指向顶点的;且此函数只对三元向量有效。

5.17 UNITY_TRANSFER_FOG宏

UNITY_TRANSFER_FOG宏相关代码定义于UnityCG.Cginc头文件中
源码如下:

//【0】实现不同版本的UNITY_CALC_FOG_FACTOR宏。
#if defined(FOG_LINEAR)
    // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
    #define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
    // factor = exp(-density*z)
    #define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
    // factor = exp(-(density*z)^2)
    #define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
    #define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = 0.0
#endif

//【1】若已经定义了FOG_LINEAR、FOG_EXP、FOG_EXP2宏三者之中至少之一,便可以进行到此#if实现部分
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)

    //【1-1】定义UNITY_FOG_COORDS(idx)宏
    #define UNITY_FOG_COORDS(idx) float fogCoord : TEXCOORD##idx;

    //【1-2】定义UNITY_TRANSFER_FOG(o,outpos)宏
    //【1-2-1】若满足着色目标模型的版本小于Shader Model 3.0,或者定义了SHADER_API_MOBILE宏,便可以进行到此#if实现部分
//UNITY_CALC_FOG_FACTOR宏的实现见上
    #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
        // 移动平台和Shader Model 2.0:计算每顶点的雾效因子 || mobile or SM2.0: calculate fog factor per-vertex
        #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord = unityFogFactor
    //【1-2-2】否则
    #else
        // Shader Model 3.0和PC/游戏主机平台:计算每顶点的雾距离,以及每像素雾效因子 || SM3.0 and PC/console: calculate fog distance per-vertex, and fog factor per-pixel
        #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord = (outpos).z
    #endif
//【2】否则,直接用UNITY_FOG_COORDS宏计算雾效参数
#else
    #define UNITY_FOG_COORDS(idx)
    #define UNITY_TRANSFER_FOG(o,outpos)
#endif

5.18 fragForwardBase(片段着色器函数)

源码如下:

//----------------------------------------【fragForwardBase函数】----------------------
//  用途:正向渲染基础通道的片段着色函数
//  输入:VertexOutputForwardBase结构体
//  输出:一个half4类型的颜色值
//------------------------------------------------------------------------------------------------------------------
half4 fragForwardBase (VertexOutputForwardBase i) : SV_Target
{
    //定义并初始化类型为FragmentCommonData的变量s
    FRAGMENT_SETUP(s)
    //若定义了UNITY_OPTIMIZE_TEXCUBELOD,则由输入的顶点参数来设置反射光方向向量
#if UNITY_OPTIMIZE_TEXCUBELOD
    s.reflUVW       = i.reflUVW;
#endif

    //设置主光照
    UnityLight mainLight = MainLight (s.normalWorld);

    //设置阴影的衰减系数
    half atten = SHADOW_ATTENUATION(i);

    //计算全局光照
    half occlusion = Occlusion(i.tex.xy);
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);

    //加上BRDF-基于物理的光照
    half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
    //加上BRDF-全局光照
    c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);
    //加上自发光
    c.rgb += Emission(i.tex.xy);

    //设置雾效
    UNITY_APPLY_FOG(i.fogCoord, c.rgb);

    //返回最终的颜色
    return OutputForward (c, s.alpha);
}

1.FRAGMENT_SETUP(x) 宏
FRAGMENT_SETUP(x)宏定义于UnityStandardCore.cginc头文件中,其作用其实就是用FragmentSetup函数初始化括号中的x变量。

#define FRAGMENT_SETUP(x) FragmentCommonData x = \
    FragmentSetup(i.tex, i.eyeVec, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndParallax, IN_WORLDPOS(i));

调用此宏,也就是表示写了如下的代码,定义了一个x变量:

FragmentCommonData x =FragmentSetup(i.tex, i.eyeVec, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndParallax, IN_WORLDPOS(i));

其中FragmentSetup函数也定义于UnityStandardCore.cginc头文件中,用于填充一个FragmentCommonData结构体并于返回值中返回,也就是进行片段函数相关参数的初始化
源码如下:

//函数FragmentSetup:填充一个FragmentCommonData结构体并于返回值中返回,进行片段函数相关参数的初始化
inline FragmentCommonData FragmentSetup (float4 i_tex, half3 i_eyeVec, half3 i_viewDirForParallax, half4 tangentToWorld[3], half3 i_posWorld)
{
    i_tex = Parallax(i_tex, i_viewDirForParallax);

    half alpha = Alpha(i_tex.xy);
    #if defined(_ALPHATEST_ON)
        clip (alpha - _Cutoff);
    #endif

    FragmentCommonData o = UNITY_SETUP_BRDF_INPUT (i_tex);
    o.normalWorld = PerPixelWorldNormal(i_tex, tangentToWorld);
    o.eyeVec = NormalizePerPixelNormal(i_eyeVec);
    o.posWorld = i_posWorld;

    // NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
    o.diffColor = PreMultiplyAlpha (o.diffColor, alpha, o.oneMinusReflectivity, /*out*/ o.alpha);
    return o;
}

其中的FragmentCommonData结构体也是定义于UnityStandardCore.cginc头文件中:

//FragmentCommonData结构体:存放片段着色常用变量
struct FragmentCommonData
{
    half3 diffColor, specColor;//漫反射颜色;镜面反射颜色
    // Note: oneMinusRoughness & oneMinusReflectivity for optimization purposes, mostly for DX9 SM2.0 level.
    // Most of the math is being done on these (1-x) values, and that saves a few precious ALU slots.
    half oneMinusReflectivity, oneMinusRoughness;//1减去反射率;1减去粗糙度
    half3 normalWorld, eyeVec, posWorld;//世界空间中的法线向量坐标;视角向量坐标;在世界坐标中的位置坐标
    half alpha;//透明度

#if UNITY_OPTIMIZE_TEXCUBELOD || UNITY_STANDARD_SIMPLE
    half3 reflUVW;//反射率的UVW
#endif

#if UNITY_STANDARD_SIMPLE
    half3 tangentSpaceNormal;//切线空间中的法线向量
#endif
};

2.MainLight函数
MainLight函数定义于UnityStandardCore.cginc头文件中,用途是实例化一个UnityLight结构体对象,并进行相应的填充,其返回值作为主光源
源码如下:

//  用途:该函数为主光照函数
//  说明:实例化一个UnityLight结构体对象,并进行相应的填充
/*
//注:UnityLight结构体定义于UnityLightingCommon.cginc文件中,原型如下:
struct UnityLight
{
half3 color;
half3 dir;
half  ndotl;
};
*/

//------------------------------------【函数3】MainLight函数-----------------------------------------
//  用途:该函数为主光照函数
//  说明:实例化一个UnityLight结构体对象,并进行相应的填充
//---------------------------------------------------------------------------------------------------------
UnityLight MainLight (half3 normalWorld)
{
    //【1】实例化一个UnityLight的对象
    UnityLight l;

    //【2】填充UnityLight的各个参数
    //若光照贴图选项为关,使用Unity内置变量赋值
    #ifdef LIGHTMAP_OFF
        //获取光源的颜色
        l.color = _LightColor0.rgb;
        //获取光源的方向
        l.dir = _WorldSpaceLightPos0.xyz;
        //获取法线与光源方向的点乘的积
        l.ndotl = LambertTerm (normalWorld, l.dir);

    //光照贴图选项为开,将各项值设为0
    #else
        l.color = half3(0.f, 0.f, 0.f);
        l.ndotl  = 0.f;
        l.dir = half3(0.f, 0.f, 0.f);
    #endif

    //返回赋值完成的UnityLight结构体对象
    return l;
}

3.SHADOW_ATTENUATION宏
SHADOW_ATTENUATION宏相关的代码位于AutoLight.cginc头文件中,用于实现阴影渲染相关的辅助工作
源码如下:

// ----------------
//  阴影相关工具代码 || Shadow helpers
// ----------------

// ---- 屏幕空间阴影 || Screen space shadows
#if defined (SHADOWS_SCREEN)
……
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif

// ----聚光灯光源阴影 || Spot light shadows
#if defined (SHADOWS_DEPTH) && defined (SPOT)
    #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
    #define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_World2Shadow[0], mul(_Object2World,v.vertex));
    #define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)
#endif

// ----点光源阴影 ||  Point light shadows
#if defined (SHADOWS_CUBE)
    #define SHADOW_COORDS(idx1) unityShadowCoord3 _ShadowCoord : TEXCOORD##idx1;
    #define TRANSFER_SHADOW(a) a._ShadowCoord = mul(_Object2World, v.vertex).xyz - _LightPositionRange.xyz;
    #define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)
#endif

// ---- 关闭阴影 || Shadows off
#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
    #define SHADOW_COORDS(idx1)
    #define TRANSFER_SHADOW(a)
    #define SHADOW_ATTENUATION(a) 1.0
#endif

SHADOW_ATTENUATION(a)宏除了在关闭阴影的状态是等于1以外,其他几种情况都是等价于UnitySampleShadowmap(a._ShadowCoord)函数的调用。而这里的UnitySampleShadowmap函数,定于于UnityShadowLibrary.cginc函数中
源码如下:

//------------------------------【UnitySampleShadowmap函数】-----------------------
// 用途:采样阴影贴图,得到阴影衰减值
// 输入参数: float3型的阴影向量坐标vec
// 返回值:阴影衰减值
//-------------------------------------------------------------------------------------------------------
inline half UnitySampleShadowmap (float3 vec)
{
    float mydist = length(vec) * _LightPositionRange.w;
    mydist *= 0.97; // bias

    #if defined (SHADOWS_SOFT)
        float z = 1.0/128.0;
        float4 shadowVals;
        shadowVals.x = SampleCubeDistance (vec+float3( z, z, z));
        shadowVals.y = SampleCubeDistance (vec+float3(-z,-z, z));
        shadowVals.z = SampleCubeDistance (vec+float3(-z, z,-z));
        shadowVals.w = SampleCubeDistance (vec+float3( z,-z,-z));
        half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f;
        return dot(shadows,0.25);
    #else
        float dist = SampleCubeDistance (vec);
        return dist < mydist ? _LightShadowData.r : 1.0;
    #endif
}

4.Occlusion函数
Occlusion函数用于进行全局光照的第一步。其输入参数为一个float2型的纹理坐标,而其half型的返回值将作为FragmentGI函数的一个输入参数。
源码如下:

half Occlusion(float2 uv)
{
#if (SHADER_TARGET < 30)
    // SM20: instruction count limitation
    // SM20: simpler occlusion
    return tex2D(_OcclusionMap, uv).g;
#else
    half occ = tex2D(_OcclusionMap, uv).g;
    return LerpOneTo (occ, _OcclusionStrength);
#endif
}

其中的LerpOneTo函数很简单,用于线性插值,输入两个值b和t,返回1+(b-1)*t,具体定义如下:

half LerpOneTo(half b, half t)
{
    half oneMinusT = 1 - t;
    return oneMinusT + b * t;
}

5.UnityGl结构体
UnityGI结构体是Unity中存放全局光照光源信息的结构体,定义于UnityLightingCommon.cginc头文件中
源码如下:

//全局光照结构体
struct UnityGI
{
    UnityLight light;//定义第一个光源参数结构体,表示第一个光源
    //若定义了DIRLIGHTMAP_SEPARATE(单独的方向光源光照贴图)
    #ifdef DIRLIGHTMAP_SEPARATE
        //若定义了LIGHTMAP_ON(打开光照贴图)
        #ifdef LIGHTMAP_ON
            UnityLight light2;//定义第二个光源参数结构体,表示第二个光源
        #endif
        //若定义了DYNAMICLIGHTMAP_ON(打开动态光照贴图)
        #ifdef DYNAMICLIGHTMAP_ON
            UnityLight light3;//定义第三个光源参数结构体,表示第三个光源
        #endif
    #endif
    UnityIndirect indirect;//Unity中间接光源参数的结构体
};

其中包含了UnityLight结构体和UnityIndirect结构体,其中UnityLight结构体是Unity Shader中最基本的光照结构体,而UnityIndirect是Unity中存放间接光源信息的结构体。它们两者也定义于UnityLightingCommon.cginc头文件中
源码如下:

//Unity中光源参数的结构体
struct UnityLight
{
    half3 color;//光源颜色
    half3 dir;//光源方向
    half  ndotl; //入射光方向和当前表面法线方向的点积
};

//Unity中间接光源参数的结构体
struct UnityIndirect
{
    half3 diffuse;//漫反射颜色
    half3 specular;//镜面反射颜色
};

6.FragmentGl函数
FragmentGI函数是片段着色部分全局光照的处理函数,定义于UnityStandardCore.cginc头文件中
源码如下:

//函数:片段着色部分全局光照的处理函数
inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)
{
    //【1】实例化一个UnityGIInput的对象
    UnityGIInput d;
    //【2】填充此UnityGIInput对象的各个值
    d.light = light;
    d.worldPos = s.posWorld;
    d.worldViewDir = -s.eyeVec;
    d.atten = atten;
    #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
        d.ambient = 0;
        d.lightmapUV = i_ambientOrLightmapUV;
    #else
        d.ambient = i_ambientOrLightmapUV.rgb;
        d.lightmapUV = 0;
    #endif
    d.boxMax[0] = unity_SpecCube0_BoxMax;
    d.boxMin[0] = unity_SpecCube0_BoxMin;
    d.probePosition[0] = unity_SpecCube0_ProbePosition;
    d.probeHDR[0] = unity_SpecCube0_HDR;

    d.boxMax[1] = unity_SpecCube1_BoxMax;
    d.boxMin[1] = unity_SpecCube1_BoxMin;
    d.probePosition[1] = unity_SpecCube1_ProbePosition;
    d.probeHDR[1] = unity_SpecCube1_HDR;

    //【3】根据填充好的UnityGIInput结构体对象,调用一下UnityGlobalIllumination函数
    if(reflections)
    {
        Unity_GlossyEnvironmentData g;
        g.roughness     = 1 - s.oneMinusRoughness;
    #if UNITY_OPTIMIZE_TEXCUBELOD || UNITY_STANDARD_SIMPLE
        g.reflUVW       = s.reflUVW;
    #else
        g.reflUVW       = reflect(s.eyeVec, s.normalWorld);
    #endif

        return UnityGlobalIllumination (d, occlusion, s.normalWorld, g);
    }
    else
    {
        return UnityGlobalIllumination (d, occlusion, s.normalWorld);
    }
}

inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light)
{
    return FragmentGI(s, occlusion, i_ambientOrLightmapUV, atten, light, true);
}

其中的UnityGIInput结构体定义了全局光照所需要的一些函数,定义如下:

//全局光照的输入参数结构体
struct UnityGIInput
{
    UnityLight light; // 像素光源,由引擎准备并传输过来 || pixel light, sent from the engine

    float3 worldPos;//世界空间中的位置坐标
    half3 worldViewDir;//世界空间中的视角方向向量坐标
    half atten;//衰减值
    half3 ambient;//环境光颜色
    half4 lightmapUV; //光照贴图的UV坐标,其中 取.xy = static lightmapUV(静态光照贴图的UV) , .zw = dynamic lightmap UV(动态光照贴图的UV)

    float4 boxMax[2];//box最大值
    float4 boxMin[2];//box最小值
    float4 probePosition[2];//光照探针的位置
    float4 probeHDR[2];//光照探针的高动态范围图像(High-Dynamic Range)
};

UnityGIInput 中还包含了UnityLight结构体
FragmentGI函数最终利用了UnityGlobalIllumination函数,其定义于UnityGlobalIllumination.cginc头文件中,实现如下:
inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld)
{
    return UnityGI_Base(data, occlusion, normalWorld);
}

此FragmentGI函数就是嵌套了一层UnityGI_Base函数位于UnityGlobalIllumination.cginc头文件中,源码如下:
//UnityGI_Base函数:Unity的全局光照Base版
inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
    //【1】实例化一个UnityGI类型的结构体
    UnityGI o_gi;
    //【2】重置此UnityGI的结构体
    ResetUnityGI(o_gi);

    //【3】开始逐个填充参数
    #if !defined(LIGHTMAP_ON)
        o_gi.light = data.light;
        o_gi.light.color *= data.atten;
    #endif

    #if UNITY_SHOULD_SAMPLE_SH
        #if UNITY_SAMPLE_FULL_SH_PER_PIXEL
            half3 sh = ShadeSH9(half4(normalWorld, 1.0));
        #elif (SHADER_TARGET >= 30) && !UNITY_STANDARD_SIMPLE
            half3 sh = data.ambient + ShadeSH12Order(half4(normalWorld, 1.0));
        #else
            half3 sh = data.ambient;
        #endif

        o_gi.indirect.diffuse = sh;
    #endif

    #if defined(LIGHTMAP_ON)
        // Baked lightmaps
        fixed4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
        half3 bakedColor = DecodeLightmap(bakedColorTex);

        #ifdef DIRLIGHTMAP_OFF
            o_gi.indirect.diffuse = bakedColor;

            #ifdef SHADOWS_SCREEN
                o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex);
            #endif // SHADOWS_SCREEN

        #elif DIRLIGHTMAP_COMBINED
            fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
            o_gi.indirect.diffuse = DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);

            #ifdef SHADOWS_SCREEN
                o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex);
            #endif // SHADOWS_SCREEN

        #elif DIRLIGHTMAP_SEPARATE
            // Left halves of both intensity and direction lightmaps store direct light; right halves - indirect.

            // Direct
            fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
            o_gi.indirect.diffuse = DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light);

            // Indirect
            half2 uvIndirect = data.lightmapUV.xy + half2(0.5, 0);
            bakedColor = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, uvIndirect));
            bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, uvIndirect);
            o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light2);
        #endif
    #endif

    #ifdef DYNAMICLIGHTMAP_ON
        // Dynamic lightmaps
        fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
        half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);

        #ifdef DIRLIGHTMAP_OFF
            o_gi.indirect.diffuse += realtimeColor;

        #elif DIRLIGHTMAP_COMBINED
            half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
            o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);

        #elif DIRLIGHTMAP_SEPARATE
            half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
            half4 realtimeNormalTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicNormal, unity_DynamicLightmap, data.lightmapUV.zw);
            o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (realtimeColor, realtimeDirTex, normalWorld, true, realtimeNormalTex, o_gi.light3);
        #endif
    #endif

    o_gi.indirect.diffuse *= occlusion;

    //【4】返回此UnityGI类型的结构体
    return o_gi;
}

7.UNITY_BRDF_PBS宏
定义于UnityPBSLighting.cginc头文件中,根据不同的情况,将UNITY_BRDF_PBS宏定义为不同版本的UNITY_BRDF_PBS宏——是BRDF3_Unity_PBS、BRDF2_Unity_PBS还是BRDF1_Unity_PBS。
源码如下:

//-------------------------------------------------------------------------------------
// 默认使用BRDF || Default BRDF to use:
#if !defined (UNITY_BRDF_PBS) // 允许显式地在自定义着色器中重写BRDF的实现细节 || allow to explicitly override BRDF in custom shader
    //满足着色目标模型的版本小于Shader Model 3.0,或者是PlayStation 2平台
    #if (SHADER_TARGET < 30) || defined(SHADER_API_PSP2)
        // 为小于SM3.0的着色模型回退为低保真度的BRDF版本 || Fallback to low fidelity one for pre-SM3.0
        #define UNITY_BRDF_PBS BRDF3_Unity_PBS
    #elif defined(SHADER_API_MOBILE)
        // 为移动平台简化的BRDF版本 || Somewhat simplified for mobile
        #define UNITY_BRDF_PBS BRDF2_Unity_PBS
    #else
        //最高特效的SM3、PC平台或者游戏主机平台的BRDF版本 || Full quality for SM3+ PC / consoles
        #define UNITY_BRDF_PBS BRDF1_Unity_PBS
    #endif
#endif

三种情况下,BRDF3_Unity_PBS、BRDF2_Unity_PBS、 BRDF1_Unity_PBS三个函数的参数和返回值都一样,区别是内部的实现。在这边,以BRDF1_Unity_PBS为例。

half4 BRDF1_Unity_PBS (half3 diffColor,half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,half3 normal,half3 viewDir,UnityLight light, UnityIndirect gi)

第一个参数,half3型的diffColor,表示漫反射颜色的值。
第二个参数,half3型的specColor,表示镜面反射颜色值。
第三个参数,half型的oneMinusReflectivity,表示1减去反射率的值。
第四个参数,half型的oneMinusRoughness,表示1减去粗糙度的值。
第五次参数,half3型的normal,表示法线的方向。
第六个参数,half3型的viewDir,表示视线的方向。
第七个参数,UnityLight型的light,表示Unity中光源参数的结构体,包含half3型的光源颜色color,half3型的光源方向dir,half型的入射光方向和当前表面法线方向的点乘的积ndotl。

struct UnityLight
{
    half3 color;//光源颜色
    half3 dir;//光源方向
    half  ndotl; //入射光方向和当前表面法线方向的点积
};

第八个参数,UnityIndirect类型的gi ,一个包含了half3型的漫反射颜色diffuse和half3型的镜面反射颜色specular的光线反射结构体,表示间接光照信息。

struct UnityIndirect
{
    half3 diffuse;//漫反射颜色
    half3 specular;//镜面反射颜色
};

三种版本的函数分别贴出来,它们都定义于UnityStandardBRDF.cginc头文件中。
(1)BRDF1_Unity_PBS

//最高特效的SM3、PC平台或者游戏主机平台的BRDF版本 || Full quality for SM3+ PC / consoles
//-------------------------------------------------------------------------------------

// Note: BRDF entry points use oneMinusRoughness (aka "smoothness") and oneMinusReflectivity for optimization
// purposes, mostly for DX9 SM2.0 level. Most of the math is being done on these (1-x) values, and that saves
// a few precious ALU slots.

// Main Physically Based BRDF
// Derived from Disney work and based on Torrance-Sparrow micro-facet model
//
//   BRDF = kD / pi + kS * (D * V * F) / 4
//   I = BRDF * NdotL
//
// * NDF (depending on UNITY_BRDF_GGX):
//  a) Normalized BlinnPhong
//  b) GGX
// * Smith for Visiblity term
// * Schlick approximation for Fresnel
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
    half3 normal, half3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    half roughness = 1-oneMinusRoughness;
    half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);

    half nl = light.ndotl;
    half nh = BlinnTerm (normal, halfDir);
    half nv = DotClamped (normal, viewDir);
    half lv = DotClamped (light.dir, viewDir);
    half lh = DotClamped (light.dir, halfDir);

#if UNITY_BRDF_GGX
    half V = SmithGGXVisibilityTerm (nl, nv, roughness);
    half D = GGXTerm (nh, roughness);
#else
    half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
    half D = NDFBlinnPhongNormalizedTerm (nh, RoughnessToSpecPower (roughness));
#endif

    half nlPow5 = Pow5 (1-nl);
    half nvPow5 = Pow5 (1-nv);
    half Fd90 = 0.5 + 2 * lh * lh * roughness;
    half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5);

    // HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
    // BUT 1) that will make shader look significantly darker than Legacy ones
    // and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
    // NOTE: multiplication by Pi is part of single constant together with 1/4 now

    half specularTerm = max(0, (V * D * nl) * unity_LightGammaCorrectionConsts_PIDiv4);// Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
    half diffuseTerm = disneyDiffuse * nl;

    half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
    half3 color =   diffColor * (gi.diffuse + light.color * diffuseTerm)
                    + specularTerm * light.color * FresnelTerm (specColor, lh)
                    + gi.specular * FresnelLerp (specColor, grazingTerm, nv);

    return half4(color, 1);
}

(2)BRDF2_Unity_PBS

// 为移动平台简化的BRDF版本 || Somewhat simplified for mobile
// Based on Minimalist CookTorrance BRDF
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
//
// * BlinnPhong as NDF
// * Modified Kelemen and Szirmay-Kalos for Visibility term
// * Fresnel approximated with 1/LdotH
half4 BRDF2_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
    half3 normal, half3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);

    half nl = light.ndotl;
    half nh = BlinnTerm (normal, halfDir);
    half nv = DotClamped (normal, viewDir);
    half lh = DotClamped (light.dir, halfDir);

    half roughness = 1-oneMinusRoughness;
    half specularPower = RoughnessToSpecPower (roughness);
    // Modified with approximate Visibility function that takes roughness into account
    // Original ((n+1)*N.H^n) / (8*Pi * L.H^3) didn't take into account roughness
    // and produced extremely bright specular at grazing angles

    // HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
    // BUT 1) that will make shader look significantly darker than Legacy ones
    // and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
    // NOTE: multiplication by Pi is cancelled with Pi in denominator

    half invV = lh * lh * oneMinusRoughness + roughness * roughness; // approx ModifiedKelemenVisibilityTerm(lh, 1-oneMinusRoughness);
    half invF = lh;
    half specular = ((specularPower + 1) * pow (nh, specularPower)) / (unity_LightGammaCorrectionConsts_8 * invV * invF + 1e-4h); // @TODO: might still need saturate(nl*specular) on Adreno/Mali

    // Prevent FP16 overflow on mobiles
#if SHADER_API_GLES || SHADER_API_GLES3
    specular = clamp(specular, 0.0, 100.0);
#endif

    half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
    half3 color =   (diffColor + specular * specColor) * light.color * nl
                    + gi.diffuse * diffColor
                    + gi.specular * FresnelLerpFast (specColor, grazingTerm, nv);

    return half4(color, 1);
}

(3)BRDF3_Unity_PBS

// 为小于SM3.0的着色模型回退为低保真度的BRDF版本 || Fallback to low fidelity one for pre-SM3.0
// Old school, not microfacet based Modified Normalized Blinn-Phong BRDF
// Implementation uses Lookup texture for performance
//
// * Normalized BlinnPhong in RDF form
// * Implicit Visibility term
// * No Fresnel term
//
// TODO: specular is too weak in Linear rendering mode
half4 BRDF3_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
    half3 normal, half3 viewDir,
    UnityLight light, UnityIndirect gi)
{
    half3 reflDir = reflect (viewDir, normal);

    half nl = light.ndotl;
    half nv = DotClamped (normal, viewDir);

    // Vectorize Pow4 to save instructions
    half2 rlPow4AndFresnelTerm = Pow4 (half2(dot(reflDir, light.dir), 1-nv));  // use R.L instead of N.H to save couple of instructions
    half rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp
    half fresnelTerm = rlPow4AndFresnelTerm.y;

    half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));

    half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, oneMinusRoughness);
    color *= light.color * nl;
    color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm);

    return half4(color, 1);
}

ps:BRDF1_Unity_PBS函数的实现部分用到了最多的变量,最终表现效果最好,主要用于Shader Model 3.0、PC平台或者游戏主机平台。BRDF2_Unity_PBS简化了一部分计算,主要用于移动平台,而BRDF3_Unity_PBS是为Shader Model 小于3.0的着色模型提供基本版的BRDF,实现细节最为简陋。

8.UNITY_BRDF_GI宏
UNITY_BRDF_GI宏位于UnityPBSLighting.cginc头文件中
源码如下:

//-------------------------------------------------------------------------------------
// 从间接的方向光照贴图中进行BRDF(双向反射分布函数)的光照提取 || BRDF for lights extracted from *indirect* directional lightmaps (baked and realtime).
// 使用UNITY_BRDF_PBS从方向光源烘焙方向光照贴图, || Baked directional lightmap with *direct* light uses UNITY_BRDF_PBS.
// 若想得到更好的效果,可以使用BRDF1_Unity_PBS || For better quality change to BRDF1_Unity_PBS.
// SM2.0中的非方向光照贴图|| No directional lightmaps in SM2.0.

//若没有定义UNITY_BRDF_PBS_LIGHTMAP_INDIRECT宏
#if !defined(UNITY_BRDF_PBS_LIGHTMAP_INDIRECT)
    //定义UNITY_BRDF_PBS_LIGHTMAP_INDIRECT = BRDF2_Unity_PBS
    #define UNITY_BRDF_PBS_LIGHTMAP_INDIRECT BRDF2_Unity_PBS
#endif
//若没有定义UNITY_BRDF_GI宏
#if !defined (UNITY_BRDF_GI)
    //定义UNITY_BRDF_GI = BRDF_Unity_Indirect
    #define UNITY_BRDF_GI BRDF_Unity_Indirect
#endif

上面这段代码中关于UNITY_BRDF_GI宏的地方,就是说若没有定义UNITY_BRDF_GI宏,就定义一个UNITY_BRDF_GI宏等价于BRDF_Unity_Indirect。这边的BRDF_Unity_Indirect是一个函数名,就紧紧跟在上面这段宏代码的后面:

//间接光照的BRDF
inline half3 BRDF_Unity_Indirect (half3 baseColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, half3 normal, half3 viewDir, half occlusion, UnityGI gi)
{
    half3 c = 0;
    #if defined(DIRLIGHTMAP_SEPARATE)
        gi.indirect.diffuse = 0;
        gi.indirect.specular = 0;

        #ifdef LIGHTMAP_ON
            c += UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (baseColor, specColor, oneMinusReflectivity, oneMinusRoughness, normal, viewDir, gi.light2, gi.indirect).rgb * occlusion;
        #endif
        #ifdef DYNAMICLIGHTMAP_ON
            c += UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (baseColor, specColor, oneMinusReflectivity, oneMinusRoughness, normal, viewDir, gi.light3, gi.indirect).rgb * occlusion;
        #endif
    #endif
    return c;
}

关于此段代码,BRDF_Unity_Indirect 函数的核心部分其实就是在调用UNITY_BRDF_PBS_LIGHTMAP_INDIRECT,而上文的宏有交代过,UNITY_BRDF_PBS_LIGHTMAP_INDIRECT宏等价于 BRDF2_Unity_PBS。而BRDF2_Unity_PBS函数,其定义于UnityStandardBRDF.cginc中

9.Emission函数
Emission函数定于于UnityStandardInput.cginc头文件中,根据指定的自发光光照贴图,利用tex2D函数,对输入的纹理进行光照贴图的采样
源码如下:

//---------------------------------------【Emission函数】--------------------------------
// 用途:根据指定的自发光光照贴图,利用tex2D函数,对输入的纹理进行光照贴图的采样
// 输入参数:float2型的纹理坐标
// 输出参数:经过将自发光纹理和输入纹理进行tex2D采样得到的half3型的自发光颜色
//-----------------------------------------------------------------------------------------------
half3 Emission(float2 uv)
{
#ifndef _EMISSION
    return 0;
#else
    return tex2D(_EmissionMap, uv).rgb * _EmissionColor.rgb;
#endif
}

其中用于采样的自发光贴图对应的函数定义于UnityStandardInput.cginc头文件的一开始部分。

sampler2D     _EmissionMap;

相当于在CGPROGRAM中的顶点和片段着色函数之前,对这个变量进行声明,以便于CG语言块中使用的时候,能识别到他的含义。因为在Standard.shader源码的一开始,Properties块也就是属性值声明部分,对其进行了属性的声明:

//自发光纹理图
_EmissonMap(“Emisson”,2D) = “white” { }

10.UNITY_APPLY_FOG宏
UNITY_APPLY_FOG宏相关的一些代码用于雾效的启用与否的辅助工作,定义于UnityCG.cginc头文件中
源码如下:

//UNITY_FOG_LERP_COLOR宏的定义
#define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))

//【1】若已经定义了FOG_LINEAR、FOG_EXP、FOG_EXP2宏三者至少之一,便可以进行到此#if实现部分
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
    //【1-1】若满足着色目标模型的版本小于Shader Model 3.0,或者定义了SHADER_API_MOBILE宏,便可以进行到此#if实现部分
    #if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
        //移动平台和Shader Model 2.0:已经计算了每顶点的雾效因子,所以插一下值就可以了 ||mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp the color
        //定义 UNITY_APPLY_FOG_COLOR(coord,col,fogCol) 等价于UNITY_FOG_LERP_COLOR(col,fogCol,coord)
    #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,coord)

    //【1-2】 Shader Model 3.0和PC/游戏主机平台:计算雾效因子以及进行雾颜色的插值 ||SM3.0 and PC/console: calculate fog factor and lerp fog color
    #else
        //定义 UNITY_APPLY_FOG_COLOR(coord,col,fogCol)等价于UNITY_CALC_FOG_FACTOR(coord); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
        #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR(coord); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
    #endif
//【2】否则,直接定义UNITY_APPLY_FOG_COLOR宏
#else
    #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
#endif
//【3】若定义了UNITY_PASS_FORWARDADD(正向附加渲染通道)宏
#ifdef UNITY_PASS_FORWARDADD
    //定义UNITY_APPLY_FOG(coord,col) 等价于UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
    #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
//【4】否则,UNITY_APPLY_FOG(coord,col) 等价于 UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
#else
    #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
#endif

11.OutputForward函数
OutputForward函数定义于UnityStandardCore.cginc头文件中,其为正向渲染通道的输出函数
源码如下:

//-----------------------------【函数OutputForward】-----------------------------------
// 用途:正向渲染通道输出函数
//  输入参数:一个half4类型的一个颜色值output,一个half型的透明度值alphaFromSurface
// 返回值:经过透明处理的half4型的输出颜色值
//-------------------------------------------------------------------------------------------------
half4 OutputForward (half4 output, half alphaFromSurface)
{
    #if defined(_ALPHABLEND_ON) || defined(_ALPHAPREMULTIPLY_ON)
        output.a = alphaFromSurface;
    #else
        UNITY_OPAQUE_ALPHA(output.a);
    #endif
    return output;
}

其中UNITY_OPAQUE_ALPHA宏的定义为:

#define UNITY_OPAQUE_ALPHA(outputAlpha)  outputAlpha = 1.0

六、Bidirectional ReflectanceDistribution Function,BRDF(双向反射分布函数)
双向反射分布函数(Bidirectional ReflectanceDistribution Function,BRDF)用来定义给定入射方向上的辐射照度(irradiance)如何影响给定出射方向上的辐射率(radiance)。更笼统地说,它描述了入射光线经过某个表面反射后如何在各个出射方向上分布——这可以是从理想镜面反射到漫反射、各向同性(isotropic)或者各向异性(anisotropic)的各种反射。

参考:

  1. 如何正确理解 BRDF (双向反射分布函数)? - 计算机 - 知乎
    2.图形学理论知识:BRDF 双向反射分布函数
  2. An Introduction to BRDF-based Lighting –Nvidia

你可能感兴趣的:(Shaderlab Notizen 7-2 Standard Shader)