The next challenge I’d look at is what happens when the configurator grows beyond a single pergola. Imagine a garden design tool with 30 pergolas, thousands of louvers, and a tilt slider that updates every frame. You’ve reduced the draw calls, but you could still end up rebuilding thousands of instance matrices on every interaction.
I’d probably separate the static transforms from the parts that actually need updating, and keep an eye on bounding volumes when instances move outside their original bounds. There’s also the picking side: with InstancedMesh, raycasting gives you an instanceId, so mapping a clicked louver back to the correct product component becomes part of the interaction model.
Have you experimented with updating only the affected instances, or does recalculating the whole batch turn out to be cheap enough for your configurator?
For a single product I honestly don’t bother being clever, recalculating the whole batch is cheap. With 40 or 120 louvers the JS loop is noise, the only real cost is the buffer re-upload when you flip instanceMatrix.needsUpdate, and that fires once no matter how many you touched.
Your scale flips it though. 30 pergolas of static geometry shouldn’t get re-uploaded because one tilt slider moved, so I’d split it: static parts in their own InstancedMesh I write once and never touch, the animating louvers in a separate one so only that buffer goes up per frame. If you want to push it further, newer three.js lets you set updateRanges on the instance attribute and upload just the slice that changed instead of the whole thing. And yeah, you’re right about picking, the instanceId from the raycast maps back to the component through a lookup array I build when I place the instances, that’s what makes a clicked louver actually mean something.
That split makes sense. I'd be curious about one detail with updateRanges, though: how well does it hold up when the affected louvers aren't contiguous in the instance buffer?
For example, if a configurator has 30 pergolas sharing a batch and a slider only changes the louvers on pergola #17, the dirty instances might be scattered across the buffer. At that point, you're trading a larger upload for multiple smaller ranges, and the overhead of managing those ranges can eat into the savings.
I'd probably benchmark three cases: one contiguous range, several scattered ranges, and a full-buffer update. The result could also influence how instances are laid out in memory, grouping them by product and update frequency rather than just by geometry and material.
Have you found a point where partial updates stop paying off, or is it still mostly dependent on the renderer and target hardware?
The next challenge I’d look at is what happens when the configurator grows beyond a single pergola. Imagine a garden design tool with 30 pergolas, thousands of louvers, and a tilt slider that updates every frame. You’ve reduced the draw calls, but you could still end up rebuilding thousands of instance matrices on every interaction.
I’d probably separate the static transforms from the parts that actually need updating, and keep an eye on bounding volumes when instances move outside their original bounds. There’s also the picking side: with
InstancedMesh, raycasting gives you aninstanceId, so mapping a clicked louver back to the correct product component becomes part of the interaction model.Have you experimented with updating only the affected instances, or does recalculating the whole batch turn out to be cheap enough for your configurator?