Phong
$$ I = \underbrace{I_a k_a}_{\text{环境光项 (Ambient)}} + \underbrace{I_d k_d (\vec{L} \cdot \vec{N})}_{\text{漫反射项 (Diffuse)}} + \underbrace{I_s k_s (\vec{R} \cdot \vec{V})^\alpha}_{\text{镜面高光项 (Specular)}} $$-
环境光项(Ambient):$I_a k_a$
-
$I_a$:环境光强度(Ambient light intensity)
-
$k_a$:材质的环境光反射系数(Ambient reflection coefficient)
-
漫反射项(Diffuse):$I_d k_d (\vec{L} \cdot \vec{N})$
-
$I_d$:入射漫反射光的强度
-
$k_d$:材质的漫反射系数
-
$\vec{L}$:从表面点指向光源的单位方向向量
-
$\vec{N}$:表面该点处的法单位向量
-
镜面高光项(Specular):$I_s k_s (\vec{R} \cdot \vec{V})^\alpha$
-
$I_s$:入射镜面光的强度
-
$k_s$:材质的镜面高光反射系数
-
$\vec{R}$:光线在表面处的反射单位向量
-
$\vec{V}$:从表面点指向观察者/摄像机的单位方向向量
-
$\alpha$:物体的反光度 / 粗糙度因子(Shininess / Specular exponent),$\alpha$ 越大高光点越小越亮。

1. Ambient
环境光
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;2. Diffuse
入射光线越靠近物体法线漫反射越强
// 计算漫反射值(光照向量 dot 法线)
// 注意会出现负值
float diff = max(dot(lightDir, Normal), 0.0);
// 合并
vec3 diffuse = diff * lightColor;3. Specular
视角向量越靠近出射光线镜面反射越强
// 强度系数
float specularStrength = 0.5;
float shininess = 32;
// 计算反射光方向向量(光照向量 dot 法线)
vec3 reflectDir = reflect(-lightDir, Normal);
// 计算镜面反射值(视角向量 dot 反射向量)
// shininess:此处pow的意义在于使光斑收敛更快(控制硬度)
float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
// 合并
vec3 specular = specularStrength * spec * lightColor;终. Phong
最终效果
vec3 result = (ambient + diffuse + specular)* objectColor;Blinn-Phong
“高光截断”问题


解决方案:半程向量

代码实现:
vec3 halfwayDir = normalize(lightDir + viewDir);
spec = pow(max(dot(normal, halfwayDir),0.0), 1.0);效果:

顺带的优化
经典 Phong 的开销
计算反射向量 $\vec{R}$ 需要使用反射公式,这就意味着像素着色器中的每一个像素,都必须根据该点动态变化的法线 $\vec{N}$ 重新计算一次的 $\vec{R}$ 向量。
Blinn-Phong 的优化
半角向量 $\vec{H}$ 的计算只需要简单的向量加法和归一化。更重要的是,在一些特别的场景下,如方向光/太阳光 + 正交相机,$\vec{L}$ 和 $\vec{V}$ 在全场景中是恒定的。 此时,$\vec{H}$ 是一个帧常量,在整帧渲染中只需要计算一次。