Урок
⏱️ ~40 хвилин 🎓 Початковий рівень 🛠️ HTML · JavaScript · Three.js

Основи Three.js — сцена, камера, рендерер

Three.js абстрагує WebGL у зручний для JS граф сцени. Цей урок крок за кроком проходить кожну ключову концепцію з нуля: налаштування рендерера, типи камер, геометрію, матеріали, освітлення та цикл анімації. Наприкінці ви матимете 3D-меш, який обертається і який ви повністю розумієте.

Необхідні знання

Створення WebGLRenderer

Рендерер володіє елементом <canvas> та контекстом WebGL. Створіть його, задайте розмір і додайте до DOM:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <style> body { margin: 0; overflow: hidden; background: #000; } </style>
</head>
<body>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.168/build/three.module.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(renderer.domElement);
</script>
</body>
</html>

Навіщо setPixelRatio(Math.min(..., 2))? Дисплеї Retina мають devicePixelRatio від 2 до 3. Рендеринг у 3× коштує у 9× більше пікселів заради ледь помітного приросту якості. Обмеження до 2 — хороше значення за замовчуванням.

Сцена та PerspectiveCamera

Кожен проєкт Three.js потребує рівно однієї Scene (кореневого контейнера) і щонайменше однієї камери.

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0f); // темне тло

// PerspectiveCamera(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(
  60,                      // вертикальне поле зору в градусах
  innerWidth / innerHeight, // співвідношення сторін
  0.01,                    // ближня площина відсікання
  1000                     // дальня площина відсікання
);
camera.position.set(0, 1, 5); // відсуваємо камеру назад, щоб бачити початок координат

Площини near/far визначають видимий діапазон глибини. Об'єкти, ближчі за near або дальші за far, відсікаються (невидимі). Уникайте near = 0 — це спричиняє артефакти z-fighting.

Додавання геометрії та матеріалу

Three.js розділяє геометрію (форму: вершини, грані) і матеріал (зовнішній вигляд: колір, блиск, текстуру). Поєднайте їх у Mesh (меш):

// Геометрія: вбудовані примітиви
const geo = new THREE.BoxGeometry(1, 1, 1); // ширина, висота, глибина

// Матеріал: як це виглядає
const mat = new THREE.MeshStandardMaterial({
  color: 0x4488ff,
  metalness: 0.3,
  roughness: 0.6,
});

// Mesh = геометрія + матеріал
const cube = new THREE.Mesh(geo, mat);
scene.add(cube);

Інші корисні примітиви геометрії: SphereGeometry(radius, widthSeg, heightSeg), CylinderGeometry, PlaneGeometry(w, h), TorusGeometry.

Типи матеріалів, які варто знати:

Додавання освітлення

MeshStandardMaterial потребує джерел світла — без них меш повністю чорний:

// Розсіяне світло: рівномірне низькорівневе освітлення всюди
const ambient = new THREE.AmbientLight(0xffffff, 0.4); // колір, інтенсивність
scene.add(ambient);

// Спрямоване світло: паралельні промені (імітує сонце)
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 8, 5);
scene.add(dirLight);

// Точкове світло: всенаправлене, згасає з відстанню
const point = new THREE.PointLight(0x4488ff, 2, 20); // колір, інтенсивність, відстань
point.position.set(-3, 2, 3);
scene.add(point);

Поширене налаштування: одне AmbientLight (низької інтенсивності) + одне DirectionalLight (сильне) — цього достатньо для більшості сцен.

Анімація через requestAnimationFrame

Three.js не має вбудованого циклу — викликайте renderer.render(scene, camera) самостійно кожен кадр за допомогою requestAnimationFrame:

let prevTime = performance.now();

function animate(time) {
  requestAnimationFrame(animate);
  const dt = Math.min((time - prevTime) / 1000, 0.05); // секунди
  prevTime = time;

  // Обертаємо куб з часом
  cube.rotation.x += dt * 0.8;
  cube.rotation.y += dt * 1.2;

  renderer.render(scene, camera);
}

requestAnimationFrame(animate);

Завжди множте рух на dt (дельту часу). Це робить анімацію незалежною від частоти кадрів — вона працює однаково на 30fps, 60fps та 120fps.

Обробка зміни розміру вікна

Без цього зміна розміру вікна браузера розтягує/стискає полотно:

window.addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix(); // обов'язково після зміни співвідношення сторін!
  renderer.setSize(innerWidth, innerHeight);
});

Повний приклад

Усі кроки, об'єднані в один файл, готовий до копіювання:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <style> * { margin:0; } body { overflow: hidden; background: #000; } </style>
</head>
<body>
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.168/build/three.module.js';

// Рендерер
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(renderer.domElement);

// Сцена + камера
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0f);
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 1000);
camera.position.set(0, 1, 5);

// Меш
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshStandardMaterial({ color: 0x4488ff, metalness: 0.3, roughness: 0.6 })
);
scene.add(cube);

// Освітлення
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(5, 8, 5);
scene.add(dirLight);

// Зміна розміру
window.addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

// Анімація
let prevTime = performance.now();
(function animate(time) {
  requestAnimationFrame(animate);
  const dt = Math.min((time - prevTime) / 1000, 0.05);
  prevTime = time;
  cube.rotation.x += dt * 0.8;
  cube.rotation.y += dt * 1.2;
  renderer.render(scene, camera);
})(performance.now());
</script>
</body>
</html>

✅ Що ви побачите

Синій металевий куб, що повільно обертається на темному тлі. Швидкість обертання не залежить від частоти кадрів. Змініть розмір вікна — співвідношення сторін залишається коректним.

Продовжуйте навчання

🛠

Експериментуйте в Playground

Пишіть, запускайте та налаштовуйте код Three.js прямо у браузері — без жодних налаштувань.

Відкрити Playground → Переглянути симуляцію ↗