这篇文章用来讨论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
}
网友评论