【Unity】Phong反射モデルを実装する

UnityのshaderでPhong反射モデルを実装する。
Phong反射モデルは以下の３つを合成する。

- 拡散反射光
- 鏡面反射光
- 環境光

ここではディレクショナルライトのPhong反射モデルを実装する。

# 拡散反射光

ここでは**Lambert拡散反射モデル**を使用する。これは、**光が当たっている部分は明るく、そうでない部分は暗くなる**、直感的でわかりやすいモデル。

```Lambert.shader
Shader "Boo/Lambert"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            // _LightColor0を利用するのに必要
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                // ローカル空間座標なので、ワールド空間座標に変換する。
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o    ;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // サーフェイスからライトへの向き
                float3 toLightDir = _WorldSpaceLightPos0.xyz;
                float t = dot(i.normal, toLightDir);
                // 0以下の値は必要ないので0~1にクランプする
                t = saturate(t);
                fixed4 diffuseColor = _LightColor0 * t;
                
                fixed4 col = tex2D(_MainTex, i.uv);
                // テクスチャの色に拡散反射光を乗算して最終的な出力とする
                col *= diffuseColor;
                return col;
            }
            ENDCG
        }
    }
}
```

![スクリーンショット 2022-03-13 214820.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/2554a410-cc14-cacf-7308-833209d42a94.png)


- Lambert拡散反射モデルでは法線が必要なので頂点シェーダーへの入力（appdata）で受け取るようにする。
- `UnityObjectToWorldNormal`を利用して法線をローカル座標からワールド座標に変換できる。
- サーフェイスから光源への向きは`_WorldSpaceLightPos0.xyz`で取得できる。（並行光源の場合）
- 光源色は`#include "Lighting.cginc"`を追加した上で、`_LightColor0`で取得できる。

# 鏡面反射光

ここでは**Phong鏡面反射モデル**を用いる。これは**金属**のような反射も表現するモデルである。以下の二つのベクトルの向きが同じである程強い反射光が得られる。

1. 光源から発せられた光がサーフェイスで反射したベクトル
1. サーフェイスから視線（実際にはカメラ）方向へのベクトル

```Phong.shader
Shader "Boo/Phong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 vertex : SV_POSITION;
                // ワールド座標系での頂点座標
                float3 vertexW : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            fixed4 frag (v2f i) : SV_Target
            {
                // 光源からサーフェイスへのベクトル
                float3 lightToSurface = -_WorldSpaceLightPos0.xyz;
                // サーフェイスに入射した光の反射光を求める
                float3 r = reflect(lightToSurface, i.normal);
                r = normalize(r);

                // サーフェイスから視線方向へのベクトル
                float3 toEye = normalize(_WorldSpaceCameraPos -  i.vertexW);

                float t = dot(r, toEye);
                t = saturate(t);
                // 適当に絞る
                t = pow(t, 10.0f);

                float4 specular = _LightColor0 * t;
                
                fixed4 col = tex2D(_MainTex, i.uv);
                return col * specular;
            }
            ENDCG
        }
    }
}
```

![スクリーンショット 2022-03-13 214649.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/29680757-8ea9-e98c-9bab-0422b127fb06.png)


- `unity_ObjectToWorld`はローカル座標系からワールド座標系への変換行列。
- `reflect`を使って反射ベクトルを求めることができる。
- カメラの位置は`_WorldSpaceCameraPos`で取得できる。

# 環境光

現実世界では光は何度も反射を繰り返すことで、光源の影になる面でも多少の光をうけとることになる。これを計算することは困難なので、**すべてのサーフェイスを一律で明るくする**。

# 拡散反射光 + 鏡面反射光 + 環境光

これまでの３つを足し算してPhong反射モデルが求まる。

```Phongの反射モデル.shader
Shader "Boo/PhongLightModel"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
                float3 vertexW : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            fixed4 frag (v2f i) : SV_Target
            {
                // Lambert拡散反射
                float3 toLightDir = _WorldSpaceLightPos0.xyz;
                float t = dot(i.normal, toLightDir);
                t = saturate(t);
                fixed4 diffuseColor = _LightColor0 * t;

                // Phong鏡面反射
                float3 lightToSurface = -_WorldSpaceLightPos0.xyz;
                float3 r = reflect(lightToSurface, i.normal);
                r = normalize(r);
                float3 toEye = normalize(_WorldSpaceCameraPos -  i.vertexW);
                t = saturate(dot(r, toEye));
                t = pow(t, 10.0f);
                float4 specular = _LightColor0 * t;

                float4 light = diffuseColor + specular;
                // 環境光（一律で底上げ）
                light.xyz += 0.2f;
                
                fixed4 col = tex2D(_MainTex, i.uv);
                col *= light;
                return col;
            }
            ENDCG
        }
    }
}
```

![スクリーンショット 2022-03-13 220805.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85509/73b9fac0-3f9e-6250-c327-e25d7b27a760.png)