上节我们学习了LitShader大致是怎样的起作用的,留下了PBR相关的疑问,这节我们来解答一下URP下的PBR究竟做了什么优化。
要知道做了什么优化,就必须知道原来的PBR是什么样子。
Unity原来的Standard是这样写的:
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness, float3 normal, float3 viewDir, UnityLight light, UnityIndirect gi) { float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness); float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir); #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0 #if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV // The amount we shift the normal toward the view vector is defined by the dot product. half shiftAmount = dot(normal, viewDir); normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal; // A re-normalization should be applied here but as the shift is small we don't do it to save ALU. //normal = normalize(normal); half nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here #else half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact #endif half nl = saturate(dot(normal, light.dir)); float nh = saturate(dot(normal, halfDir)); half lv = saturate(dot(light.dir, viewDir)); half lh = saturate(dot(light.dir, halfDir)); // Diffuse term half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl; // Specular term // HACK: theoretically we should divide diffuseTerm by Pi 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 too in cases when they are injected into ambient SH float roughness = PerceptualRoughnessToRoughness(perceptualRoughness); #if UNITY_BRDF_GGX // GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping. roughness = max(roughness, 0.002); half V = SmithJointGGXVisibilityTerm (nl, nv, roughness); float D = GGXTerm (nh, roughness); #else // Legacy half V = SmithBeckmannVisibilityTerm (nl, nv, roughness); half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness)); #endif half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later # ifdef UNITY_COLORSPACE_GAMMA specularTerm = sqrt(max(1e-4h, specularTerm)); # endif // specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value specularTerm = max(0, specularTerm * nl); #if defined(_SPECULARHIGHLIGHTS_OFF) specularTerm = 0.0; #endif // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1) half surfaceReduction; # ifdef UNITY_COLORSPACE_GAMMA surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1] # else surfaceReduction = 1.0 / (roughness*roughness + 1.0); // fade in [0.5;1] # endif // To provide true Lambert lighting, we need to be able to kill specular completely. specularTerm *= any(specColor) ? 1.0 : 0.0; half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) + specularTerm * light.color * FresnelTerm (specColor, lh) + surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv); return half4(color, 1); }
我们直接看最后color的计算:
Color = 漫反射颜色*(GI漫反射+光照颜色*漫反射项)+高光反射项*光照颜色*菲涅耳项+表面反射缩减参数*GI高光反射*菲涅耳插值
其中,漫反射项是DisneyDiffuse,由D(NDF)V(Visiblity)组成的高光反射项和后面的菲涅尔项是BRDF的核心,环境高光由GI高光反射颜色、表面反射缩减参数和菲涅尔插值(将specularColor当作F0,grazingTerm当作F90)共同决定。
笔者看完两段代码做了个对比,发现环境光的计算URP基本上没有上面变化,关键是BRDF做了较大变化。
首先漫反射原来管线用的是Disney,而URP会将DirectBRDF所得漫反射和高光反射全部乘上一个radiance,radiance代表单位片元辐射量,具体计算就是光照、光照衰减和ndotl(重点),NdotL乘到BRDF漫反射项就是一个兰伯特(当然,这样是为了方便理解,其实按照辐射量的理解更好)。
然后就是BRDF高光项的计算URP做出了优化。BRDF中最重要的三个因子D、V、F(V是unity将G项和分母项进行了结合)中,V和F在URP做出了优化,URP中把VF的乘积综合到一个简化的公式中达到了优化的效果,我们可以对比一下公式:
内置管线Visibility:
URP的VF:
可以看出URP对于V上做了非常大的优化,至于这种优化的依据是什么,Unity官方也给出了指向:See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course
应该是Siggraph 2015的文章,文章的文件有点大,链接放这里:http://s2015.siggraph.org/sites/default/files/firstpages.reduced.pdf
有兴趣的小伙伴自己可以去看看。本来感觉这篇应该会写很多内容,后来看完之后发现一直贴代码会影响观感,于是还是自己总结出来直接放在这里好了(虽然总结的也不多~),这篇文章纯属是讲述URP Litshader过程中临时扩充出来的,就当做个记录吧,下一节我们学习什么呢,想了良久后,发现应该紧扣主题,所以我们下一节来看看URP的RenderFeature以及如何扩展URP(至于LitShader中其他的Pass一方面决定没什么必要讲,一方面时间也不是很充裕,应该会在其他系列再一起来学习:))。
各位小伙伴们如果有意见或者建议,可以在文章下方留言哦~