Physically Based Rendering
目录+
三大物理原理
所有复杂的公式,都是在模拟下面三个现象。
微表面理论 (Microfacet Theory)
宏观上光滑的表面,在微观尺度下都是由无数朝向随机的微小镜面(Microfacets)组成的。粗糙度 (Roughness) 描述的就是这些微观平面朝向的混乱程度:Roughness ≈ 0 时微观平面朝向基本一致,整体像一面大镜子,高光集中而清晰;Roughness ≈ 1 时朝向混乱,光线被散射到四面八方,高光模糊且范围大。
数学上用法线分布函数 (Normal Distribution Function, NDF) 来描述这种混乱程度——它回答的问题是:有多大比例的微观平面朝向恰好能把光反射进你的眼睛。现代 PBR 最常用的是 GGX (Trowbridge-Reitz),因为它产生的高光带有柔和的长尾,最接近真实观察结果。
能量守恒 (Energy Conservation)
一个表面反射出去的光,不可能多于它接收到的光。光照到表面时分成两部分:一部分直接被反射(高光),另一部分折射进物体内部。对不透明物体,折射进去的光要么被吸收,要么经过次表面散射后再射出来——后者就是漫反射(Diffuse)。所以永远有:漫反射 + 高光反射 ≤ 入射光。
由此推出一个重要结论:高光反射能力越强的物体,漫反射就越弱。金属是极端情况——折射进内部的光全部被吸收,几乎没有漫反射,只剩高光。
菲涅尔效应 (Fresnel Effect)
观察角度会影响表面的反射率:几乎所有表面在掠射角(视线接近与表面平行)下反射率都会急剧升高,趋近 100%。看平静的水面就是这个效应——垂直向下看能看清水底,接近水平看过去水面就成了镜子。
精确描述这个效应的是菲涅尔方程,实时渲染中通常用 Schlick 近似。其中的关键参数是 F0:垂直入射时的基础反射率,它是区分物质类别的核心。电介质(塑料、水、木头、皮肤)的 F0 很低,约 0.02–0.05;金属(金、银、铜)的 F0 很高(0.7–1.0)且带颜色。
金属度工作流就建立在这个区别上:Metallic 参数决定如何解读 BaseColor(Albedo)贴图——同一张贴图,非金属时被当作固有色(漫反射颜色),金属时被当作 F0(镜面反射颜色)。
| F0(镜面基础反射率) | 漫反射颜色 | |
|---|---|---|
| 金属 (Metallic = 1) | BaseColor 贴图(带颜色,0.7–1.0) | 0(黑) |
| 非金属 (Metallic = 0) | 0.04(写死的灰度常数) | BaseColor 贴图 |
这张表的每一格都有物理来由:非金属的 F0 几乎都在 4% 左右且不带颜色,所以引擎直接硬编码 0.04,不给美术调;金属的反射自带颜色(金黄、铜红来自反射本身),每种金属不同,所以交给贴图;金属没有漫反射,是因为折射进内部的光被自由电子全部吸收。URP 源码(ShaderLibrary/BRDF.hlsl,kDielectricSpec 就是那个 0.04)中对应两个 lerp:
brdfSpecular = lerp(kDieletricSpec.rgb, albedo, metallic); // F0:非金属取0.04,金属取Albedo
brdfDiffuse = albedo * (1.0 - metallic) * ...; // 漫反射:金属时归零
注意表中的 F0 是"起点"不是"上限":实际高光还要经过菲涅尔随角度放大,掠射角下非金属也能反射接近 100%。Metallic 本身是连续值,中间值就是在两套解读之间插值,但物理上不存在"半金属",中间值只应出现在金属与非金属的过渡像素上(锈迹、灰尘)。
这套"一图两用"也是金属度工作流相对 Specular 工作流省纹理的原因:后者需要一张单独的 RGB 高光颜色贴图,前者用一个单通道灰度图就编码了同样的信息。
渲染方程与 BRDF
图形学的终极目标是解渲染方程,它完整描述了光能在场景中的传播与反射:

- 是出射辐射度(最终看到的颜色), 是自发光;
- 积分对半球上所有方向的入射光 求和, 是兰伯特余弦项;
- 就是双向反射分布函数 (BRDF)——输入入射方向、观察方向、法线和材质属性,输出表面对光的反射比例。
这个方程无法实时精确求解,PBR 的核心就是用一个物理上可信的 BRDF 去近似 。URP 的 Lit 材质所用的 BRDF 拆成两项:
- 折射进内部再散射出来的漫反射
- 在界面上被立即反弹的镜面反射
漫反射项:Lambert
漫反射模拟的是光折射(Refraction)进入物体内部、经过多次次表面散射(Subsurface Scattering)后再从表面射出的部分。Lambert 是最经典的完美漫反射模型:它假设光一旦进入材质内部就被均匀散射,从任何方向射出的亮度都相同,与观察角度无关。
Albedo:固有色,材质的基础色,物理上代表光进入材质内部散射后最终"幸存"射出的颜色和比例。
除以 是能量守恒的要求:一个反射率为 1(全白)的朗伯表面,接收来自整个半球的光后,反射的总能量不能超过入射总能量。对 BRDF 乘余弦项在半球上积分,朗伯余弦项的积分结果正好是 ,所以 BRDF 本身必须除以 才能归一化。
镜面反射项:Cook-Torrance
镜面反射模拟光在物体表面(界面)被立即反弹的部分。Cook-Torrance 是基于微表面理论的 BRDF 模型,三大物理原理在这里全部落地:
- D(法线分布函数):决定高光的大小和形状
- 描述微表面法线的分布:有多少微表面的朝向恰好能把光源反射进你眼睛。分布越集中(粗糙度低)高光越小越锐,越分散(粗糙度高)高光越大越糊。由 Roughness 控制。
下面分别看 D、F、G 三项的具体选型。
Specular D
法线分布函数的常见模型有:
- Beckmann [1963]、
- Blinn-Phong [1977]、
- GGX [2007](即 Trowbridge-Reitz [1975])、
- GTR [2012],
- 以及各向异性版本的 Beckmann [2012] 和 GGX [2015]。
业界主流是 GGX,胜在高光长尾更真实:

Specular F
菲涅尔项的常见模型有
- Cook-Torrance [1982]、
- Schlick [1994]、
- Gotanda [2014]。
业界基本都用 Schlick 近似——计算成本低,精度足够:
Specular G
几何项的常见模型有
- Smith [1967]、
- Cook-Torrance [1982]、
- Neumann [1999]、
- Kelemen [2001]、
- Implicit [2013]。
Eric Heitz 在 [Heitz14] 中论证了 Smith 几何阴影函数是正确且更准确的 G 项,并将其拓展为 Smith 联合遮蔽阴影函数(Smith Joint Masking-Shadowing Function),共四种形式:分离型、高度相关型、方向相关型、高度-方向相关型。目前较常用的是其中最简单的分离遮蔽阴影型 (Separable Masking and Shadowing):把 G 拆成光线方向和视线方向两个独立的部分,用相同的分布函数分别计算再相乘。
按这个思想把 Smith 框架和不同的 NDF / 近似组合,就得到 Smith-GGX、Smith-Beckmann、Smith-Schlick、Schlick-Beckmann、Schlick-GGX 等变体。UE4 用的是 Schlick-GGX:以 Schlick 近似为基础,通过重新映射 去匹配 GGX 的 Smith 方程——解析光源用 ,IBL 用 :
与 :能量守恒把两项绑在一起
(漫反射系数)和 (镜面反射系数)不是两个独立的参数,它们被菲涅尔项 绑定。菲涅尔项已经精确给出了"有 比例的光在界面被立即反射",那么根据能量守恒,剩下 的光必然折射进了材质内部——而这部分折射光是漫反射的唯一能量来源。所以:
这个绑定关系直接解释了一个日常现象:斜着看塑料桌面时只见反光不见桌面自己的颜色,因为掠射角下 趋近 1.0,、,漫反射颜色(Albedo)被挤没了,剩下的全是镜面反射。
基于物理的环境光照
以上处理的是直接光。环境光照(IBL)要解决的是渲染方程中来自整个环境的积分,同样拆成漫反射和镜面反射两半,各自预计算。

漫反射:SH 球谐光照
要解决的问题:一个点接收到的环境光是"每个方向多亮"的球面函数,直接存就是一张 Cubemap,太贵。
SH 的原理:把这个球面函数分解成一组固定形状"积木"的加权和,只存权重系数,用的时候代入方向重新加权求和还原光照——相当于球面上的傅里叶变换,存频率系数,不存原始信号。
积木一共 9 块,按复杂度分三组:
- 1 块均匀的球:管平均亮度(环境光底色);
- 3 块哑铃(沿 x/y/z 轴,一头亮一头暗):管方向性,比如"上面比下面亮";
- 5 块花瓣:管更细一档的明暗起伏。
为什么 9 块就够?因为漫反射本来就把光"揉"得极度平滑——它只有低频没有高频,更复杂的积木给了也用不上(Ramamoorthi 2001 证明了误差在 1% 以内)。
于是间接漫反射的流程变成:
- 烘焙时:把环境 Cubemap 按漫反射 BRDF 卷积(重度模糊),再分解到 9 块积木上,算出权重——每通道 9 个、RGB 共 27 个浮点数。一张 Cubemap 就这样被压成了 27 个数。
- 渲染时:Shader 拿法线方向代入 9 块积木求值,乘上权重加起来,就得到该方向的环境漫反射颜色。全程只是几次乘加,不采样任何贴图,比读纹理还便宜。
URP 用的是 L2 球谐(取到 ,就是上面这 9 块;有人叫二阶、有人叫三阶,同一个东西,写 L2 最不含糊)。那 27 个权重装在 7 个 Shader 常量里:unity_SHAr/SHAg/SHAb(常数项 + 哑铃项)、unity_SHBr/SHBg/SHBb 和 unity_SHC(花瓣项),采样入口是 SampleSH()。
间接漫反射在 Unity 中的存储
烘焙时要回答的问题是:一个点接收到的间接光怎么存?Unity 按物体是否移动分成两条路,关键区别在于烘焙时法线是否已知:
- 静态物体 → 光照贴图 (Lightmap)。表面固定,每个纹素的法线烘焙时就已知,可以直接把"来自所有方向的入射光对着这个法线积分"的结果算出来,存一个颜色值,方向维度在烘焙时就消掉了。运行时用第二套 UV 采样即可,它不需要 SH。
- 动态物体 → 光照探针 (Light Probes)。物体会动,烘焙时不知道将来站在这个位置的表面法线朝哪,只能把整个球面的光照都保留下来,运行时再按实际法线解码。"高效存储一个球面函数"正是 SH 要解决的问题,每个探针存 27 个 SH 系数。
- 两者都没有时的兜底是 Ambient Probe:天空盒环境光的一组全局 SH 系数。
| 存储方式 | 存的是什么 | 为什么 | |
|---|---|---|---|
| 静态物体 | Lightmap | 每纹素一个颜色(不是 SH) | 法线已知,方向可预先积分掉 |
| 动态物体 | Light Probes | 每探针 27 个 SH 系数 | 法线未知,必须保留球面信息 |
| 兜底 | Ambient Probe | 一组全局 SH 系数 | 同上,取自天空盒 |
在 Unity 里可以这么记:SH 是"探针"的数学载体,凡是探针(Light Probe / Ambient Probe / 新一代的 APV),里面装的都是 SH 系数;间接漫反射 = 探针(SH)+ Lightmap(非 SH)两条腿。SH 也只管漫反射这半边,间接高光是高频信号,9 块积木拼不出清晰的倒影,只能走贴图,也就是下一节的 Reflection Probe。
镜面反射:Split Sum
环境光镜面反射要求解的是渲染方程中关于环境的镜面积分:
其中 是来自环境贴图的入射光, 是镜面 BRDF。这个积分无法实时计算,现代 PBR 管线的行业标准是分离求和近似 (Split Sum Approximation):把积分拆成两个相对独立的部分分别预计算,运行时在 Shader 中采样两个结果组合起来。这两部分是:
- 预滤波环境贴图 (Pre-filtered Environment Map) —— 负责光照项 ;
- 环境 BRDF (Environment BRDF) —— 负责 BRDF 项。
预滤波环境贴图
粗糙表面的反射是无数微观平面反射结果的平均,实时渲染不可能对每个像素采样 Cubemap 上成百上千个点。解法是把这个平均预先算好:Cubemap 生成后,引擎对它做卷积,模拟不同粗糙度下的反射效果,结果存进 Mipmap 链——Mip 0 是最清晰的原图,对应 Roughness = 0 的完美镜面;Mip 级别越高图像越模糊,对应越高的粗糙度。
运行时,Shader 根据材质的 Roughness 算出对应的 Mip 级别,采样一次就得到近似正确的模糊反射。
环境 BRDF
材质的"反射效率表"—— 给定粗糙度和观察角度,它告诉你从整个半球打来的环境光中,有百分之几会被反射进你的眼睛。预滤波环境贴图给"环境有什么光",环境 BRDF 给"材质吃下多少",两者相乘就是间接高光。
"Environment BRDF" 是个概念性的昵称,更准确的叫法是半球-方向反射率:对于给定的观察方向 和粗糙度 ,当来自整个半球的所有光线(假设强度均为 1.0)照射到表面时,BRDF 总共会向 方向反射多少比例的能量。它只依赖 Roughness 和 两个变量,因此可以预计算。实现上有两条路线:
- 2D LUT:UE4 的经典方案。把结果烘焙成一张 2D 查找表(BRDF Integration Map),输入 Roughness 和 NdotV,输出一个缩放因子和一个偏移,在 Shader 中作用于 F0 完成修正。
- 解析拟合:URP 的方案。不用查找表,而是用一个拟合出的数学解析式直接计算,整个过程在 GPU 的 ALU 中完成,省掉一次纹理采样的内存访问,对移动端这类带宽受限的设备更友好。