需要预先学习:


1. PBR 基于物理的渲染

Phong模型与Blinn-Phong模型 中的两个模型都是经验模型(empirical model):用环境光、漫反射、高光三项拼凑出"看起来对"的结果,系数(shininess、ambientStrength)没有物理含义,换个光照环境就得重新调参。

PBR(Physically Based Rendering,基于物理的渲染)从物理规律出发建立反射模型:能量守恒、微表面反射、菲涅尔效应都被显式建模,材质参数直接对应真实世界的属性(金属度、粗糙度)。同一套参数在任何光照环境下都成立,这是 PBR 被游戏与影视工业广泛采用的原因。这篇笔记介绍渲染方程与 Cook-Torrance BRDF,并给出直接光照的 GLSL 实现。


2. 渲染方程与 BRDF


2.1 辐射度量学速览

描述光的能量需要一套度量单位,辐射度量学(radiometry)提供这套语言:

物理量 符号 含义 单位
辐射通量 Φ\Phi 单位时间通过的能量 W
辐射强度 II 单位立体角的辐射通量 W/sr\text{W}/\text{sr}
辐照度 EE 单位面积的辐射通量 W/m2\text{W}/\text{m}^2
辐射亮度 LL 单位面积、单位立体角的辐射通量 W/(m2⋅sr)\text{W}/(\text{m}^2 \cdot \text{sr})

其中立体角(solid angle)是二维角度的推广:一个锥体在单位球面上截出的面积。半径为 rr 的球面上,面积 AA 对应的立体角为 Ω=A/r2\Omega = A / r^2,单位是球面度(steradian,sr)。整个球面的立体角是 4π4\pi。

立体角示意图

着色关心的量是辐射亮度 LL:它同时固定了"从哪个方向来、打在哪个面上",是渲染方程的基本输入输出。光强随距离平方衰减(见 着色与渲染管线 的 Ir2\frac{I}{r^2})在辐射度量学里的来源是:同样的辐射通量摊到更大的球面上,单位面积的量变小。


2.2 BRDF 与渲染方程

一个表面点把来自各方向的光反射到各方向,这个映射由 BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数)描述。它回答的问题是:来自方向 ωi\omega_i 的入射光,有多少被反射到出射方向 ωo\omega_o?

fr(ωi,ωo)=dLo(ωo)dEi(ωi)f_r(\omega_i, \omega_o) = \frac{dL_o(\omega_o)}{dE_i(\omega_i)}

把它代入渲染方程(rendering equation)——出射亮度等于自发光加上所有入射方向反射亮度的积分:

Lo(p,ωo)=Le(p,ωo)+∫Ωfr(p,ωi,ωo) Li(p,ωi) (n⋅ωi) dωiL_o(p, \omega_o) = L_e(p, \omega_o) + \int_{\Omega} f_r(p, \omega_i, \omega_o)\, L_i(p, \omega_i)\, (\mathbf{n} \cdot \omega_i)\, d\omega_i

  • LiL_i:方向 ωi\omega_i 射来的入射亮度;
  • n⋅ωi\mathbf{n} \cdot \omega_i:Lambert 余弦项(见 着色与渲染管线),倾斜入射的有效能量;
  • 积分范围 Ω\Omega:法线方向所在的半球面。

渲染方程把"反射"描述为所有入射方向贡献的积分。实时渲染无法直接求积分,只能对有限的光源求和——PBR 的直接光照部分就是这样做的。


3. 微表面模型与 Cook-Torrance BRDF


3.1 微表面模型

宏观上光滑的表面,放大到微观尺度其实是凹凸不平的,由大量朝向各异的微小镜面(microfacet)组成。每个微表面只沿自己的镜像方向反射光——微表面模型(microfacet model)假设反射只发生在微表面上,宏观的反射特性由微表面朝向的统计分布决定:

  • 粗糙表面:微表面朝向杂乱,反射光散开,高光宽而暗;
  • 光滑表面:微表面朝向接近,反射光集中,高光窄而亮。

微表面模型的能量守恒体现在:反射出去的能量不能超过入射能量。粗糙度越高,微表面间互相遮蔽与自遮挡越严重,高光越弱——这部分由几何遮蔽项建模。


3.2 Cook-Torrance BRDF

Cook-Torrance BRDF 是目前最常用的微表面反射模型,把反射分解为漫反射与高光两项:

fr=kdflambert+ksfcook−torrancef_r = k_d f_{lambert} + k_s f_{cook-torrance}

flambert=cπ,fcook−torrance=DFG4 (ωo⋅n) (ωi⋅n)f_{lambert} = \frac{c}{\pi}, \qquad f_{cook-torrance} = \frac{DFG}{4\,(\omega_o \cdot \mathbf{n})\,(\omega_i \cdot \mathbf{n})}

  • flambertf_{lambert}:Lambert 漫反射项,cc 是表面颜色(albedo),除以 π\pi 保证半球积分后能量守恒;
  • fcook−torrancef_{cook-torrance}:高光项,由三个函数相乘:
    • DD:法线分布函数(Normal Distribution Function),描述微表面朝向的统计分布;
    • GG:几何函数(Geometry Function),描述微表面间的自遮挡;
    • FF:菲涅尔项(Fresnel),描述反射能量与入射角的关系。

分母 4(ωo⋅n)(ωi⋅n)4(\omega_o \cdot \mathbf{n})(\omega_i \cdot \mathbf{n}) 是微表面模型的几何修正项,保证能量守恒。kdk_d、ksk_s 是漫反射与高光的能量比例,由菲涅尔项决定(见下文金属度工作流)。

法线分布函数 D

DD 回答:有多少微表面的法线恰好朝向半程向量 h\mathbf{h}?GGX(Trowbridge-Reitz)分布是目前的主流选择:

DGGX(h)=α2π[(n⋅h)2(α2−1)+1]2D_{GGX}(\mathbf{h}) = \frac{\alpha^2}{\pi \left[ (\mathbf{n} \cdot \mathbf{h})^2 (\alpha^2 - 1) + 1 \right]^2}

α\alpha 是粗糙度 rr 的平方(α=r2\alpha = r^2)。n⋅h\mathbf{n} \cdot \mathbf{h} 接近 1 的微表面数量决定高光亮度:粗糙度越小,分布越集中于 h\mathbf{h} 附近,高光越亮越窄。GGX 相比早期模型(如 Beckmann)有更长的"拖尾",高光边缘过渡更自然。

几何函数 G

GG 描述微表面的两种自遮挡:

  • 遮蔽(shadowing):入射光被相邻微表面挡住;
  • 遮挡(masking):出射光被相邻微表面挡住。

两者都随粗糙度加剧。常用 Smith 模型,由 Schlick-GGX 近似:

GSchlickGGX(n,v)=n⋅v(n⋅v)(1−k)+k,k=(r+1)28G_{SchlickGGX}(\mathbf{n}, \mathbf{v}) = \frac{\mathbf{n} \cdot \mathbf{v}}{(\mathbf{n} \cdot \mathbf{v})(1 - k) + k}, \qquad k = \frac{(r + 1)^2}{8}

G=GSchlickGGX(n,v) GSchlickGGX(n,l)G = G_{SchlickGGX}(\mathbf{n}, \mathbf{v})\, G_{SchlickGGX}(\mathbf{n}, \mathbf{l})

入射与出射方向各算一次相乘。掠射角下 n⋅v\mathbf{n} \cdot \mathbf{v} 趋近于 0,GG 急剧变小——这正是粗糙表面在掠射角高光变暗的物理来源(Phong模型与Blinn-Phong模型 中 Blinn-Phong 无法表现这个现象)。

菲涅尔项 F

菲涅尔效应(Fresnel effect):光斜射到表面时反射率升高——掠射角下任何表面都像镜子。Schlick 近似:

F=F0+(1−F0) (1−h⋅v)5F = F_0 + (1 - F_0)\,(1 - \mathbf{h} \cdot \mathbf{v})^5

F0F_0 是法线方向(垂直入射)的反射率,是材质的关键参数:

材质 F0F_0
电介质(非金属,如塑料、木头) 约 0.04(几乎所有电介质都接近这个值)
金属(如铁、金) 0.5 ~ 1.0,且带颜色(albedo 本身)

注:菲涅尔项在 光线追踪 中已经出现过——反射光线与折射光线的权重分配就由它决定。PBR 里它同样决定高光强度,而且这个强度是随视角变化的,不是常数。


3.3 金属度-粗糙度工作流

金属与电介质的反射行为差异巨大:金属没有漫反射(光被吸收再反射,呈 albedo 颜色),电介质有漫反射且 F0F_0 恒定 0.04。金属度-粗糙度工作流(metallic-roughness workflow)用两个参数统一描述所有材质:

  • 金属度(metallic):0 表示电介质,1 表示金属;
  • 粗糙度(roughness):0 光滑,1 粗糙;
  • albedo:电介质时是漫反射颜色,金属时是高光颜色(F0F_0)。

由金属度推导 F0F_0 与能量分配:

F0=mix(0.04,albedo,metallic),kd=(1−F)(1−metallic)F_0 = \text{mix}(0.04, \text{albedo}, \text{metallic}), \qquad k_d = (1 - F)(1 - \text{metallic})

ks=Fk_s = F:菲涅尔项本身就是高光的能量比例。金属的 kd=0k_d = 0,没有漫反射;电介质的 F0=0.04F_0 = 0.04,高光弱、漫反射主导。这就是 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 的 α=r2\alpha = r^2 与几何函数的 k=(r+1)28k = \frac{(r+1)^2}{8} 都来自经验拟合,不同实现略有差异,但不影响整体观感。关键是粗糙度只在 DD、GG 里出现,FF 与粗糙度无关——高光的颜色只由菲涅尔决定。


4.2 直接光照

对每个光源,把 Cook-Torrance 公式与渲染方程逐项对应:光源即一个方向的 LiL_i,积分退化为求和。能量分配遵循金属度工作流:

/*
 *  直接光照:对每个光源累加 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)
高光核心 max⁡(0,n⋅h)p\max(0, \mathbf{n} \cdot \mathbf{h})^p D⋅G⋅F4(n⋅v)(n⋅l)\frac{D \cdot G \cdot F}{4(\mathbf{n} \cdot \mathbf{v})(\mathbf{n} \cdot \mathbf{l})}
高光强度 常数 ksk_s 菲涅尔项,随视角变化
掠射角行为 高光始终按 ksk_s 衰减 菲涅尔增强反射,粗糙面高光变暗
能量守恒 不保证 显式建模(分母修正、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 一脉相承:实时渲染就是用预计算换实时性。