LLumen
← All articles

An invisible clash: the three.js transparency trap

Cover — threejs-transparency-trap

In the BIM clash viewer, clicking a clash isolates the offending pair and ghosts the rest of the model to glass — the way a coordination review reads a conflict through the surrounding structure. It worked for some elements and silently did nothing for others.

The symptom

The ghosting code was trivial: for every element that isn't in the clash, set material.opacity = 0.07. Elements that were already translucent (the architecture layer) faded correctly. Elements that started fully opaque (columns, beams) stayed solid — the same line, ignored.

The cause

three.js compiles a material into a GPU shader program once, and whether the material is transparent is baked into that program at compile time — it changes the render pass, blending and depth-write behaviour. Flipping material.transparent = true at runtime doesn't recompile the program, so a material that was born opaque keeps rendering opaque no matter what you do to opacity.

The fix is one line — force the recompile:

  • material.transparent = true

  • material.opacity = 0.07

  • material.needsUpdate = true ← the line that was missing

The nuance worth keeping

Not every property behaves this way. color and opacity are shader uniforms — live, cheap, per-frame. transparent, clipping planes, and light counts are compile-time — changing them costs a program rebuild. So needsUpdate is a sledgehammer: right once on a state change, wrong every frame in the render loop.

In retained-mode 3D, "set a property" isn't always "and it takes effect." Know which properties are uniforms and which recompile the shader.