Tiles not visible but loading
Description
Hi, I have encountered a problem loading tilesets where the tiles do not appear.
I can confirm:
- The GLB's all load individually fine in any viewer (including our scene if I use GLTFLoader and add directly)
- The Tileset is loaded correctly by 3d-tiles-renderer (appears to be parsed correctly)
- The individual tiles are loaded correctly by 3d-tiles-renderer over the network (you can see this in network tab)
-
DebugTilesPlugindoes NOT display bounds or subdivided tiles
The result is that no tiles are visible in the scene. I have made a reproduction: https://codepen.io/fungus1487/pen/XJbqLOL
I can also confirm this same tileset is displayed in cesium:
I've spent a day testing different properties on tiles renderer, event listener to see if any strange behaviour happens. As far as I can tell, they simply are never made visible.
I can confirm other tilesets work, the only peculiar thing about this one is that the gltf's have a zero height (they effectively are a plane and only have x/y) but other tilesets like this load correctly. Formatted tileset.json is attached further down for readability
Reproduction steps
- Load the tileset at:
https://raw.githubusercontent.com/cmcnicholas/3d-tiles-renderer-debug/refs/heads/main/tileset.json
Or just see repro: https://codepen.io/fungus1487/pen/XJbqLOL
Code
The code is similar to the github readme, can be seen in the repro: https://codepen.io/fungus1487/pen/XJbqLOL
Snapshot below:
const tilesRenderer = new TilesRenderer(
"https://raw.githubusercontent.com/cmcnicholas/3d-tiles-renderer-debug/refs/heads/main/tileset.json"
);
tilesRenderer.setCamera(camera);
tilesRenderer.setResolutionFromRenderer(camera, renderer);
tilesRenderer.addEventListener("load-tile-set", () => {
// Position camera at the sphere's center
const sphere = new THREE.Sphere();
tilesRenderer.getBoundingSphere(sphere);
// move ball to center
ball.position.copy(sphere.center);
// move camera to center and move back 10 units
camera.position.copy(sphere.center);
camera.translateZ(10);
// camera look at center
camera.lookAt(sphere.center);
controls.target = sphere.center.clone();
});
const debug = new DebugTilesPlugin();
debug.enabled = true;
debug.displayBoxBounds = true;
tilesRenderer.registerPlugin(debug);
scene.add(tilesRenderer.group);
Live example
https://codepen.io/fungus1487/pen/XJbqLOL
Screenshots & Tile Set
{
"asset": {
"version": "1.0",
"extras": {
"ion": {
"georeferenced": false,
"movable": true
}
}
},
"geometricError": 100000,
"root": {
"boundingVolume": {
"box": [4966.5, 7021.5, 0, 4966.5, 0, 0, 0, 7021.5, 0, 0, 0, 0]
},
"refine": "REPLACE",
"geometricError": 99999,
"children": [
{
"boundingVolume": {
"box": [0, 0, 0, 2483, 0, 0, 0, 3510.5, 0, 0, 0, 0]
},
"geometricError": 0,
"content": {
"uri": "tile-1-1-1.glb"
},
"transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7449, 10531.5, 0, 1]
},
{
"boundingVolume": {
"box": [0, 0, 0, 2483, 0, 0, 0, 3510.5, 0, 0, 0, 0]
},
"geometricError": 0,
"content": {
"uri": "tile-1-0-1.glb"
},
"transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2483, 10531.5, 0, 1]
},
{
"boundingVolume": {
"box": [0, 0, 0, 2483, 0, 0, 0, 3510.5, 0, 0, 0, 0]
},
"geometricError": 0,
"content": {
"uri": "tile-0-1-1.glb"
},
"transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7449, 3510.5, 0, 1]
},
{
"boundingVolume": {
"box": [0, 0, 0, 2483, 0, 0, 0, 3510.5, 0, 0, 0, 0]
},
"geometricError": 0,
"content": {
"uri": "tile-0-0-1.glb"
},
"transform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2483, 3510.5, 0, 1]
}
]
}
}
Library Version
v0.4.10
Three.js Version
r174
The issue is that the cache is filling up causing the renderer to reject any more tile loads after the first 3, resulting in the first root tile not being able to "refine" to show the children (because they're not all loaded). You can fix this by increasing the lru caches' bytes size allowance:
tiles.lruCache.maxBytesSize *= 2;
But it's worth looking at why your tileset is filling up the cache so quickly in the first place. Your data set only contains four tiles but each of those tiles uses a 4966 x 7021 pixel texture, or over double the size of 2 4K textures, taking over 185MB each with mipmaps enabled. By default the lru cache caps memory at ~430MB. It's more typical to limit textures to around 256 to improve load times, GPU upload times, and memory usage.
Regarding why Cesium can load it by default and this project can't - it's not clear to me, yet. Cesium uses a similar cache limit but it's possible Cesium does not use or account for mipmaps when calculating texture memory usage (disabling mipmaps allows the tile set to load here). This is a good simple test case data set, though, so I'd appreciate it if you could keep your tile tileset.json (commit for reference) around so I can investigate further when I have time.
Regardless, my recommendation is to structure the tile set more hierarchically with smaller textures so tiles renderer can load data as designed.
Related to #1065
Not a problem, I'll leave the test repo up with sample data. Thanks for the quick feedback, I have no idea how you spotted this, would appreciate what you traced/enabled/saw to get to this conclusion so quickly so I don't recreate similar issues in the future. I couldn't see any warnings etc. in console.
Thanks again.
I have no idea how you spotted this, would appreciate what you traced/enabled/saw to get to this conclusion so quickly so I don't recreate similar issues in the future.
Realistically it's in large part being very familiar with the codebase and having helped people with these kinds of hiccups before. I checked to make sure the model was really being centered first (which it was). Then checked the loading state of the child tiles that should be loaded and saw that all but one was loading. Then I checked tiles.lruCache.isFull() and saw that the cache was full.
I'm open to any suggestions for how to improve the ergonomics here. The cache is valuable for avoiding loading significantly more memory than desired but it is fairly opaque in situations like this. Eventually it would be nice to have an online tool that analyzes a tileset and not only validates it against the specification but can also make recommendations for poorly formed tile sets (large textures, massive per-tile memory usage, too many children, flat hierarchy, etc).
A more in-situ solution might be to modify the DebugTilesPlugin to log a warning if the cache becomes full or encounters particularly large tile data to help surface these kinds of issues.
@cmcnicholas Can you provide an example of Cesium loading the tile set? I'm seeing that three of the tile geometries unload after loading. And in fact the tiles take up more memory since Cesium seems to resize the textures to the nearest power of two (8K textures).
@cmcnicholas Can you provide an example of Cesium loading the tile set? I'm seeing that three of the tile geometries unload after loading. And in fact the tiles take up more memory since Cesium seems to resize the textures to the nearest power of two (8K textures).
I'm out today but will sort out an example tomorrow 👍
@cmcnicholas Can you provide an example of Cesium loading the tile set? I'm seeing that three of the tile geometries unload after loading. And in fact the tiles take up more memory since Cesium seems to resize the textures to the nearest power of two (8K textures).
https://sandcastle.cesium.com/#c=jVNtT9swEP4rp34YrVQ5ZS0bbB0aKwWl0FSlATGUL25ypaaOndnu68R/3+WljE3btA+RY99zj++ee+x5cGm4ctBDK5bpYAI8jtFacBq2emlAaAXcWnQ2UiWG+VqxBGd8Kd1ZAQ71AhV8gqiG28F8ehmLkRj4tzv/MBC+9dXNUdzz3/mL7P6uNzhhBPqWXC4I5K9Hl/1NEJ51hpPW2+DpZnEdjnfB7qsbhUEabFtHwdOdHJ73d0H+7YbiujfIHohs+NTvjMJxO99/vR+L0VO/PQzjNWGORuHDCev01XiF8fT2IWzd42h6dYXH7v239GKetuzmfLE7ecy+3PfkVdiJah8jFalYK+tgJXCNhppRuK5EYXfFWT2qxcW+p5XjQqGJao0i05ktfI8UQEnhhEQSjDj4mou9tKxc2udhGWYzo1OS8iwX10/q7c67w077uFkyAXheOBf2hSzRaNWBgzlfIXCQOuaOZtMEq2GNBwZhaYV6pFCqE5RD7ozY5FPMJI8RqAzuoNWEFgiqEXkCegaJ4esiiYqeI8SoHPVOgXyH3Lh5Wcsryg/7dkKyjZ1pk1qG3LpAE/o2C/WF2GByYXiK9TIZXgQgQvrjql30fo6PBtHW86IazRJbrs+5rFCNglkqC1lmRCqcWKFlPEnqlSwlsJS5gu+0TkP9GpBDPA/Oskxui84q84J1W0nSzHJ1cCNs7vH9EHHjDLc0w4qIFY+AlcfFrZRXdVj1R7TkiqReYhrw5s0/woyG9x+Q/UOb5KU2cnBjb5B9YWUXvxj2tdWKzL8yFp08R+oZyE/xHOpojDaNn3bWEpnUj9U5wQlca9a6xa2nOeqzSDMaPiyNrDPmOUzJcjRob7qMF1RfbG2e1/X2Kd1ErEAkn/7wniCWpDNFZkspJ2KHUe206xH+lzSpeUK2Ha3QSL4lSF5Gd354el0GGGNdj7b5pb/nOq3llJtXvD8A
Thanks - I'm not sure my other approach was unloading tiles. As I mentioned it looks like this is just related to the default cache size of the TilesRenderer. In Cesium the max cache size is ~536MB to ~1073MB (Cesium3DTileset.cacheBytes + maximumCacheOverflowBytes) while the cache in this project defaults to ~300 to 400MB.
Some other note-able things about the different implementations:
- Cesium upscales the textures to 8k due to their non-power-of-two size, inflating the memory usage while the original sizes are retained here.
- Cesium is disabling mip map generation while this project is using the inferred by the GLTFLoader based on the texture map settings in the glTF file.
If mipmaps are disabled then the estimated memory usage in this project when loading this tile set is ~557MB while it's around ~1073MB in Cesium.
Overall my recommendation is still that this tile set extremely large and should generated in a more traditionally tiled-image structure so the 3d tiles algorithm can function and detail can be loaded up to the level needed and memory can be kept low. If this tile set really needs to be loaded as-is then the generateMipmaps setting can be disabled (using something like the TilesCompressionPlugin) and / or increasing the size of the bytes storage in LRUCache.
Thanks - I'm not sure my other approach was unloading tiles. As I mentioned it looks like this is just related to the default cache size of the TilesRenderer. In Cesium the max cache size is ~536MB to ~1073MB (
Cesium3DTileset.cacheBytes + maximumCacheOverflowBytes) while the cache in this project defaults to ~300 to 400MB.Some other note-able things about the different implementations:
- Cesium upscales the textures to 8k due to their non-power-of-two size, inflating the memory usage while the original sizes are retained here.
- Cesium is disabling mip map generation while this project is using the inferred by the GLTFLoader based on the texture map settings in the glTF file.
If mipmaps are disabled then the estimated memory usage in this project when loading this tile set is ~557MB while it's around ~1073MB in Cesium.
Overall my recommendation is still that this tile set extremely large and should generated in a more traditionally tiled-image structure so the 3d tiles algorithm can function and detail can be loaded up to the level needed and memory can be kept low. If this tile set really needs to be loaded as-is then the
generateMipmapssetting can be disabled (using something like the TilesCompressionPlugin) and / or increasing the size of the bytes storage in LRUCache.
thanks for the comprehensive update on this, I'll take the recommendations to the team 👍