2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2022-03-14

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

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

ここではディレクショナルライトの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

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

鏡面反射光

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

  1. 光源から発せられた光がサーフェイスで反射したベクトル
  2. サーフェイスから視線(実際にはカメラ)方向へのベクトル
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

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

環境光

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

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

これまでの3つを足し算して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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?