第6.1章:光照模型详解

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

🎯 学习目标

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

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

💡 光照基础理论

光线与表面的交互

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

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

光照计算的数学基础

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

1
2
3
4
5
6
// 关键向量定义
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 1975年提出,包含三个组成部分:

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

环境光计算

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

1
2
3
4
5
6
7
8
9
10
11
// 环境光计算
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
11
// 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
24
25
26
27
28
// 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
7
8
// 性能对比示例
// 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
30
31
// 多光源光照计算
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
19
// 性能测试
#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
10
11
// 卡通化的多级光照
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
8
9
10
// 边缘光效果
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
7
8
// 简化的菲涅尔计算
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
23
// 调试模式宏定义
#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改进:更高效的镜面反射计算
  • 实际应用技术:多光源、卡通化、边缘光等效果
  • 调试和优化:可视化调试和性能优化方法

🚀 下一步学习

掌握了基础光照模型后,建议继续学习:
👉 第6.2章:PBR光照模型深入

💡 实践练习

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

参考资料

系列导航