Thin instances are such by their nature. The documentation is correct. A mesh with N thin instances is treated as a single, gigantic object by the GPU. When the renderer checks if this mesh is on screen (frustum culling), it looks at the collective bounding box that encompasses every single thin instance.
So, if you have instances spread across a massive map, this collective bounding box is also massive. It will almost always be at least partially in the camera’s view. Therefore, the entire set of hundreds of thousands of instances is sent to the GPU for rendering every single frame, even if you can only see a few hundred of them. This is the “all” part of “all or nothing,” and it may be a huge performance killer.
When you split your original mesh, for example, into 10 clones (one for each tile), you now have 10 separate meshes, each with their own set of thin instances and, crucially, their own bounding box.
Now, the rendering engine performs frustum culling on each tile mesh individually. If your camera is only looking at, say, 2 tiles, then only the thin instances belonging to those 2 tile meshes are sent to the GPU. The instances belonging to the other 8 tiles are culled and never processed.
Babylon.js will handle this for you automatically. You do not need to do anything extra. The engine’s internal frustum culling system will correctly check each individual tile mesh’s bounding box and skip rendering it (and all its thin instances) if it’s not in the view frustum.
You can verify this is working by using the Inspector and turning on bounding boxes.
But when tiling you need to set the mesh’s bounding box to tightly fit ONLY its instances.
Here is a conceptual code example with some comments:
// Original mesh (your template)
let originalMesh = /* ... your mesh creation code ... */;
originalMesh.setEnabled(false); // Hide the original
// Create your tile meshes using CLONE, not createInstance
let tileMeshes = [];
let instancesPerTile = 10000; // Example
let tileSize = 100; // Example tile size
for (let tileX = 0; tileX < 10; tileX++) {
for (let tileZ = 0; tileZ < 10; tileZ++) {
// Use clone() instead of createInstance()
let tileMesh = originalMesh.clone("tile_" + tileX + "_" + tileZ);
// Reset position since clone copies transform - we'll use thin instance matrices instead
tileMesh.position.set(0, 0, 0);
// Create thin instances for this specific tile
let matricesData = new Float32Array(instancesPerTile * 16);
for (let i = 0; i < instancesPerTile; i++) {
let matrix = BABYLON.Matrix.Translation(
tileX * tileSize + Math.random() * tileSize, // X within tile
0,
tileZ * tileSize + Math.random() * tileSize // Z within tile
);
matrix.copyToArray(matricesData, i * 16);
}
tileMesh.thinInstanceSetBuffer("matrix", matricesData, 16);
// Set the mesh's bounding box to tightly fit ONLY its instances
let tileMin = new BABYLON.Vector3(tileX * tileSize, 0, tileZ * tileSize);
let tileMax = new BABYLON.Vector3((tileX + 1) * tileSize, 10, (tileZ + 1) * tileSize);
tileMesh.setBoundingBox(new BABYLON.BoundingInfo(tileMin, tileMax));
tileMeshes.push(tileMesh);
}
}
Hope it may help 