Shader Lab
17.Lambert

Lambert

Arc 4 · 3D Lighting · Lesson 17 of 35
coordinate signal color
45
0

Purpose

Diffuse shading is one dot product — how aligned the surface is with the light — and every lighting model from here on is a variation or addition on top of that one dot.

Key insight

In 2D, every pixel asked "where am I on the screen?" In 3D, every pixel also asks "which way am I facing?" That facing is the surface normal, a unit vector pointing straight out of the surface. The light has a direction too. When the surface points at the light, dot(normal, light) is 1 (fully lit). When it's perpendicular, the dot is 0 (the terminator). When it faces away, the dot goes negative — we clamp it to 0, giving pitch black.

That single number, multiplied by the surface color, is Lambert. It is what chalk, matte paper, and unfinished wood look like — light scatters equally in all directions, so brightness depends only on how much light hits the surface, not on where the camera is. Move your head around a Lambert sphere and nothing changes.

Drag sun-angle and watch the bright band follow the light. Everything you'll ever learn about lighting is variations on that band.

Break it

Park the sun directly behind the hero meshes (around sun-angle = 270, with 90 lighting them from the camera's side). The visible faces go pitch black — every normal pointing at the camera points away from the light, so the dot is negative and we clamp.

Teaches: pure Lambert has no ambient term. "Away from the light" means zero photons, full stop. This is why real engines always add a tiny ambient floor or a fill light — otherwise shadowed sides are unreadable.

Direct Claude

"darker shadow side" lower ambient floor (or stay pure Lambert) "softer wrap around the terminator" Half-Lambert (remap dot with *0.5 + 0.5) "warmer daylight" light color shift "light it from camera-left" sun-angle parameter
Meta-phrase you gain here: "how aligned are the normal and the light" is the signal under every lighting model you'll meet. Specular, Fresnel, toon, PBR — all variations on this one dot.
Combines with: L18 (Blinn-Phong adds a specular term on top), L20 (toon quantizes this exact scalar), L21 (PBR replaces the model but keeps the "angle to light" intuition).
the vertex + fragment shader running above
// VERTEX — pass the world-space normal to the fragment stage
varying vec3 vNormal;

void main() {
  vNormal = normalize(normalMatrix * normal);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

// FRAGMENT — one dot product, clamped to zero, times base color
uniform vec3 uLightDir;      // world-space light direction (unit vector)
uniform vec3 uBaseColor;
varying vec3 vNormal;

void main() {
  vec3 n = normalize(vNormal);
  float lambert = max(0.0, dot(n, uLightDir));   // THE lighting signal
  vec3 col = uBaseColor * lambert;                // no ambient, no specular
  gl_FragColor = vec4(col, 1.0);
}