JKになりたい

何か書きたいことを書きます。主にWeb方面の技術系記事が多いかも。

Shaderやっていく〜Phong反射とBlinn-Phong反射〜

完成図

f:id:sakata_harumi:20190518175030p:plain:w400

(左から、Phong、BilinnPhong、Standard)

Phong反射とは

前回は、拡散反射のみを扱う反射モデルであるランバート反射を取り扱いましたが、今回取り扱うPhong反射では鏡面反射も考慮することができます。

鏡面反射とは、一定方向からの光が別の一定方向へ出ていくような反射です。これにより、光沢、艶のある表面を表現することができます。

Phong反射では最終的な表面の光の強度はI = 拡散成分 + 鏡面成分で計算されます。
拡散成分は、ランバート反射のときと同様、面の法線ベクトルと入射光ベクトルの内積から計算されます。

では、鏡面成分ですがこれはS=(R \cdot V)^ pで計算されます。ここのpは鏡面反射の強度になります。 光の反射ベクトルRとViewベクトルのなす角が小さいほど(近いほど)反射は強くなる、ということを示していることがわかると思います。

f:id:sakata_harumi:20190518183056p:plain:w400

反射ベクトルRは、R=2N \cdot (N \cdot L) -Lで計算できます。

Phong反射の実装

Shader "Custom/Phong"
{
    Properties
    {
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1) 
        _MainTex ("Base (RGB)", 2D) = "white" {} 
        _SpecularColor ("Specular Color", Color) = (1,1,1,1) 
        _SpecPower ("Specular Power", Range(0,30)) = 1 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Phong
        #pragma target 3.0

        struct Input
        {
            float2 uv_MainTex;
        };

        float4 _SpecularColor; 
        sampler2D _MainTex; 
        float4 _MainTint; 
        float _SpecPower;

        void surf (Input IN, inout SurfaceOutput o)
        {
            half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        fixed4 LightingPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) 
        { 
            float NdotL = dot(s.Normal, lightDir); 
            float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir); 
            
            float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower); 
            float3 finalSpec = _SpecularColor.rgb * spec; 

            fixed4 c; 
            c.rgb = (s.Albedo * _LightColor0.rgb * max(0,NdotL) * atten) + (_LightColor0.rgb * finalSpec); 
            c.a = s.Alpha; 
            return c; 
        }
        ENDCG
    }
    FallBack "Diffuse"
}

数式と一対一で対応しているので、特に言及するところはないのですが、一応見ていきます。

float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);
この部分が反射ベクトルR、R=2N \cdot (N \cdot L) -Lを計算している部分になります。

float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower);
ここで、鏡面成分であるS=(R \cdot V)^ pを計算しています。鏡面成分がマイナスにならないようにしています。

c.rgb = (s.Albedo * _LightColor0.rgb * max(0,NdotL) * atten) + (_LightColor0.rgb * finalSpec);
最後に、最終的な表面カラーを決定しています。拡散成分+鏡面成分です。
拡散成分は、前回みましたランバート反射になっていますね。

Blinn-Phong反射とは

Phong反射は、反射ベクトルRをR=2N \cdot (N \cdot L) -Lし、RとViewベクトルの内積で反射のパラメータを決定していました。
このRの計算量を減らすため、別のパラメータを用いるのがBlinn-Phong反射です。

Blinn-Phong反射ではRの代わりに、「ハーフベクトル」と呼ばれるViewベクトル+入射光ベクトルを正規化したものを採用します。
このハーフベクトルと、法線ベクトルとの内積を反射のパラメータとします。

f:id:sakata_harumi:20190518213916p:plain:w400

ハーフベクトルの計算は「Viewベクトル+入射光ベクトルを正規化したもの」ですので、H=\frac{V+L}{|V+L|}になります。

Blinn-Phong反射の実装

Shader "Custom/BlinnPhong"
{
    Properties
    {
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1) 
        _MainTex ("Base (RGB)", 2D) = "white" {} 
        _SpecularColor ("Specular Color", Color) = (1,1,1,1) 
        _SpecPower ("Specular Power", Range(0,60)) = 3
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf CustomBlinnPhong 
        #pragma target 3.0

        struct Input
        {
            float2 uv_MainTex;
        };

        float4 _SpecularColor; 
        sampler2D _MainTex; 
        float4 _MainTint; 
        float _SpecPower;

        void surf (Input IN, inout SurfaceOutput o)
        {
            half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten) 
        { 
            float NdotL = max(0,dot(s.Normal, lightDir)); 

            float3 halfVector = normalize(lightDir + viewDir); 
            float NdotH = max(0, dot(s.Normal, halfVector)); 
            float spec = pow(NdotH, _SpecPower) * _SpecularColor; 

            float4 color; 
            color.rgb = (s.Albedo * _LightColor0.rgb * NdotL) + 
                    (_LightColor0.rgb * _SpecularColor.rgb * spec) * atten; 
            color.a = s.Alpha; 
            return color; 
        } 
        ENDCG
    }
    FallBack "Diffuse"
}

LightingCustomBlinnPhongの実装だけ少しみていきたいと思います。

ハーフベクトルの計算 H=\frac{V+L}{|V+L|}normalize(lightDir + viewDir)で行えます。簡単ですね。

float NdotH = max(0, dot(s.Normal, halfVector));
反射の強さのパラメータは、ハーフベクトルと法線ベクトルの内積でした。

float spec = pow(NdotH, _SpecPower) * _SpecularColor;
_SpecPowerパラメータによって反射の強さ(表面の光沢具合)を調節できます。

おわりに

次で反射モデル系は最後にしたいと思います。等方性の拡散反射、鏡面反射をやったので、次は異方性の鏡面反射を見ていきたいと思います!