本节摘要:Phong 光照模型用三项之和近似一个表面点的亮度:环境光(无方向兜底)、漫反射(粗糙表面的均匀散射)、镜面高光(光滑表面的亮斑)。本节逐项讲物理直觉与公式实现,给出可运行的着色代码,并说明 Blinn-Phong 改进与能量截断的细节。像素在这一节获得亮度——它该是什么颜色,第一次有了物理依据。
阅读完本节,你应当能够:

手电筒照桌面:桌面整体均匀变亮,没有亮斑——粗糙表面把光向四面八方散射,亮度只取决于光射来的角度,这叫漫反射。手电筒照镜子或光滑金属:光几乎全部沿反射方向弹走,只有当你恰好在反射路径上才看到刺眼亮斑——这叫镜面反射。再关掉手电,桌面仍不是全黑,墙面反弹的微光给它铺了层底色——这就是环境光的近似。
Phong 模型(1975 年)正是这三句话的代数化。它是局部模型:只看"灯到表面点"这一段路,不管光在被照之前是否弹射过别处——全局光路的账记在 5.4 节。
环境光项:常数兜底。I_a = k_a · A,k_a 是材质的环境反射系数。物理上草率(把所有间接弹射压成一个常数),工程上有效,防止背光面死黑一片。
漫反射项:Lambert 余弦定律。表面法线 N 与光方向 L 的夹角越小,单位面积接收的光越多,按投影面积算就是余弦——第 1 章的点积在这里上岗:
I_d = k_d · (L · N) · 光颜色 L、N 均须单位化;L·N < 0 时截断为 0(光在表面背面,不照亮)
镜面项:Phong 的经验公式。把光方向 L 关于法线 N 反射得 R,与视线方向 V 的夹角越小、亮斑越强,用幂次 n 控制光斑大小:
I_s = k_s · max(0, R · V)^n n 越大光斑越小越锐利:塑料约 30,金属可到几百
三项相加(或相乘纹理色)即完整模型:
import numpy as np def reflect(L, N): # 光方向关于法线的反射向量(L 指向光源) return 2 * np.dot(N, L) * N - L def phong_shade(N, L, V, mat, light_color=(1,1,1), ambient=(0.15,0.15,0.15)): # N/L/V 单位化;mat = (环境系数, 漫反射系数, 镜面系数, 光泽度) ka, kd, ks, shininess = mat N, L, V = map(lambda v: v/np.linalg.norm(v), (N, L, V)) diffuse = kd * max(0.0, np.dot(N, L)) # 漫反射:余弦定律 R = reflect(L, N) spec = ks * max(0.0, np.dot(R, V)) ** shininess # 镜面:反射向量的幂 color = ka*np.array(ambient) + (diffuse + spec)*np.array(light_color) return np.clip(color, 0, 1) # 场景:法线朝上的地板,灯在斜上方,眼睛从斜上方看 N = np.array([0, 1, 0.0]) L = np.array([0.707, 0.707, 0.0]) V = np.array([0, 0.5, -0.866]); V /= np.linalg.norm(V) print(np.round(phong_shade(N, L, V, (0.1, 0.7, 0.5, 60)), 4)) # 输出: [0.6865 0.6865 0.6865] 环境底 0.15 + 漫反射 0.495 + 高光约 0.04 # 眼睛偏离反射主方向,高光只剩尾巴——把 V 改到反射路径上,spec 会飙到 0.5
调参的直觉:k_d 是"物体本身的颜色"(乘上纹理色即可给物体上妆);k_s 与材质光泽相关,金属高、粗糙织物趋近 0;shininess 决定亮斑的收敛速度,是视觉上"材质感"最敏感的一个旋钮。
Phong 的 R 每次都要算反射向量。Blinn 的改法:取 L 与 V 的角平分方向 H(半程向量),只用 H·N 的幂次。效果相近,且当光源或视线很远(方向近似恒定)时 H 几乎不变,逐像素计算量更小——GPU 时代的标准替代品:
def blinn_phong_spec(N, L, V, ks, shininess): # 半程向量版本:省去 reflect 计算 H = (L + V) / np.linalg.norm(L + V) return ks * max(0.0, np.dot(N, H)) ** shininess # 同一场景下对比两项强度 R = reflect(L, N) print(round(max(0, np.dot(R, V))**60, 4), round(blinn_phong_spec(N, L, V, 1.0, 60), 4)) # 输出: 0.0436 0.0211 半程版亮斑整体略钝,调高光泽度即可对齐观感
其一,光照必须在线性空间计算。若在 sRGB 伽马空间做这些乘加,暗部会发闷、高光会偏灰——2.2 节的转换流程(纹理→线性→计算→回编码)在这里全面生效。其二,光的距离衰减。点光源强度按距离平方反比衰减,但纯物理反比在游戏里衰减过快,实践中常用"物理衰减 + 手工平滑区间"的混合公式,把可照范围控制在设计尺度内。
💡 关键直觉:Phong 三件套里,漫反射只跟 L 和 N 有关(换视角亮度不变),镜面项还依赖 V(挪一步亮斑跟着挪)。观察一个物体时稍微移动视线,如果亮斑在动,你看到的就是镜面项在工作。
公式有了,下一个问题是这个公式在多少个点上算——5.2 节谈着色频率。