1

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 ?

2 Answers 2

1

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/[email protected]/build/three.min.js"></script>

4
  • Thank you. I will definitely use LOD. But I don't like the sudden change of levels, it's very noticeable. Don't know how to make a smooth level change? Commented Apr 17 at 13:12
  • I have updated my answer with more information and snippet. You may try to tweak it and also search of ways to apply some shaders maybe depending on your requirements. Commented Apr 17 at 13:54
  • I tried it and it looks much better, I will use it. Thanks. Commented Apr 18 at 12:52
  • If the answer solved your issue, please mark it as accepted so it helps other users. Commented Apr 18 at 20:26
1

The first step is to identify what the bottleneck is. Instancing and batching are optimizations meant to reduce CPU overhead of draw calls, but your CPU or GPU might be struggling with something different, like: number of vertices, expensive materials, large textures, or heavy overdraw. You could test by drawing simpler models (fewer vertices), cheaper materials (MeshBasicMaterial), or without textures. Don't worry about making the scene look the same when trying things yet - just see what helps performance.

That said... I am surprised if 100 models and 1000 models gave you the same, low FPS. You might also need to check https://webglreport.com/ to see if your device or GPU have known WebGL performance issues, and whether most examples on https://threejs.org/examples/ work smoothly for you.

Performance questions can often be more of a back-and-forth conversation than a question with a clear and general answer ... I'd recommend https://discourse.threejs.org/ if you need more help!

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.