Базове налаштування
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js';
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // cap at 2x
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0a);
// Camera
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 10);
// Lights
const ambient = new THREE.AmbientLight(0xffffff, 0.5);
const sun = new THREE.DirectionalLight(0xffffff, 2);
sun.position.set(5, 10, 5);
scene.add(ambient, sun);
Адаптивна зміна розміру
Використовуйте ResizeObserver на контейнері канвасу для
точного відстеження розміру; уникайте window.onresize,
який на мобільних спрацьовує під час прокручування.
// ResizeObserver approach — fires only on actual size change
const ro = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height, false); // false = don't override canvas CSS size
});
ro.observe(renderer.domElement.parentElement ?? document.body);
// Alternative: resize on canvas element itself
const ro2 = new ResizeObserver(() => {
const w = renderer.domElement.clientWidth;
const h = renderer.domElement.clientHeight;
if (renderer.domElement.width !== w || renderer.domElement.height !== h) {
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.updateProjectionMatrix();
}
});
ro2.observe(renderer.domElement);
Звільнення памʼяті
Обʼєкти Three.js виділяють памʼять GPU. Завжди звільняйте їх під час видалення обʼєктів зі сцени, щоб запобігти витокам памʼяті.
// Dispose a single mesh completely
function disposeMesh(mesh) {
mesh.geometry.dispose();
// Material can be shared — only dispose if not reused elsewhere
if (Array.isArray(mesh.material)) {
mesh.material.forEach(m => disposeMaterial(m));
} else {
disposeMaterial(mesh.material);
}
mesh.removeFromParent();
}
function disposeMaterial(mat) {
// Dispose all texture maps
for (const key of Object.keys(mat)) {
const val = mat[key];
if (val?.isTexture) val.dispose();
}
mat.dispose();
}
// Dispose entire scene recursively
function disposeScene(scene) {
scene.traverse(obj => {
if (obj.isMesh) disposeMesh(obj);
});
}
// Dispose the renderer itself (call when unmounting)
renderer.dispose();
renderer.forceContextLoss();
renderer.info.memory до та після звільнення,
щоб переконатися, що обʼєкти звільняються. Лічильники
geometries і textures мають зменшуватися.
Пулінг обʼєктів
Створення та знищення Vector3, Matrix4 і
Quaternion усередині гарячих циклів запускає збирання
сміття. Натомість повторно використовуйте їх.
// Pre-allocate temporaries outside loops
const _v = new THREE.Vector3();
const _q = new THREE.Quaternion();
const _m = new THREE.Matrix4();
const _box = new THREE.Box3();
function updateObjects(objects) {
for (const obj of objects) {
// Use _ prefixed temporaries — no allocation inside loop
_v.set(obj.x, obj.y, obj.z);
_box.setFromCenterAndSize(_v, _v.setScalar(obj.size));
obj.mesh.position.copy(_v.set(obj.x, obj.y, obj.z));
}
}
// Pool pattern for mesh instances
class MeshPool {
constructor(geo, mat, maxSize) {
this.im = new THREE.InstancedMesh(geo, mat, maxSize);
this.im.count = 0;
this._free = Array.from({length: maxSize}, (_, i) => i).reverse();
this._active = new Set();
}
acquire() {
const id = this._free.pop();
this._active.add(id);
this.im.count = Math.max(this.im.count, id + 1);
return id;
}
release(id) {
this._active.delete(id);
this._free.push(id);
// Hide the instance by sending it off-screen
_m.makeTranslation(99999, 0, 0);
this.im.setMatrixAt(id, _m);
this.im.instanceMatrix.needsUpdate = true;
}
}
Цикл анімації
let lastTime = 0;
function tick(now) {
const dt = Math.min((now - lastTime) / 1000, 0.1); // seconds, capped at 100ms
lastTime = now;
// 1. Update systems (physics, particles, animations)
updatePhysics(dt);
updateParticles(dt);
controls.update(); // OrbitControls damping
// 2. Render
renderer.render(scene, camera);
// 3. Stats (dev mode only)
// stats.update();
}
// Three.js animation loop (handles XR too)
renderer.setAnimationLoop(tick);
// To stop:
// renderer.setAnimationLoop(null);
renderer.setAnimationLoop() перед
requestAnimationFrame() — він сумісний з WebXR і
автоматично викликає колбек із частотою оновлення дисплея.
Ідіоми продуктивності
geo.attributes.position.needsUpdate = true; geo.attributes.color.needsUpdate = true; geo.computeBoundingSphere(); // needed for correct frustum culling
BufferGeometry і Material — Three.js
опрацьовує інстансовані юніформи. Спільне використання зменшує
перемикання стану GPU.
const geo = new THREE.BoxGeometry();
const mat = new THREE.MeshStandardMaterial({ color: 0xff0000 });
for (let i = 0; i < 1000; i++) {
scene.add(new THREE.Mesh(geo, mat)); // shared geo + mat
}
Points можуть бути помилково
відсічені, якщо обмежувальна сфера застаріла. Встановіть
mesh.frustumCulled = false або завжди викликайте
computeBoundingSphere() після оновлень.
points.frustumCulled = false; // disable culling entirely for dynamic particles
generateMipmaps = false.
const dataTexture = new THREE.DataTexture(buffer, 200, 100); dataTexture.type = THREE.FloatType; dataTexture.format = THREE.RGBAFormat; dataTexture.minFilter = THREE.LinearFilter; dataTexture.generateMipmaps = false; // not needed for data / simulation textures dataTexture.needsUpdate = true;