Shader Lab
18.Blinn-Phong

Blinn-Phong

Arc 4 · 3D Lighting · Lesson 18 of 35
coordinate signal color
48

Purpose

The shiny highlight on a classic 3D render is a second dot product raised to a power — Lambert tells you how much light hits the surface, specular tells you how much of that light bounces toward the camera.

Key insight

Lambert is camera-blind. Move your head around a Lambert sphere and nothing changes — the lit side stays the lit side. But real objects have a bright spot that follows your eye. That's because the camera matters now. Blinn-Phong finds the halfway vector between the light direction and the view direction, then asks how closely the surface normal matches that halfway vector. When they match, the surface is reflecting the light directly at you, so you see the brightest highlight.

Raising that dot to a power controls how tight the highlight is. Low power (say 4) smears it across half the sphere; high power (say 256) shrinks it to a pinpoint. The exponent is the single most important lever in pre-PBR shading — it is the difference between a matte ceramic and a glossy pool ball.

Drag glossiness and watch the highlight tighten from a broad sheen to a hotspot to a star.

Break it

Glossiness to 1. The specular lobe spreads across the entire lit hemisphere, and the surface stops looking "shiny" separately from "bright" — it just looks overexposed.

Teaches: specular needs to be spatially concentrated to read as a highlight. Without tightness, it's just more diffuse. This is why pre-PBR games look plastic when tuned wrong — flat specular everywhere reads as cheap.

Direct Claude

"more matte" glossiness down (or kill specular) "more glossy / wet-looking" glossiness up "tighter highlight" glossiness up "softer, broader highlight" glossiness down "warmer highlight" specular color shift (colored spec is right for metals, wrong for dielectrics — L21 makes this rigorous)
Meta-phrase you gain here: "shininess" is literally a steepness knob on a single power function. This is exactly what roughness will invert in the next lesson's PBR preview — low roughness is high glossiness.
Combines with: L17 (sits on top of Lambert), L19 (rim light is the other grazing-angle term, orthogonal to specular), L20 (toon often quantizes specular into a single hard shape).
the vertex + fragment shader running above
// VERTEX — pass normal + world position so fragment can build a view vector
varying vec3 vNormal;
varying vec3 vWorldPos;

void main() {
  vNormal = normalize(normalMatrix * normal);
  vec4 wp = modelMatrix * vec4(position, 1.0);
  vWorldPos = wp.xyz;
  gl_Position = projectionMatrix * viewMatrix * wp;
}

// FRAGMENT — Lambert (diffuse) + Blinn-Phong specular (halfway dot, raised to power)
uniform vec3 uLightDir;
uniform vec3 uBaseColor;
uniform float uGlossiness;   // the specular exponent
uniform vec3 uCameraPos;

varying vec3 vNormal;
varying vec3 vWorldPos;

void main() {
  vec3 n = normalize(vNormal);
  vec3 v = normalize(uCameraPos - vWorldPos);     // view direction
  vec3 h = normalize(uLightDir + v);              // halfway vector

  float diffuse = max(0.0, dot(n, uLightDir));
  float spec    = pow(max(0.0, dot(n, h)), uGlossiness);

  vec3 col = uBaseColor * (0.08 + diffuse) + vec3(1.0) * spec;
  gl_FragColor = vec4(col, 1.0);
}