基于图像的照明(Image-Based Lighting, IBL)把一张 HDR 环境贴图当作来自整个球面的入射光源,让物体能"实时"地反映出周围环境的照明信息——天光、太阳、室内灯具、地面反弹色,全部一气呵成。它是现代 PBR 管线在环境光(ambient)这一项上的终极形态。
而要在 fragment shader 里
对环境贴图做余弦加权卷积,得到 Irradiance Map。运行时按法线 N 单次纹理采样即可。一张极低分辨率(32² × 6 面)就够了,因为余弦核是非常平滑的低通滤波器。
核心是 Karis 2013 的 Split Sum Approximation:把高维积分撕成两份独立预计算——Prefiltered Cubemap(按粗糙度分层模糊)+ BRDF LUT(2D 查找表)。运行时两次纹理采样 + 一次乘加。
朴素 Split Sum 仅算单次微表面散射,高粗糙度下能量损失 30–35%。Fdez-Agüera 2019 给出了无需额外贴图的补偿公式,从 BRDF LUT 现有通道直接推导。现代引擎标配。
从渲染方程出发 → 分别讲漫反射通道(Irradiance Map)→ 镜面通道(Split Sum:拆解、预滤波、LUT)→ 着色器重组 → 能量补偿。共 8 个交互演示,包括浏览器现场跑蒙特卡洛烘焙 BRDF LUT、per-pixel JS 渲染的 PBR 球、能量损失曲线等。约 40 分钟。
对不发光不透明表面,出射 Radiance 是入射 Radiance 在上半球
PBR 把 BRDF 拆成漫反射 + 镜面两支独立处理:
| 分量 | 物理意义 | 常用模型 |
|---|---|---|
| 法线分布函数(NDF):法线对齐半向量 |
GGX / Trowbridge–Reitz | |
| 菲涅尔项:随视角变化的反射比例 | Schlick 近似 | |
| 几何遮蔽-阴影项:微表面互相挡住的概率 | Smith Joint G | |
| 分母 |
从 half-vector 域换元到 light-vector 域的 Jacobian | — |
真正的菲涅尔方程很复杂;Schlick 给出了一个5 次幂的工程近似,在大多数视角下误差 < 1%:
注意当
朴素地求
漫反射通道的处理优雅至极。对 Lambert BRDF
关键观察:
对每一个方向
余弦加权采样的 PDF
| 属性 | 典型值 | 说明 |
|---|---|---|
| 分辨率 | 32² × 6 面 (cube) 或 9 SH 系数 | 余弦核极平滑,低频信号即可 |
| 采样数 | 每像素 1024–4096 | 烘焙时;运行时 1 次 |
| 显存 | 约 24 KB(cube)/ 9 × 12 字节(SH) | SH 法异常省 |
| 运行时开销 | 1 次 cube 采样 |
因为辐照度信号极低频,直接用 9 个 SH 系数(前两阶,
上方是程序化"天空+太阳" 1D 切片,高频(太阳是细尖峰);下方是对它做余弦加权卷积后的辐照度,极平滑,太阳被"涂抹"成宽缓的鼓包。这就是为什么辐照度贴图只需 32² 分辨率:低频信号根本采不出锯齿。
三行代码搞定漫反射 IBL:
float3 irradiance = SAMPLE_TEXTURECUBE(_IrradianceMap, sampler, N).rgb;
float3 kD = (1 - F) * (1 - metallic);
float3 diffuseIBL = kD * albedo * irradiance;
其中
我们想求的镜面反射积分形如:
Karis 注意到一个事实:当我们用蒙特卡洛重要性采样估计这个积分时,离散和形如
两个独立的求和——这就是名字 Split Sum(拆分和)的由来。
等价地,写成连续积分形式:
第一项是"按 BRDF 重要性给环境贴图做一次模糊"——粗糙度越大、卷积核越宽、结果越像低 mip。第二项是"假设环境是纯白时 BRDF 自身能积出多少能量"——只跟视角和粗糙度有关。两项独立、维度低,都可以离线烘焙。
这个近似假设
这里径向绘出 GGX 分布
第一项
典型映射:mipLevel = roughness * (numMips - 1)。UE4
选 maxMip = 8,对应 256² → 1² 的 cube faces。
注意:低分辨率 mip 需要更高样本数(采样波瓣变宽,方差大),通常 mip 0 用 1
样本(直接采原贴图),mip N 用 1024 样本。
GGX 重要性采样的反 CDF 公式(半向量在切空间):
其中
这是 Split Sum 的第二个近似:因为我们不知道每个 texel 将来会被哪个视角采样,索性假设视线沿法线方向。代价是掠射角的高光被错误地变成圆形(实际应该被拉成各向异性条纹)。Karis 在 UE4 talk 中坦承这是"明显但可接受的失真"。
关键代码片段(Compute Shader 化伪码):
分母是权重归一化,不是 PDF。这一步等价于做"按余弦加权的 GGX 卷积",是 Karis 的另一处实战优化。
▲ 点击每一步展开实现细节
蓝色点是 Hammersley 序列经 GGX 反 CDF 映射后的入射方向。低粗糙度(α 小)时点紧紧聚拢在镜面方向(红轴);粗糙度增大时点散布到整个半球。这就是"卷积核宽度随 mip 变化"的视觉来源。
这里把一张程序化的 HDR "天空盒"(亮日轮 + 蓝天 + 暗地)按不同粗糙度做卷积模糊,模拟 Cubemap 各 mip 的内容。Mip 越高 → roughness 越大 → 越接近平均色。注意亮区如何被"摊开"。
Mip 0 (镜面) → Mip 7 (粗糙) · 每条带表示一个粗糙度级别的 1D 环形环境
第二项
把
剩下的两个积分只依赖于两个标量参数:
U 轴:
V 轴:粗糙度
R 通道:Scale,
G 通道:Bias,与
一张 256×256 的 RG16F 二维纹理,约 256
KB,全场景共享一份。
按下"重新生成",会用 Hammersley + GGX 重要性采样在 JS 里算每个像素。低样本数能看到明显噪点;高样本数收敛到平滑曲面。这就是 UE4 那张著名 LUT 的本质。
左下角(粗糙 = 0、视角正面):Scale 接近 1,Bias 接近 0——意味着此时反射量约等于
把前面三份预计算资源(Irradiance Map、Prefiltered Cubemap、BRDF LUT)合在一起,IBL 的整条 fragment shader 就长这样:
// ── 完整 IBL 着色(HLSL,URP 风格) ──
float3 ImageBasedLighting(float3 N, float3 V, float roughness,
float3 albedo, float metallic)
{
float3 R = reflect(-V, N);
float NdotV = saturate(dot(N, V));
// 解算 F0:金属取 albedo,电介质取 0.04
float3 F0 = lerp(0.04, albedo, metallic);
// ── 镜面通道 (Section 04-06) ──
float mip = roughness * MAX_REFLECTION_LOD;
float3 prefilteredColor = SAMPLE_TEXTURECUBE_LOD(_PrefilterMap,
sampler_PrefilterMap,
R, mip).rgb;
float2 envBRDF = SAMPLE_TEXTURE2D(_BRDFLut, sampler_BRDFLut,
float2(NdotV, roughness)).rg;
float3 specular = prefilteredColor * (F0 * envBRDF.x + envBRDF.y);
// ── 漫反射通道 (Section 03) ──
float3 irradiance = SAMPLE_TEXTURECUBE(_IrradianceMap, sampler_IrrMap, N).rgb;
float3 kS = F0 + (1 - F0) * pow(1 - NdotV, 5); // Schlick
float3 kD = (1 - kS) * (1 - metallic);
float3 diffuse = kD * albedo * irradiance;
return diffuse + specular;
}
| 方法 | 每像素纹理采样 | 每像素 ALU | 实时? |
|---|---|---|---|
| 朴素蒙特卡洛 IBL | 256+ × Cube | ~2k 次三角函数 | 否 |
| 完整 IBL(漫反射+镜面) | 3 次(IrrMap + Prefilter + LUT) | ~15 次乘加 | 是 |
这个球是纯 JavaScript per-pixel 渲染的:每像素的色彩 = (kD × albedo × irradianceMap.sample(N)) + (prefilteredEnv(R, roughness) × (F₀ × scale + bias))。Irradiance Map 是页面加载时算好的;Scale/Bias 来自上方刚生成的 LUT;这就是真实 IBL Shader 在做的事。
⚡ 试试:选"银"预设,把 Roughness 拖到最大 (1.0),再切换"单次/多重散射"——这就是 Section 08 要解决的问题,肉眼可见的亮度差就是损失的 30% 能量。
Cook-Torrance / GGX 微表面模型只算了单次反射——光线撞上一个 microfacet → 反弹一次 → 出场。但真实粗糙表面是沟壑纵横的:光线可能在多个 microfacet 之间反弹 N 次才能离开表面(Multiple Scattering),而单次散射模型把这部分能量直接丢弃了。
设想一个理想"白色炉膛":四面八方都是
下图横轴是粗糙度,纵轴是白炉单次散射能量
修复多重散射有许多方法。最早的 Kulla–Conty (2017) 需要额外烘焙
能量被镜面通道补回了一部分,意味着漫反射通道的"剩余能量"也要相应调整。完整守恒形式:
注意
// ── Fdez-Agüera 2019 多重散射补偿(增量 4 行) ──
float2 envBRDF = SAMPLE_TEXTURE2D(_BRDFLut, sampler_BRDFLut, float2(NdotV, roughness)).rg;
float3 Fss_Ess = F0 * envBRDF.x + envBRDF.y; // 单次散射
float Ess_W = envBRDF.x + envBRDF.y; // 白炉能量 (F0=1)
float3 F_avg = F0 + (1 - F0) / 21.0; // 平均 Fresnel
float3 Fms_Ems = (Fss_Ess * F_avg * (1 - Ess_W)) /
(1 - F_avg * (1 - Ess_W)); // 多次散射补偿
float3 specular = (Fss_Ess + Fms_Ems) * prefilteredColor;
float3 kD = (1 - Fss_Ess - Fms_Ems) * (1 - metallic);
float3 diffuse = kD * albedo * irradiance;
能量补偿对低 F₀ 材质几乎不可见(塑料、皮革、布料),因为初始反射本就少;对高 F₀ 金属在中-高粗糙度下显著提亮(粗糙金、磨砂铜、刷漆铝)。回到 Section 07 的 PBR 球,选"银 + roughness=1"切换看效果——多 30% 的亮度,颜色不再发灰,符合金属的"哪怕磨毛也很亮"的视觉直觉。
Kulla–Conty 2017:经典多重散射补偿,烘焙
Turquin
2019:解析逼近,把 BRDF LUT 升级到 RGBA 把
Hill–Heitz
2020:随机化版本,路径追踪适配。
Fdez-Agüera 是实时管线下的最优解——零额外资源、4 行 ALU。
头发、拉丝金属、CD 表面都需要各向异性 BRDF(沿切线方向 vs 副法线方向粗糙度不同)。常用扩展:
当硬件支持光线追踪后(RTX、Lumen),高质量镜面反射改用反射射线 + ReSTIR / SVGF 降噪,但 IBL
依然作为fallback:远处反射、low-roughness 远景、超出 BVH 的物体仍走 Split Sum。
典型组合:粗糙度 < 0.3 用 RT,> 0.3 用
IBL;中间用距离场+插值过渡。这就是 UE5 Lumen Reflections 的核心策略。
车漆、清漆木料、皮革表面有 双层结构:上层光滑透明的清漆 + 下层有色基底。Filament/UE5 的解法:
对漫反射,9 系数 SH 投影是工业首选:
烘焙时把环境贴图按球谐基函数积分,得到 9 个 RGB 系数;运行时按法线方向求 9 项加权和即可——0 次纹理采样,纯 ALU。Frostbite、Unreal Light Probes 全用此法。缺点是只能表达低频信号(不能表达"墙边阴影锐利变化"),需要配合 SDF / Lightmap 处理高频。
IBL 这套"两通道 + 三贴图 + 三次纹理采样"的体系,是图形学史上"明知有近似但仍然惊艳"的范例。它把不可解的运行时高维积分,通过精心设计的近似(Lambert 余弦核、Karis 拆分、N=V
简化、Fdez-Agüera 闭式补偿)和合理的预计算(Irradiance Map、Prefiltered Cubemap、BRDF LUT)压缩到
它的成功不在于数学严谨,而在于误差落在了感知不敏感的方向——人眼对高频细节有限的容忍度,正好被工程师抓住作为"省算力的余地"。从 2013 Karis 在 SIGGRAPH 演示 UE4 那只镀金茶壶起,到 2026 年的 Lumen / Strand 头发 / Clearcoat 车漆,整套 IBL 框架都在这个底座上自然演化。
如果你正在写自己的渲染管线,把这三份资产手撸一遍——亲手用 Hammersley 采 GGX、亲手做余弦加权卷积、亲手把 Schlick 拆成 Scale + Bias、亲手算 Fdez-Agüera 补偿——比读 100 篇论文都更能让你"看见"PBR 的物理含义。
| 主题 | 文献 | 年份 |
|---|---|---|
| Split Sum 原始 | Karis, Real Shading in Unreal Engine 4 (SIGGRAPH Course) | 2013 |
| SH 漫反射 IBL | Ramamoorthi & Hanrahan, An Efficient Representation for Irradiance Environment Maps | 2001 |
| 能量补偿(经典) | Kulla & Conty, Revisiting Physically Based Shading at Imageworks | 2017 |
| 能量补偿(紧凑) | Fdez-Agüera, A Multiple-Scattering Microfacet Model for Real-Time IB (JCGT)L | 2019 |
| 工业实现 | Filament Documentation (Google) · Khronos glTF 2.0 | 持续更新 |
| 各向异性扩展 | Heitz, Understanding the Masking-Shadowing Function (JCGT) | 2014 |