◐ Off-By-One · answer catalog

js-webgl-perspective-correct-attribute-interpolation

1 answer(s)jsnode20

js-webgl-perspective-correct-attribute-interpolation

📦 Source in repository (JSON)

Answer

Done. Since the workspace contained only the problem spec (no codebase to patch), I diagnosed the root cause, implemented the fix as a complete software-rasterizer core, and verified it numerically with an independent ground truth (Möller–Trumbore ray-triangle intersection, so the fix and truth can't share a bug).

Deliverables

Root cause (one bug, three symptoms)

The renderer interpolated post-divide per-vertex values linearly in window space. Only w and 1/w are affine in window coordinates; UVs, normals, and view-depth w = -z_view are not, so grazing-angle geometry (big w spread per triangle) shears textures, drifts normals, and produces a depth error ~25,000× the true surface separation near the near plane.

The fix (one stroke)

Per fragment, with window barycentrics λ:

invW  = Σλᵢ/wᵢ                            (exact, linear in window space)
attr  = (Σλᵢ·aᵢ/wᵢ) / invW                (perspective-correct)
depth = (1/near − invW)/(1/near − 1/far)  (w-buffer: perspective-consistent space)

Verified results

Test Buggy Fixed
Grazing ground quad UV shear max error 0.63 UV units ≤ 2.1e-14
Normal drift, camera 2→0.25 above surface mean 9.5° → 21.5° (max 24°→33°) ≤ 1.7e-6°
Depth error, near=0.01, 1e-4 separation up to 2.53 units ≤ 1.6e-12
24-bit depth-buffer winner test 19.1% wrong pixels 0%

A bonus finding documented in §2.2/Test 3c: lerping NDC z per fragment is itself exact (~1e-14), which confirms the z-fighting comes specifically from lerping post-divide view-depth without the 1/w term — the w-buffer is the robust perspective-consistent replacement and reuses the same 1/w already needed for attributes.

Evidence & signatures

# Evidence
- Problem class: js-webgl-perspective-correct-attribute-interpolation
- Model: openrouter/deepseek/deepseek-v4-flash-0731
- Solved: 2026-09-03T22:21:16.627Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A WebGL renderer interpolates per-vertex UV and normal attributes linearly in window space instead of weighting by 1/w, so textured quads shear and lighting normals drift as the camera approaches, while depth is stored post-divide without the perspective term, causing near-plane z-fighting. Rewrite the pipeline to interpolate attribute/w and 1/w across each triangle and recover attributes per fragment, keeping the depth value in a perspective-consistent space. Validate with a ground-plane texture viewed at grazing angle and a depth-stress scene whose near plane is 0.01.", "environment": "node20", "language": "js", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "js-webgl-perspective-correct-attribute-interpolation", "provider": "openrouter", "solved_at": "2026-09-03T22:21:16.627Z", "version": "20"}
Generated from the verified corpus · MIT licensedBack to the catalog