javascriptthree.jsgraphics3dgltf

How can I optimize three.js the rendering of a gltf model? I tried 4 ways, but the result is the same


I'm trying to find a way to optimize three.js . I have a gltf tree model with 2144 triangles. I am adding 1000 trees to the scene in different ways.

  1. I clone the tree in a simple loop.
  2. I use InstancedMesh.
  3. Using BatchedMesh
  4. Clone instance tree in Blender and load all tree.

All four methods give the same number of fps. I also tried 100 pieces, 1000 pieces and 5000 pieces of trees. I also tried WebGPURenderer and WebGLRenderer. The result is always about the same. Is there any way to speed up the rendering of gltf models, or is it already maximally optimized when loading into three.js ?


Solution

  • As an addition to above answer, there are several factors can cause to this as you have so many objects and lighting can also affect to this.

    I would use LODs too and depending on how far/near you are to those objects you can add/remove details.

    For simple objects:

    for( let i = 0; i < 3; i++ ) {
        const geometry = new THREE.IcosahedronGeometry( 10, 3 - i );
        const mesh = new THREE.Mesh( geometry, material );
        lod.addLevel( mesh, i * 75 );
    }
    
    scene.add( lod );
    

    If you want to implement it with GLTF models then you may try to create different versions (high/medium/low) of the models and add them to LOD.

    You might also check your lighting setup as lights taking quite some memory.

    Edit

    But I don't like the sudden change of levels, it's very noticeable. Don't know how to make a smooth level change?

    You may apply some shaders or play with opacity values depending on the requirements. I created updateLOD function to demonstrate how you can apply opacity to objects depending on their distance to the camera. This is simple scenario, but you can make it better.

    const cursor = {
      x: 0,
      y: 0,
    }
    const scene = new THREE.Scene()
    const camera = new THREE.PerspectiveCamera(
      60,
      window.innerWidth / window.innerHeight,
      1,
      1000,
    )
    camera.position.set(0, 0, 200)
    
    const renderer = new THREE.WebGLRenderer({
      antialias: true,
    })
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setAnimationLoop(animate)
    document.body.appendChild(renderer.domElement)
    
    const materials = [
      new THREE.MeshBasicMaterial({
        color: 0xff4444,
        transparent: true,
        depthWrite: false,
      }),
      new THREE.MeshBasicMaterial({
        color: 0x44ff44,
        transparent: true,
        depthWrite: false,
      }),
      new THREE.MeshBasicMaterial({
        color: 0x4444ff,
        transparent: true,
        depthWrite: false,
      }),
    ]
    
    const lodObjects = []
    
    for (let i = 0; i < 1000; i++) {
      const lod = new THREE.Object3D()
      const position = new THREE.Vector3(
        (Math.random() - 0.5) * 800,
        (Math.random() - 0.5) * 800,
        (Math.random() - 0.5) * 800,
      )
      lod.position.copy(position)
    
      const lodLevels = []
    
      for (let l = 0; l < 3; l++) {
        const geometry = new THREE.ConeGeometry(5, 20, 32)
        const mesh = new THREE.Mesh(geometry, materials[l].clone())
        mesh.visible = true
        lod.add(mesh)
        lodLevels.push(mesh)
      }
    
      lod.userData.levels = lodLevels
      lodObjects.push(lod)
      scene.add(lod)
    }
    
    function updateLOD() {
      for (const lod of lodObjects) {
        const distance = camera.position.distanceTo(lod.position)
    
        const levels = lod.userData.levels
        const fadeRange = 60
    
        for (let i = 0; i < levels.length; i++) {
          const mesh = levels[i]
          const minDist = i * fadeRange
          const maxDist = (i + 1) * fadeRange
    
          if (distance >= minDist && distance <= maxDist && i < levels.length - 1) {
            // apply crossfade
            const t = (distance - minDist) / (maxDist - minDist)
            mesh.material.opacity = 1 - t
            levels[i + 1].material.opacity = t
    
            mesh.visible = true
            levels[i + 1].visible = true
          } else if (distance < minDist && i === 0) {
            mesh.material.opacity = 1
            mesh.visible = true
          } else if (distance > maxDist && i === levels.length - 1) {
            mesh.material.opacity = 1
            mesh.visible = true
          } else {
            mesh.visible = false
          }
        }
      }
    }
    
    function animate() {
      updateLOD()
      renderer.render(scene, camera)
    }
    
    window.addEventListener("mousemove", () => {
      cursor.y = -event.clientY / window.innerHeight
      camera.position.z = cursor.y * 200
    })
    window.addEventListener("resize", () => {
      camera.aspect = window.innerWidth / window.innerHeight
      camera.updateProjectionMatrix()
      renderer.setSize(window.innerWidth, window.innerHeight)
    })
    <style>
      * {
        margin: 0;
        padding: 0
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>