Unity中的BRDF公式

Unity中的PBR
1.PBR的相關(guān)文件
2.Unity中的BRDF (漫反射項(xiàng)與基于GGX的高光反射項(xiàng))
3.能量守恒
4.簡單的PBR_Shader

UnityPBR模型存在與他的幫助文件里。shader中沒有類和命名空間的概念。通過#include的方式將不同的文件加載到當(dāng)前shader中。
下面是Unity中PBR涉及到的cg文件及其包含結(jié)構(gòu)



Unity實(shí)際上有多個(gè)實(shí)現(xiàn)。它會(huì)根據(jù)目標(biāo)平臺(tái)、硬件和API級(jí)別來決定使用哪個(gè)。這個(gè)算法可以通過UNITY_BRDF_PBS宏進(jìn)行訪問,這個(gè)宏在UnityPBSLighting中進(jìn)行定義。而 BRDF代表的是雙向反射分布函數(shù)。

//UNITY_BRDF_PBS定義 根據(jù)平臺(tái)的不同選擇 選擇三個(gè)等級(jí)的BRDF 默認(rèn)會(huì)選擇BRDF1也是效果最好的
// Default BRDF to use:
#if !defined (UNITY_BRDF_PBS) // allow to explicitly override BRDF in custom shader
    // still add safe net for low shader models, otherwise we might end up with shaders failing to compile
    #if SHADER_TARGET < 30 || defined(SHADER_TARGET_SURFACE_ANALYSIS) // only need "something" for surface shader analysis pass; pick the cheap one
        #define UNITY_BRDF_PBS BRDF3_Unity_PBS
    #elif defined(UNITY_PBS_USE_BRDF3)
        #define UNITY_BRDF_PBS BRDF3_Unity_PBS
    #elif defined(UNITY_PBS_USE_BRDF2)
        #define UNITY_BRDF_PBS BRDF2_Unity_PBS
    #elif defined(UNITY_PBS_USE_BRDF1)
        #define UNITY_BRDF_PBS BRDF1_Unity_PBS
    #else
        #error something broke in auto-choosing BRDF
    #endif
#endif

BRDF函數(shù)在 UnityStandardBRDF.cginc 中定義

BRDF1:

漫反射項(xiàng) ,BRDF 用了 DisneyDiffuse :
// Note: Disney diffuse must be multiply by diffuseAlbedo / PI. This is done outside of this function.
half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
    half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
    // Two schlick fresnel term
    half lightScatter   = (1 + (fd90 - 1) * Pow5(1 - NdotL));
    half viewScatter    = (1 + (fd90 - 1) * Pow5(1 - NdotV));

    return lightScatter * viewScatter;
}

根據(jù)Cook-Torrance 的微表面高光BRDF公式

把高光分成三項(xiàng)
D項(xiàng) Normal Distribution Function (NDF) 法線分布函數(shù)

DGGX = a2 / π((a2 – 1) (n · h)2 + 1)2
a = roughness2

inline float GGXTerm (float NdotH, float roughness)
{
    float a2 = roughness * roughness;
    float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
    return UNITY_INV_PI * a2 / (d * d + 1e-7f); // This function is not intended to be running on Mobile,
                                            // therefore epsilon is smaller than what can be represented by half
}
G項(xiàng) Geometric Shadowing 陰影遮擋函數(shù)

G(l, v) = G1(l) G1(v)
G1(x) = 1 / ((n · x) (1 - K) + K)
K = roughness2 / 2
上述公式對(duì)應(yīng)的函數(shù)為SmithGGXVisibilityTerm,而實(shí)際使用的函數(shù)為SmithJointGGXVisibilityTerm

// Ref: http://jcgt.org/published/0003/02/03/paper.pdf
inline float SmithJointGGXVisibilityTerm (float NdotL, float NdotV, float roughness)
{
#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
    // Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
    float a = roughness;
    float lambdaV = NdotL * (NdotV * (1 - a) + a);
    float lambdaL = NdotV * (NdotL * (1 - a) + a);

#if defined(SHADER_API_SWITCH)
    return 0.5f / (lambdaV + lambdaL + 1e-4f); // work-around against hlslcc rounding error
#else
    return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif

#endif
}
F項(xiàng) 菲涅耳公式

RF(l, h) = F0 + (1 - F0) (1 - l · h)5

inline half3 FresnelTerm (half3 F0, half cosA)
{
    half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
    return F0 + (1-F0) * t;
}

BRDF1_Unity_PBS 源碼

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);

    float 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

    float 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);
    float 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

    float 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);
}
順便提下金屬流的能量守恒 用UnityStandardUtils.cginc 中的DiffuseAndSpecularFromMetallic
inline half OneMinusReflectivityFromMetallic(half metallic)
{
    // We'll need oneMinusReflectivity, so
    //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
    // store (1-dielectricSpec) in unity_ColorSpaceDielectricSpec.a, then
    //   1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
    //                  = alpha - metallic * alpha
    half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}

inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
    specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
    oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
    return albedo * oneMinusReflectivity;
}

簡單的PBS_Shader

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/03_UnityBRDF"
{
    Properties
    {
        _Metallic("_Metallic",Range(0,1))=0
        _DiffuseColor("DiffuseColor",Color)=(1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
        _Smoothness("Smoothness",Range(0,1))=0
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            #include"UnityStandardUtils.cginc"
            #include"AutoLight.cginc"
            #include "UnityCG.cginc"
            #include "UnityPBSLighting.cginc"

            struct a2v
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal:TEXCOORD1;
                float2 uv : TEXCOORD0;
                float3 worldPos:TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Metallic;
            float _Smoothness;
            fixed3 _DiffuseColor;

            v2f vert (a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldPos=mul(unity_ObjectToWorld,v.vertex);
                o.normal=UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //data
                float3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos)) ;
                float3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos)) ;
                float3 worldNormal=normalize(i.normal);

                fixed3 albedo=tex2D(_MainTex,i.uv).xyz*_DiffuseColor.xyz;

                half3 specColor;
                half oneMinusReflectivity;
                albedo=DiffuseAndSpecularFromMetallic(albedo,_Metallic,specColor,oneMinusReflectivity);

                UnityLight DirectLight;
                DirectLight.dir=worldLightDir;
                DirectLight.color=_LightColor0.xyz;
                DirectLight.ndotl=DotClamped(worldNormal,worldLightDir);
            
                UnityIndirect InDirectLight;
                InDirectLight.diffuse=0;
                InDirectLight.specular=0;
        
                return UNITY_BRDF_PBS(albedo,specColor,oneMinusReflectivity,
                                                            _Smoothness,worldNormal,worldViewDir,
                                                            DirectLight,InDirectLight);
            }
            ENDCG
        }
    }
}

效果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 每個(gè)周末自己的時(shí)間最不能掌控,寶寶今天身體不舒服,所以今天的打卡先完成,后面再完美吧。 1.篩選的基本用法,舉例說...
    Jessie1988閱讀 190評(píng)論 0 0
  • 請(qǐng)教高手我的樣品如何去鹽更好:低豐度血漿樣品,蛋白量約300ug,體積約60ul,因含鹽濃度高達(dá)160mM,而我的...
    xiaosine閱讀 590評(píng)論 0 0
  • 不為人知的排序和篩選的高級(jí)用法 看了70頁“黃紅發(fā)”的《人力資源在左,員工在右》,重新定義了管理的方法。楊潔的“...
    文瑜_19d3閱讀 313評(píng)論 0 0
  • E戰(zhàn)到底打卡?第3天 越是碎片化時(shí)代,越要系統(tǒng)性學(xué)習(xí)。 大家好,我是office職場(chǎng)大學(xué)的跳跳。今天我學(xué)習(xí)了不為人...
    2隊(duì)_跳跳閱讀 227評(píng)論 0 0
  • 4月8日跨境電子商務(wù)零售進(jìn)口稅收新政將落地,行郵稅政策也將同步調(diào)整:將行郵稅原來有四檔稅率10%、20%、30%和...
    排骨湯不加鹽閱讀 545評(píng)論 0 2

友情鏈接更多精彩內(nèi)容