How to improve 3DTiles rendering performance?

Hi all,

I am current try to port the google 3DTiles service into BJS.

I took a reference from the following GitHub projects.
ebeaufay/threedtiles: 3DTiles viewer for three.js (github.com)
playcanvas/earthatile: Engine-agnostic runtime for 3D Tiles geospatial datasets (github.com)

It is more or less working now. But I am facing extreme low FPS when rendering 3DTiles.

The above two projects in other engine e.g. PlayCanvas and ThreeJS works smoothly. And I cannot find any magic code implemented in these projects. I am new to BJS and JavaScript, thus not very sure about what is the exact issue.

Question 1: Is there any way that tiles can be rendered incrementally without freeze the scene?

The two methods so far I have tried, but with no luck.

Method 1. Set Interval
setInterval(function () { tileRenderer.update(); }, 10);

Method 2. Coroutine (load each tile node in different frame)
function* updateTileManager(tileManager, cameraPos) {
const expandedNodes = Array.from(tileManager.expandedNodes);
for (const node of expandedNodes) {
// If the node is not in range or in view, collapse it
if (!(tileManager.isInView(node) && tileManager.isInRange(node, cameraPos))) {
tileManager.collapseNode(node);
}

        if (node.children) {
            for (const child of node.children) {
                if (child.children && tileManager.isInView(child) && tileManager.isInRange(child, cameraPos)) {
                    tileManager.expandNode(child).catch(err => console.error(`Error expanding node: ${node.id}`, err)); // load tile content from glb 
                }
            }
        }
        yield;
    }
}

The other question is about the way to handle tile assets. I am currently using AssetContainer to handle each tile resource (glb file). And when the tile is in range, addAllToScene() and when out, removeAllFromScene() will be called (code below). I can experience low FPS with this method (even after all the tiles are downloaded and cached in memory). I also tried using setEnabled() but seems the performance is similar.

Question 2: Is there any suggestion how I can improve the performance? Do i need to split the assetcontainer into different frames as well?

const load = async (node) => {
let container = nodeToEntity.get(node);
if (!container) {
const uri = node.content.uri;
// console.log('LOADING: ’ + uri);
const url = ${this._apiUrl}${uri}?key=${this._apiKey}&session=${this.tileManager.session};

                try {
                    container = await BABYLON.SceneLoader.LoadAssetContainerAsync("", url, scene); 
                  
                    container.meshes.forEach(m => {
                        m.alwaysSelectAsActiveMesh = true
                    })
                    container.materials.forEach(m => {
                        m.freeze();
                    })                   
                } catch (err) {
                    console.error("An error occurred while loading the GLB:", err);
                }
            }

            container.addAllToScene();     
            container.rootNodes[0].parent = tileMapNode;
            nodeToEntity.set(node, container);            
        };
        const unload = (node) => {
            // console.log('UNLOADING: ' + node.content.uri);

            const container = nodeToEntity.get(node);
            if (container) {            
                // container.dispose();
                // nodeToEntity.delete(node);
                container.rootNodes[0].parent = null;
                container.removeAllFromScene();
            }
        };
        const show = (node) => {
            // console.log('SHOWING: ' + node.content.uri);
            const container = nodeToEntity.get(node);
            if (container) {
                // container.rootNodes[0].setEnabled(true);
                container.addAllToScene(); // entries.rootNodes[0].setEnabled(true);
                // container.rootNodes[0].resetLocalMatrix(false);
                container.rootNodes[0].parent = tileMapNode;
            }
        };
        const hide = (node) => {
            // console.log('HIDING: ' + node.content.uri);
            const container = nodeToEntity.get(node);
            if (container) {
                // container.rootNodes[0].setEnabled(false);
                container.rootNodes[0].parent = null;
                container.removeAllFromScene();// entries.rootNodes[0].setEnabled(false);
            }
        };

I can point you to this page https://doc.babylonjs.com/features/featuresDeepDive/scene/optimize_your_scene
but judging from you code snippet, it looks like you already did a bunch of improvements.

can you share a small PG that illustrate the performance issue?

do you see smth I’m missing @sebavan ?

Thanks @Cedric for your suggestions.

As the project involves a number of local javascript components and relies on external Google 3DTiles, I don’t know how to create a PG easily.

Below is a screen record of the operation in BJS, you can see the sudden drop of the FPS.

In contrast you can try the 3DTiles in ThreeJS. (not an apple to apple compare)

Ultra Globe (jdultra.com)

Have a nice weekend.

Unfortunately, without a repro we could deep dive in, it will be hard to troubleshoot.

At the end, we all simply emit webgl commands so we should be on parity perf wise WHEN relying on the same shortcuts and trickery :slight_smile: