Lighting turns flat shapes into believable surfaces. Learn the classic diffuse and specular models, how surface normals drive them, and the Phong/Blinn-Phong lighting every game built on before PBR.
A surface’s brightness depends on its angle to the light, encoded by its normal — the unit vector pointing straight out of the surface. Diffuse (Lambertian) lighting takes the dot product of the normal and the direction to the light: fully lit when facing the light, dark when facing away. This single dot product is the foundation of all shading.
#version 330 core
in vec3 vNormal; // interpolated surface normal
in vec3 vFragPos; // world-space position of the fragment
uniform vec3 uLightPos;
uniform vec3 uColor;
out vec4 fragColor;
void main() {
vec3 N = normalize(vNormal);
vec3 L = normalize(uLightPos - vFragPos); // direction to the light
// diffuse = how much the surface faces the light (clamped to >= 0)
float diff = max(dot(N, L), 0.0);
fragColor = vec4(uColor * diff, 1.0);
}
// dot(normal, lightDir) — the single most important operation in shading.Diffuse alone looks matte; shiny surfaces have specular highlights — bright spots where light reflects toward the eye. The specular term depends on the angle between the view direction and the reflected (or, in Blinn-Phong, the halfway) vector, raised to a "shininess" power that tightens the highlight. Adding specular is what makes plastic, metal, and wet surfaces read as such.
void main() {
vec3 N = normalize(vNormal);
vec3 L = normalize(uLightPos - vFragPos);
vec3 V = normalize(uViewPos - vFragPos); // direction to the camera
float diff = max(dot(N, L), 0.0);
// Blinn-Phong specular: use the HALFWAY vector between L and V
vec3 H = normalize(L + V);
float spec = pow(max(dot(N, H), 0.0), 32.0); // 32 = shininess (higher = tighter)
vec3 color = uColor * diff + vec3(1.0) * spec; // add a white highlight
fragColor = vec4(color, 1.0);
}
// specular = the shiny reflection toward the eye. Shininess controls its size.Real scenes are never fully black in shadow because light bounces around. A cheap ambient term adds a small constant base light so unlit areas are still visible. Ambient + diffuse + specular is the classic Phong lighting model that powered games for decades — approximate, fast, and good enough before physically-based rendering became standard.
void main() {
vec3 N = normalize(vNormal);
vec3 L = normalize(uLightPos - vFragPos);
vec3 V = normalize(uViewPos - vFragPos);
vec3 H = normalize(L + V);
vec3 ambient = 0.1 * uColor; // constant base light
vec3 diffuse = max(dot(N, L), 0.0) * uColor;
vec3 specular = pow(max(dot(N, H), 0.0), 32.0) * vec3(1.0);
fragColor = vec4(ambient + diffuse + specular, 1.0); // Phong/Blinn-Phong
}
// ambient + diffuse + specular = the classic model games used for decades.Games use a few light archetypes that differ in how the light direction and attenuation are computed. A directional light (the sun) has one direction everywhere and no falloff; a point light radiates from a position and fades with distance; a spotlight is a point light limited to a cone. Recognizing these types tells you how to compute L and how brightness falls off for each.
Common light types (change how you get the light direction + falloff):
DIRECTIONAL parallel rays, one direction everywhere, no falloff
(the sun). L = -lightDirection.
POINT radiates from a position, fades with distance
(a bulb/torch). L = normalize(lightPos - fragPos);
attenuation = 1 / (distance²).
SPOT a point light restricted to a CONE (flashlight)
= point light + a check that the fragment is within the cone.
AREA emits from a surface -> soft, realistic (costlier).
Same diffuse/specular math; only L and attenuation differ per type.