ピクセル単位の計算

前回のシェーダーで鏡面反射光を実装したので、今回はフォンシェーディングを実装します。

これまでは各頂点の法線を利用して頂点色を求め、頂点間の線形補間によるシェーディングを行っていました。
フラグメントシェーダは受け取った COLOR0 の色を塗るだけでしたが、
今回からは各ピクセルの法線を利用したピクセル単位の計算を行うことで滑らかなシェーディングを行います。

基本的には、頂点シェーダで行っていた計算をフラグメントシェーダに移すことになりますが、
法線とあわせてワールド座標系における頂点ベクトルを TEXCOORD0 セマンティクスに格納しておき、
フラグメントシェーダ側でカメラベクトルを得るために使います。

計算式の定義については、

$$ \vec{L} \cdot \vec{N} = |\vec{L}||\vec{N}|\cos \theta $$

を拡散反射光に適用して、

$$ \vec{R} = 2\vec{N}(\vec{L} \cdot \vec{N}) - \vec{L} $$ $$ (\vec{R} \cdot \vec{V})^n $$

を鏡面反射光に適用することとします。

鏡面反射光の実装

フォンシェーディングはグローシェーディングに比べて、特に鏡面反射光に品質差が生じるため、
前回と同様の流れでまずは鏡面反射光だけを実装してみます。

Shader "Custom/Shader" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct v2f {
                float4 pos      : SV_POSITION;
                float3 normal   : NORMAL;
                float4 posWorld : TEXCOORD0;
            };

            fixed4 _LightColor0;

            v2f vert(appdata v) {
                v2f o;
                o.pos      = mul(UNITY_MATRIX_MVP, v.vertex);
                o.normal   = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            half4 frag(v2f i) : SV_Target {
                float3 light = normalize(_WorldSpaceLightPos0.xyz);
                float3 view  = normalize(_WorldSpaceCameraPos.xyz - i.posWorld);
                float3 r     = max(0, dot(reflect(-light, i.normal), view));

                fixed3 specular = _LightColor0.rgb * pow(r, 10.0);

                return half4(specular, 1.0);
            }
            ENDCG
        }
    }
}

環境光・拡散反射光・鏡面反射光の合成

環境光・拡散反射光・鏡面反射光をあわせると下記のような結果になります。

Shader "Custom/Shader" {
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct v2f {
                float4 pos      : SV_POSITION;
                float3 normal   : NORMAL;
                float4 posWorld : TEXCOORD0;
            };

            fixed4 _LightColor0;

            v2f vert(appdata v) {
                v2f o;
                o.pos      = mul(UNITY_MATRIX_MVP, v.vertex);
                o.normal   = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            half4 frag(v2f i) : SV_Target {
                float3 light = normalize(_WorldSpaceLightPos0.xyz);
                float3 view  = normalize(_WorldSpaceCameraPos.xyz - i.posWorld);
                float3 r     = max(0, dot(reflect(-light, i.normal), view));

                fixed3 diffuse  = _LightColor0.rgb * max(0, dot(light, i.normal));
                fixed3 specular = _LightColor0.rgb * pow(r, 10.0);

                return half4(UNITY_LIGHTMODEL_AMBIENT.rgb + diffuse + specular, 1.0);
            }
            ENDCG
        }
    }
}

出力結果の比較

左が前回の頂点単位の照明計算、右が今回のピクセル単位の照明計算の結果です。
どちらも同じモデルですが、並べて見ることで特に鏡面反射光の品質向上が確認できます。

ライセンス表記

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています