第6.1章:光照模型详解

光照模型是计算机图形学中的核心概念,它决定了物体表面如何与光线交互产生视觉效果。本章将深入讲解经典光照模型的数学原理和实现方法。

🎯 学习目标

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

  • 光照计算的基本理�?- Phong光照模型的原理与实现
  • Blinn-Phong改进算法
  • 环境光、漫反射、镜面反射的详细计算
  • 在Cocos Creator中实现自定义光照模型

💡 光照基础理论

光线与表面的交互

当光线照射到物体表面时,会发生以下几种现象:

1
// 光照计算的基本组�?vec3 finalColor = ambientLight + diffuseLight + specularLight;
  1. **反射(Reflection�?*:光线按照入射角等于反射角的规律反射
  2. **折射(Refraction�?*:光线进入物体内部并改变传播方向
  3. **吸收(Absorption�?*:部分光能被物体吸收转化为热�?4. **散射(Scattering�?*:光线在物体内部发生多次反射

光照计算的数学基础

光照计算涉及几个重要的向量:

1
2
3
4
5
// 关键向量定义
vec3 N = normalize(normal); // 表面法向�?vec3 L = normalize(lightDirection); // 光照方向
vec3 V = normalize(viewDirection); // 视线方向
vec3 R = reflect(-L, N); // 反射方向
vec3 H = normalize(L + V); // 半程向量

🌟 Phong光照模型

模型概述

Phong光照模型由Bui Tuong Phong�?975年提出,包含三个组成部分�?

  1. **环境光(Ambient�?*:模拟环境中的间接光�?2. **漫反射(Diffuse�?*:模拟理想漫反射表面
  2. **镜面反射(Specular�?*:模拟光滑表面的镜面高光

环境光计�?

环境光是最简单的光照组件,提供基础的全局照明�?

1
2
3
4
5
6
7
8
9
// 环境光计�?vec3 calculateAmbient(vec3 lightColor, vec3 materialColor, float ambientStrength) {
return ambientStrength * lightColor * materialColor;
}

// 使用示例
vec3 ambient = calculateAmbient(
vec3(1.0, 1.0, 1.0), // 白光
albedo, // 材质颜色
0.1 // 环境光强�?);

漫反射计�?

漫反射遵循Lambert定律,亮度与光线入射角的余弦成正比:

1
2
3
4
5
6
7
8
9
10
// Lambert漫反射计�?vec3 calculateDiffuse(vec3 normal, vec3 lightDir, vec3 lightColor, vec3 materialColor) {
float NdotL = max(dot(normal, lightDir), 0.0);
return NdotL * lightColor * materialColor;
}

// 改进版本:半Lambert模型
vec3 calculateHalfLambert(vec3 normal, vec3 lightDir, vec3 lightColor, vec3 materialColor) {
float NdotL = dot(normal, lightDir) * 0.5 + 0.5;
return NdotL * lightColor * materialColor;
}

镜面反射计算

Phong镜面反射基于反射向量与视线向量的夹角�?

1
2
3
4
5
6
7
8
9
10
11
12
13
// Phong镜面反射计算
vec3 calculatePhongSpecular(
vec3 normal,
vec3 lightDir,
vec3 viewDir,
vec3 lightColor,
float shininess
) {
vec3 reflectDir = reflect(-lightDir, normal);
float RdotV = max(dot(reflectDir, viewDir), 0.0);
float spec = pow(RdotV, shininess);
return spec * lightColor;
}

完整的Phong光照实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Surface Shader中的Phong光照实现
void surf (in SurfaceIn In, inout SurfaceOut Out) {
// 获取材质属�? vec4 albedo = texture(mainTexture, In.uv);
vec3 normal = normalize(In.worldNormal);
vec3 viewDir = normalize(cc_cameraPos.xyz - In.worldPos);

// 主光源参�? vec3 lightDir = normalize(-cc_mainLitDir.xyz);
vec3 lightColor = cc_mainLitColor.rgb * cc_mainLitColor.a;

// 环境�? vec3 ambient = cc_ambientSky.rgb * albedo.rgb * 0.1;

// 漫反�? float NdotL = max(dot(normal, lightDir), 0.0);
vec3 diffuse = NdotL * lightColor * albedo.rgb;

// 镜面反射
vec3 reflectDir = reflect(-lightDir, normal);
float RdotV = max(dot(reflectDir, viewDir), 0.0);
float spec = pow(RdotV, 32.0);
vec3 specular = spec * lightColor;

// 最终颜�? Out.albedo = vec4(ambient + diffuse + specular, albedo.a);
Out.normal = normal;
}

�?Blinn-Phong改进模型

改进的动�?

Phong模型在某些情况下会出现问题:

  • 当视线与反射方向夹角大于90°时,会出现不连续
  • 计算反射向量较为昂贵

半程向量方法

Blinn-Phong使用半程向量代替反射向量�?

1
2
3
4
5
6
7
8
9
10
11
12
13
// Blinn-Phong镜面反射计算
vec3 calculateBlinnPhongSpecular(
vec3 normal,
vec3 lightDir,
vec3 viewDir,
vec3 lightColor,
float shininess
) {
vec3 halfDir = normalize(lightDir + viewDir);
float NdotH = max(dot(normal, halfDir), 0.0);
float spec = pow(NdotH, shininess);
return spec * lightColor;
}

性能对比

1
2
3
4
5
6
// 性能对比示例
// Phong: 需要计算reflect函数
vec3 reflectDir = reflect(-lightDir, normal); // 较昂�?float RdotV = max(dot(reflectDir, viewDir), 0.0);

// Blinn-Phong: 只需要normalize和dot
vec3 halfDir = normalize(lightDir + viewDir); // 较便�?float NdotH = max(dot(normal, halfDir), 0.0);

🔧 实际应用案例

案例1:基础Phong材质

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
CCEffect %{
techniques:
- name: opaque
passes:
- vert: unlit-vs:vert
frag: unlit-fs:frag
properties: &props
mainTexture: { value: white }
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
shininess: { value: 32.0, range: [1.0, 128.0] }
specularStrength: { value: 1.0, range: [0.0, 2.0] }
}%

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

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

uniform Properties {
vec4 mainColor;
float shininess;
float specularStrength;
};
uniform sampler2D mainTexture;

void surf (in SurfaceIn In, inout SurfaceOut Out) {
vec4 albedo = texture(mainTexture, In.uv) * mainColor;
vec3 normal = normalize(In.worldNormal);
vec3 viewDir = normalize(cc_cameraPos.xyz - In.worldPos);
vec3 lightDir = normalize(-cc_mainLitDir.xyz);
vec3 lightColor = cc_mainLitColor.rgb * cc_mainLitColor.a;

// Phong光照计算
vec3 ambient = cc_ambientSky.rgb * albedo.rgb * 0.1;

float NdotL = max(dot(normal, lightDir), 0.0);
vec3 diffuse = NdotL * lightColor * albedo.rgb;

vec3 reflectDir = reflect(-lightDir, normal);
float RdotV = max(dot(reflectDir, viewDir), 0.0);
float spec = pow(RdotV, shininess) * specularStrength;
vec3 specular = spec * lightColor;

Out.albedo = vec4(ambient + diffuse + specular, albedo.a);
Out.normal = normal;
}
}%

案例2:多光源Blinn-Phong

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
// 多光源光照计�?vec3 calculateMultipleLight(SurfaceIn In, vec3 albedo, vec3 normal) {
vec3 viewDir = normalize(cc_cameraPos.xyz - In.worldPos);
vec3 finalColor = vec3(0.0);

// 主方向光
vec3 mainLightDir = normalize(-cc_mainLitDir.xyz);
vec3 mainLightColor = cc_mainLitColor.rgb * cc_mainLitColor.a;
finalColor += calculateBlinnPhong(normal, mainLightDir, viewDir,
mainLightColor, albedo, 64.0);

// 附加点光源(示例�? for(int i = 0; i < 4; i++) {
vec3 lightPos = cc_sphereLitPos[i].xyz;
vec3 lightColor = cc_sphereLitColor[i].rgb;
float lightRange = cc_sphereLitPos[i].w;

vec3 lightDir = lightPos - In.worldPos;
float distance = length(lightDir);
lightDir = lightDir / distance;

// 距离衰减
float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
lightColor *= attenuation;

finalColor += calculateBlinnPhong(normal, lightDir, viewDir,
lightColor, albedo, 64.0);
}

return finalColor;
}

📊 光照模型对比

视觉效果对比

模型计算复杂�?视觉质量适用场景
Lambert�?基础漫反射材�?
Phong�?良好一般光滑表�?
Blinn-Phong�?良好实时渲染
Half-Lambert�?特殊卡通风�?

性能测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 性能测试�?#define LIGHTING_MODEL_LAMBERT 0
#define LIGHTING_MODEL_PHONG 1
#define LIGHTING_MODEL_BLINN_PHONG 2

#ifndef LIGHTING_MODEL
#define LIGHTING_MODEL LIGHTING_MODEL_BLINN_PHONG
#endif

vec3 calculateLighting(vec3 normal, vec3 lightDir, vec3 viewDir,
vec3 lightColor, vec3 albedo) {
#if LIGHTING_MODEL == LIGHTING_MODEL_LAMBERT
return calculateLambert(normal, lightDir, lightColor, albedo);
#elif LIGHTING_MODEL == LIGHTING_MODEL_PHONG
return calculatePhong(normal, lightDir, viewDir, lightColor, albedo);
#else
return calculateBlinnPhong(normal, lightDir, viewDir, lightColor, albedo);
#endif
}

🎨 高级光照技�?

1. 卡通化光照

1
2
3
4
5
6
7
8
9
// 卡通化的多级光�?float calculateToonShading(float NdotL, int levels) {
float scaledNdotL = NdotL * float(levels);
float level = floor(scaledNdotL);
return level / float(levels - 1);
}

// 使用示例
float NdotL = max(dot(normal, lightDir), 0.0);
float toonNdotL = calculateToonShading(NdotL, 4); // 4级光�?vec3 diffuse = toonNdotL * lightColor * albedo;

2. Rim Lighting(边缘光�?

1
2
3
4
5
6
7
// 边缘光效�?float calculateRimLighting(vec3 normal, vec3 viewDir, float power) {
float rim = 1.0 - max(dot(normal, viewDir), 0.0);
return pow(rim, power);
}

// 应用边缘�?float rimFactor = calculateRimLighting(normal, viewDir, 2.0);
vec3 rimColor = rimFactor * vec3(0.5, 0.8, 1.0); // 蓝色边缘�?finalColor += rimColor;

3. 菲涅尔效�?

1
2
3
4
5
6
// 简化的菲涅尔计�?float calculateFresnel(vec3 normal, vec3 viewDir, float power) {
return pow(1.0 - max(dot(normal, viewDir), 0.0), power);
}

// 在镜面反射中应用菲涅�?float fresnel = calculateFresnel(normal, viewDir, 5.0);
vec3 specular = spec * lightColor * fresnel;

🔍 调试技�?

可视化光照组�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 调试模式宏定�?#define DEBUG_MODE_NONE 0
#define DEBUG_MODE_DIFFUSE 1
#define DEBUG_MODE_SPECULAR 2
#define DEBUG_MODE_NORMAL 3

#ifndef DEBUG_MODE
#define DEBUG_MODE DEBUG_MODE_NONE
#endif

void surf (in SurfaceIn In, inout SurfaceOut Out) {
// 计算光照...

#if DEBUG_MODE == DEBUG_MODE_DIFFUSE
Out.albedo = vec4(diffuse, 1.0);
#elif DEBUG_MODE == DEBUG_MODE_SPECULAR
Out.albedo = vec4(specular, 1.0);
#elif DEBUG_MODE == DEBUG_MODE_NORMAL
Out.albedo = vec4(normal * 0.5 + 0.5, 1.0);
#else
Out.albedo = vec4(ambient + diffuse + specular, albedo.a);
#endif
}

参数调节面板

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
// TypeScript中的光照参数控制
@ccclass('LightingController')
export class LightingController extends Component {
@property({ type: Material })
targetMaterial: Material = null!;

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

@property({ range: [1, 128] })
shininess: number = 32;

@property({ range: [0, 2] })
specularStrength: number = 1.0;

start() {
this.updateMaterialProperties();
}

updateMaterialProperties() {
this.targetMaterial.setProperty('ambientStrength', this.ambientStrength);
this.targetMaterial.setProperty('shininess', this.shininess);
this.targetMaterial.setProperty('specularStrength', this.specularStrength);
}
}

📖 本章总结

通过本章学习,我们深入理解了�?

  • �?光照基础理论:光线与表面交互的物理原�?- �?Phong光照模型:经典的三组件光照计�?- �?Blinn-Phong改进:更高效的镜面反射计�?- �?**实际应用技�?*:多光源、卡通化、边缘光等效�?- �?**调试和优�?*:可视化调试和性能优化方法

🚀 下一步学�?

掌握了基础光照模型后,建议继续学习�?
👉 �?.2章:PBR光照模型深入

💡 实践练习

  1. 基础练习:实现一个可调节光泽度的Blinn-Phong材质
  2. 进阶练习:添加多个点光源的衰减计�?3. 高级练习:实现卡通风格的多级光照效果

*参考资�?

系列导航