Level of detail

레벨 오브 디테일

Swapping to a simpler mesh as an object moves farther from the camera, so the GPU never spends effort on detail no one can actually see.

Also known as: LODTHREE.LOD
···
html
<span id="lod-label" class="lod-label">LOD 0 · high</span>
css
.lod-label{position:absolute;left:0;right:0;bottom:5%;text-align:center;font:600 clamp(9px,2.6vmin,13px)/1 ui-monospace,monospace;color:#dfe3ffdd;letter-spacing:.02em}
js
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0d0d16);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 0.6, 2.2);

const mat = new THREE.MeshStandardMaterial({ color: 0x8ecbff, roughness: 0.5 });
const lod = new THREE.LOD();
const levels = [
  { geo: new THREE.IcosahedronGeometry(0.9, 4), dist: 0, label: 'LOD 0 · high' },
  { geo: new THREE.IcosahedronGeometry(0.9, 2), dist: 5, label: 'LOD 1 · medium' },
  { geo: new THREE.IcosahedronGeometry(0.9, 0), dist: 9, label: 'LOD 2 · low' },
];
for (const lv of levels) lod.addLevel(new THREE.Mesh(lv.geo, mat), lv.dist);
scene.add(lod);
const key = new THREE.DirectionalLight(0xffffff, 1.8); key.position.set(3, 4, 5); scene.add(key);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));

const label = document.getElementById('lod-label');
function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
camera.lookAt(0, 0, 0);
renderer.setAnimationLoop((t) => {
  const z = -1.5 - (Math.sin(t * 0.00035) * 0.5 + 0.5) * 9.5;
  lod.position.z = z;
  lod.rotation.y = t * 0.0004;
  lod.update(camera);
  const dist = camera.position.distanceTo(lod.position);
  let idx = 0;
  for (let i = levels.length - 1; i >= 0; i--) { if (dist >= levels[i].dist) { idx = i; break; } }
  label.textContent = levels[idx].label;
  renderer.render(scene, camera);
});

A character 3 meters from the camera and the same character at 300 meters occupy wildly different pixel counts on screen. Computing fine cloth wrinkles on the distant one every frame is wasted work — it’s getting reduced to a few pixels anyway. LOD pre-builds several versions of the same object at different polygon counts (high, medium, low) and swaps between them automatically based on distance.

To keep the switch from popping visibly, some setups cross-fade between two levels over a short distance (LOD blending), but the default behavior is an instant swap. Hiding the transition behind fog or depth of field is another common way to make it go unnoticed.

three.js’s THREE.LOD object takes several meshes registered with their distances, and checks the camera distance every frame to swap automatically. Blender, Maya, and game engines like Unity and Unreal call the same idea LOD groups or proxy meshes — it pays off most in scenes with lots of objects: terrain, crowds, open worlds.

When to use

Use it to protect frame rate in scenes where many copies of the same object appear at varying distances — terrain, crowds, open worlds.