第6.1章:光照模型详解
光照模型是计算机图形学中的核心概念,它决定了物体表面如何与光线交互产生视觉效果。本章将深入讲解经典光照模型的数学原理和实现方法。
🎯 学习目标
通过本章学习,你将掌握:
- 光照计算的基本理�?- Phong光照模型的原理与实现
- Blinn-Phong改进算法
- 环境光、漫反射、镜面反射的详细计算
- 在Cocos Creator中实现自定义光照模型
💡 光照基础理论
光线与表面的交互
当光线照射到物体表面时,会发生以下几种现象:
- **反射(Reflection�?*:光线按照入射角等于反射角的规律反射
- **折射(Refraction�?*:光线进入物体内部并改变传播方向
- **吸收(Absorption�?*:部分光能被物体吸收转化为热�?4. **散射(Scattering�?*:光线在物体内部发生多次反射
光照计算的数学基础
光照计算涉及几个重要的向量:
1 2 3 4 5
| vec3 N = normalize(normal); vec3 V = normalize(viewDirection); vec3 R = reflect(-L, N); vec3 H = normalize(L + V);
|
🌟 Phong光照模型
模型概述
Phong光照模型由Bui Tuong Phong�?975年提出,包含三个组成部分�?
- **环境光(Ambient�?*:模拟环境中的间接光�?2. **漫反射(Diffuse�?*:模拟理想漫反射表面
- **镜面反射(Specular�?*:模拟光滑表面的镜面高光
环境光计�?
环境光是最简单的光照组件,提供基础的全局照明�?
1 2 3 4 5 6 7 8 9
| 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
| 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
| void surf (in SurfaceIn In, inout SurfaceOut Out) { vec3 normal = normalize(In.worldNormal); vec3 viewDir = normalize(cc_cameraPos.xyz - In.worldPos); vec3 lightColor = cc_mainLitColor.rgb * cc_mainLitColor.a; 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.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
|
vec3 reflectDir = reflect(-lightDir, normal);
vec3 halfDir = normalize(lightDir + viewDir);
|
🔧 实际应用案例
案例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
| 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); 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_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 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);
|
2. Rim Lighting(边缘光�?
1 2 3 4 5 6 7
| float rim = 1.0 - max(dot(normal, viewDir), 0.0); return pow(rim, power); }
vec3 rimColor = rimFactor * vec3(0.5, 0.8, 1.0);
|
3. 菲涅尔效�?
1 2 3 4 5 6
| return pow(1.0 - max(dot(normal, viewDir), 0.0), power); }
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_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改进:更高效的镜面反射计�?- �?**实际应用技�?*:多光源、卡通化、边缘光等效�?- �?**调试和优�?*:可视化调试和性能优化方法
🚀 下一步学�?
掌握了基础光照模型后,建议继续学习�?
👉 �?.2章:PBR光照模型深入
💡 实践练习
- 基础练习:实现一个可调节光泽度的Blinn-Phong材质
- 进阶练习:添加多个点光源的衰减计�?3. 高级练习:实现卡通风格的多级光照效果
*参考资�?
系列导航