1726 字
9 分钟
PBR 材质模型——从 Phong 到基于物理的渲染

一、Phong 做错了什么#

在 DX11 系列第三篇里,你学了 Phong/Blinn-Phong 光照模型。它看起来不错,但它有一个根本问题:不遵守能量守恒

Phong 的 Ambient + Diffuse + Specular:
→ 三个分量独立调参
→ 美术可以写出 "反射的光比入射的光还多" 的材质
→ 不同光照环境下,同一个材质看起来完全不同

PBR(Physically Based Rendering)的核心理念就两条:

  1. 能量守恒:出射光能量 ≤ 入射光能量
  2. 基于物理的 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))

三个核心项:

全称在做什么
DNormal Distribution Function(法线分布)多少微表面朝向半程向量
FFresnel(菲涅尔)多少光被反射(vs 折射进材质内部)
GGeometry(几何遮蔽)微表面之间互相遮挡了多少光

分母 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 + RoughnessDiffuse + 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-TorranceD * F * G / (4 * NdotL * NdotV)——PBR 的数学核心
GGXUE 使用的法线分布函数,“长尾”让粗糙表面高光更真实
Fresnel任何材质在掠射角都变成镜子,Schlick 近似计算
金属度工作流Metallic 0/1 决定 F0,非金属 F0≈0.04,金属 F0=BaseColor
IBL环境光也按 PBR 走——辐照度图 + 预过滤环境图 + BRDF LUT

PBR 不只是”让画面更真实”。它的真正价值是让材质在不同光照环境下保持一致性——同一张贴图,在室内、室外、夜景、水下都看起来正确。这种”光照无关”的材质定义,才是现代引擎选 PBR 作为标准的根本原因。

PBR 材质模型——从 Phong 到基于物理的渲染
https://www.m4doka.xyz/posts/cg/cg-5-pbr/
作者
m4doka
发布于
2026-02-15
许可协议
CC BY-NC-SA 4.0