一、Phong 做错了什么
在 DX11 系列第三篇里,你学了 Phong/Blinn-Phong 光照模型。它看起来不错,但它有一个根本问题:不遵守能量守恒。
Phong 的 Ambient + Diffuse + Specular: → 三个分量独立调参 → 美术可以写出 "反射的光比入射的光还多" 的材质 → 不同光照环境下,同一个材质看起来完全不同PBR(Physically Based Rendering)的核心理念就两条:
- 能量守恒:出射光能量 ≤ 入射光能量
- 基于物理的 BRDF:用真实世界测量的表面反射行为替代经验公式
二、微表面模型(Microfacet Model)
1 核心假设
PBR 不把材质表面当成完美平面,而是假设它由无数微小镜面(microfacets)组成:
光滑表面 粗糙表面──────────── ╱╲ ╱╲ ╱╲微观法线全朝上 ╱ ╲ ╱ ╲ ╱ ╲ ╲ 微观法线四散反射集中(镜面高光小且亮) 反射分散(高光大且暗)法线分布函数(NDF) 描述了有多少微表面的法线方向指向给定的半程向量(即有多少微表面能把光反射到你的眼睛里)。
2 Cook-Torrance BRDF
PBR 的标准 BRDF 公式(也是 UE 的 Default Lit 所用的):
f(l, v) = diffuse + specular
specular = D * F * G / (4 * (n·l) * (n·v))三个核心项:
| 项 | 全称 | 在做什么 |
|---|---|---|
| D | Normal Distribution Function(法线分布) | 多少微表面朝向半程向量 |
| F | Fresnel(菲涅尔) | 多少光被反射(vs 折射进材质内部) |
| G | Geometry(几何遮蔽) | 微表面之间互相遮挡了多少光 |
分母 4 * (n·l) * (n·v) 是微表面模型的数学推导结果,不是什么魔法调参——这是 PBR 和 Phong 的本质区别。
三、三项逐一看
1 D:法线分布函数
UE 使用 GGX(Trowbridge-Reitz) 分布:
float D_GGX(float NdotH, float Roughness) { float a = Roughness * Roughness; // UE 把 Roughness 平方后再用 float a2 = a * a; float denom = NdotH * NdotH * (a2 - 1.0) + 1.0; return a2 / (PI * denom * denom);}关键:GGX 有一个”长尾”——在高粗糙度时,高光不是均匀分布的,而是有一个较宽但亮度较低的边缘衰减。这使得粗糙金属的边缘看起来更真实。
Roughness(粗糙度)是 PBR 材质最重要的参数。0 = 完美镜面,1 = 完全漫反射。
2 F:Fresnel 效应
所有材质在掠射角(grazing angle)时都变成镜面。站在湖边低头看,水是透明的;望向远方,水面变成镜子。这就是 Fresnel。
float3 F_Schlick(float3 F0, float VdotH) { return F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);}F0 是”垂直看时的反射率”:
- 非金属(电介质):约 0.02-0.04(反射很弱)
- 金属(导体):带颜色的反射率(金的 F0 偏黄,铜的偏红)
这就是金属度(Metallic)参数发挥作用的地方——Metallic=0 时 F0 是一个固定的小值(约 0.04),Metallic=1 时 F0 取 BaseColor 的值。
3 G:几何遮蔽
粗糙表面上的微表面会互相遮挡——有些微表面对着光源但被邻居挡住(Shadowing),有些对着眼睛但被邻居挡住(Masking):
float G_SmithGGX(float NdotL, float NdotV, float Roughness) { float k = (Roughness + 1.0) * (Roughness + 1.0) / 8.0; // GGX float G1V = NdotV / (NdotV * (1.0 - k) + k); float G1L = NdotL / (NdotL * (1.0 - k) + k); return G1V * G1L;}粗糙度越高,G 值越小,材质越暗——因为微表面的互相遮挡吃掉了更多光。这完全符合物理直觉。
四、Diffuse 项:Lambert 就够了
PBR 的漫反射部分反而简单。标准的 Lambert 漫反射加上能量守恒修正:
float3 Diffuse_Lambert(float3 BaseColor, float Metallic) { // 金属没有漫反射——所有颜色来自镜面反射 return BaseColor * (1.0 - Metallic) / PI;}除以 π 是为了能量守恒——Lambert BRDF 在整个半球积分为 1,但它的值是 1/π。
五、完整的 PBR 直接光照(像素着色器)
float3 PBR_DirectLight(float3 N, float3 V, float3 L, float3 LightColor, float3 BaseColor, float Metallic, float Roughness) { float3 H = normalize(V + L); // 半程向量
float NdotL = saturate(dot(N, L)); float NdotV = saturate(dot(N, V)); float NdotH = saturate(dot(N, H)); float VdotH = saturate(dot(V, H));
// F0:垂直入射反射率 float3 F0 = lerp(float3(0.04, 0.04, 0.04), BaseColor, Metallic);
// Cook-Torrance 三项 float D = D_GGX(NdotH, Roughness); float3 F = F_Schlick(F0, VdotH); float G = G_SmithGGX(NdotL, NdotV, Roughness);
// 镜面反射 float3 Specular = D * F * G / (4.0 * NdotL * NdotV + 0.001);
// 漫反射 float3 Diffuse = BaseColor * (1.0 - Metallic) / PI;
return (Diffuse + Specular) * LightColor * NdotL;}这就是 UE 的 Default Lit 材质节点背后在跑的 Shader 代码的本质。
六、PBR 工作流:金属度 vs 镜面度
两种 PBR 工作流:
| Metallic/Roughness(UE 默认) | Specular/Glossiness | |
|---|---|---|
| 贴图 | BaseColor + Metallic + Roughness | Diffuse + Specular + Glossiness |
| 金属/非金属 | Metallic 二值(0 或 1) | 手调 Specular 颜色 |
| 容易用错吗 | 不容易——Metallic 只有 0/1 | 容易——Specular 值域大,容易出非物理结果 |
| 占用 | 更小(Metallic 可以存为灰度图) | 更大 |
Metallic/Roughness 工作流更简单、更难出非物理的错误——这就是 UE 选它作为默认的原因。
七、图片照明(IBL):环境光也是 PBR 的
PBR 不仅改变了直接光照,还改变了环境光照的处理方式。
1 漫反射 IBL
预计算环境的辐照度图(Irradiance Map)——对 Cubemap 的每个方向,计算半球积分后的漫反射结果。由于是低频信息,可以用非常低的分辨率(32×32)存储。
2 镜面 IBL
用 Split-Sum Approximation(UE 用的方法):
∫ f(l, v) * Li(l) * (n·l) dl ← 完整公式,太贵了
≈ ∫ f(l, v) * (n·l) dl * ∫ Li(l) * D(l) * (n·l) dl 预计算为 LUT(2D 纹理) 预过滤环境图(Mipmap 链)Pre-Filtered Environment Map:每个 Roughness 级别对应 Cubemap 的一个 Mipmap 级别。Roughness=0(光滑)用 Mip 0(清晰反射),Roughness=1(粗糙)用高 Mip(模糊反射)。
BRDF Integration LUT:一张 2D 纹理,横轴是 NdotV,纵轴是 Roughness。存储 BRDF 积分的结果(Scale 和 Bias)。这个 LUT 是标准的,整个引擎共享同一个。
// IBL 镜面反射的 UE 做法float3 PrefilteredColor = PrefilterEnvMap.SampleLevel(Sampler, R, Roughness * MAX_MIP);float2 EnvBRDF = BRDF_LUT.Sample(Sampler, float2(NdotV, Roughness));float3 SpecularIBL = PrefilteredColor * (F0 * EnvBRDF.x + EnvBRDF.y);八、总结
| 概念 | 一句话 |
|---|---|
| 微表面模型 | 表面 = 无数微小镜面,粗糙度反映微表面法线分散程度 |
| Cook-Torrance | D * F * G / (4 * NdotL * NdotV)——PBR 的数学核心 |
| GGX | UE 使用的法线分布函数,“长尾”让粗糙表面高光更真实 |
| Fresnel | 任何材质在掠射角都变成镜子,Schlick 近似计算 |
| 金属度工作流 | Metallic 0/1 决定 F0,非金属 F0≈0.04,金属 F0=BaseColor |
| IBL | 环境光也按 PBR 走——辐照度图 + 预过滤环境图 + BRDF LUT |
PBR 不只是”让画面更真实”。它的真正价值是让材质在不同光照环境下保持一致性——同一张贴图,在室内、室外、夜景、水下都看起来正确。这种”光照无关”的材质定义,才是现代引擎选 PBR 作为标准的根本原因。