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.
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 ?
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>