Rendering in Retrospect · #5 of 12

Lighting Models Break at the Edges

Retrospective — written in 2026, looking back.

Most lighting models look convincing in the middle of their parameter space. Put a moderately rough, non-metallic sphere under a soft light, pointed more or less at the camera, and almost anything — even a model you know is wrong — will produce a plausible image. The failures live at the edges: when the view grazes the surface, when roughness approaches zero or one, when a material stops behaving like a dielectric. Looking back, I learned more about physically based rendering from building small scenes that deliberately pushed a model to those edges than from any scene that was designed to look good.

This is a retrospective about that habit: take one idea, isolate it in a personal demo, and turn the dial until it breaks.

Why the Middle Hides the Problem

A classic diffuse-plus-specular model splits reflected light into a Lambertian term and a shiny highlight. It is fast, controllable, and for decades it was enough. Its weaknesses are structural, though. The two terms are added without accounting for each other, so nothing stops their sum from reflecting more energy than arrived. At normal incidence that excess can be small enough to ignore. At grazing angles it is not.

Physically based models start from a different accounting rule: for any incoming direction, the total reflected energy cannot exceed the incoming energy. That constraint alone explains a lot of what the older models get wrong, because a highlight that brightens without the diffuse term dimming in compensation is not just inaccurate — it is creating light.

When I first re-implemented a simple material in my own renderer, I kept the test scene deliberately boring: a row of spheres, one light, a black background. Boredom is a feature. With no textures, no environment, and no post-processing to hide behind, an energy violation has nowhere to disguise itself as art direction.

Fresnel Is Not a Highlight Trick

The Fresnel effect is usually introduced as "surfaces get more reflective at grazing angles," which is true and also easy to misread as a stylistic gloss. It is really a statement about how reflection and refraction partition energy at an interface.

Schlick's approximation captures the shape well enough for real-time work:

F(theta) ≈ F0 + (1 - F0) * (1 - cos(theta))^5

Here F0 is the reflectance when you look straight at the surface, and cos(theta) is the dot product of the view direction and the surface normal. Two facts fall out of this one line.

First, at grazing angles cos(theta) goes to zero, the second term goes to one, and F goes to one for every material. Wood, plastic, skin, water — viewed edge-on, all of them approach a mirror. That is why a matte tabletop still catches a long streak of window light when you lower your head to its level.

Second, the difference between materials lives in F0, and it is smaller and stranger than intuition suggests. For most dielectrics — materials that refract light into themselves, like plastic, water, and glass — F0 is around 0.02 to 0.05. Only a few percent of light reflects directly off the surface; the rest enters, scatters, and some of it comes back out as diffuse color. That is why dielectric specular reflections are essentially white: the surface reflection never entered the material, so the material's pigment never tinted it.

Metals invert this. A metal absorbs light that enters it almost immediately, so there is effectively no subsurface diffuse component at all. Its reflection happens at the surface, and its F0 is both high — often 0.5 to 1.0 — and colored. Gold looks gold in its reflections, not in spite of being a metal but because of how it is one.

In the demo this became obvious only when I forced the comparison: three spheres with the same roughness, one dielectric tinted red, one metal tinted with the same values feeding F0 instead of a diffuse albedo, and one where I incorrectly fed the red into both places. The third sphere was the instructive failure. It looked like candy — too saturated, too bright — because the same color was being applied twice, once on the way in and once as if it had never gone in at all.

GGX and the Shape of a Highlight

If Fresnel decides how much light reflects specularly, a normal distribution function decides where it goes. GGX became the common choice in real-time rendering because its highlight has a bright core and a long, soft tail, which matches how many real surfaces behave.

Conceptually, GGX treats a surface as a collection of microscopic facets, each a tiny perfect mirror with its own orientation. Roughness is a statistical statement about how varied those orientations are:

That last clause is where simplified models quietly cheat. If you widen a highlight without dimming it, you have again invented energy. If you dim it without widening it, rough surfaces look dead. A correct implementation does both at once, which is why a roughness sweep in a GGX demo looks like the highlight is melting rather than simply fading.

My roughness sweep taught me the extremes are different failure modes. Near zero, the highlight becomes a razor-thin aliasing problem: a single pixel can contain the entire highlight, and tiny camera movements make it sparkle and vanish. That is not a lighting bug so much as a sampling bug exposed by lighting — the signal has higher frequency than the pixel grid can represent. Near one, the opposite problem appears: geometry masking and shadowing between microfacets matter enormously, and if your implementation skips the geometry term, very rough surfaces glow at grazing angles in a way no real surface does.

Grazing Angles Are the Exam, Not the Exception

It is tempting to treat a grazing view as an unusual camera position. In a real scene it is everywhere: every floor extending to a horizon, every curved surface at its silhouette, every sphere at its rim. The silhouette of any object is a grazing-angle test.

That is why I now build a specific check into material demos: a large flat plane, a low light, and a camera that can drop almost to the plane's surface. The plane removes curvature as a variable. As the camera lowers, a correct model shows diffuse reflection fading, specular reflection strengthening and stretching, and the total staying plausible. A broken model usually reveals itself in one of three ways — the plane gets uniformly brighter (energy creation), the diffuse color tints the stretched reflection (metals and dielectrics confused), or the highlight pops in at a threshold (an approximation with a visible seam).

The same test, turned sideways, explains a common confusion about metals in dark environments. A polished metal sphere in a black room with one small light is mostly black, with one intense highlight. Beginners often read that as a bug and reach for a diffuse term to "fill it in." But a metal has almost nothing to fill in: with no environment to reflect, most directions honestly reflect darkness. Give the same sphere an environment map and it suddenly reads as metal, because you have given its mirror-like reflection something to show. Dielectrics survive a bare scene; metals need a world.

What the Experiments Actually Taught Me

None of this required a large renderer. A raymarched test scene or a single fullscreen shader would have shown most of it. What mattered was the discipline of changing one variable at a time and predicting the result before running it. When my prediction about the metal sphere was wrong — I expected dim gray, I got near-black — the mismatch forced the actual understanding, which is that tint, diffuse, and reflection are not three decorative layers but one energy budget spent three ways.

The compact lesson I would give my earlier self is this: implement the constraint, not the look. If outgoing energy never exceeds incoming energy, if Fresnel rises to one at grazing for everything, if dielectric F0 stays small and untinted while metal F0 carries the color, and if widening a highlight always dims it — then plausible images follow across the whole parameter range, not just in the comfortable middle.

And when something still looks wrong, do not add a term. Move the camera to the edge — lower it, push the roughness to an extreme, remove the environment — and watch which assumption gives way first. That is where the model was always going to break. The edge is not the exception to the lighting model. It is the part that tells you whether you had one.