fix: guard shader pow() bases so a NaN pixel can no longer black out the frame

This commit is contained in:
2026-10-12 04:23:25 +03:00
parent 50da616a46
commit 06e6a59b6f
18 changed files with 165 additions and 34 deletions

View File

@@ -236,14 +236,19 @@ export function createTraffic(
float core = exp(-pow(abs(c.x) * 2.3, 6.0) - pow(abs(c.y) * 2.4, 6.0));
float r = length(c * vec2(1.0, 1.6));
float glow = core * 2.4 + exp(-r * r * 22.0) * 1.2 + exp(-r * r * 5.0) * 0.3;`);
// Every pow() base is clamped: interpolation can land a fragment a hair
// outside 0..1 (a quad edge-on or clipped by the near plane), and pow() of a
// negative base is NaN. One NaN pixel in the HDR target is spread by the
// bloom blurs and the lens streak over the whole frame: a one-frame black
// flash (tests/unit/shader-pow-safety.test.ts).
// Trails: thin, brightest at the lamp, fading over their length.
const trailMaterial = additive(`
float across = exp(-pow((vUv.x - 0.5) * 5.0, 2.0));
float glow = across * pow(vUv.y, 1.6) * 0.8;`);
float across = exp(-pow(abs(vUv.x - 0.5) * 5.0, 2.0));
float glow = across * pow(clamp(vUv.y, 0.0, 1.0), 1.6) * 0.8;`);
// Wet-road pools: long and soft, brightest at the vehicle.
const poolMaterial = additive(`
float across = exp(-pow((vUv.x - 0.5) * 3.2, 2.0));
float glow = across * pow(vUv.y, 2.2) * 0.2;`);
float across = exp(-pow(abs(vUv.x - 0.5) * 3.2, 2.0));
float glow = across * pow(clamp(vUv.y, 0.0, 1.0), 2.2) * 0.2;`);
// Bodies: near-black coachwork that reads as a silhouette against the
// horizon, with the fog glow on the roof, the reader's headlights on the
@@ -321,12 +326,12 @@ export function createTraffic(
? `// Rear window: glass reflects a lavender band of sky.
float glass = rear * step(1.12, vLocal.y) * step(vLocal.z, -0.9);
color = mix(color, vec3(0.05, 0.05, 0.12) + uSheen * 0.22 * smoothstep(1.2, 1.55, vLocal.y), glass);
float lampBand = exp(-pow((vLocal.y - 0.92) / 0.18, 2.0));`
float lampBand = exp(-pow(abs(vLocal.y - 0.92) / 0.18, 2.0));`
: `// Box doors: a centre seam and two lock bars.
float seam = 1.0 - smoothstep(0.02, 0.05, abs(vLocal.x));
float bars = 1.0 - smoothstep(0.03, 0.06, abs(abs(vLocal.x) - 0.42));
color *= 1.0 - rear * max(seam * 0.6, bars * 0.35);
float lampBand = exp(-pow((vLocal.y - 1.3) / 0.2, 2.0));
float lampBand = exp(-pow(abs(vLocal.y - 1.3) / 0.2, 2.0));
// The under-run bar is a reflector strip: it answers the beam from far off.
float bar = rear * step(0.42, vLocal.y) * step(vLocal.y, 0.56);
color += vec3(1.0, 0.34, 0.22) * bar * headlightCone(vWorld) * smoothstep(18.0, 50.0, beamDistance) * (1.0 - smoothstep(180.0, 420.0, beamDistance)) * (0.2 + 1.4 * lift);`