Post 120

The Streak That Wouldn't Turn

Brushed-metal and hair anisotropy now stretch along the surface's own grain instead of a fixed screen direction. Rotate the lamp, and the streak turns with it; before this, it didn't move at all.

A wall-mounted barn lamp with a tight anisotropic highlight streaking straight down the brushed-metal shade, following the grain

Anisotropy has had a strange history in Lux. It shipped as a compiled lobe, sat dead for a release, got wired to the GPU, went invisible behind a prefilter bug, and finally became visible in The Mirror Test. At every one of those steps, the actual direction of the brushed-metal grain was reading off a coordinate frame built from the shading normal and a hardcoded world-up vector. It had nothing to do with the mesh. Rotate the object, and the streak stayed exactly where it was, like a tattoo painted on the inside of the camera lens instead of the surface.

Nobody noticed for the same reason the two earlier bugs went unnoticed: the test spheres. A sphere has a normal facing every direction at once, so a world-space heuristic finds some plausible-looking band to streak no matter which way you turn the object, and a plausible-looking band is enough to pass “does anisotropy do something.” It is not enough to pass “does the grain follow the metal,” which nobody had actually asked it to do until a real asset with a real brushing pattern showed up.

The barn lamp says otherwise

Khronos ships AnisotropyBarnLamp as one of its conformance assets: a wall sconce with a radially brushed shade, texture-driven direction map included. Point Lux’s old shader at it and the highlight traces a big looping arc across the dish, a shape with no relationship to the shade’s actual brushing. Fix the frame and the same light, same camera, same everything else produces a clean streak running straight down the grain. Nothing else in the scene changed. The image at the top of this post is the fixed render; here’s the same asset a moment earlier:

The same lamp, same camera, same light: pre-fix the highlight loops across the dish instead of following the brushed grain

The fix itself is the boring kind that’s satisfying to write: one function, aniso_basis, replaces two separate copies of the wrong heuristic (the direct highlight and the reflection-bending pass from The Fine Print Audit each had their own). It Gram-Schmidts the mesh’s real tangent against the shading normal and rotates it by the authored angle, the same construction the Khronos reference viewer uses. Meshes without UVs still get a graceful fallback rather than a NaN; everything else follows its own grain now, whether that grain came from an artist’s UV layout or the direction map baked into AnisotropyBarnLamp’s shade.

Proving a streak moved, not just that it exists

The old gates checked that anisotropy stretched a highlight into a band. They never checked which way the band pointed relative to anything, so a frame that was wrong in a fixed, consistent way could pass forever. The new ones do: a flat plate rotated forty-five degrees under the camera must show its highlight rotate the same forty-five degrees, checked against an angle computed independently from the plate’s own geometry, not against Lux’s own shader. Run that gate against the old heuristic and the plate’s streak doesn’t move at all, which is a satisfying way to watch a bug confess. The barn lamp gets the same treatment: its measured streak angle is checked against a hand-rolled ray-cast into the asset’s own tangents and its own direction-map texture, arriving within half a degree of the actual render.

Related: The Mirror Test and The Fine Print Audit, where this frame was wrong in exactly the same way, twice, and nobody could tell.

← All posts