How can I sync camera frustum and LOD levels together?

Hey, there!

I’m stuck on one issue with my loaded model. That model is large enough so I cannot add the external playground link here. But, let me try to explain what I’m trying to achieve. I’ve added my old playground link below, which is quite similar to the new model so I hope you guys can understand.

https://playground.babylonjs.com/#G95938

My new model looks like below with low-level polygons and buildings over the continents. What I’ve done is I’m checking if my continents are in camera frustum or not using the camera.isInFrustum method on camera.onViewMatrixChangedObservable event. That is working fine and If I move/drag my camera so it hides/shows the continents which is inside camera frustum.


const loadGLTF = () => {    
    SceneLoader.Append('../../../src/assets/verge3d/', 'Map_Figma_Text_export2_new.glb', scene, (scene) => {    
        console.log('scene loaded', scene);
        // lod
        scene.meshes.forEach((mesh) => {
            const meshName = mesh.name;
            // console.log(mesh.getPositionExpressedInLocalSpace())
            // if(mesh && meshName.includes('mountain')){
            //     mesh.addLODLevel(15, null);
            //     // mesh.useLODScreenCoverage = true
            // }
            
            if(mesh && meshName.includes('Bossnet')){
                // mesh.isVisible = false
                mesh.addLODLevel(7, null)
            }
            if(mesh && meshName.includes('Bossverse')){
                // mesh.isVisible = false
                mesh.addLODLevel(6, null)
            }
            if(mesh && meshName.includes('primitive0')){
                mesh.addLODLevel(10, null)
            }
            if(mesh && meshName.includes('primitive1')){
                mesh.addLODLevel(8, null)
            }
        })      
    }, () => {
        console.log('loading...')
    }, (error, message) => {
        console.log(message)
    },
    '.glb')    
}

camera.onViewMatrixChangedObservable.add(() => {            
            if(scene.meshes.length > 0){
                scene.meshes.forEach((mesh) => {
                    const meshName = mesh.name;
                    if(meshName.includes('mountain')){
                        if(mesh){
                            // console.log(mesh)
                            const isInFrustum = camera.isInFrustum(mesh);
                            if(!isInFrustum){
                                mesh.isVisible = false;
                            }else{
                                setTimeout(() => {
                                    mesh.isVisible = true;
                                }, 100)
                            }
                            
                        }
                    }
                    else {
                        // const _isInFrustum = camera.isInFrustum(mesh);
                        if(mesh.name !== 'water'){
                            // const lodLevel = mesh.getLODLevels();
                            // console.log(mesh.name, lodLevel);
                            // var boundingSphere = mesh.getBoundingInfo().boundingSphere;
                            // console.log('boundingSphere', )

                            if (camera.isInFrustum(mesh)) {
                                console.log('in')
                                setTimeout(() => {
                                    mesh.isVisible = true;
                                    if(mesh && mesh.name.includes('Bossnet')){
                                        // mesh.isVisible = false
                                        mesh.addLODLevel(7, null)
                                    }
                                    if(mesh && mesh.name.includes('Bossverse')){
                                        // mesh.isVisible = false
                                        mesh.addLODLevel(6, null)
                                    }
                                    if(mesh && mesh.name.includes('primitive0')){
                                        mesh.addLODLevel(10, null)                                 
                                    }
                                    if(mesh && mesh.name.includes('primitive1')){                                        
                                        mesh.addLODLevel(8, null)
                                    }
                                }, 100)
                                // LOD level is inside camera frustum
                            } else {
                                mesh.removeLODLevel(null);
                                mesh.isVisible = false;
                                console.log('out')
                                // LOD level is outside camera frustum
                            }
                        }
                    }
                })
            }
        })

Apart from that, I’ve added LOD levels to these low-level polygons, and buildings, which is also working fine, but the issue is my low-level polygons and buildings (as marked in the red rectangle below) are kept visible and my continent is showing after a slight delay when the camera moves/drag.

Instead, it should work like below, when I move/drag the camera the continents first show when it comes inside the camera frustum then low-level polygons and building even if they are at their LOD level.

This is the video regarding the issue.

Can someone help me with this? How can I sync the LODs and camera frustum? So, continents should be visible first when they are inside the camera frustum and then low-level polygons/buildings and hidden when it’s not.

Hello! Wanted to start by saying your video looks super nice already :smiley:

About your issue, first, I’m wondering why are you setting the mesh’s visibility based on frustum as that’s already done by the framework? Is it for optimization?

Will also ping @Evgeni_Popov here to check what’s going on

Hey, @carolhmj Thanks for the response. I tried giving a LOD level to my continents(mountain) meshes too but that didn’t seem to work fine so I’m setting the continents(mountain) mesh’s visibility based on camera frustum to look like lazy loaded meshes.

The main motto here is to build something similar to Google Maps. As we zoom/move the map it loads more things on the map, showing texts and polygons on different zoom levels. That’s what I want to do in my model.

That’s hard to tell without a repro…

Are you sure the bounding boxes of the mountains are ok?

It’s strange that the mountains pop in your video while they should already be visible in the frames before…

Hmmmm getting the LOD correct here would probably be more beneficial for the performance. Can you tell more what issues did you have with LODs?

Hey, @Evgeni_Popov @carolhmj , Here is the playground I’ve created for this issue.

https://playground.babylonjs.com/#R9KLBY

Please have a look and let me know how can we make frustum and LOD sync together or is there anything that I can use to achieve what I’m trying too. Thanks!

To synchronize the level of the mountain with the level of the meshes, you need to make sure both objects have their bounding spheres at the same position. Basically, it means they both should be at the same world position.

This way, the LOD calculation will be the same in both cases, and when LOD x is used for the mountain, the same LOD x will also be used by the meshes.

Babylon does this already, so it takes actually more time to do it on your end because you are doing a loop over the meshes that could be avoided.

@Evgeni_Popov

To synchronize the level of the mountain with the level of the meshes, you need to make sure both objects have their bounding spheres at the same position. Basically, it means they both should be at the same world position.

But, there if you see all nodes have the same position (0,0,0). I tried checking the world position using mesh.getAbsolutePosition() and it gives the same for all. I tried setting the old level same for the mountain and low level layer but happens the same.

Sorry,

is wrong!

The position of the bounding sphere also depends on how the mesh is constructed.

For eg, if you take mountain.002 and Plane.027_primitive1 which should be lodded the same way because Plane.027_primitive1 appears on top of mountain.002:

image

The easiest way to do it is to set the same bounding info for the elements than the bounding info of the corresponding mountain, something like:

const m1 = scene.getMeshByName("mountain.002");
const e1 = scene.getMeshByName("Plane.027_primitive1");

const b1 = m1.getBoundingInfo();

e1.setBoundingInfo(b1);
3 Likes