Coming from Three.js: How to correctly implement hover/color change on massive Thin Instances?

Hello everyone!

I’m currently running some performance benchmarks between Three.js and Babylon.js for an interactive web simulator I’m building. My goal is to render a massive amount of objects (e.g., 100k to 200k cubes) and change the color of a specific instance dynamically when the user hovers over it with the mouse.

The Three.js Approach (Working perfectly): In Three.js, this was quite straightforward. I used an InstancedMesh alongside a Raycaster. I mapped an instanceColor buffer, and inside the render loop, I check for intersections. Once hit, I get the instanceId, update the specific RGB values in the float array, and flag needsUpdate = true. It works flawlessly and smoothly at 144fps on my RTX 4070 Ti.

The Babylon.js Struggle: I am trying to replicate this exact behavior using Thin Instances. Rendering 1.5 million static cubes is incredibly fast and matches the performance perfectly. However, I have completely failed to make the individual hover/color change work.

Here is what I have tried so far:

  1. Setting box.thinInstanceEnablePicking = true and box.isPickable = true.

  2. Initializing a dynamic color buffer: box.thinInstanceSetBuffer("color", colorsArray, 4, false).

  3. Using scene.pick() in the render loop to get the pickResult.thinInstanceIndex.

  4. Updating the color using box.thinInstanceSetColorAt() (or updating the array directly and calling box.thinInstanceBufferUpdated("color")).

  5. Trying different materials to ensure the color isn’t being overwritten by lighting (StandardMaterial with emissive white, and PBRMaterial with unlit = true).

Despite my efforts, the individual instance color simply does not change on the screen.

For reference, here is the core logic of what works beautifully in Three.js:

// Three.js working logic
const raycaster = new THREE.Raycaster();
// ... setup InstancedMesh with instanceColor buffer ...

function animate() {
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObject(instancedMesh);

    // Reset previous hovered color
    if (hoveredId !== -1) {
        colors[hoveredId * 3] = defaultR; // ... G and B
        hoveredId = -1;
        instancedMesh.instanceColor.needsUpdate = true;
    }

    // Set new hover color
    if (intersects.length > 0) {
        hoveredId = intersects[0].instanceId;
        colors[hoveredId * 3] = hoverR; // ... G and B
        instancedMesh.instanceColor.needsUpdate = true;
    }
    
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
}

Is there a fundamental architectural difference or a missing flag I am overlooking? What is the correct, performant “Babylon way” to achieve this real-time individual color update on hover for hundreds of thousands of thin instances?

Any help, insights, or a Playground example would be hugely appreciated! Thank you!

1 Like

Building upon the picking example in:

You can do as in Three.js:

Or only do a partial update of the GPU buffer, to avoid updating several megabytes when only a few bytes change:

After this PR is merged, you can just update the CPU buffer and have the GPU buffer updated accordingly:

2 Likes