第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 Effect)

1
2
3
4
5
6
7
8
// Schlick���Ƶķ���������
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

// F0�Dz��ʵĻ���������
// ���������ڵ������Եĸ���ֵ��ͨ���ϸ�(0.04-1.0)
// ����ʣ�ͨ��Ϊ0.04���ҵij���ֵ

΢��������(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 = DFG / (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; // ��ɫͨ��
mat.roughness = metallicRoughness.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(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
// �������ղ���
vec3 sampleIBL(vec3 N, vec3 V, float roughness, vec3 F0, vec3 albedo, float metallic) {
// ���㷴�䷽��
vec3 R = reflect(-V, N);

// ���淴�价����
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;

// ��������
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���ִ���Ϸ��Ⱦ�ı�׼��������ԭ����ʵ�ֶ��ڴ�����������3D����������Ҫ����ס���õ�PBRЧ��������Ҫ��ȷ���㷨ʵ�֣�����Ҫ���ʵIJ�����Դ�ͺ��ʵĹ��ջ�����


��Ҫ������PBR��Ⱦ������Ҫ��ϸߣ����ƶ��˿���ʱҪ�ر�ע�������Ż�������ʹ��LODϵͳ�������ּ������ڱ�֤Ч����ͬʱά�����õ����ܱ��֡�