Three.js
What is HardGraph? HardGraph publishes curated, provenance-backed agent skills grounded in reproducible vendor documentation.
Three.js is a scene-graph abstraction over WebGL and, increasingly, WebGPU: objects, cameras, lights, and materials form a tree that gets translated into draw calls each frame. The API looks like a 3D toolkit, but most of what breaks in practice is managing GPU resources through a garbage-collected language that has no idea they exist.
The renderer choice is not cosmetic
WebGLRenderer and WebGPURenderer are not interchangeable backends behind one API. WebGPU
support means node-based materials and TSL (Three.js Shading Language) — a JS-authored shader
graph compiling to either backend — plus compute shaders WebGL cannot express. Starting on
WebGLRenderer for compatibility and later wanting compute or node materials is not a one-line
swap: custom ShaderMaterial/GLSL needs a TSL rewrite to run on WebGPURenderer. Decide early if
the project needs capabilities WebGL cannot offer.
Disposal is the actual hard part
scene.remove(mesh) detaches an object from the graph; it does not free anything. Geometries,
materials, and textures hold GPU buffers that only .dispose() releases, per-resource rather than
per-object — a shared material or texture needs its own disposal call, and disposing a resource
still referenced by another live mesh breaks that mesh. This is the single most common source of
"the tab's memory keeps growing" bugs in long-running 3D apps, and it's invisible to a review that
only checks whether objects left the scene.
Geometry vs BufferGeometry, and draw calls over triangle count
Geometry was removed years ago; everything is BufferGeometry now, vertex data in typed arrays
(BufferAttribute) rather than Vector3 arrays — code referencing THREE.Geometry, .vertices,
or .faces predates the current API. Separately, a modest-looking scene can stall a frame budget
because each distinct mesh with an unshared material is its own draw call; InstancedMesh, merged
static geometry, or shared materials collapse many into one. Profile draw-call count, not triangle
count, when a simple scene runs slowly.
When to reach for react-three-fiber instead
react-three-fiber maps the scene graph onto React's reconciler, so objects are declared as JSX
and unmounting a component disposes what it created — worth it once a scene must stay in sync with
application state. In a standalone demo or a hand-tuned performance-critical scene, the
reconciliation layer adds cost for no benefit; raw imperative Three.js stays more direct.
What to verify rather than recall
Exact class/method signatures, which addons live under three/addons/ versus core, default
material/light parameter values, and TSL node function names change between releases and are easy
to misremember with confidence. Confirm these against references/vendor/ or the live docs rather
than asserting a remembered signature — a wrong constructor argument order fails at runtime, not
review time.