第4.3章:PBR着色器基础原理

基于物理的渲染(Physically Based Rendering, PBR)是现代3D图形学的核心技术,通过基于真实的物理学原理,能够创造出更加逼真统一的渲染效果。本章将深入探讨PBR的核心原理及在Cocos Creator中的实现。

🎯 学习目标

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

  • PBR渲染的核心物理原理
  • 金属度/粗糙度工作流
  • BRDF 的数学原理与实现
  • Cocos Creator 中的 PBR 着色器使用
  • IBL(基于图像的光照)系统
  • PBR 实践要点与常见问题

📖 PBR基本原理

能量守恒

PBR 基于能量守恒原则:

能量守恒定义

1
2
3
4
5
6
7
// 入射能量 = 反射 + 折射 + 吸收
Incident = Reflected + Refracted + Absorbed

// 在 BRDF 框架中:
// 漫反射 + 镜面反射 ≈ 1.0(忽略吸收与能量损失时)
vec3 totalReflection = diffuse + specular;
// 确保不超过 1.0 以满足能量守恒

菲涅耳效应 (Fresnel)

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);
}

// F0 为法线入射时的反射率
// 绝缘体通常在 0.04 左右,金属随材质而变化(0.04~1.0)

微表面理论 (Microfacet Theory)

真实世界中的表面由大量微小的“微面”组成,粗糙度反映了这些微面的分布特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 法线分布函数 (Normal Distribution Function)
// Trowbridge-Reitz GGX 分布
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;
}

BRDF 的基本公式

双向反射分布函数 (BRDF) 描述了入射与出射之间的反射特性:

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
// Cook-Torrance BRDF
// BRDF = kd * lambert + ks * cook-torrance
// 其中:
// kd = 漫反射系数
// ks = 镜面反射系数
// lambert = 1/π
// cook-torrance = D·F·G / (4 (wo·n)(wi·n))

vec3 BRDF(vec3 L, vec3 V, vec3 N, vec3 albedo, float metallic, float roughness) {
vec3 H = normalize(V + L);

// 常用中间量
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float HdotV = max(dot(H, V), 0.0);
float NdotH = max(dot(N, H), 0.0);

// 计算 F0(法线入射反射率)
vec3 F0 = mix(vec3(0.04), albedo, metallic);

// 计算 D(法线分布)
float D = DistributionGGX(N, H, roughness);

// 计算 F(菲涅耳)
vec3 F = fresnelSchlick(HdotV, F0);

// 计算 G(几何遮蔽)
float G = GeometrySmith(N, V, L, roughness);

// 计算镜面反射项
vec3 numerator = D * G * F;
float denominator = 4.0 * NdotV * NdotL + 0.0001; // 防止除零
vec3 specular = numerator / denominator;

// 计算 kS/kD
vec3 kS = F;
vec3 kD = vec3(1.0) - kS;
kD *= 1.0 - metallic; // 金属不产生漫反射

return (kD * albedo / PI + specular) * NdotL;
}

🔧 材质参数/工作流

材质结构定义

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
// PBR 材质的核心参数
struct PBRMaterial {
vec3 albedo; // 反照率/基础色
float metallic; // 金属度 (0=非金属, 1=金属)
float roughness; // 粗糙度 (0=光滑, 1=粗糙)
vec3 normal; // 法线
float ao; // 环境遮蔽
vec3 emission; // 自发光
};

// 从贴图中采样材质参数
PBRMaterial sampleMaterial(vec2 uv) {
PBRMaterial mat;

// 基础色(sRGB,需要转线性)
mat.albedo = pow(texture(albedoMap, uv).rgb, vec3(2.2));

// 金属度与粗糙度(常见为合并通道的纹理)
vec3 metallicRoughness = texture(metallicRoughnessMap, uv).rgb;
mat.metallic = metallicRoughness.b; // 通常 B 通道
mat.roughness = metallicRoughness.g; // 通常 G 通道

// 法线贴图(切线空间)
vec3 normalMap = texture(normalTexture, uv).rgb * 2.0 - 1.0;
mat.normal = normalize(TBN * normalMap);

// 环境遮蔽
mat.ao = texture(aoMap, uv).r;

// 自发光
mat.emission = texture(emissionMap, uv).rgb;

return mat;
}

材质预设示例

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
@ccclass('PBRMaterialPresets')
export class PBRMaterialPresets extends Component {
@property(Material)
pbrMaterial: Material = null!;

// 金属材质预设
setupMetalMaterial() {
this.pbrMaterial.setProperty('albedo', new Color(155, 155, 155, 255));
this.pbrMaterial.setProperty('metallic', 1.0); // 完全金属
this.pbrMaterial.setProperty('roughness', 0.1); // 较光滑
}

// 塑料材质预设
setupPlasticMaterial() {
this.pbrMaterial.setProperty('albedo', new Color(200, 50, 50, 255));
this.pbrMaterial.setProperty('metallic', 0.0); // 非金属
this.pbrMaterial.setProperty('roughness', 0.4); // 中等粗糙
}

// 陶瓷材质预设
setupCeramicMaterial() {
this.pbrMaterial.setProperty('albedo', new Color(240, 240, 240, 255));
this.pbrMaterial.setProperty('metallic', 0.0); // 非金属
this.pbrMaterial.setProperty('roughness', 0.1); // 较光滑
}

// 橡胶材质预设
setupRubberMaterial() {
this.pbrMaterial.setProperty('albedo', new Color(40, 40, 40, 255));
this.pbrMaterial.setProperty('metallic', 0.0); // 非金属
this.pbrMaterial.setProperty('roughness', 0.9); // 高粗糙
}

// 皮革材质预设
setupLeatherMaterial() {
this.pbrMaterial.setProperty('albedo', new Color(101, 67, 33, 255));
this.pbrMaterial.setProperty('metallic', 0.0); // 非金属
this.pbrMaterial.setProperty('roughness', 0.8); // 粗糙
}
}

💡 基于图像的光照(IBL)

IBL 贡献项

IBL(Image-Based Lighting)通过预计算的环境贴图提供真实的环境光照贡献:

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
// IBL 采样函数
vec3 sampleIBL(vec3 N, vec3 V, float roughness, vec3 F0, vec3 albedo, float metallic) {
// 反射方向
vec3 R = reflect(-V, N);

// 镜面反射部分(由预过滤环境贴图 + BRDF 积分 LUT)
vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb;
vec2 brdf = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
vec3 specular = prefilteredColor * (F0 * brdf.x + brdf.y);

// 漫反射部分(由辐照度贴图)
vec3 irradiance = texture(irradianceMap, N).rgb;
vec3 diffuse = irradiance * albedo;

// 组合镜面/漫反射权重
vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness);
vec3 kD = 1.0 - kS;
kD *= 1.0 - metallic;

return kD * diffuse + specular;
}

// 粗糙度修正的菲涅耳近似
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
36
37
38
39
40
41
42
43
44
45
@ccclass('IBLProcessor')
export class IBLProcessor extends Component {
@property(TextureCube)
hdrCubemap: TextureCube = null!;

@property(RenderTexture)
irradianceMap: RenderTexture = null!;

@property(RenderTexture)
prefilterMap: RenderTexture = null!;

@property(Texture2D)
brdfLUT: Texture2D = null!;

start() {
this.preprocessIBL();
}

private preprocessIBL() {
// 1. 生成辐照度贴图(Irradiance Map)
this.generateIrradianceMap();

// 2. 生成预过滤环境贴图(Prefiltered Environment Map)
this.generatePrefilterMap();

// 3. 生成 BRDF 积分查找表(BRDF Integration LUT)
this.generateBRDFLUT();
}

private generateIrradianceMap() {
// 生成低频漫反射的辐照度
// 可在编辑时预处理或使用 GPU Compute Shader
console.log('Generating irradiance map...');
}

private generatePrefilterMap() {
// 按粗糙度分层生成预过滤的镜面反射
console.log('Generating prefilter map...');
}

private generateBRDFLUT() {
// 预计算 BRDF 积分查找表
console.log('Generating BRDF LUT...');
}
}

🧪 典型 PBR 着色器实现

Cocos Creator PBR Effect

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
CCEffect %{
techniques:
- name: opaque
passes:
- vert: pbr-vs:vert
frag: pbr-fs:frag
properties:
# 基础颜色
albedoMap: { value: white }
albedo: { value: [1,1,1,1], editor: { type: color } }

# PBR 参数
metallicRoughnessMap: { value: white }
metallic: { value: 0.5, editor: { range: [0,1,0.01] } }
roughness: { value: 0.5, editor: { range: [0,1,0.01] } }

# 法线贴图
normalMap: { value: normal }
normalScale: { value: 1.0, editor: { range: [0,2,0.01] } }

# 环境遮蔽
aoMap: { value: white }
aoStrength: { value: 1.0, editor: { range: [0,1,0.01] } }

# 自发光
emissionMap: { value: black }
emission: { value: [0,0,0,1], editor: { type: color } }
emissionScale: { value: 1.0, editor: { range: [0,5,0.01] } }

# 环境贴图
envMap: { value: default-cube }
envIntensity: { value: 1.0, editor: { range: [0,3,0.01] } }
}%

CCProgram pbr-vs %{
precision highp float;
#include <builtin/uniforms/cc-global>
#include <builtin/uniforms/cc-local>

in vec3 a_position;
in vec3 a_normal;
in vec2 a_texCoord;
in vec4 a_tangent;

out vec2 v_uv;
out vec3 v_worldPos;
out vec3 v_worldNormal;
out vec3 v_worldTangent;
out vec3 v_worldBitangent;

vec4 vert () {
vec4 pos = vec4(a_position, 1);

// 世界空间位置
v_worldPos = (cc_matWorld * pos).xyz;

// 世界空间法线
v_worldNormal = normalize((cc_matWorldIT * vec4(a_normal, 0.0)).xyz);

// 世界空间切线和副切线
v_worldTangent = normalize((cc_matWorld * vec4(a_tangent.xyz, 0.0)).xyz);
v_worldBitangent = cross(v_worldNormal, v_worldTangent) * a_tangent.w;

// 传递 UV
v_uv = a_texCoord;

return cc_matViewProj * vec4(v_worldPos, 1.0);
}
}%

CCProgram pbr-fs %{
precision mediump float;
#include <builtin/uniforms/cc-global>

in vec2 v_uv;
in vec3 v_worldPos;
in vec3 v_worldNormal;
in vec3 v_worldTangent;
in vec3 v_worldBitangent;

// 纹理采样
uniform sampler2D albedoMap;
uniform sampler2D metallicRoughnessMap;
uniform sampler2D normalMap;
uniform sampler2D aoMap;
uniform sampler2D emissionMap;
uniform samplerCube envMap;

// 材质参数
uniform PBR_MATERIAL {
vec4 albedo;
float metallic;
float roughness;
float normalScale;
float aoStrength;
vec4 emission;
float emissionScale;
float envIntensity;
};

// 光照参数
uniform PBR_LIGHTING {
vec3 lightDirection;
vec3 lightColor;
float lightIntensity;
};

const float PI = 3.14159265359;

// 法线分布函数
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;
}

// 几何遮蔽函数
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);
}

vec4 frag () {
// 采样基础色贴图
vec4 albedoSample = texture(albedoMap, v_uv);
vec3 baseColor = pow(albedoSample.rgb * albedo.rgb, vec3(2.2)); // sRGB 转线性

vec3 metallicRoughnessSample = texture(metallicRoughnessMap, v_uv).rgb;
float metallicValue = metallicRoughnessSample.b * metallic;
float roughnessValue = metallicRoughnessSample.g * roughness;

// 法线计算
vec3 N = normalize(v_worldNormal);
vec3 normalSample = texture(normalMap, v_uv).rgb * 2.0 - 1.0;
normalSample.xy *= normalScale;
mat3 TBN = mat3(
normalize(v_worldTangent),
normalize(v_worldBitangent),
N
);
N = normalize(TBN * normalSample);

// 视线方向
vec3 V = normalize(cc_cameraPos.xyz - v_worldPos);

// 计算 F0
vec3 F0 = mix(vec3(0.04), baseColor, metallicValue);

// 直接光照分量
vec3 L = normalize(-lightDirection);
vec3 H = normalize(V + L);
vec3 radiance = lightColor * lightIntensity;

float NDF = DistributionGGX(N, H, roughnessValue);
float G = GeometrySmith(N, V, L, roughnessValue);
vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);

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

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;

float NdotL = max(dot(N, L), 0.0);
vec3 directLighting = (kD * baseColor / PI + specular) * radiance * NdotL;

// 环境光照(简化版本)
vec3 R = reflect(-V, N);
vec3 envColor = textureLod(envMap, R, roughnessValue * 7.0).rgb;
vec3 ambient = envColor * baseColor * envIntensity;

// 环境遮蔽
float ao = texture(aoMap, v_uv).r;
ambient *= mix(1.0, ao, aoStrength);

// 自发光
vec3 emissionColor = texture(emissionMap, v_uv).rgb * emission.rgb * emissionScale;

// 合成最终颜色
vec3 finalColor = directLighting + ambient + emissionColor;

// 色调映射与 Gamma 校正
finalColor = finalColor / (finalColor + vec3(1.0)); // Reinhard 色调映射
finalColor = pow(finalColor, vec3(1.0/2.2)); // Gamma 校正

return vec4(finalColor, albedoSample.a * albedo.a);
}
}%

🧭 实际应用案例

动态材质切换系统

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
@ccclass('DynamicPBRMaterial')
export class DynamicPBRMaterial extends Component {
@property(MeshRenderer)
meshRenderer: MeshRenderer = null!;

@property(Material)
basePBRMaterial: Material = null!;

// 材质预设集合
private materialPresets = {
gold: {
albedo: new Color(255, 215, 0, 255),
metallic: 1.0,
roughness: 0.1
},
iron: {
albedo: new Color(196, 199, 199, 255),
metallic: 1.0,
roughness: 0.3
},
wood: {
albedo: new Color(139, 107, 66, 255),
metallic: 0.0,
roughness: 0.8
},
glass: {
albedo: new Color(255, 255, 255, 100),
metallic: 0.0,
roughness: 0.0
}
};

private materialInstance: MaterialInstance | null = null;

start() {
// 创建材质实例
this.materialInstance = new MaterialInstance({
parent: this.basePBRMaterial
});
this.meshRenderer.setMaterialInstance(this.materialInstance, 0);

// 应用默认参数
this.applyMaterialPreset('iron');
}

applyMaterialPreset(presetName: keyof typeof this.materialPresets) {
const preset = this.materialPresets[presetName];
if (preset && this.materialInstance) {
this.materialInstance.setProperty('albedo', preset.albedo);
this.materialInstance.setProperty('metallic', preset.metallic);
this.materialInstance.setProperty('roughness', preset.roughness);
}
}

// 平滑过渡到目标预设
transitionToPreset(presetName: keyof typeof this.materialPresets, duration: number = 1.0) {
if (!this.materialInstance) return;

const targetPreset = this.materialPresets[presetName];
const currentAlbedo = this.materialInstance.getProperty('albedo') as Color;
const currentMetallic = this.materialInstance.getProperty('metallic') as number;
const currentRoughness = this.materialInstance.getProperty('roughness') as number;

// 使用 Tween 做平滑过渡
const tweenData = {
metallic: currentMetallic,
roughness: currentRoughness,
r: currentAlbedo.r,
g: currentAlbedo.g,
b: currentAlbedo.b
};

tween(tweenData)
.to(duration, {
metallic: targetPreset.metallic,
roughness: targetPreset.roughness,
r: targetPreset.albedo.r,
g: targetPreset.albedo.g,
b: targetPreset.albedo.b
})
.call(() => {
if (this.materialInstance) {
this.materialInstance.setProperty('metallic', tweenData.metallic);
this.materialInstance.setProperty('roughness', tweenData.roughness);
this.materialInstance.setProperty('albedo', new Color(tweenData.r, tweenData.g, tweenData.b, 255));
}
})
.start();
}

onDestroy() {
if (this.materialInstance) {
this.materialInstance.destroy();
}
}
}

实时环境光控制

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
@ccclass('IBLController')
export class IBLController extends Component {
@property([Material])
pbrMaterials: Material[] = [];

@property(TextureCube)
environmentMap: TextureCube = null!;

@property({ range: [0, 3, 0.1] })
environmentIntensity: number = 1.0;

@property({ range: [0, 1, 0.1] })
exposure: number = 1.0;

update() {
// 实时更新 PBR 材质的环境参数
this.pbrMaterials.forEach(material => {
material.setProperty('envMap', this.environmentMap);
material.setProperty('envIntensity', this.environmentIntensity);
material.setProperty('exposure', this.exposure);
});
}

// 切换环境贴图
switchEnvironment(newEnvMap: TextureCube) {
this.environmentMap = newEnvMap;
}

// 模拟日夜循环
simulateDayNightCycle() {
const dayDuration = 60; // 60秒为一个周期
const time = (director.getTotalTime() % dayDuration) / dayDuration;

// 根据时间调整环境强度
this.environmentIntensity = 0.5 + 0.5 * Math.sin(time * Math.PI * 2);

// 根据时间调整曝光,模拟不同时间的光照
this.exposure = 0.8 + 0.4 * Math.sin(time * Math.PI * 2);
}
}

🚀 性能与质量优化

LOD 与质量等级

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
@ccclass('PBRQualityManager')
export class PBRQualityManager extends Component {
enum QualityLevel {
LOW = 0,
MEDIUM = 1,
HIGH = 2
}

@property(Material)
basePBRMaterial: Material = null!;

private currentQuality: QualityLevel = QualityLevel.MEDIUM;

start() {
this.setQualityLevel(this.detectDevicePerformance());
}

private detectDevicePerformance(): QualityLevel {
const devicePixelRatio = screen.devicePixelRatio;
const screenArea = screen.windowSize.width * screen.windowSize.height;

if (devicePixelRatio > 2 && screenArea > 2073600) {
return QualityLevel.HIGH;
} else if (devicePixelRatio > 1.5 && screenArea > 921600) {
return QualityLevel.MEDIUM;
} else {
return QualityLevel.LOW;
}
}

setQualityLevel(level: QualityLevel) {
this.currentQuality = level;

switch (level) {
case QualityLevel.LOW:
// 低质量配置
this.basePBRMaterial.recompileShaders({
USE_IBL: false,
USE_NORMAL_MAP: false,
QUALITY_LEVEL: 0
});
break;

case QualityLevel.MEDIUM:
// 中质量配置
this.basePBRMaterial.recompileShaders({
USE_IBL: true,
USE_NORMAL_MAP: true,
USE_AO_MAP: false,
QUALITY_LEVEL: 1
});
break;

case QualityLevel.HIGH:
// 高质量配置
this.basePBRMaterial.recompileShaders({
USE_IBL: true,
USE_NORMAL_MAP: true,
USE_AO_MAP: true,
USE_EMISSION_MAP: true,
QUALITY_LEVEL: 2
});
break;
}
}
}

✅ 验证与质量检查

PBR 参数校验器

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
@ccclass('PBRValidator')
export class PBRValidator extends Component {
@property(Material)
testMaterial: Material = null!;

@property(Label)
validationResult: Label = null!;

validatePBRMaterial(): string[] {
const issues: string[] = [];

// 检查金属度范围
const metallic = this.testMaterial.getProperty('metallic') as number;
if (metallic < 0 || metallic > 1) {
issues.push('金属度应在 0-1 范围内');
}

// 检查粗糙度范围
const roughness = this.testMaterial.getProperty('roughness') as number;
if (roughness < 0 || roughness > 1) {
issues.push('粗糙度应在 0-1 范围内');
}

// 检查反照率亮度合理性
const albedo = this.testMaterial.getProperty('albedo') as Color;
const luminance = 0.299 * albedo.r + 0.587 * albedo.g + 0.114 * albedo.b;
if (metallic > 0.9 && luminance < 0.5) {
issues.push('高金属度下反照率不应过暗');
}
if (metallic < 0.1 && luminance > 0.9) {
issues.push('低金属度下反照率不应过亮');
}

return issues;
}

update() {
const issues = this.validatePBRMaterial();
if (issues.length > 0) {
this.validationResult.string = '?? ' + issues.join('\n');
this.validationResult.node.color = Color.YELLOW;
} else {
this.validationResult.string = '✔ PBR 参数校验通过';
this.validationResult.node.color = Color.GREEN;
}
}
}

📚 下一步学习

完成 PBR 基础学习后,建议继续:

  1. 第5.1章:Surface Shader详解 - Surface Shaderϵͳ
  2. 第6.1章:光照模型详解 - 高级光照模型

💡 总结

通过本章学习,你应当掌握:

  • ✅ PBR 渲染的核心物理原理
  • ✅ 金属度/粗糙度工作流
  • ✅ BRDF 的实现与优化
  • ✅ 基于图像的光照(IBL)系统
  • ✅ 典型 PBR 着色器的实现
  • ✅ 性能优化与质量分级策略

PBR 已成为现代游戏渲染的标准。要获得稳定的高质量 PBR 效果,既需要正确的算法实现,也需要高质量的材质贴图与合理的参数管理。


重要提示:PBR 渲染对性能要求较高,移动端开发时要特别注意优化,使用 LOD 与质量等级系统,在保证效果的同时维持良好的性能表现。