5.1 光照模型:环境光、漫反射与镜面高光


5.1 光照模型:环境光、漫反射与镜面高光

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

学习目标

阅读完本节,你应当能够:

  1. 写出 Lambert 余弦定律并解释负值截断的必要性
  2. 实现 Phong 镜面项与 Blinn-Phong 半程向量版本,说明后者为何更快
  3. 组合三项完成完整局部光照,按材质调参
  4. 说出光照计算必须在线性空间进行的理由(承接 2.2)

三件套的亮度拼装

三件套的亮度拼装

从真实世界出发:表面如何回应光

手电筒照桌面:桌面整体均匀变亮,没有亮斑——粗糙表面把光向四面八方散射,亮度只取决于光射来的角度,这叫漫反射。手电筒照镜子或光滑金属:光几乎全部沿反射方向弹走,只有当你恰好在反射路径上才看到刺眼亮斑——这叫镜面反射。再关掉手电,桌面仍不是全黑,墙面反弹的微光给它铺了层底色——这就是环境光的近似。

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 决定亮斑的收敛速度,是视觉上"材质感"最敏感的一个旋钮。

Blinn-Phong:省一次反射计算

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(挪一步亮斑跟着挪)。观察一个物体时稍微移动视线,如果亮斑在动,你看到的就是镜面项在工作。

本节要点回顾

  • 三项分工:环境兜底、漫反射定体量、镜面点睛
  • Lambert = 点积截断:1.1 节的余弦判据直接变成亮度
  • Blinn-Phong 更省:半程向量免反射计算,是硬件时代默认
  • 材质三旋钮:k_d 定色、k_s 定光泽、shininess 定光斑大小
  • 线性空间与距离衰减:两处偷懒都会让画面"说不出的脏"

公式有了,下一个问题是这个公式在多少个点上算——5.2 节谈着色频率。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U