第6.1章:光照模型详解
光照模型是计算机图形学中的核心概念,它决定了物体表面如何与光线交互产生视觉效果。本章将深入讲解经典光照模型的数学原理和实现方法。
🎯 学习目标
通过本章学习,你将掌握:
- 光照计算的基本理论
- Phong光照模型的原理与实现
- Blinn-Phong改进算法
- 环境光、漫反射、镜面反射的详细计算
- 在Cocos Creator中实现自定义光照模型
💡 光照基础理论
光线与表面的交互
当光线照射到物体表面时,会发生以下几种现象:
1 2
| vec3 finalColor = ambientLight + diffuseLight + specularLight;
|
- 反射(Reflection):光线按照入射角等于反射角的规律反射
- 折射(Refraction):光线进入物体内部并改变传播方向
- 吸收(Absorption):部分光能被物体吸收转化为热能
- 散射(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年提出,包含三个组成部分:
- 环境光(Ambient):模拟环境中的间接光
- 漫反射(Diffuse):模拟理想漫反射表面
- 镜面反射(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
| vec3 calculateDiffuse(vec3 normal, vec3 lightDir, vec3 lightColor, vec3 materialColor) { float NdotL = max(dot(normal, lightDir), 0.0); return NdotL * lightColor * materialColor; }
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
| 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
| 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
| 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
|
vec3 reflectDir = reflect(-lightDir, normal); float RdotV = max(dot(reflectDir, viewDir), 0.0);
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; 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); 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
| @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光照模型深入
💡 实践练习
- 基础练习:实现一个可调节光泽度的Blinn-Phong材质
- 进阶练习:添加多个点光源的衰减计算
- 高级练习:实现卡通风格的多级光照效果
参考资料
系列导航