ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

【Unity】Disney Diffuse BRDF

記号 説明
 v 視線方向を表す単位ベクトル
 l 入射光の方向を表す単位ベクトル
 n 法線方向を表す単位ベクトル
 h ハーフベクトル
 f_{d} BRDFの拡散反射成分
 \sigma 拡散反射率(ディフューズアルベド
 f_{0} 法線方向から入射する光の反射率
 f_{90} グレージング角から入射する光の反射率

Disney Diffuse BRDFとは?

Disney Diffuse BRDFとはBurley Diffuseとも呼ばれ、エネルギー保存の法則を厳密に守っているわけではなく、発案者の経験則に基づいて提案されました。 特徴的なのは、DiffuseモデルにFresnelの効果が組み込まれている点です。

 {\displaystyle
f_{d}(v,l)=\frac{{\sigma}}{\pi}F_{Schlick}(n,l,1,f_{90})F_{Schlick}(n,v,1,f_{90}) \
}

 {\displaystyle
f_{90}=0.5+2 \alpha cos^{2}(\theta_{d}) \
}

{\frac{\sigma}{\pi}}はディフューズアルベドに円周率を除算しています。 これは古典的なランバート反射です。 その後に続く {\displaystyle
F_{Schlick} \
}はFresnelのSchlickによる近似式で、以下のような式です。

 {\displaystyle
F_{Schlick}(v,h,f_{0},f_{90})=f_{0}+(f_{90}-f_{0})(1-v \cdot h)^{5} 
}

これを最初の式に代入した式がこちらです。

 {\displaystyle
f_{d}(v,l)=\frac{\sigma}{\pi}(1+(f_{90}-1)(1-NdotL)^{5})(1+(f_{90}-1)(1-NdotV)^{5})
}

 {\displaystyle
f_{90}=0.5+2 * LdotH^{2} * roughnees
}

式の考察

{\frac{\sigma}{\pi}}の係数であるフレネル部分について考えていきます。

 {\displaystyle
f_{90}}の値が大きくなるほどフレネルの値が大きくなることは明白です。

roughneesは粗さを表しており、金属に近い質感にしたいほど大きな値をデザイナーが設定します。 金属はフレネル反射が強くなるため、値が大きくなるほど、 {\displaystyle
f_{90}}の値も大きくなるようになっています。

 {\displaystyle LdotH^{2}
}はLightとViewの角度が同一方向の時にHdotLが1となり、角度が離れるほど値が小さくなります。 そのため、拡散反射というよりは、指向性を持った再帰反射モデルに近いものだと思います。

指向性 ・・・太陽光ように表面の輝度がどの角度から見ても一定である拡散反射とは異なり、光源の方向により、表面の輝度が異なる性質

https://jsdkk.com/home/wp-content/uploads/2018/08/708b54f80fc4ded690f5831cf1bee86a.png 用語解説【レーザー】 | 日本システムデザイン株式会社引用

再帰反射・・・反射光が入射光と同一方向に反射する現象

f:id:Ny_Program:20211018101500p:plain 第4回 光の話 再帰(再帰性)反射|CCS:シーシーエス株式会社引用

実装

さきほどの公式をシェーダーで書くとこうなります。

Shader "DisneyDiffuseBRDF"
{
    Properties
    {
        _Roughness("Roughness", Range(0.0, 1.0)) = 0.5
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                half3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                half3 worldNormal : TEXCOORD2;
                half3 viewDir : TEXCOORD3;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.viewDir = UnityWorldSpaceViewDir(worldPos);
                return o;
            }

            sampler2D _MainTex;
            float _Roughness;
            float3 _LightColor0;

            inline half3 F_Schlick(half3 f0, half3 f90, half cos)
            {
                return f0 + (f90 - f0) * pow(1 - cos, 5);
            }

            inline half Fd_Burley(half ndotv, half ndotl, half ldoth, half roughness)
            {
                half fd90 = 0.5 + 2 * ldoth * ldoth * roughness;
                half lightScatter = F_Schlick(1,fd90,ndotl);
                half viewScatter = F_Schlick(1,fd90,ndotv);
                half diffuse = lightScatter * viewScatter / UNITY_PI;
                return diffuse;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half3 normal = normalize(i.worldNormal);
                half3 viewDir = normalize(i.viewDir);
                half ndotv = abs(dot(normal, viewDir));
                float ndotl = max(0, dot(normal, _WorldSpaceLightPos0.xyz));
                float3 halfDir = normalize(_WorldSpaceLightPos0.xyz + viewDir);
                half ldoth = max(0, dot(_WorldSpaceLightPos0.xyz, halfDir));
                
                
                half diffuse = Fd_Burley(ndotv,ndotl,ldoth,_Roughness);
                return fixed4(diffuse * _LightColor0.rgb,1);
            }
            ENDCG
        }
    }
}