Files
english/.opencode/skills/threejs/references/12-performance.md
2026-04-12 01:06:31 +07:00

6.5 KiB

Performance Optimization

Techniques for fast, smooth 3D experiences.

Instancing

Render many copies of same geometry efficiently:

// Instead of creating 10,000 individual meshes
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const mesh = new THREE.InstancedMesh(geometry, material, 10000);

// Set transforms for each instance
const matrix = new THREE.Matrix4();
const position = new THREE.Vector3();
const rotation = new THREE.Euler();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3(1, 1, 1);

for (let i = 0; i < 10000; i++) {
  position.set(
    Math.random() * 100 - 50,
    Math.random() * 100 - 50,
    Math.random() * 100 - 50
  );

  rotation.set(
    Math.random() * Math.PI,
    Math.random() * Math.PI,
    Math.random() * Math.PI
  );

  quaternion.setFromEuler(rotation);
  matrix.compose(position, quaternion, scale);
  mesh.setMatrixAt(i, matrix);
}

mesh.instanceMatrix.needsUpdate = true;
scene.add(mesh);

// Per-instance colors
mesh.instanceColor = new THREE.InstancedBufferAttribute(
  new Float32Array(10000 * 3),
  3
);

for (let i = 0; i < 10000; i++) {
  mesh.setColorAt(i, new THREE.Color(Math.random(), Math.random(), Math.random()));
}

Level of Detail (LOD)

Switch between detail levels based on distance:

const lod = new THREE.LOD();

// High detail (close)
const geometryHigh = new THREE.IcosahedronGeometry(10, 4);
const meshHigh = new THREE.Mesh(geometryHigh, material);
lod.addLevel(meshHigh, 0);

// Medium detail
const geometryMed = new THREE.IcosahedronGeometry(10, 2);
const meshMed = new THREE.Mesh(geometryMed, material);
lod.addLevel(meshMed, 50);

// Low detail (far)
const geometryLow = new THREE.IcosahedronGeometry(10, 0);
const meshLow = new THREE.Mesh(geometryLow, material);
lod.addLevel(meshLow, 100);

scene.add(lod);

// Update LOD in animation loop
function animate() {
  lod.update(camera);
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Frustum Culling

Automatic - objects outside camera view aren't rendered.

// Force disable for specific object
object.frustumCulled = false;

// Manually test if in view
const frustum = new THREE.Frustum();
const cameraViewProjectionMatrix = new THREE.Matrix4();
cameraViewProjectionMatrix.multiplyMatrices(
  camera.projectionMatrix,
  camera.matrixWorldInverse
);
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);

if (frustum.intersectsObject(object)) {
  // Object is visible
}

Geometry Optimization

// Merge geometries (reduce draw calls)
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';

const geometries = [geom1, geom2, geom3];
const mergedGeometry = mergeGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);

// Dispose old geometries
geometries.forEach(g => g.dispose());

// Simplify geometry
import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js';

const modifier = new SimplifyModifier();
const simplified = modifier.modify(geometry, Math.floor(geometry.attributes.position.count * 0.5));

Texture Optimization

// Use appropriate sizes (power of 2)
// 512x512, 1024x1024, 2048x2048

// Compress textures
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';

// Use mipmaps (auto-generated by default)
texture.generateMipmaps = true;

// Appropriate filtering
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.magFilter = THREE.LinearFilter;

// Anisotropic filtering (balance quality/performance)
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

// Dispose unused textures
texture.dispose();

Material Sharing

// Share materials between meshes (reduce memory)
const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });

const mesh1 = new THREE.Mesh(geometry1, sharedMaterial);
const mesh2 = new THREE.Mesh(geometry2, sharedMaterial);
const mesh3 = new THREE.Mesh(geometry3, sharedMaterial);

Shadow Optimization

// Reduce shadow map resolution
light.shadow.mapSize.width = 1024;  // instead of 2048
light.shadow.mapSize.height = 1024;

// Limit shadow camera frustum
light.shadow.camera.near = 0.5;
light.shadow.camera.far = 50;  // only cast shadows within this range
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;

// Use fewer shadow-casting objects
object.castShadow = false; // for distant/small objects
object.receiveShadow = false; // for objects that don't need shadows

// Cheaper shadow type
renderer.shadowMap.type = THREE.PCFShadowMap; // instead of PCFSoftShadowMap

Render Target Optimization

// Lower resolution for post-processing
const renderTarget = new THREE.WebGLRenderTarget(
  window.innerWidth * 0.5,  // half resolution
  window.innerHeight * 0.5
);

// Appropriate pixel format
renderTarget.texture.format = THREE.RGBAFormat;
renderTarget.texture.type = THREE.UnsignedByteType;

// Dispose when done
renderTarget.dispose();

Object Pooling

// Reuse objects instead of creating/destroying
class ObjectPool {
  constructor(factory, initialSize) {
    this.factory = factory;
    this.pool = [];
    for (let i = 0; i < initialSize; i++) {
      this.pool.push(factory());
    }
  }

  get() {
    return this.pool.length > 0 ? this.pool.pop() : this.factory();
  }

  release(obj) {
    this.pool.push(obj);
  }
}

const bulletPool = new ObjectPool(() => {
  return new THREE.Mesh(bulletGeometry, bulletMaterial);
}, 100);

// Use
const bullet = bulletPool.get();
scene.add(bullet);

// Return when done
scene.remove(bullet);
bulletPool.release(bullet);

Monitoring Performance

// FPS counter
const stats = new Stats();
document.body.appendChild(stats.dom);

function animate() {
  stats.begin();
  // ... rendering
  stats.end();
  requestAnimationFrame(animate);
}

// Renderer info
console.log(renderer.info);
// Shows: geometries, textures, programs, calls, triangles, points, lines

// GPU timing
const query = renderer.extensions.get('EXT_disjoint_timer_query_webgl2');

General Best Practices

  • Limit draw calls (merge geometries, use instancing)
  • Reduce polygon count (LOD, simplification)
  • Optimize textures (compression, appropriate sizes)
  • Share materials and geometries
  • Use frustum culling
  • Limit number of lights (3-5 max)
  • Avoid transparent materials when possible
  • Use object pooling for frequently created/destroyed objects
  • Profile with browser DevTools
  • Test on target devices
  • Use WebGL 2 features when available