Phong和Blinn-Phong光照模型
Phong和Blinn-Phong是计算镜面反射光的两种光照模型,两者仅仅有很小的不同之处。
1.Phong模型
公式为:
在Phong光照模型中,关键是对发射向量的计算(如下图R向量)
上图中的位于表面“下面”的向量 ‘I’ 是原始 ‘I’ 向量的拷贝,并且二者是一样的,现在我们的目标计算出向量 ‘R’ 。根据向量相加原则,向量 ‘R’ 等于 ‘I’ + ‘V’,‘I’ 是已知的,所以我们需要做的就是找出向量 ‘V’。注意法向量 ‘N’ 的负方向就是 ‘-N’,我们可以在 ‘I’ 和 ‘-N’ 之间使用一个点乘运算就能得到 ‘I’ 在 ‘-N’ 上面的投影的模。这个模正好是 ‘V’ 的模的一半,由于 ‘V’ 与 ‘N’ 有相同的方向,我们可以将这个模乘上 ‘N’ (其模为 1 )再乘上 2 即可得到 ‘V’。总结一下就是下面的公式:
Unity中Shader代码如下:
Shader "Custom/Phong"
{
Properties
{
_LightDir("LightDir",Color) = (1,0,0)
_LightColor("LightColor",Color) = (1,1,1)
_Power("Power",Range(1,256)) = 1
_SpecularColor("SpecularColor",Color) = (1,1,1)
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal:NORMAL;
float3 viewDir:TEXCOORD;
};
float3 _LightColor;
float3 _LightDir;
float _Power;
float3 _SpecularColor;
float3 _DiffuseColor;
v2f vert (appdata v)
{
v2f o;
o.normal = UnityObjectToWorldNormal(v.normal);
o.vertex = UnityObjectToClipPos(v.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//反射光方向
_LightDir = normalize(_LightDir);
float3 ReflectLightDir = normalize(2 * dot(i.normal,_LightDir) * i.normal -_LightDir);
//ReflectLightDir = reflect(-_LightDir,i.normal);
half reflectiveFactor = max(0.0,dot(ReflectLightDir,i.viewDir));
half specularFactor =pow(reflectiveFactor,_Power);
half diffuseFactor = max(0.0, dot(i.normal, _LightDir));
fixed3 diffuse = (diffuseFactor * _DiffuseColor + specularFactor * _SpecularColor) * _LightColor;
return fixed4(diffuse,1);
}
ENDCG
}
}
}
2.Blinn-Phong模型
Phong模型中计算反射光线的向量是一件相对比较耗时的任务,因此Blinn-Phong对这一点进行了改进。
Ks:物体对于反射光线的衰减系数
N:表面法向量
H:光入射方向L和视点方向V的中间向量
Shininess:高光系数
可见,通过该式计算镜面反射光是符合基本规律的,当视点方向和反射光线方向一致时,计算得到的H与N平行,dot(N,H)取得最大;当视点方向V偏离反射方向时,H也偏离N。
同时H的计算比起反射向量R的计算简单的多,R向量的计算需要若干次的向量乘法与加法,而H的计算仅仅需要一次加法。
Unity中Shader代码如下:
Shader "Custom/BlinnPhong"
{
Properties
{
_LightDir("LightDir",Color) = (1,0,0)
_LightColor("LightColor",Color) = (1,1,1)
_Power("Power",Range(1,256)) = 1
_SpecularColor("SpecularColor",Color) = (1,1,1)
_DiffuseColor("DiffuseColor",Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal:NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 normal:NORMAL;
float3 viewDir:TEXCOORD;
};
float3 _LightColor;
float3 _LightDir;
float _Power;
float3 _SpecularColor;
float3 _DiffuseColor;
v2f vert (appdata v)
{
v2f o;
o.normal = UnityObjectToWorldNormal(v.normal);
o.vertex = UnityObjectToClipPos(v.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//反射光方向
_LightDir = normalize(_LightDir);
half3 h = normalize (_LightDir + i.viewDir);
fixed diffFactor = max (0, dot (i.normal, _LightDir));
float nh = max (0, dot (i.normal, h));
float specularFactor = pow (nh, _Power);
fixed3 diffuse = (diffFactor * _DiffuseColor + specularFactor * _SpecularColor) * _LightColor;
return fixed4(diffuse,1);
}
ENDCG
}
}
}
Phong和BlinnPhong在_Power值为16时候的对比图如下:
(左侧为BlinnPhong右侧为Phong)
3.用途区别
Phong适合模拟塑料,比"反射"材质表现出的介质更光滑一些,适合模拟玻璃、水、冰等高反光特性的介质
BlinnPhong大多适用于金属材质