第6.2章:PBR光照模型深入

基于物理的渲染(Physically Based Rendering, PBR)是现代实时渲染的标准,它基于物理原理来模拟光线与物体表面的交互,能够在各种光照条件下提供一致且真实的渲染效果。

🎯 学习目标

通过本章学习,你将掌握:

  • PBR的基础物理原理
  • Cook-Torrance BRDF模型的数学实�?- 金属度与粗糙度工作流
  • IBL(Image-Based Lighting)环境光�?- 在Cocos Creator中实现完整的PBR材质

🌟 PBR基础理论

什么是PBR

PBR基于以下核心原理�?

  1. 能量守恒:反射光不能超过入射�?2. **菲涅尔反�?*:所有表面都有菲涅尔效应
  2. **微表面理�?*:表面由无数微小镜面组成
1
2
3
4
5
6
7
8
9
10
11
12
13
// PBR的基本方�?vec3 Lo = (kD * albedo / PI + kS * D * G * F / (4.0 * NdotV * NdotL)) * radiance * NdotL;
// 其中�?// kD = 漫反射系�? kS = 镜面反射系数
// D = 分布函数, G = 几何函数, F = 菲涅尔函�?```

### 材质属�?
PBR使用以下核心参数�?
```glsl
struct PBRMaterial {
vec3 albedo; // 基础颜色
float metallic; // 金属�?[0,1]
float roughness; // 粗糙�?[0,1]
float ao; // 环境遮蔽 [0,1]
vec3 normal; // 法向�? vec3 emissive; // 自发�?};

🔬 Cook-Torrance BRDF模型

BRDF概述

双向反射分布函数(BRDF)描述了入射光如何反射:

1
2
3
4
5
// Cook-Torrance BRDF
vec3 BRDF = kD * lambertian + kS * cookTorrance;

// 其中�?// lambertian = albedo / PI
// cookTorrance = D * G * F / (4.0 * NdotV * NdotL)

分布函数 D (Distribution)

描述微表面法向量的分布,常用GGX/Trowbridge-Reitz�?

1
2
3
4
5
6
7
8
9
10
11
12
13
// GGX/Trowbridge-Reitz分布函数
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 num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;

return num / denom;
}

几何函数 G (Geometry)

考虑微表面的阴影和遮蔽,使用Smith模型�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Smith几何函数
float GeometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;

float num = NdotV;
float denom = NdotV * (1.0 - k) + k;

return num / denom;
}

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);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);

return ggx1 * ggx2;
}

菲涅尔函�?F (Fresnel)

使用Schlick近似计算菲涅尔反射:

1
2
3
4
5
6
7
// Schlick菲涅尔近�?vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

// 带粗糙度的菲涅尔近似(用于IBL�?vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) {
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

🎨 金属度工作流

金属与电介质

材质可分为两大类�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 根据金属度混合材质属�?vec3 F0 = mix(vec3(0.04), albedo, metallic);  // F0�?vec3 kS = fresnelSchlick(max(dot(H, V), 0.0), F0);
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic; // 金属没有漫反�?```

### 完整的PBR实现

```glsl
vec3 calculatePBR(vec3 albedo, float metallic, float roughness, vec3 normal,
vec3 viewDir, vec3 lightDir, vec3 lightColor) {
vec3 H = normalize(viewDir + lightDir);

// 计算角度
float NdotV = max(dot(normal, viewDir), 0.0);
float NdotL = max(dot(normal, lightDir), 0.0);
float HdotV = max(dot(H, viewDir), 0.0);

// 材质属�? vec3 F0 = mix(vec3(0.04), albedo, metallic);

// Cook-Torrance BRDF
float D = DistributionGGX(normal, H, roughness);
float G = GeometrySmith(normal, viewDir, lightDir, roughness);
vec3 F = fresnelSchlick(HdotV, F0);

vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;

vec3 numerator = D * G * F;
float denominator = 4.0 * NdotV * NdotL + 0.0001;
vec3 specular = numerator / denominator;

vec3 diffuse = kD * albedo / PI;

return (diffuse + specular) * lightColor * NdotL;
}

🌍 IBL环境光照

IBL原理

Image-Based Lighting使用环境贴图提供环境光照�?

1
2
3
4
5
6
7
8
9
// IBL漫反射(辐照度贴图)
vec3 irradiance = texture(irradianceMap, normal).rgb;
vec3 diffuseIBL = irradiance * albedo;

// IBL镜面反射(预过滤环境贴图�?vec3 R = reflect(-viewDir, normal);
float lod = roughness * MAX_REFLECTION_LOD;
vec3 prefilteredColor = textureLod(prefilterMap, R, lod).rgb;
vec2 brdfLUT = texture(brdfLUTMap, vec2(NdotV, roughness)).rg;
vec3 specularIBL = prefilteredColor * (kS * brdfLUT.x + brdfLUT.y);

简化的IBL实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 简化的IBL环境光照
vec3 calculateIBL(vec3 normal, vec3 viewDir, vec3 albedo,
float metallic, float roughness, float ao) {
vec3 F0 = mix(vec3(0.04), albedo, metallic);
vec3 F = fresnelSchlickRoughness(max(dot(normal, viewDir), 0.0), F0, roughness);

vec3 kS = F;
vec3 kD = 1.0 - kS;
kD *= 1.0 - metallic;

// 使用球谐光照近似IBL
vec3 irradiance = cc_ambientSky.rgb;
vec3 diffuse = irradiance * albedo;

// 简化的镜面反射
vec3 R = reflect(-viewDir, normal);
float lod = roughness * 4.0; // 假设�?级mipmap
vec3 specular = cc_ambientSky.rgb * F * (1.0 - roughness);

vec3 ambient = (kD * diffuse + specular) * ao;
return ambient;
}

🔧 Cocos Creator PBR实现

完整的PBR Surface Shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
CCEffect %{
techniques:
- name: opaque
passes:
- vert: pbr-vs:vert
frag: pbr-fs:frag
properties: &props
albedoMap: { value: white }
normalMap: { value: normal }
metallicMap: { value: white }
roughnessMap: { value: white }
aoMap: { value: white }
emissiveMap: { value: black }
albedoFactor: { value: [1, 1, 1, 1], editor: { type: color } }
metallicFactor: { value: 1.0, range: [0, 1] }
roughnessFactor: { value: 1.0, range: [0, 1] }
normalScale: { value: 1.0, range: [0, 2] }
aoStrength: { value: 1.0, range: [0, 1] }
emissiveFactor: { value: [0, 0, 0, 1], editor: { type: color } }
}%

CCProgram pbr-vs %{
#include <surface-vertex>
}%

CCProgram pbr-fs %{
#include <surface-fragment>

uniform PBRProperties {
vec4 albedoFactor;
float metallicFactor;
float roughnessFactor;
float normalScale;
float aoStrength;
vec4 emissiveFactor;
};

uniform sampler2D albedoMap;
uniform sampler2D normalMap;
uniform sampler2D metallicMap;
uniform sampler2D roughnessMap;
uniform sampler2D aoMap;
uniform sampler2D emissiveMap;

// PBR函数定义...
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 num = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = 3.14159265 * denom * denom;

return num / denom;
}

float GeometrySchlickGGX(float NdotV, float roughness) {
float r = (roughness + 1.0);
float k = (r * r) / 8.0;

float num = NdotV;
float denom = NdotV * (1.0 - k) + k;

return num / denom;
}

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);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);

return ggx1 * ggx2;
}

vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

void surf (in SurfaceIn In, inout SurfaceOut Out) {
// 采样贴图
vec4 albedo = texture(albedoMap, In.uv) * albedoFactor;
vec3 normal = normalize(In.worldNormal);
float metallic = texture(metallicMap, In.uv).r * metallicFactor;
float roughness = texture(roughnessMap, In.uv).r * roughnessFactor;
float ao = mix(1.0, texture(aoMap, In.uv).r, aoStrength);
vec3 emissive = texture(emissiveMap, In.uv).rgb * emissiveFactor.rgb;

// 处理法线贴图
#if HAS_NORMAL_MAP
vec3 normalMap = texture(normalMap, In.uv).rgb * 2.0 - 1.0;
normalMap.xy *= normalScale;
normal = normalize(In.worldTangent.xyz * normalMap.x +
In.worldBinormal.xyz * normalMap.y +
In.worldNormal.xyz * normalMap.z);
#endif

vec3 viewDir = normalize(cc_cameraPos.xyz - In.worldPos);

// 主光源PBR计算
vec3 lightDir = normalize(-cc_mainLitDir.xyz);
vec3 lightColor = cc_mainLitColor.rgb * cc_mainLitColor.a;
vec3 H = normalize(viewDir + lightDir);

float NdotV = max(dot(normal, viewDir), 0.0);
float NdotL = max(dot(normal, lightDir), 0.0);
float HdotV = max(dot(H, viewDir), 0.0);

// 材质属�? vec3 F0 = mix(vec3(0.04), albedo.rgb, metallic);

// Cook-Torrance BRDF
float D = DistributionGGX(normal, H, roughness);
float G = GeometrySmith(normal, viewDir, lightDir, roughness);
vec3 F = fresnelSchlick(HdotV, F0);

vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic;

vec3 numerator = D * G * F;
float denominator = 4.0 * NdotV * NdotL + 0.0001;
vec3 specular = numerator / denominator;

vec3 diffuse = kD * albedo.rgb / PI;
vec3 directLighting = (diffuse + specular) * lightColor * NdotL;

// 环境光照
vec3 ambient = cc_ambientSky.rgb * albedo.rgb * ao * 0.03;

// 最终颜�? vec3 color = ambient + directLighting + emissive;

Out.albedo = vec4(color, albedo.a);
Out.normal = normal;
Out.metallic = metallic;
Out.roughness = roughness;
Out.ao = ao;
Out.emissive = emissive;
}
}%

📱 移动端优�?

性能优化技�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 移动端优化版本的PBR
vec3 calculatePBRMobile(vec3 albedo, float metallic, float roughness,
vec3 normal, vec3 viewDir, vec3 lightDir, vec3 lightColor) {
// 简化的GGX分布
float roughness2 = roughness * roughness;
float roughness4 = roughness2 * roughness2;

vec3 H = normalize(viewDir + lightDir);
float NdotH = max(dot(normal, H), 0.0);
float NdotV = max(dot(normal, viewDir), 0.0);
float NdotL = max(dot(normal, lightDir), 0.0);
float VdotH = max(dot(viewDir, H), 0.0);

// 简化的分布函数
float D = roughness4 / (3.14159265 * pow(NdotH * NdotH * (roughness4 - 1.0) + 1.0, 2.0));

// 简化的几何函数
float k = roughness2 * 0.5;
float G1L = NdotL / (NdotL * (1.0 - k) + k);
float G1V = NdotV / (NdotV * (1.0 - k) + k);
float G = G1L * G1V;

// 简化的菲涅�? vec3 F0 = mix(vec3(0.04), albedo, metallic);
vec3 F = F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);

vec3 kS = F;
vec3 kD = (1.0 - kS) * (1.0 - metallic);

vec3 diffuse = kD * albedo / 3.14159265;
vec3 specular = D * G * F / (4.0 * NdotV * NdotL + 0.001);

return (diffuse + specular) * lightColor * NdotL;
}

LOD系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 基于距离的LOD
uniform float cameraDistance;
uniform float lodDistance;

void surf (in SurfaceIn In, inout SurfaceOut Out) {
float distance = length(cc_cameraPos.xyz - In.worldPos);
float lodFactor = clamp(distance / lodDistance, 0.0, 1.0);

// LOD 0: 完整PBR
// LOD 1: 简化PBR
// LOD 2: Lambert光照

vec3 color;
if (lodFactor < 0.3) {
color = calculatePBRFull(/*...*/);
} else if (lodFactor < 0.7) {
color = calculatePBRMobile(/*...*/);
} else {
color = calculateLambert(/*...*/);
}

Out.albedo = vec4(color, 1.0);
}

🎯 实际应用案例

案例1:金属材�?

1
2
3
4
// 金属材质参数
albedo = vec3(0.7, 0.7, 0.7); // 银色
metallic = 1.0; // 完全金属
roughness = 0.1; // 光滑表面

案例2:塑料材�?

1
2
3
4
5
6
7
8
9
// 塑料材质参数
albedo = vec3(0.8, 0.2, 0.2); // 红色
metallic = 0.0; // 非金�?roughness = 0.5; // 中等粗糙�?```

### 案例3:皮革材�?
```glsl
// 皮革材质参数
albedo = vec3(0.4, 0.2, 0.1); // 棕色
metallic = 0.0; // 非金�?roughness = 0.8; // 粗糙表面

🔍 调试与验�?

PBR验证工具

1
2
3
4
5
6
7
8
9
10
// 能量守恒检�?float energyCheck(vec3 kD, vec3 kS) {
return dot(kD + kS, vec3(0.333)); // 应该 <= 1.0
}

// 白炉测试
vec3 whiteFurnaceTest(vec3 albedo, float metallic, float roughness) {
// 在均匀白光环境下,输出应该等于albedo
vec3 result = calculatePBR(albedo, metallic, roughness, /*...*/);
return result - albedo; // 应该接近0
}

可视化调�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 调试模式
#define DEBUG_ALBEDO 0
#define DEBUG_METALLIC 1
#define DEBUG_ROUGHNESS 2
#define DEBUG_NORMAL 3
#define DEBUG_AO 4

#ifndef DEBUG_MODE
#define DEBUG_MODE DEBUG_ALBEDO
#endif

void surf (in SurfaceIn In, inout SurfaceOut Out) {
// 常规PBR计算...

#if DEBUG_MODE == DEBUG_ALBEDO
Out.albedo = vec4(albedo.rgb, 1.0);
#elif DEBUG_MODE == DEBUG_METALLIC
Out.albedo = vec4(vec3(metallic), 1.0);
#elif DEBUG_MODE == DEBUG_ROUGHNESS
Out.albedo = vec4(vec3(roughness), 1.0);
#elif DEBUG_MODE == DEBUG_NORMAL
Out.albedo = vec4(normal * 0.5 + 0.5, 1.0);
#elif DEBUG_MODE == DEBUG_AO
Out.albedo = vec4(vec3(ao), 1.0);
#else
Out.albedo = vec4(finalColor, albedo.a);
#endif
}

📖 本章总结

通过本章学习,我们深入掌握了�?

  • �?PBR理论基础:能量守恒、菲涅尔效应、微表面理论
  • �?Cook-Torrance BRDF:D、G、F三个函数的数学实�?- �?**材质工作�?*:金属度/粗糙度参数化
  • �?IBL环境光照:基于图像的光照计算
  • �?性能优化:移动端适配和LOD系统
  • �?实际应用:不同材质类型的参数配置

🚀 下一步学�?

掌握了PBR理论后,建议继续学习�?
👉 �?.3章:自定义光照函数

💡 实践练习

  1. 基础练习:实现一个可调节的PBR材质�?2. 进阶练习:创建不同类型的PBR材质预设
  2. 高级练习:实现基于距离的PBR LOD系统

*参考资�?

系列导航