May 21, 2026 · 2 min
MarchingCubes lets you color blobs. The material has to ask.
Six blobs, three colors, all rendered hot pink. The fix is one boolean.
Building a metaballs demo. Three.js’s MarchingCubes does the heavy lift — overlapping spheres get merged into one isosurface, the algorithm rebuilds the mesh every frame, you get those gooey morphing blobs.
The plan: seven blobs, three colors. The cyan one would chase the cursor. That was the whole hook.
We shipped it. Every blob was hot pink. Including the cyan one. Especially the cyan one, because it was the biggest and you couldn’t miss it.
The API takes a color. addBall(x, y, z, strength, subtract, color). The constructor takes an enableColors flag. We’d set both.
const blobs = new MarchingCubes(48, mat, true, /* enableColors */ true);
blobs.addBall(0.5, 0.5, 0.5, strength, subtract, new Color(0x00d9ff));
What we missed: the material has to ask for the colors. Three.js generates the vertex color buffer either way. The material decides whether to read it.
const mat = new MeshPhongMaterial({
- color: 0xff006e,
+ color: 0xffffff,
+ vertexColors: true,
});
Two fixes layered. vertexColors: true switches on the read. The base color has to be white because Three multiplies the material color by the vertex color, and a pink material times a cyan vertex color produces approximately black-pink, which on screen reads as just darker pink.
The thing this surfaces about Three’s API: “you gave me color data” and “you want me to render with color data” are two separate switches. The geometry holds the data. The material has to opt in.
I keep falling for this. It’s been six years since I started using Three.js. I’ll fall for it again.