How to handle Instancing for massive Hierarchical models in 3D Tiles 1.1?

I’ve been diving into the CesiumJS for about one month now, and I’m absolutely blown away by the technology. However, as a beginner, I’ve run into a performance “wall” that I’m struggling to overcome regarding the rendering of many complex models.

My Project Context:
I need to render about 800+ high-poly buildings at specific coordinates. Each building is a GLB model with a complex internal Node Hierarchy (it’s not just a single mesh; it has nested parts, sub-meshes, etc.).

Since I’m starting fresh, I went straight to 3D Tiles 1.1 because I wanted to use the latest features like Implicit Tiling and GPU Instancing (EXT_mesh_gpu_instancing). But I feel like I’m stuck between two bad options.

The Struggle:

  1. Hierarchy vs. Instancing: I learned that EXT_mesh_gpu_instancing works at the Node level. To instance my “entire building,” it seems I have to flatten the hierarchy into one mesh. But doing this destroys my model’s structure.
  2. Performance: When I don’t use instancing (to keep the hierarchy), my Draw Calls go through the roof, and my FPS drops significantly on the web browser.
  3. LOD Confusion: I’ve tried a hybrid approach (using simple Billboards/Quads for far distances and full models for close-up), but I’m not sure if I’m implementing the Implicit Tiling correctly to swap these out efficiently without losing the hierarchy at the final level.

I would love some guidance on:

  • Is there a “standard” way to instance complex hierarchical objects in 3D Tiles 1.1 without breaking them?
  • Can I somehow “re-use” the same hierarchical GLB resource across many tiles in an Implicit Quadtree (like how the old.i3dm used to point to a URL)?
  • Am I overcomplicating things? Should I stick to 3D Tiles 1.0, or is there a specific 1.1 feature (like Structural Metadata) that handles this better?

I’ve been reading the documentation and trying to use
@gltf-transform for my pipeline, but I feel I might be missing
a fundamental concept here.

Here is the node structure of one building:

This is how I structured my Implicit Tileset:

{
  "asset": {
    "version": "1.1",
    "generator": "Implicit-Hybrid-Generator"
  },
  "geometricError": 4096,
  "root": {
    "boundingVolume": {
      "region": [
        1.8448277673901856,
        0.35906409119196303,
        1.845137039733639,
        0.3592540179211651,
        -100,
        1000
      ]
    },
    "geometricError": 2048,
    "refine": "REPLACE",
    "content": {
      "uri": "content/{level}/{x}/{y}.glb"
    },
    "implicitTiling": {
      "subdivisionScheme": "QUADTREE",
      "availableLevels": 5,
      "subtreeLevels": 5,
      "subtrees": {
        "uri": "subtrees/{level}/{x}/{y}.subtree"
      }
    }
  }
}

Technical Setup:

  • CesiumJS 1.115+ (1 month experience)
  • 3D Tiles 1.1 (Implicit Tiling)
  • Models: High-poly GLB with nested nodes.

Any advice, patterns, or tips would be immensely helpful!

Thank you so much for your patience and help!

In many cases, the former approach of I3DM files can be emulated with EXT_mesh_gpu_instancing. But you have indeed identified a case where this is not necessarily possible. While I3DM operated on “the whole model”, EXT_mesh_gpu_instancing only operates on single meshes. And one consequence of that is that node hierarchies within the glTF cannot sensibly be retained.

(There could be cases where it could make sense to keep the node structure, and add an EXT_mesh_gpu_instancing in each node, but it’s unlikely that this is the case here)

I actually did struggle with this as well: The ‘3d-tiles-tools’ library offers a functionality to “upgrade” a tileset from 1.0 to 1.1, and one tiny part of that was the attempt to upgrade I3DM data and convert it into glTF+EXT_mesh_gpu_instancing. Some sort of “diary” about how that went is around Upgrade I3DM to GLB with `EXT_mesh_gpu_instancing` by javagl · Pull Request #52 · CesiumGS/3d-tiles-tools · GitHub . Eventually, I used the approach to “flatten things out”, even when the node structure is lost in these cases. After all: When the model contains animations (e.g. nodes where the translation is animated), then this cannot be modeled in the same way as it was in I3DM anyhow.

Long story short: Even though the I3DM format is currently referred to as ‘legacy’, it is still a valid tile format. And from what you described, it sounds like you could be the most appropriate solution here.

(Note that your tileset would and could still be a valid 3D Tiles 1.1 tileset - using I3DM does not enforce a lower version)

(Somehow related: Earlier CesiumJS versions offered a ‘ModelInstanceCollection’ class that could create the instantiations at runtime, but this has been removed in the meantime. There are considerations to add it back, in this issue, but there is no timeline for that. I recently described a workaround, and … this does, in fact, use I3DM data under the hood)

Thank you so much for the incredibly fast and detailed reply!

I followed your advice, switched to the .i3dm approach, and it worked perfectly.

To ensure the best possible performance on the web, I also made sure to optimize the original model’s mesh as much as possible before packing it into the .i3dm file. The instancing is now running very smoothly even with 1.000 of buildings.

Thanks again for your invaluable insight—it really helped me overcome a major roadblock in my project!