PBR
需要预先学习:
1. PBR 基于物理的渲染
Phong模型与Blinn-Phong模型 中的两个模型都是经验模型(empirical model):用环境光、漫反射、高光三项拼凑出"看起来对"的结果,系数(shininess、ambientStrength)没有物理含义,换个光照环境就得重新调参。
PBR(Physically Based Rendering,基于物理的渲染)从物理规律出发建立反射模型:能量守恒、微表面反射、菲涅尔效应都被显式建模,材质参数直接对应真实世界的属性(金属度、粗糙度)。同一套参数在任何光照环境下都成立,这是 PBR 被游戏与影视工业广泛采用的原因。这篇笔记介绍渲染方程与 Cook-Torrance BRDF,并给出直接光照的 GLSL 实现。
2. 渲染方程与 BRDF
2.1 辐射度量学速览
描述光的能量需要一套度量单位,辐射度量学(radiometry)提供这套语言:
| 物理量 | 符号 | 含义 | 单位 |
|---|---|---|---|
| 辐射通量 | 单位时间通过的能量 | W | |
| 辐射强度 | 单位立体角的辐射通量 | ||
| 辐照度 | 单位面积的辐射通量 | ||
| 辐射亮度 | 单位面积、单位立体角的辐射通量 |
其中立体角(solid angle)是二维角度的推广:一个锥体在单位球面上截出的面积。半径为 的球面上,面积 对应的立体角为 ,单位是球面度(steradian,sr)。整个球面的立体角是 。

着色关心的量是辐射亮度 :它同时固定了"从哪个方向来、打在哪个面上",是渲染方程的基本输入输出。光强随距离平方衰减(见 着色与渲染管线 的 )在辐射度量学里的来源是:同样的辐射通量摊到更大的球面上,单位面积的量变小。
2.2 BRDF 与渲染方程
一个表面点把来自各方向的光反射到各方向,这个映射由 BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数)描述。它回答的问题是:来自方向 的入射光,有多少被反射到出射方向 ?
把它代入渲染方程(rendering equation)——出射亮度等于自发光加上所有入射方向反射亮度的积分:
- :方向 射来的入射亮度;
- :Lambert 余弦项(见 着色与渲染管线),倾斜入射的有效能量;
- 积分范围 :法线方向所在的半球面。
渲染方程把"反射"描述为所有入射方向贡献的积分。实时渲染无法直接求积分,只能对有限的光源求和——PBR 的直接光照部分就是这样做的。
3. 微表面模型与 Cook-Torrance BRDF
3.1 微表面模型
宏观上光滑的表面,放大到微观尺度其实是凹凸不平的,由大量朝向各异的微小镜面(microfacet)组成。每个微表面只沿自己的镜像方向反射光——微表面模型(microfacet model)假设反射只发生在微表面上,宏观的反射特性由微表面朝向的统计分布决定:
- 粗糙表面:微表面朝向杂乱,反射光散开,高光宽而暗;
- 光滑表面:微表面朝向接近,反射光集中,高光窄而亮。
微表面模型的能量守恒体现在:反射出去的能量不能超过入射能量。粗糙度越高,微表面间互相遮蔽与自遮挡越严重,高光越弱——这部分由几何遮蔽项建模。
3.2 Cook-Torrance BRDF
Cook-Torrance BRDF 是目前最常用的微表面反射模型,把反射分解为漫反射与高光两项:
- :Lambert 漫反射项, 是表面颜色(albedo),除以 保证半球积分后能量守恒;
- :高光项,由三个函数相乘:
- :法线分布函数(Normal Distribution Function),描述微表面朝向的统计分布;
- :几何函数(Geometry Function),描述微表面间的自遮挡;
- :菲涅尔项(Fresnel),描述反射能量与入射角的关系。
分母 是微表面模型的几何修正项,保证能量守恒。、 是漫反射与高光的能量比例,由菲涅尔项决定(见下文金属度工作流)。
法线分布函数 D
回答:有多少微表面的法线恰好朝向半程向量 ?GGX(Trowbridge-Reitz)分布是目前的主流选择:
是粗糙度 的平方()。 接近 1 的微表面数量决定高光亮度:粗糙度越小,分布越集中于 附近,高光越亮越窄。GGX 相比早期模型(如 Beckmann)有更长的"拖尾",高光边缘过渡更自然。
几何函数 G
描述微表面的两种自遮挡:
- 遮蔽(shadowing):入射光被相邻微表面挡住;
- 遮挡(masking):出射光被相邻微表面挡住。
两者都随粗糙度加剧。常用 Smith 模型,由 Schlick-GGX 近似:
入射与出射方向各算一次相乘。掠射角下 趋近于 0, 急剧变小——这正是粗糙表面在掠射角高光变暗的物理来源(Phong模型与Blinn-Phong模型 中 Blinn-Phong 无法表现这个现象)。
菲涅尔项 F
菲涅尔效应(Fresnel effect):光斜射到表面时反射率升高——掠射角下任何表面都像镜子。Schlick 近似:
是法线方向(垂直入射)的反射率,是材质的关键参数:
| 材质 | |
|---|---|
| 电介质(非金属,如塑料、木头) | 约 0.04(几乎所有电介质都接近这个值) |
| 金属(如铁、金) | 0.5 ~ 1.0,且带颜色(albedo 本身) |
注:菲涅尔项在 光线追踪 中已经出现过——反射光线与折射光线的权重分配就由它决定。PBR 里它同样决定高光强度,而且这个强度是随视角变化的,不是常数。
3.3 金属度-粗糙度工作流
金属与电介质的反射行为差异巨大:金属没有漫反射(光被吸收再反射,呈 albedo 颜色),电介质有漫反射且 恒定 0.04。金属度-粗糙度工作流(metallic-roughness workflow)用两个参数统一描述所有材质:
- 金属度(metallic):0 表示电介质,1 表示金属;
- 粗糙度(roughness):0 光滑,1 粗糙;
- albedo:电介质时是漫反射颜色,金属时是高光颜色()。
由金属度推导 与能量分配:
:菲涅尔项本身就是高光的能量比例。金属的 ,没有漫反射;电介质的 ,高光弱、漫反射主导。这就是 PBR 材质只需要三个参数的原因——物理规律替你算好了其余一切。
4. GLSL 实现
4.1 材质参数与函数框架
先实现 Cook-Torrance 的三个核心函数。材质参数用 uniform 传入:albedo、metallic、roughness,再加光源数组。
#version 330 core
const float PI = 3.14159265359;
in vec3 FragPos; // 世界空间位置
in vec3 Normal; // 世界空间法线(已由正规矩阵变换)
uniform vec3 albedo; // 表面颜色:电介质漫反射色 / 金属高光色
uniform float metallic; // 金属度,0.0 电介质,1.0 金属
uniform float roughness; // 粗糙度,0.0 光滑,1.0 粗糙
#define MAX_LIGHTS 4
uniform vec3 lightPos[MAX_LIGHTS]; // 点光源位置(世界空间)
uniform vec3 lightColor[MAX_LIGHTS]; // 光源颜色(光强)
uniform int lightCount;
uniform vec3 viewPos; // 摄像机位置(世界空间)
out vec4 FragColor;
/*
* 法线分布函数(GGX)
*
* 参数:
* N 表面法线,已归一化
* H 半程向量,已归一化
* roughness 粗糙度 r,alpha = r * r
*
* 返回:
* 朝向半程向量的微表面密度
*/
float distributionGGX(vec3 N, vec3 H, float roughness)
{
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return a2 / denom;
}
/*
* 几何函数(Smith-Schlick-GGX 的单方向部分)
*
* 参数:
* NdotV n 点乘 v 或 n 点乘 l(已钳制非负)
* roughness 粗糙度
*
* 返回:
* 该方向上的遮蔽/遮挡系数
*/
float geometrySchlickGGX(float NdotV, float roughness)
{
float r = roughness + 1.0;
float k = (r * r) / 8.0;
return NdotV / (NdotV * (1.0 - k) + k);
}
/*
* 几何函数(Smith 模型,入射出射各一次)
*/
float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
return geometrySchlickGGX(NdotV, roughness) *
geometrySchlickGGX(NdotL, roughness);
}
/*
* 菲涅尔项(Schlick 近似)
*
* 参数:
* cosTheta h 点乘 v
* F0 垂直入射反射率(由金属度推导)
*
* 返回:
* 当前视角下的反射率(RGB)
*/
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}
注:GGX 的 与几何函数的 都来自经验拟合,不同实现略有差异,但不影响整体观感。关键是粗糙度只在 、 里出现, 与粗糙度无关——高光的颜色只由菲涅尔决定。
4.2 直接光照
对每个光源,把 Cook-Torrance 公式与渲染方程逐项对应:光源即一个方向的 ,积分退化为求和。能量分配遵循金属度工作流:
/*
* 直接光照:对每个光源累加 Cook-Torrance 高光与漫反射
*
* 参数:
* N 片元法线(世界空间,已归一化)
* V 片元指向摄像机(世界空间,已归一化)
*
* 返回:
* 该片元从所有点光源接收的辐射亮度
*/
vec3 calcDirectLight(vec3 N, vec3 V)
{
vec3 Lo = vec3(0.0);
for (int i = 0; i < lightCount; ++i)
{
// 每个光源的入射方向、半程向量与辐射亮度
vec3 L = normalize(lightPos[i] - FragPos);
vec3 H = normalize(V + L);
float dist = length(lightPos[i] - FragPos);
float attenuation = 1.0 / (dist * dist); // 距离平方衰减
vec3 radiance = lightColor[i] * attenuation;
// Cook-Torrance 高光项:D * G * F / (4 * NdotV * NdotL)
float NDF = distributionGGX(N, H, roughness);
float G = geometrySmith(N, V, L, roughness);
// F0:电介质 0.04,金属取 albedo,按金属度混合
vec3 F0 = mix(vec3(0.04), albedo, metallic);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 numerator = NDF * G * F;
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
vec3 specular = numerator / denominator; // 加 0.0001 防止除零
// 能量分配:菲涅尔项 kS 决定高光占比,其余为漫反射
// 金属(metallic == 1)没有漫反射,kD 为 0
vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);
float NdotL = max(dot(N, L), 0.0);
Lo += (kD * albedo / PI + specular) * radiance * NdotL;
}
return Lo;
}
void main()
{
vec3 N = normalize(Normal);
vec3 V = normalize(viewPos - FragPos);
vec3 Lo = calcDirectLight(N, V);
// 环境光:PBR 的间接光照需要 IBL,这里先用常数近似(见文末)
vec3 ambient = vec3(0.03) * albedo;
FragColor = vec4(ambient + Lo, 1.0);
}
4.3 与 Blinn-Phong 的对比
| 维度 | Blinn-Phong | PBR(Cook-Torrance) |
|---|---|---|
| 高光核心 | ||
| 高光强度 | 常数 | 菲涅尔项,随视角变化 |
| 掠射角行为 | 高光始终按 衰减 | 菲涅尔增强反射,粗糙面高光变暗 |
| 能量守恒 | 不保证 | 显式建模(分母修正、kD/kS 分配) |
| 参数含义 | 无物理意义 | metallic / roughness 直接对应材质属性 |
| 调参 | 换环境需重新调 | 一套参数跨环境通用 |
注:PBR 不是"更高级的 Blinn-Phong",两者是不同目标下的设计:Blinn-Phong 追求便宜与可控,PBR 追求物理一致。很多实时引擎用 Blinn-Phong 处理廉价材质、用 PBR 处理主角与场景关键物体,两者可以共存。
5. 环境光照与 IBL
上面的实现把环境光当成常数,但 PBR 的漫反射与高光都需要方向性的间接光:间接光从四面八方射来,每个方向都要按 BRDF 积分——这就是 IBL(Image Based Lighting,基于图像的光照):用一张环境贴图(cubemap,见 纹理)记录周围环境的光照,渲染方程中的半球积分用预计算代替:
- 漫反射 IBL:把环境贴图按余弦权重预滤波成一张模糊贴图,采样一次即可;
- 高光 IBL:按粗糙度把环境贴图预滤波成 mipmap 链(粗糙度越高越模糊),再配合 BRDF 查找表(LUT)采样。
IBL 让 PBR 材质在"环境光"下也保持能量守恒与金属度表现——金属物体反射出环境的颜色,粗糙金属的反射是模糊的——这是 PBR 观感完整的最后一块。预计算的思路(把昂贵的积分提前算好存起来)与 ShadowMap与PCF、PCSS 的阴影贴图、纹理 的 mipmap 一脉相承:实时渲染就是用预计算换实时性。



