WebGL 光照模型概述
光照模型用于模拟光线与物体表面的交互,是 3D 渲染中实现真实感的关键技术。WebGL 中常用的光照模型包括:环境光(Ambient)、漫反射(Diffuse)和镜面反射(Specular)。
基本光照模型
1. 环境光(Ambient Lighting)
模拟场景中无处不在的间接光照,不考虑光源位置和方向。
glslvec3 ambient = ambientStrength * lightColor;
2. 漫反射(Diffuse Lighting)
模拟光线照射到粗糙表面后向各个方向均匀反射的效果。遵循朗伯余弦定律。
glsl// 计算光线方向与法线的夹角 float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * lightColor;
3. 镜面反射(Specular Lighting)
模拟光线在光滑表面的定向反射,产生高光效果。
glsl// Phong 模型 vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); vec3 specular = specularStrength * spec * lightColor; // Blinn-Phong 模型(更高效) vec3 halfwayDir = normalize(lightDir + viewDir); float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess); vec3 specular = specularStrength * spec * lightColor;
Phong 光照模型详解
Phong 光照模型是三种光照成分的组合:
最终颜色 = 环境光 + 漫反射 + 镜面反射
顶点着色器
glslattribute vec3 a_position; attribute vec3 a_normal; attribute vec2 a_texCoord; uniform mat4 u_modelMatrix; uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; uniform mat3 u_normalMatrix; // 法线矩阵(用于正确变换法线) uniform vec3 u_lightPosition; uniform vec3 u_cameraPosition; varying vec3 v_normal; varying vec3 v_lightDir; varying vec3 v_viewDir; varying vec2 v_texCoord; void main() { vec4 worldPos = u_modelMatrix * vec4(a_position, 1.0); gl_Position = u_projectionMatrix * u_viewMatrix * worldPos; // 变换法线到世界空间 v_normal = normalize(u_normalMatrix * a_normal); // 计算光线方向(从片段指向光源) v_lightDir = normalize(u_lightPosition - worldPos.xyz); // 计算视线方向(从片段指向相机) v_viewDir = normalize(u_cameraPosition - worldPos.xyz); v_texCoord = a_texCoord; }
片段着色器(Phong 模型)
glslprecision mediump float; varying vec3 v_normal; varying vec3 v_lightDir; varying vec3 v_viewDir; varying vec2 v_texCoord; uniform vec3 u_lightColor; uniform vec3 u_ambientColor; uniform sampler2D u_diffuseMap; uniform float u_shininess; uniform float u_ambientStrength; uniform float u_specularStrength; void main() { vec3 normal = normalize(v_normal); vec3 lightDir = normalize(v_lightDir); vec3 viewDir = normalize(v_viewDir); // 环境光 vec3 ambient = u_ambientStrength * u_ambientColor; // 漫反射 float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * u_lightColor; // 镜面反射(Phong) vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_shininess); vec3 specular = u_specularStrength * spec * u_lightColor; // 采样纹理 vec4 texColor = texture2D(u_diffuseMap, v_texCoord); // 组合光照 vec3 lighting = ambient + diffuse + specular; vec3 result = lighting * texColor.rgb; gl_FragColor = vec4(result, texColor.a); }
片段着色器(Blinn-Phong 模型)
Blinn-Phong 是 Phong 的改进版本,使用半角向量代替反射向量,计算更高效。
glslvoid main() { vec3 normal = normalize(v_normal); vec3 lightDir = normalize(v_lightDir); vec3 viewDir = normalize(v_viewDir); // 环境光 vec3 ambient = u_ambientStrength * u_ambientColor; // 漫反射 float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * u_lightColor; // 镜面反射(Blinn-Phong) vec3 halfwayDir = normalize(lightDir + viewDir); float spec = pow(max(dot(normal, halfwayDir), 0.0), u_shininess); vec3 specular = u_specularStrength * spec * u_lightColor; // 组合光照 vec3 lighting = ambient + diffuse + specular; vec3 result = lighting * texture2D(u_diffuseMap, v_texCoord).rgb; gl_FragColor = vec4(result, 1.0); }
法线矩阵的计算
javascript// 法线矩阵是模型矩阵的逆转置矩阵的左上 3x3 部分 function createNormalMatrix(modelMatrix) { const normalMatrix = mat4.create(); mat4.invert(normalMatrix, modelMatrix); mat4.transpose(normalMatrix, normalMatrix); // 提取 3x3 部分 return new Float32Array([ normalMatrix[0], normalMatrix[1], normalMatrix[2], normalMatrix[4], normalMatrix[5], normalMatrix[6], normalMatrix[8], normalMatrix[9], normalMatrix[10] ]); } // 或使用 gl-matrix 的 mat3 const normalMatrix = mat3.create(); mat3.fromMat4(normalMatrix, modelMatrix); mat3.invert(normalMatrix, normalMatrix); mat3.transpose(normalMatrix, normalMatrix);
多光源处理
glsl#define MAX_LIGHTS 4 struct Light { vec3 position; vec3 color; float intensity; }; uniform Light u_lights[MAX_LIGHTS]; uniform int u_numLights; vec3 calculateLighting(vec3 normal, vec3 viewDir, vec3 fragPos) { vec3 result = u_ambientColor * u_ambientStrength; for (int i = 0; i < MAX_LIGHTS; i++) { if (i >= u_numLights) break; Light light = u_lights[i]; vec3 lightDir = normalize(light.position - fragPos); // 漫反射 float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * light.color * light.intensity; // 镜面反射 vec3 halfwayDir = normalize(lightDir + viewDir); float spec = pow(max(dot(normal, halfwayDir), 0.0), u_shininess); vec3 specular = u_specularStrength * spec * light.color * light.intensity; // 衰减 float distance = length(light.position - fragPos); float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance); result += (diffuse + specular) * attenuation; } return result; }
不同光源类型
方向光(Directional Light)
glsl// 方向光只有方向,没有位置 uniform vec3 u_lightDirection; // 光线方向 vec3 lightDir = normalize(-u_lightDirection); // 指向光源 float diff = max(dot(normal, lightDir), 0.0);
点光源(Point Light)
glsl// 点光源有位置,向四面八方发射 uniform vec3 u_lightPosition; uniform float u_constant; uniform float u_linear; uniform float u_quadratic; vec3 lightDir = normalize(u_lightPosition - fragPos); float distance = length(u_lightPosition - fragPos); float attenuation = 1.0 / (u_constant + u_linear * distance + u_quadratic * distance * distance);
聚光灯(Spot Light)
glsl// 聚光灯有位置、方向和角度限制 uniform vec3 u_lightPosition; uniform vec3 u_lightDirection; uniform float u_cutOff; // 内切角余弦 uniform float u_outerCutOff; // 外切角余弦 vec3 lightDir = normalize(u_lightPosition - fragPos); float theta = dot(lightDir, normalize(-u_lightDirection)); float epsilon = u_cutOff - u_outerCutOff; float intensity = clamp((theta - u_outerCutOff) / epsilon, 0.0, 1.0);
材质属性
glslstruct Material { vec3 ambient; vec3 diffuse; vec3 specular; float shininess; }; uniform Material u_material; void main() { vec3 ambient = u_light.ambient * u_material.ambient; vec3 diffuse = u_light.diffuse * (diff * u_material.diffuse); vec3 specular = u_light.specular * (spec * u_material.specular); }
Gouraud 着色 vs Phong 着色
| 特性 | Gouraud 着色 | Phong 着色 |
|---|---|---|
| 计算位置 | 顶点着色器 | 片段着色器 |
| 质量 | 较低(插值) | 较高(逐像素) |
| 性能 | 更快 | 较慢 |
| 高光 | 可能不准确 | 准确 |
glsl// Gouraud 着色(顶点着色器中计算光照) varying vec3 v_lighting; void main() { // 在顶点级别计算光照 vec3 ambient = ...; vec3 diffuse = ...; vec3 specular = ...; v_lighting = ambient + diffuse + specular; } // 片段着色器 void main() { gl_FragColor = vec4(v_lighting * texColor, 1.0); }
性能优化建议
-
顶点着色器 vs 片段着色器:
- 顶点数 < 片段数时,在顶点着色器计算
- 需要高质量光照时,在片段着色器计算
-
使用 Blinn-Phong:
- 比 Phong 更高效(避免计算 reflect)
- 视觉效果相似
-
限制光源数量:
- 移动端建议 1-2 个光源
- 桌面端建议 4-8 个光源
-
使用延迟渲染:
- 大量光源时使用
- 避免对每个光源遍历所有片段