方向光照

一盏点光源只对应一个入射方向,环境则从四周提供光。环境贴图 Environment Map 将不同方向的入射辐亮度保存下来,使一个材质能够受到天空、窗户、墙面等方向结构的照明。这种利用图像描述光照的做法常称为基于图像的照明 Image-Based Lighting,IBL。

经纬环境图与六面立方体展开表示不同方向的光

左侧是森林的经纬展开,右侧是教堂环境的 Cube Map 展开。它们各自将方向对应到纹理位置,不是在给模型表面贴一张普通颜色图。两图来自不同环境,作用是说明表示方式,不能比较同一方向的数值。

基本模型将环境放在很远处,入射光只随方向变化,不随着色点位置变化。附近墙面、局部视差和随位置变化的遮挡不能完整装进这一张方向图。环境也常包含远高于普通显示白色的亮度,需要高动态范围 High Dynamic Range,HDR 数据;先截到 [0,1][0,1] 会丢失照明能量。

无阴影积分

先暂时忽略遮挡,令可见性 V=1V=1,求环境在单个表面点上的反射:

Lo(ωo)=∫Ω+Li(ωi)fr(ωi,ωo)(n⋅ωi) dωi.L_o(\boldsymbol\omega_o) =\int_{\Omega^+}L_i(\boldsymbol\omega_i) f_r(\boldsymbol\omega_i,\boldsymbol\omega_o) (\mathbf n\cdot\boldsymbol\omega_i)\,\mathrm d\omega_i.

法线 n\mathbf n 定义接收半球,ωo\boldsymbol\omega_o 是从表面指向观察者的方向。入射方向 ωi\boldsymbol\omega_i 也以离开表面的方向表示,用于查询光来自哪里;实际入射传播方向与它相反。

蒙特卡洛积分 Monte Carlo Integration 可以处理这个问题。按方向概率密度 pωp_\omega 取 NN 个样本,计算

L^o=1N∑j=1NLi(ωj)fr(ωj,ωo)(n⋅ωj)pω(ωj).\widehat L_o=\frac1N\sum_{j=1}^N \frac{L_i(\boldsymbol\omega_j) f_r(\boldsymbol\omega_j,\boldsymbol\omega_o) (\mathbf n\cdot\boldsymbol\omega_j)}{p_\omega(\boldsymbol\omega_j)}.

它通用,但每个片元都要查询许多方向。实时着色时,一个像素还可能混合多个局部环境探针,直接重复积分更贵。问题因此转向:哪些工作与当前像素无关,可以提前完成,运行时只查少量纹理?

这里讨论的是无阴影环境着色,不包含自发光与完整间接反弹。不能因为结果中有复杂环境反射,就认为所有局部遮挡也已经计算。

反射范围

理想镜面只反射一个方向;粗糙表面由许多不同朝向的微面组成,环境中一片方向都可能贡献到当前视线。反射瓣 Reflection Lobe 描述这份方向分布,粗糙度 Roughness 增大时通常使相关方向范围变宽。

对镜面,若观察方向为 v\mathbf v,查询环境的反射方向为

r=2(n⋅v)n−v.\mathbf r=2(\mathbf n\cdot\mathbf v)\mathbf n-\mathbf v.

GLSL 中对应 reflect(-v, n);输入的向量方向约定不同,就会查到错误环境。粗糙反射则需要以相应反射方向为中心,收集一个方向范围,而不只取一束光。

一片反射方向的贡献近似为预过滤后沿r查询

图左侧黑色方向 ii 经表面反射形成围绕 rr 的方向族,右侧把这种范围贡献转成预过滤后的一次查询。借助 BRDF 的互易性,固定观察方向时也可以反过来分析哪些入射方向会贡献。它说明“方向范围平均与中心查询”的思路,并不表示任意粗糙反射都只是均匀圆形范围。

拆分积分

沿用乘积近似,记 K=fr(n⋅ωi)K=f_r(\mathbf n\cdot\boldsymbol\omega_i),可以近似写成

∫LiK dωi≈L‾i∫K dωi.\int L_iK\,\mathrm d\omega_i \approx \overline L_i\int K\,\mathrm d\omega_i.

L‾i\overline L_i 代表相关反射范围中经过适当过滤的环境光,第二项代表材质在该观察条件下的整体反射响应。光照与响应的相关性被近似分开,所以两者可以分别预计算。

窄反射瓣使有效方向范围较小;宽而平滑的材质响应则能降低对环境细节的敏感度。这些观察给近似提供动机,但不是“任意 BRDF 都能安全提出环境项”的证明。环境中一个非常亮的小窗恰被高光挑中时,相关性可能很强。

工程中积分通过求和实现,这种将两份求和分开预计算的方案称为 Split-Sum Approximation。两个阶段分别处理环境图与 BRDF,不是把所有材质参数和环境变化一起塞进一张高维表。

环境预过滤

第一阶段为不同粗糙度预先生成环境的过滤结果。运行时用反射方向 r\mathbf r 查对应层级,再在相邻粗糙度层之间插值。

环境图在不同过滤宽度下逐渐丢失方向细节

左上树干和天空细节较清晰,中间范围平均后变模糊,右下只剩宽范围的亮暗与颜色结构。粗糙反射要收集更多方向,正是这种由局部细节向宽范围平均的变化,而非把已着色的物体图像再整体模糊。

这类层级常放在 Cube Map 的 mip 层中,但各层应按目标反射分布构造。GGX / Trowbridge–Reitz 统计不同朝向微面的面积,用较长尾部表达粗糙反射的方向范围,具体定义与推导见GGX长尾。普通 2×22\times2 图像平均并不自动等于这种反射瓣的预过滤;球面方向面积、Cube Map 边缘和纹素立体角也会影响结果。

常见方案为控制数据维度,预过滤时采用 n=v=r\mathbf n=\mathbf v=\mathbf r 的近似,只保留反射方向和粗糙度。真实微表面反射瓣还随观察角变化,掠射时可能被拉长,所以这一处理会丢掉部分方向形状。更换环境图后也需要更新预过滤数据,不能沿用旧环境的光照。

漫反射分流

漫反射可以更直接处理。Lambertian BRDF 为 ρ/π\rho/\pi,则

Lodiffuse(n)=ρπE(n),E(n)=∫Ω+Li(ω)max⁡(0,n⋅ω) dω.L_o^{\rm diffuse}(\mathbf n) =\frac\rho\pi E(\mathbf n), \qquad E(\mathbf n)=\int_{\Omega^+}L_i(\boldsymbol\omega) \max(0,\mathbf n\cdot\boldsymbol\omega)\,\mathrm d\omega.

预计算的是辐照度 Irradiance,查询变量为法线 n\mathbf n。它不依赖观察方向,与沿镜面反射方向查粗糙高光的做法要分开。后面将用球谐函数高效表达这个较平滑的响应。

有些工程纹理保存 EE,有些保存 E/πE/\pi,组合时必须核对约定。漫反射和高光分量还要共享合理的能量预算,不能各按全部入射能量相加。

材质预积分

高光的第二阶段仍是积分:

I(n,v,材质)=∫Ω+fs(ωi,v)(n⋅ωi) dωi.I(\mathbf n,\mathbf v,\text{材质}) =\int_{\Omega^+}f_s(\boldsymbol\omega_i,\mathbf v) (\mathbf n\cdot\boldsymbol\omega_i)\,\mathrm d\omega_i.

若直接为全部颜色、粗糙度和方向组合建表,存储维度会很高。预积分 Preintegration 需要利用公式结构,先把能够单独组合的参数拆出来。

对常见单次微表面高光,令入射与观察方向都指离开表面,半程向量为 h=normalize⁡(ωi+v)\mathbf h=\operatorname{normalize}(\boldsymbol\omega_i+\mathbf v),有

fs=D(h)G(ωi,v)F(v⋅h)4(n⋅ωi)(n⋅v).f_s=\frac{D(\mathbf h)G(\boldsymbol\omega_i,\mathbf v) F(\mathbf v\cdot\mathbf h)} {4(\mathbf n\cdot\boldsymbol\omega_i)(\mathbf n\cdot\mathbf v)}.

DD 是法线分布函数 Normal Distribution Function,NDF,统计不同朝向微面的面积;GG 描述微面间遮挡,FF 是 Fresnel 界面反射率。完整的微表面讨论可复习材质模型。这里先利用其颜色参数的可分离结构。

Fresnel拆色

Schlick 近似 把 Fresnel 写为

F(c)=F0+(1−F0)(1−c)5,c=v⋅h.F(c)=F_0+(1-F_0)(1-c)^5, \qquad c=\mathbf v\cdot\mathbf h.

F0F_0 是正入射基础反射率,可以为 RGB;它不等于所有材料统一的“漫反射基色”。电介质常有较低的近灰色 F0F_0,金属反射可以带颜色。这里的角度是视线与微面法线 h\mathbf h 的夹角,不是任意的 n⋅ωi\mathbf n\cdot\boldsymbol\omega_i。

令 Fc=(1−c)5F_c=(1-c)^5,改写为

F=F0(1−Fc)+Fc.F=F_0(1-F_c)+F_c.

设 H=DG/[4(n⋅ωi)(n⋅v)]H=DG/[4(\mathbf n\cdot\boldsymbol\omega_i)(\mathbf n\cdot\mathbf v)],将它代入材质积分:

I=F0∫Ω+H(1−Fc)(n⋅ωi) dωi⏟A+∫Ω+HFc(n⋅ωi) dωi⏟B.I=F_0\underbrace{\int_{\Omega^+}H(1-F_c) (\mathbf n\cdot\boldsymbol\omega_i)\,\mathrm d\omega_i}_{A} +\underbrace{\int_{\Omega^+}HF_c (\mathbf n\cdot\boldsymbol\omega_i)\,\mathrm d\omega_i}_{B}.

F0F_0 不随积分方向变化,因此可以提出。A,BA,B 不再依赖基础反射颜色;改变金属颜色等参数时,不必重新计算这一方向积分。

二维查表

对给定的各向同性 D,GD,G 模型,剩余响应主要由粗糙度和 N ⁣V=n⋅vN\!V=\mathbf n\cdot\mathbf v 决定。可以将 A,BA,B 存成一张二维纹理的两个通道,称为 BRDF LUT Lookup Table。

roughness与cos观察角作为轴的两通道BRDF表

横轴是 cos⁡θv=N ⁣V\cos\theta_v=N\!V,纵轴是粗糙度;红、绿表现两个预积分通道,不是材质的实际颜色。入射方向已经在生成表时被积分,不应再拿某条入射角作查询坐标。

常见粗糙度参数 rr 与 NDF 的参数 α\alpha 有映射关系,例如采用 α=r2\alpha=r^2。预积分、环境预过滤和运行时材质必须使用一致的映射及同一 D,GD,G 约定。各向异性材质还依赖切向方位等参数,不能无条件套用这张各向同性二维表。

重要性采样

预计算阶段仍可使用较多样本,成本只是移到运行之前。重要性采样 Importance Sampling 让样本较多地落在贡献大的方向,减少用有限样本估计这些积分的噪声。

以按 NDF 采样半程向量为例,ph(h)=D(h)(n⋅h)p_h(\mathbf h)=D(\mathbf h)(\mathbf n\cdot\mathbf h)。镜面反射把 h\mathbf h 变为 ωi\boldsymbol\omega_i,方向 PDF 需要相应的 Jacobian:

pω(ωi)=D(h)(n⋅h)4∣v⋅h∣.p_\omega(\boldsymbol\omega_i) =\frac{D(\mathbf h)(\mathbf n\cdot\mathbf h)}{4|\mathbf v\cdot\mathbf h|}.

这个 4∣v⋅h∣4|\mathbf v\cdot\mathbf h| 来自半程方向与反射方向之间的立体角换元,不是随意归一化常数。将 fsf_s、余弦和 PDF 相除,DD 等项相消,得到权重

Gvis=G(ωi,v)(v⋅h)(n⋅h)(n⋅v).G_{\rm vis}=\frac{G(\boldsymbol\omega_i,\mathbf v) (\mathbf v\cdot\mathbf h)} {(\mathbf n\cdot\mathbf h)(\mathbf n\cdot\mathbf v)}.

于是预积分可用下述过程估计:

1
2
3
4
5
6
7
8
9
10
11
12
对每组粗糙度 r 和观察角 NoV:
构造单位法线 n 与满足观察角的单位视线 v
初始化 A = 0,B = 0
对 N 个半程向量样本:
按目标 NDF 的投影分布取 h
wi = 2 * dot(v, h) * h - v
若 wi 在法线上半球且所需分母合法:
计算 Gvis 与 Fc = (1 - dot(v, h))^5
A += Gvis * (1 - Fc)
B += Gvis * Fc
否则该样本贡献为 0
将 A / N、B / N 写入二维表

反射到接收半球外的样本贡献为零,仍按原总样本数归一化,不能改成只除以通过的样本数而不修正采样分布。零粗糙度的镜面极限与掠射退化需要相应分支,不能把零直接代入所有 NDF 分母。

运行组合

运行时查询粗糙度对应的环境层和 A,BA,B 表,得到

Lospecular≈Lprefilter(r,r)[F0A(N ⁣V,r)+B(N ⁣V,r)].L_o^{\rm specular} \approx L_{\rm prefilter}(\mathbf r,r) \left[F_0A(N\!V,r)+B(N\!V,r)\right].

RGB 与标量按通道组合。颜色和环境数据都应在线性空间,不能对 gamma 编码值直接执行能量运算。

1
2
3
4
5
6
求反射方向 r_dir = reflect(-v, n)
按材质粗糙度查询预过滤环境图,得到 RGB 光照 L
按 (NoV, roughness) 查询 BRDF LUT,得到 A、B
specular = L * (F0 * A + B)
按材质能量分配组合 diffuse、specular 与其他分量
再执行相应的显示处理

它避免每个片元实时积分许多方向,但纹理查询、插值、局部探针混合以及数据更新仍有成本。“没有实时采样积分”不等于没有任何纹理采样。

误差与遮挡

参考积分与split-sum在不同粗糙度上的对照

上排是参考,下排是 Split Sum;从左到右反射逐渐从清晰变宽,两组在主要颜色与光照结构上接近。它展示该环境和材质条件下的近似效果,不能作为所有方向、所有环境逐像素一致的保证。

误差包括光照与 BRDF 的相关性被分离,预过滤中观察角变化被简化,以及有限层级、有限表分辨率和积分样本带来的误差。掠射拉长高光、极亮小光源或不符合建表模型的材质,都值得单独检查。

本篇始终设 V=1V=1。环境图有许多方向,若为每个方向分别建立阴影图,成本会随数量增长;若采样方向检查可见性,局部遮挡又可能具有高频细节。后续的环境阴影将讨论主光源投影、AO、预计算传输和射线查询等预算取舍,这里的 Split Sum 本身不包含完整环境阴影。