WebGL Lighting Models Overview
Lighting models simulate the interaction between light and object surfaces, which is key technology for achieving realism in 3D rendering. Common lighting models in WebGL include: Ambient, Diffuse, and Specular lighting.
Basic Lighting Models
1. Ambient Lighting
Simulates indirect light present everywhere in the scene, without considering light source position and direction.
glslvec3 ambient = ambientStrength * lightColor;
2. Diffuse Lighting
Simulates the effect of light hitting rough surfaces and reflecting uniformly in all directions. Follows Lambert's Cosine Law.
glsl// Calculate angle between light direction and normal float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * lightColor;
3. Specular Lighting
Simulates directional reflection of light on smooth surfaces, producing highlight effects.
glsl// Phong model vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); vec3 specular = specularStrength * spec * lightColor; // Blinn-Phong model (more efficient) vec3 halfwayDir = normalize(lightDir + viewDir); float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess); vec3 specular = specularStrength * spec * lightColor;
Phong Lighting Model Explained
The Phong lighting model combines three lighting components:
Final Color = Ambient + Diffuse + Specular
Vertex Shader
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; // Normal matrix (for correct normal transformation) 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; // Transform normal to world space v_normal = normalize(u_normalMatrix * a_normal); // Calculate light direction (from fragment to light source) v_lightDir = normalize(u_lightPosition - worldPos.xyz); // Calculate view direction (from fragment to camera) v_viewDir = normalize(u_cameraPosition - worldPos.xyz); v_texCoord = a_texCoord; }
Fragment Shader (Phong Model)
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); // Ambient vec3 ambient = u_ambientStrength * u_ambientColor; // Diffuse float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * u_lightColor; // Specular (Phong) vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir), 0.0), u_shininess); vec3 specular = u_specularStrength * spec * u_lightColor; // Sample texture vec4 texColor = texture2D(u_diffuseMap, v_texCoord); // Combine lighting vec3 lighting = ambient + diffuse + specular; vec3 result = lighting * texColor.rgb; gl_FragColor = vec4(result, texColor.a); }
Fragment Shader (Blinn-Phong Model)
Blinn-Phong is an improved version of Phong, using halfway vector instead of reflection vector, more computationally efficient.
glslvoid main() { vec3 normal = normalize(v_normal); vec3 lightDir = normalize(v_lightDir); vec3 viewDir = normalize(v_viewDir); // Ambient vec3 ambient = u_ambientStrength * u_ambientColor; // Diffuse float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * u_lightColor; // Specular (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; // Combine lighting vec3 lighting = ambient + diffuse + specular; vec3 result = lighting * texture2D(u_diffuseMap, v_texCoord).rgb; gl_FragColor = vec4(result, 1.0); }
Normal Matrix Calculation
javascript// Normal matrix is the inverse transpose of the model matrix's upper-left 3x3 portion function createNormalMatrix(modelMatrix) { const normalMatrix = mat4.create(); mat4.invert(normalMatrix, modelMatrix); mat4.transpose(normalMatrix, normalMatrix); // Extract 3x3 portion return new Float32Array([ normalMatrix[0], normalMatrix[1], normalMatrix[2], normalMatrix[4], normalMatrix[5], normalMatrix[6], normalMatrix[8], normalMatrix[9], normalMatrix[10] ]); } // Or use gl-matrix's mat3 const normalMatrix = mat3.create(); mat3.fromMat4(normalMatrix, modelMatrix); mat3.invert(normalMatrix, normalMatrix); mat3.transpose(normalMatrix, normalMatrix);
Multiple Light Sources
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); // Diffuse float diff = max(dot(normal, lightDir), 0.0); vec3 diffuse = diff * light.color * light.intensity; // Specular 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; // Attenuation 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; }
Different Light Types
Directional Light
glsl// Directional light has direction but no position uniform vec3 u_lightDirection; // Light direction vec3 lightDir = normalize(-u_lightDirection); // Pointing to light source float diff = max(dot(normal, lightDir), 0.0);
Point Light
glsl// Point light has position, emits in all directions 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// Spot light has position, direction, and angle constraints uniform vec3 u_lightPosition; uniform vec3 u_lightDirection; uniform float u_cutOff; // Inner cutoff cosine uniform float u_outerCutOff; // Outer cutoff cosine 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);
Material Properties
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 Shading vs Phong Shading
| Feature | Gouraud Shading | Phong Shading |
|---|---|---|
| Calculation Location | Vertex shader | Fragment shader |
| Quality | Lower (interpolated) | Higher (per-pixel) |
| Performance | Faster | Slower |
| Highlights | May be inaccurate | Accurate |
glsl// Gouraud shading (calculate lighting in vertex shader) varying vec3 v_lighting; void main() { // Calculate lighting at vertex level vec3 ambient = ...; vec3 diffuse = ...; vec3 specular = ...; v_lighting = ambient + diffuse + specular; } // Fragment shader void main() { gl_FragColor = vec4(v_lighting * texColor, 1.0); }
Performance Optimization Tips
-
Vertex Shader vs Fragment Shader:
- When vertex count < fragment count, calculate in vertex shader
- When high-quality lighting is needed, calculate in fragment shader
-
Use Blinn-Phong:
- More efficient than Phong (avoids calculating reflect)
- Similar visual effect
-
Limit Light Source Count:
- Mobile: 1-2 light sources recommended
- Desktop: 4-8 light sources recommended
-
Use Deferred Rendering:
- Use when there are many light sources
- Avoid iterating all fragments for each light source