Michael Watson
Michael Watson

Reputation: 244

Three.js performance issue on first render of mesh - preload options?

I have a project with 100 BoxGeometries, each with their own image inside (around 10kb). I have noticed performance problems and frames skipping on mobile devices when boxes are first rendered, which I'd like to eliminate.

At the moment I load up all images first, and create textures, before adding the boxes:

let loader = new THREE.TextureLoader()

for(let img of assets) {

    loader.load(img.url, texture => {

        textures[img.name] = texture 
        assets.splice(assets.indexOf(img),1)
        if(!assets.length) {
            addBoxes()
        }

    })

}

I then lay out 100 boxes in a grid, here's some pseudo-code to illustrate:

textures.forEach((texture,i) => {

    let box = new THREE.Mesh(
        new THREE.BoxGeometry(1, .04, 1),
        [
          blackMaterial,
          blackMaterial,
          new THREE.MeshBasicMaterial({
                map: Controller.textures[boxMaterial.postID]
            }),
          blackMaterial,
          blackMaterial,
          blackMaterial
          ]
    )

    box.position.x = (i % 10 - 5)
    box.position.z = (Math.floor(i / 10) - 5)

    scene.add( box )

})

requestAnimationFrame( step )

I have a THREE.OrthographicCamera that can pan and zoom around these boxes. I have noticed that when they first come into view, they cause memory to spike, but once all boxes have been seen, the net heap falls down drastically, and performance becomes smooth and no frame rates are dropped.

Please note that after 6 seconds memory suddenly flattens out, this is once all boxes have been seen once:

Please note that after 6 seconds memory suddenly flattens out, this is once all boxes have been seen once

To combat this, I have attempted the frustrumCulled parameter on the boxes:

box.frustumCulled = false

This solves the issue in some ways. Once loaded, performance is extremely smooth from the start and the memory issues are gone. However, I do not seem to have a way to detect once all the meshes are loaded, so initial load is slow and an intro animation I have, and early interactions are jagged and performance intensive as they are starting too early.

I understand that loading all boxes with eager loading will cause a larger load time, and this would be fine for this project, to avoid the memory issues through lazyloading. However, what other options do I have? Perhaps box.frustumCulled isn't the right approach.

And is there a way to have event listeners on such loading activity? Ideally I would load all boxes proper, as if they had been seen once, with a preloader, and when the system was ready, I could fire an init method.

Upvotes: 0

Views: 562

Answers (1)

TheJim01
TheJim01

Reputation: 8896

A few ideas:

1. Share geometry

You're using all cubes, so they can share their geometry definition. Even if you want them to be different sizes, you can apply a scaling transformation to the mesh later.

let boxGeo = new THREE.BoxGeometry(1, .04, 1)
textures.forEach((texture,i) => {
    let box = new THREE.Mesh(
        boxGeo,
        [
          blackMaterial,
          blackMaterial,
          new THREE.MeshBasicMaterial({
                map: Controller.textures[boxMaterial.postID]
            }),
          blackMaterial,
          blackMaterial,
          blackMaterial
          ]
    )

Now, your program only needs to upload one geometry definition to the GPU rather than however many you created for each texture.

2. Render before textures

This is a shot in the dark, but try creating your cubes up-front, with a transparent material, and apply your textures later. My thought is that getting the upload of the geometry out of the way up front will shave some time off your initial render.

3. Instances

I'm not up-to-date on how three.js handles instanced materials, but you might be able to use InstancedMesh to create your cubes and increase even your overall rendering performance.

Upvotes: 1

Related Questions