Unity 5.x Standard Shader实现分析

这篇文章用来讨论Unity的标准着色器实现,根据Unity 5.4.2版本的代码进行分析。Unity 5.3之前的版本的据说没有增加GGX的实现,所以Unity 5.4之后的版本都可以。
Unity的Standard Shader具体实现,在UnityStandardBRDF.cginc文件中。代码中有三个BRDF的实现函数,对应不同的BRDF模型实现。这里主要分析第一个BRDF函数,也就是BRDF1_Unity_PBS,使用的BRDF模型的公式可以参考:http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html

首先,我们要清楚BRDF1_Unity_PBS函数的依据,即根据Torrance-Sparrow 微表面模型的公式:
f(l,v)=D(h)F(v,h)G(l,v,h)/(4(n⋅l)(n⋅v))
以及BRDF公式:
BRDF = kD / pi + kS * (D * V * F) / 4
然后拆分公式,一项项的实现。

D——微表面分布项
V——遮挡可见性项
F——菲涅尔反射项
kD——漫反射系数
kS——镜面反射系数
Note:V(Visibility)项即G(l,v,h)/(4(n⋅l)(n⋅v))的集合。

最简单的是菲涅尔因子的计算,在代码中实现:

//菲涅尔项的计算
//菲涅尔的近似公式为F=F0+(1-F0)*(1-(H*V))^5
//F0是光线垂直入射的反射率
inline half3 FresnelTerm (half3 F0, half cosA)

{

half t = Pow5 (1 - cosA);  // ala Schlick interpoliation

return F0 + (1-F0) * t;

}

V项和D项,在这里出现了分支:
如果UNITY_BRDF_GGX为真,V项和D项使用GGX的公式来实现。
否则,V项和D项使用Smith-Beckmann和Blinn-Phong公式实现。
Note:公式在上方链接中可查。
分支选择代码如下:

#if UNITY_BRDF_GGX
    half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
    half D = GGXTerm (nh, roughness);
#else
    // Legacy
    half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
    half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
#endif

然后,依次分析四个函数。首先是SmithJointGGXVisibilityTerm ,Unity使用了简化版的近似公式。
如下面代码所示:#if 0表示原始公式实现的这部分代码永远不会执行。

inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half roughness)
{
//#if 0分支不会被执行,执行下面的简化分支
#if 0
    //原始公式
    // Original formulation:
    //  lambda_v    = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
    //  lambda_l    = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
    //  G           = 1 / (1 + lambda_v + lambda_l);

    // Reorder code to be more optimal
    half a          = roughness;
    half a2         = a * a;

    half lambdaV    = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
    half lambdaL    = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
    //简化的可见性项
    // Simplify visibility term: (2.0f * NdotL * NdotV) /  ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
    return 0.5f / (lambdaV + lambdaL + 1e-5f);  // This function is not intended to be running on Mobile,
                                                // therefore epsilon is smaller than can be represented by half
#else
    //上述公式的近似,简化了sqrt,数学不正确但足够接近
    // Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
    half a = roughness;
    half lambdaV = NdotL * (NdotV * (1 - a) + a);
    half lambdaL = NdotV * (NdotL * (1 - a) + a);

    return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif
}

你可能感兴趣的:(Unity 5.x Standard Shader实现分析)