3月6日 21:57

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.

glsl
vec3 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

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; // 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)

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); // 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.

glsl
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 (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

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 Shading vs Phong Shading

FeatureGouraud ShadingPhong Shading
Calculation LocationVertex shaderFragment shader
QualityLower (interpolated)Higher (per-pixel)
PerformanceFasterSlower
HighlightsMay be inaccurateAccurate
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

  1. Vertex Shader vs Fragment Shader:

    • When vertex count < fragment count, calculate in vertex shader
    • When high-quality lighting is needed, calculate in fragment shader
  2. Use Blinn-Phong:

    • More efficient than Phong (avoids calculating reflect)
    • Similar visual effect
  3. Limit Light Source Count:

    • Mobile: 1-2 light sources recommended
    • Desktop: 4-8 light sources recommended
  4. Use Deferred Rendering:

    • Use when there are many light sources
    • Avoid iterating all fragments for each light source
标签:WebGL