ShaderTips

シェーダーTips

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

フォンの反射モデル

フォンの反射モデルとは

ランバート反射モデルにスペキュラ反射を追加したものです。

 

ny-program.hatenablog.com

スペキュラ反射

物体表面のハイライト(光沢)部分を表現するものです。

プラスチックや金属を見ると表面の一部に強いハイライトが発生しているあれです。

ハイライトは、サーフェスに対する視点の向きに応じて位置や大きさが変わります。

ですから、向きに依存せず同じ値を返すランバート反射モデルでは表現できません。

そこで反射色という概念を導入し、拡散色に加算してハイライトを表現します。

スペキュラ反射はこの反射色を計算するものです。

ただし、アルタイムレンダリングにおけるスペキュラ反射は、実際に光源の姿を映り込ませているわけではなく、それっぽいハイライトを描画する近似処理になります。

 

スペキュラ反射の反射量 = ( V・R )^n

V = 視点方向ベクトル

R = 反射ベクトル

n = 光沢度

 

( V・R )

視点方向ベクトルと反射ベクトルとの内積を計算しています。これは、視点と光源の方向が近いほどスペキュラ反射は強くなり、離れるほど弱くなるという観察による現象を再現した物です。

 
^n

 内積の結果をn乗した物がスペキュラ反射量になります。このnは光沢度(shininess)と言って、 表面の光沢具合を示す値です。

反射は表面がツルツルなほどハイライトは強くなり、範囲が小さくなります。逆に表面がザラザラなほど、ハイライトは弱くなり、範囲は広がります。

この変化は指数的に変化するため光沢度を乗数として使用しています。

 

反射ベクトルの求め方

当たり判定などでもよく計算する反射ベクトルについて、考えます。

「反射ベクトルと光源方向ベクトルを合成して2で除算したベクトル」と「光源方向ベクトル と法線ベクトルの内積に法線ベクトルを乗算したベクトル」が一致するのでそれを式にすると以下になります。

( R + L ) /  2 = N x ( N ・ L ) 

R  = 反射ベクトル

L = 光源方向ベクトル

N = 法線ベクトル

 

反射ベクトル以外を右辺に移行します。

R = ( N ・ ( N ・ L ) ) x 2 - L

これが反射ベクトルを求める式になります。

 

>||
Shader "Hidden/Phong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" { }
        // アンビエント光反射量
        _Ambient ("Ambient", Range(0, 1)) = 0
        //スペキュラー色
        _SpecColor ("Specular Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {

        Pass
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            //ライトカラー
            float4 _LightColor0;
            //スペキュラ色
            float4 _SpecColor;

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

            struct v2f
            {
                float2 uv: TEXCOORD0;
                float4 vertex: SV_POSITION;
                //ワールド空間の法線ベクトル
                float3 worldNormal: TEXCOORD1;
                //ワールド空間の頂点座標
                float3 worldPos: TEXCOORD2;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;

                //法線ベクトルをワールド空間座標に変換
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldNormal = worldNormal;

                //頂点をワールド空間座標に変換
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldPos = worldPos;

                return o;
            }

            fixed4 frag(v2f i): SV_Target
            {
                //法線ベクトル
                float3 normal = normalize(i.worldNormal);
                //視点方向ベクトル
                float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                //法線ベクトルと視点ベクトルの内積
                float NdotL = dot(normal, lightDir);
                //拡散色
                float diffusePower = max( 0, NdotL );
                //テクスチャマップからカラー値をサンプリング
                float tex = tex2D(_MainTex, i.uv);
                //拡散色の決定
                float4 diffuse = diffusePower * tex * _LightColor0;
                
                //カメラ方向ベクトル
                float viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                //法線 カメラの角度量
                float NdotV = dot(normal, viewDir);

                //フォンによるスペキュラ近似式1
                float3 R = -1 * viewDir + 2.0f * NdotV * normal;

                //フォンによるスペキュラ近似式2
                float LdotR = dot(lightDir, R);
                float3 specularPower = pow(max(0, LdotR), 10.0);
                
                //反射色の決定
                float4 specular = float4(specularPower, 1.0) * _SpecColor * _LightColor0;

                //拡散色と反射色を合算
                fixed4 color = diffuse + specular;
                return color;
            }
            ENDCG
            
        }
    }
}
||<

//法線ベクトル float3 normal = normalize(i.worldNormal); //視点方向ベクトル float3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); //法線ベクトルと視点ベクトルの内積 float NdotL = dot(normal, lightDir);
//拡散色
float diffusePower = max( 0, NdotL );

ランバート反射モデルの式です。

 

//拡散色の決定 float4 diffuse = diffusePower * tex * _LightColor0;

拡散色にテクスチャ色とディレクショナルライトのColorを掛けています。

 

//フォンによるスペキュラ近似式1 float3 R = -1 * viewDir + 2.0f * NdotV * normal;

先程の式を使って反射ベクトルを求めます。

視点ベクトルに-1を乗算しているのはUnityWorldSpaceViewDirが視点方向へのベクトルを返すからです。

 

//フォンによるスペキュラ近似式2 float LdotR = dot(lightDir, R);

float3 specularPower = pow(max(0, LdotR), 10.0);

 

スペキュラ反射の反射量の計算です。

反射ベクトルと光源方向ベクトルの内積を計算します。この値が 1.0 に近いほど、反射ベクトルと光 源方向ベクトルの向きが近く、光沢が強く現れる所ということななります。

角度に差がないほど光ってみえます。

powは累乗の計算をする関数で光沢度の計算をしています。

 

 

//拡散色と反射色を合算

fixed4 color = diffuse + specular; return color;

最後にランバート反射モデルで求めた拡散色とスペキュラ反射の色を合算してその色を返します。

 

以上がフォンの反射モデルの解説になります。