• URP学习之六--URP下的PBR


    上节我们学习了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一方面决定没什么必要讲,一方面时间也不是很充裕,应该会在其他系列再一起来学习:))。

    各位小伙伴们如果有意见或者建议,可以在文章下方留言哦~

  • 相关阅读:
    Linux基础命令(一)
    You've made choice
    protege推理
    字符编码
    第二次作业
    数据类型-集合set
    数据类型-元组&字典
    数据类型-列表
    数据类型-数值&字符串
    流程控制之for循环
  • 原文地址:https://www.cnblogs.com/shenyibo/p/12550937.html
Copyright © 2020-2023  润新知