Fresnel / Rim Light
Purpose
Fresnel is the opposite question from specular — instead of "where does the normal point at the camera," it asks "where does the normal point away from the camera," and that grazing-angle rim is what makes silhouettes pop.
Key insight
Every surface reflects more light at glancing angles than head-on. Look at a lake from above — it's transparent. Look at it across the horizon — it's a mirror. That's Fresnel. The formula flips the specular dot: where dot(normal, view) is near zero (surface edge-on to the camera), 1 - dot(normal, view) is near one. Raise that to a power to control sharpness, and you get a bright ring along the silhouette.
Rim light does two things nothing else does: it separates a character from the background regardless of actual lighting, and it suggests soft-edged materials — skin, velvet, bubbles, force fields. It's also the term living inside PBR's Fresnel later, so learning it isolated means L21 has nothing new conceptually.
Drag edge-sharpness and watch the glow pull toward the true silhouette as the exponent climbs.
Break it
Edge-sharpness to its minimum (~0.5). The whole object glows like an unlit sphere pretending to be a light. The "edge" disappears because Fresnel with no power is just a soft inverse dot — a different effect entirely.
Teaches: Fresnel needs the exponent to read as an edge. Without it, you've built a cheap ambient bloom, not a rim.
Direct Claude
// VERTEX — normal + world position (we need view direction in frag)
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 + Fresnel rim (grazing-angle highlight)
uniform vec3 uLightDir;
uniform vec3 uBaseColor;
uniform vec3 uRimColor;
uniform float uEdgeSharpness;
uniform vec3 uCameraPos;
varying vec3 vNormal;
varying vec3 vWorldPos;
void main() {
vec3 n = normalize(vNormal);
vec3 v = normalize(uCameraPos - vWorldPos);
float diffuse = max(0.0, dot(n, uLightDir));
float rim = pow(1.0 - max(0.0, dot(n, v)), uEdgeSharpness);
vec3 col = uBaseColor * (0.08 + diffuse) + uRimColor * rim;
gl_FragColor = vec4(col, 1.0);
}