Base-PBR

PBR(Physically Based Rendering,基于物理的渲染)不是某一个固定 Shader,而是一套尽量遵循真实光照规律的材质与渲染方法。

它的目标不是绝对还原现实,而是让材质满足一些稳定的物理约束:

  • 同一份材质换到不同光照环境中,仍然能保持合理的外观。
  • 材质参数有明确含义,而不是靠随意调颜色凑效果。
  • 表面反射的能量不能凭空增加。
  • 金属、非金属、粗糙和光滑表面能够用统一模型表达。

目前游戏引擎常见的 PBR,一般指基于微表面理论的 Cook-Torrance BRDF,再配合金属度工作流和基于图像的环境光照。

PBR 光照与表面关系

1. 光照计算到底在算什么

光线到达表面后,大致会发生两类事情:

  1. 一部分光在表面直接反射,形成镜面反射(Specular)
  2. 一部分光进入材质内部,被吸收或经过散射后重新射出,形成漫反射(Diffuse)

我们最终计算的是:从光源方向 L 入射的光,有多少能量会沿观察方向 V 离开表面。

常用方向定义:

  • N:宏观表面法线。
  • L:表面指向光源的方向。
  • V:表面指向相机的方向。
  • HL + V 归一化后的半程向量。
1
H = normalize(L + V)

如果某个微表面的法线刚好朝向 H,它就能把来自 L 的光反射到 V

2. 渲染方程与 BRDF

反射方程可以简化理解为:

1
2
Lo(x, V) = Le(x, V)
+ ∫Ω fr(x, L, V) × Li(x, L) × (N · L) dL
  • Lo:从表面沿观察方向离开的光。
  • Le:表面自己发出的光,例如自发光。
  • Li:从某个方向到达表面的光。
  • fr:BRDF,描述表面如何把入射光反射到观察方向。
  • N · L:光线与表面的夹角衰减。
  • ∫Ω:把上半球所有方向的入射光贡献加起来。

实时渲染不可能对无限方向做完整积分,所以会对直接光逐灯计算,并用预计算、探针或近似算法处理环境间接光。

2.1 BRDF 是什么

BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数)不是一张贴图,而是一个函数:

1
fr(L, V) = 某个方向入射的光,有多少被反射到观察方向

一个物理合理的 BRDF 通常应满足:

  • 能量守恒:反射出去的能量不应大于入射能量。
  • 互易性:交换入射与观察方向,结果不变。

实时 PBR 通常把 BRDF 分成漫反射和镜面反射:

1
BRDF = Diffuse BRDF + Specular BRDF

3. 微表面理论

真实表面在微观上并不平整。PBR 把宏观表面看成大量方向不同的微小镜面。

  • 光滑表面的微表面朝向集中,反射光集中,高光小而清晰。
  • 粗糙表面的微表面朝向分散,反射光分散,高光宽而模糊。

粗糙度描述的是微表面法线分布范围,不是简单降低高光亮度。粗糙表面的高光峰值通常会降低,但覆盖范围会扩大,整体能量不应该凭空消失。

4. Cook-Torrance 镜面 BRDF

常见的 Cook-Torrance 微表面镜面项为:

1
Specular BRDF = D × F × G / (4 × (N · L) × (N · V))

其中:

  • D:微表面法线分布。
  • F:菲涅尔反射。
  • G:微表面之间的遮蔽与阴影。
  • 分母:对微表面投影面积进行归一化。

实际实现时所有点积都需要限制到合理范围,并处理分母接近 0 的情况。

4.1 D:Normal Distribution Function

D 描述微表面法线有多少朝向半程向量 H

游戏中常用 GGX / Trowbridge-Reitz 分布。它相比一些早期分布具有更长的高光尾部,更接近许多真实材质:

1
D_GGX = α² / (π × ((N·H)² × (α² - 1) + 1)²)

α 由感知粗糙度转换而来,常见做法是:

1
α = roughness²

这里的平方是参数映射,并不代表所有引擎的贴图通道都以同样方式存储。跨引擎传递材质时,要确认 Smoothness、Roughness 和内部 α 的转换规则。

4.2 F:Fresnel

菲涅尔效应表示反射率随观察角度变化:越接近掠射角,表面反射通常越强。

实时渲染常用 Schlick 近似:

1
F = F0 + (1 - F0) × (1 - V·H)⁵

F0 是垂直观察表面时的基础反射率:

  • 大部分非金属的 F0 较低,游戏中常近似使用 0.04。
  • 金属的 F0 较高,而且是有颜色的,颜色来自金属本身的光谱反射特性。

因此,金属的高光会带有颜色,而非金属的高光通常接近光源颜色。

4.3 G:Geometry Function

微表面会互相遮挡:

  • 从光源方向看不到的微表面处于 Shadowing。
  • 从相机方向看不到的微表面处于 Masking。

G 用来描述这种微观遮挡。常见实现使用 Smith 方法,并为 GGX 使用 Schlick-GGX 等近似。

注意它和场景中的 Shadow Map 不是一回事:

  • Shadow Map 处理物体之间的宏观遮挡。
  • G 处理材质表面内部的微观自遮挡。

5. 漫反射与能量守恒

最简单的漫反射模型是 Lambert:

1
Diffuse BRDF = BaseColor / π

除以 π 是为了保证在整个半球积分后不超过输入能量。

但同一束光不能同时被完整地用于镜面反射和漫反射。先被表面反射的部分不会再进入材质内部,因此常写成:

1
2
kS = F
kD = (1 - kS) × (1 - Metallic)
  • kS:分给镜面反射的比例。
  • kD:分给漫反射的比例。

金属的 kD 接近 0,因为可见光进入金属后会迅速被吸收,几乎没有普通意义上的漫反射。

一些更完整的模型会使用 Burley Diffuse、多次散射能量补偿等方法,修正 Lambert 或单次散射微表面模型的不足。但理解基础 PBR 时,先掌握能量如何在 Diffuse 与 Specular 之间分配最重要。

6. 金属度工作流

游戏引擎最常见的是 Metallic-Roughness 工作流。

金属度工作流中的材质参数

6.1 Base Color

Base Color 不是简单的“物体看起来是什么颜色”。

  • 非金属:表示漫反射颜色,不应包含烘焙进去的灯光、高光和阴影。
  • 金属:表示有色镜面反射的 F0,材质几乎没有漫反射。

Base Color 通常作为颜色纹理,以 sRGB 方式读取并在线性空间参与光照计算。

6.2 Metallic

Metallic 表示材质更接近金属还是非金属:

  • 0:非金属,例如木材、塑料、石头。
  • 1:金属,例如铁、金、铜。

现实中纯材质通常是 0 或 1。贴图中的中间值主要用于:

  • 像素覆盖了金属与非金属的交界。
  • 灰尘、锈迹、涂层等混合材质。
  • 纹理过滤和抗锯齿产生的过渡。

生锈的铁表面不能简单理解成“半金属”。氧化层本身通常是非金属,它只是覆盖在下面的金属上。

6.3 Roughness / Smoothness

Roughness 控制微表面朝向的分散程度:

  • 0:光滑,反射清晰,高光集中。
  • 1:粗糙,反射模糊,高光分散。

有些引擎使用 Smoothness:

1
Smoothness = 1 - Roughness

但通道位置和内部映射未必相同。例如 glTF 的 Metallic-Roughness 纹理使用 G 通道保存 Roughness、B 通道保存 Metallic,并要求按线性数据读取。Unity 不同渲染管线和工作流的打包方式需要单独确认。

6.4 Normal、AO 和 Emission

  • Normal Map:改变光照使用的局部法线,增加表面细节,但不会改变真实轮廓。
  • Ambient Occlusion:描述小尺度缝隙对间接光的遮蔽,不应该粗暴乘到所有直接光上。
  • Emission:表面自行发出的光。它能让材质显示得很亮,但是否照亮周围物体取决于 GI、光照贴图或额外光源。

这些纹理属于数据而不是颜色,除 Base Color、Emission 等颜色纹理外,通常应以 Linear / Non-Color 方式读取。

7. 直接光照

对于一个点光源或方向光,可以把单灯贡献简化成:

1
2
3
4
5
DirectLighting = (kD × Diffuse + Specular)
× LightColor
× max(N · L, 0)
× Attenuation
× Shadow

其中:

  • BRDF 决定材质如何反射光。
  • N · L 决定表面接收到多少光。
  • Attenuation 表示距离和聚光角度衰减。
  • Shadow 表示场景级遮挡。

不要在 BRDF 内外重复乘 N · L。公式的具体组织方式在不同代码库中会有差异,判断时要看函数返回的是完整 BRDF,还是已经包含余弦项的光照响应。

8. IBL:环境光照

真实场景的光并不只来自几个解析光源。天空、墙面和周围物体会从所有方向提供入射光。

IBL(Image Based Lighting)使用 Cubemap 或环境探针表示这些方向的光照。它通常分为两部分:

8.1 Diffuse IBL

漫反射会把入射光散射到较宽方向,因此环境贴图可以卷积成低频 Irradiance Map,或使用球谐函数保存。

1
IndirectDiffuse ≈ Irradiance(N) × BaseColor × kD

8.2 Specular IBL

镜面环境反射同时依赖法线、观察方向和粗糙度。如果运行时对整个半球完整积分,成本会很高,因此通常进行 Split-Sum 近似:

  1. 把环境 Cubemap 按不同粗糙度预过滤到不同 Mip。
  2. 用预积分 BRDF LUT 保存 Fresnel 和几何项的组合结果。
1
2
IndirectSpecular ≈ PrefilteredEnvironment(R, Roughness)
× BRDF_LUT(N·V, Roughness, F0)

粗糙度越高,采样越模糊的 Mip。Reflection Probe、天空盒预过滤和 BRDF LUT 共同构成了常见的实时镜面 IBL。

这仍然是近似:探针通常不知道真实遮挡和物体在场景中的准确位置,因此会出现漏光、反射漂浮或物体无法正确出现在反射中的问题。

9. Tone Mapping 与颜色空间

PBR 光照结果通常是 HDR,可以大于 1。要显示到普通屏幕,还需要曝光和 Tone Mapping 把高动态范围映射到显示范围。

1
2
3
4
5
材质纹理 sRGB → 解码到 Linear
→ 在线性空间计算光照
→ HDR 颜色
→ Exposure / Tone Mapping
→ 显示编码

Tone Mapping 不属于 BRDF,但它会强烈影响我们最终看到的高光、对比度和颜色。相同材质在不同引擎中看起来不同,可能不是 PBR 参数错了,而是环境光、曝光、Tone Mapping 和颜色管理不同。

10. Unity 中的对应关系

以 Unity 常见 Lit 材质为例:

  • Base Map / Base Color:材质基础颜色。
  • Metallic:金属度。
  • Smoothness:通常与 Roughness 相反。
  • Normal Map:切线空间法线。
  • Occlusion:间接光遮蔽。
  • Emission:自发光。
  • Main Light / Additional Lights:直接光。
  • Reflection Probe / Environment Reflections:镜面 IBL。
  • Light Probe / Lightmap / SH:漫反射间接光。

Built-in、URP、HDRP 的 BRDF、贴图打包和能量补偿实现并不完全一致。写自定义 Shader 时,最好复用当前渲染管线提供的光照函数,并阅读对应版本源码,而不是把一份网上的 Cook-Torrance 公式直接复制进来就认为完成了 PBR。

11. 常见错误

  1. 把金属度当成高光强度。 金属度改变的是材质类别和能量分配。
  2. 在 Base Color 中画入高光和阴影。 光照改变后,这些信息仍然黏在材质上。
  3. 用纯黑金属。 金属的可见颜色主要来自有色镜面反射,过暗的 Base Color 会让它几乎无法反光。
  4. 随意使用 0 到 1 的半金属值。 大多数纯表面应接近 0 或 1。
  5. 把 Roughness 当成 Specular 强度。 它主要控制反射分布宽度。
  6. 把数据贴图按 sRGB 读取。 Metallic、Roughness、Normal、AO 通常应使用线性采样。
  7. 只调材质,不检查环境。 没有合理的 HDR 环境与曝光,再正确的金属也可能像灰塑料。
  8. 把 AO 乘到所有光照。 AO 主要用于削弱间接光;直接光遮挡应由阴影等机制决定。
  9. 法线贴图强度过大。 它会破坏宏观法线与微表面粗糙度之间的合理关系,并产生闪烁。

12. 总结

PBR 的知识链可以压缩为:

1
2
3
4
5
6
7
8
9
10
11
12
13
光照环境

入射光 Li

材质 BRDF:Diffuse + D × F × G

直接光 + IBL 间接光

HDR 结果

曝光与 Tone Mapping

最终画面

最需要记住的不是某一条公式,而是四个约束:

  • 粗糙度描述微表面分布。
  • 菲涅尔决定反射随角度变化。
  • 金属与非金属的漫反射和 F0 不同。
  • 漫反射与镜面反射必须共享有限的入射能量。

在这个基础上,再去理解 Clear Coat、Sheen、Anisotropy、Subsurface、Transmission 等扩展材质,就不会只是堆参数,而是在基础 BRDF 上增加新的光学层次。

参考资料