乐闻世界logo
搜索文章和话题

WebGL 中的光照模型有哪些?如何实现 Phong 光照模型?

3月6日 21:57

WebGL 光照模型概述

光照模型用于模拟光线与物体表面的交互,是 3D 渲染中实现真实感的关键技术。WebGL 中常用的光照模型包括:环境光(Ambient)、漫反射(Diffuse)和镜面反射(Specular)。

基本光照模型

1. 环境光(Ambient Lighting)

模拟场景中无处不在的间接光照,不考虑光源位置和方向。

glsl
vec3 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 光照模型是三种光照成分的组合:

最终颜色 = 环境光 + 漫反射 + 镜面反射

顶点着色器

glsl
attribute 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 模型)

glsl
precision 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 的改进版本,使用半角向量代替反射向量,计算更高效。

glsl
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; // 镜面反射(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);

材质属性

glsl
struct 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); }

性能优化建议

  1. 顶点着色器 vs 片段着色器

    • 顶点数 < 片段数时,在顶点着色器计算
    • 需要高质量光照时,在片段着色器计算
  2. 使用 Blinn-Phong

    • 比 Phong 更高效(避免计算 reflect)
    • 视觉效果相似
  3. 限制光源数量

    • 移动端建议 1-2 个光源
    • 桌面端建议 4-8 个光源
  4. 使用延迟渲染

    • 大量光源时使用
    • 避免对每个光源遍历所有片段
标签:WebGL