Phong

Phong光照理论:物体表面反射光由三部分组成:环境光,漫反射,镜面反射(高光)。

alt

漫反射的计算即Lambert光照模型,镜面反射的计算如下。

alt

与Lambert公式类似,不同之处在于,这里和法线点乘的向量有入射光方向变成了视线方向(人直接看到的地方才高亮),并加以光泽度约束(实际约束了高光部分的范围,越小高光区域越大)。

Shader "Custom/Phong"
{
    Properties
    {
        _MainColor("MainColor",Color) = (1, 1, 1, 1)
        _Shininess("Shininess",Range(1,100)) = 1
        _SpecularColor("Specular Color",Color) = (0, 0, 0, 0)
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityLightingCommon.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                fixed4 color : COLOR0;
            };

            fixed4 _MainColor;
            fixed4 _SpecularColor;
            half _Shininess;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //法线向量 由模型空间变换到世界空间并标准化
                float3 n = UnityObjectToWorldNormal(v.normal);
                n = normalize(n);

                //灯光法向向量
                fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
                
                //视线方向向量
                fixed3 view = normalize(WorldSpaceViewDir(v.vertex));

                //漫反射
                fixed ndotl = dot(n,l);
                fixed4 dif = _LightColor0 * _MainColor * saturate(ndotl);

                //镜面反射 光线方向取负,输入Reflect函数为光源到顶点的方向
                float3 ref = reflect(-l, n);
                ref = normalize(ref);
                fixed rdotv = saturate(dot(ref,view));
                fixed4 spec = _LightColor0 * _SpecularColor * pow(rdotv, _Shininess);

                //环境光 + 漫反射 +镜面反射
                o.color = unity_AmbientSky + dif + spec;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}

alt

Blinn-Phong

为减少计算量,将Phong光照模型中的v替换成半角向量h(视角方向和灯光方向角平分方向),视觉效果差距不大,但当观察者和灯光离物体很远时镜面反射近似为常数。

半角向量计算公式:

alt

Blinn-Phong 镜面反射计算公式:

alt

修改镜面反射部分代码。


            fixed4 frag (v2f i) : SV_Target
            {
                 //法线向量 由模型空间变换到世界空间并标准化
                float3 n = UnityObjectToWorldNormal(i.normal);
                n = normalize(n);

                //灯光法向向量
                fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
                
                //视线方向向量
                fixed3 view = normalize(WorldSpaceViewDir(i.vertex));

                //漫反射
                fixed ndotl = dot(n,l);
                fixed4 dif = _LightColor0 * _MainColor * saturate(ndotl);

                //镜面反射 光线方向取负,输入Reflect函数为光源到顶点的方向
                float3 h = normalize(l + view);
                fixed ndoth = saturate(dot(h, n));
                fixed4 spec = _LightColor0 * _SpecularColor * pow(ndoth, _Shininess);

                //环境光 + 漫反射 +镜面反射
                return unity_AmbientSky + dif + spec;
            }