What are the lighting models in WebGL? How to implement Phong lighting model?
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