Урок Початковий рівень ⏱ 30 хв · Оновлено в червні 2026

Ваша перша 3D-симуляція за 30 хвилин

Рендерер, сцена, камера, цикл анімації, частинки — усе з порожнього HTML-файлу. Без npm. Без інструментів збірки. Лише посилання на CDN із Three.js r160 і текстовий редактор.

Ваш прогрес 0 / 5 кроків
1
Крок перший

Каркас HTML і CDN Three.js

Створіть новий файл під назвою index.html будь-де на вашому комп'ютері. Сервер не потрібен — браузер може відкрити його безпосередньо з файлової системи.

Вставте цей шаблон. Єдина зовнішня залежність — це Three.js, що постачається з CDN jsDelivr як ES-модуль.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>My First Simulation</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { background: #000; overflow: hidden; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script type="module">
    import * as THREE from
      'https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.min.js';

    // Ваш код буде тут
    console.log('Three.js loaded:', THREE.REVISION);
  </script>
</body>
</html>

Відкрийте файл у сучасному браузері (Chrome, Firefox або Edge). Відкрийте консоль DevTools (F12) — ви маєте побачити "Three.js loaded: 160". Це підтверджує, що імпорт працює.

ES-модулі та file://

Сучасні браузери дозволяють імпорт ES-модулів з CDN навіть тоді, коли ви відкриваєте файл через file://. Якщо ви отримуєте помилку CORS, запустіть невеликий локальний сервер: python -m http.server 8080 або скористайтеся розширенням Live Server для VS Code.

Контрольна точка: у консолі відображається "Three.js loaded: 160". Сторінка чорна й порожня.
2
Крок другий

Сцена, камера і рендерер

Кожній симуляції на Three.js потрібні три речі:

Замініть коментар // Ваш код буде тут на:

index.html — усередині <script type="module">
// ── Сцена ──────────────────────────────────────────────────
const scene = new THREE.Scene();
scene.background = new THREE.Color('#050813'); // темний синьо-чорний

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

// ── Рендерер ────────────────────────────────────────────────
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); // обмежуємо до 2× заради продуктивності
document.body.appendChild(renderer.domElement); // додаємо <canvas> на сторінку

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

// Рендеримо один кадр, щоб підтвердити, що камера бачить темну сцену
renderer.render(scene, camera);

Перезавантажте браузер. Тепер сторінка темно-синя — її відрендерив WebGL. Геометрії ще немає, але конвеєр працює.

Що ви створили

Контекст WebGL 2, перспективну камеру, що стоїть на 5 одиниць позаду вздовж осі Z, і рендерер, який заповнює область перегляду. Чорний кадр — це порожня сцена.

3
Крок третій

Ваша перша геометрія

У Three.js видимі об'єкти називаються мешами (Mesh). Меш поєднує:

Додайте сяючу сферу та просте точкове джерело світла після обробника зміни розміру:

// ── Геометрія ────────────────────────────────────────────────
const geometry = new THREE.SphereGeometry(
  1,    // радіус
  32,   // widthSegments (більше = гладкіше)
  16    // heightSegments
);

const material = new THREE.MeshStandardMaterial({
  color:     0x4f88f8,   // шістнадцятковий колір (синій)
  roughness: 0.3,
  metalness: 0.6,
  emissive:  0x1a2a6c,   // легке власне світіння
});

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

// ── Освітлення ────────────────────────────────────────────────
// MeshStandardMaterial потребує джерел світла, щоб бути видимим
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);

const pointLight = new THREE.PointLight(0x60a5fa, 80, 20);
pointLight.position.set(3, 4, 3);
scene.add(pointLight);

renderer.render(scene, camera); // оновлюємо один статичний кадр
Чому MeshStandardMaterial?

Він використовує фізично коректний рендеринг (PBR) — ту саму модель затінення, що й ігрові рушії та 3D-програми на кшталт Blender. Для швидкого попереднього перегляду без освітлення скористайтеся натомість MeshBasicMaterial (він ігнорує джерела світла).

Перезавантажте сторінку. Ви маєте побачити затінену синю сферу в центрі екрана. Далі ми змусимо її рухатися.

Контрольна точка: у центрі темно-синьої області перегляду з'являється затінена синя сфера, що ледь сяє.
4
Крок четвертий

Цикл анімації

Статичні рендери — це лише початок. Щоб анімувати, ми замінюємо одинарний виклик renderer.render() циклом, керованим requestAnimationFrame.

Чому requestAnimationFrame? Він синхронізує ваш код із частотою оновлення дисплея (зазвичай 60 Гц), автоматично призупиняється, коли вкладку приховано (заощаджуючи заряд батареї), і запобігає розривам зображення.

Видаліть останній рядок renderer.render(scene, camera); і додайте:

// ── Годинник (для анімації на основі dt) ──────────────────────────
const clock = new THREE.Clock();

// ── Цикл анімації ──────────────────────────────────────────
function animate() {
  requestAnimationFrame(animate); // плануємо наступний кадр

  const t = clock.getElapsedTime(); // секунд від старту

  // Обертаємо сферу
  mesh.rotation.y = t * 0.6;
  mesh.rotation.x = t * 0.2;

  // М'яке погойдування вгору-вниз
  mesh.position.y = Math.sin(t) * 0.3;

  renderer.render(scene, camera);
}

animate(); // запускаємо цикл

Тепер сфера безперервно обертається та погойдується вгору-вниз. Зверніть увагу, як clock.getElapsedTime() забезпечує плавний рух, незалежний від частоти кадрів.

Незалежність від частоти кадрів

Використання минулого часу (t) замість простого лічильника гарантує, що швидкість анімації залишається однаковою за 30 fps, 60 fps чи 144 fps. Для фізики ви б використовували дельту (clock.getDelta()), щоб просувати симуляцію на точну кількість мілісекунд, що минула з попереднього кадру.

Контрольна точка: сфера плавно обертається й погойдується на ~60 fps. Без ривків, без блимання.
5
Крок п'ятий

1 000 рухомих частинок

Одна сфера — це іграшковий приклад. Реальним симуляціям потрібні тисячі об'єктів. Створення окремого Mesh для кожної частинки зруйнувало б частоту кадрів, адже кожен меш означає окремий виклик відмалювання на GPU. Рішення — Points: один виклик відмалювання для всіх частинок.

Замініть усе, починаючи з geometry, на:

const COUNT = 1000;
const positions  = new Float32Array(COUNT * 3); // [x,y,z, x,y,z, …]
const velocities = new Float32Array(COUNT * 3); // швидкість кожної частинки

// Засіваємо випадкові позиції в кубі −5 → +5
for (let i = 0; i < COUNT; i++) {
  positions[i * 3    ] = (Math.random() - 0.5) * 10;
  positions[i * 3 + 1] = (Math.random() - 0.5) * 10;
  positions[i * 3 + 2] = (Math.random() - 0.5) * 10;

  velocities[i * 3    ] = (Math.random() - 0.5) * 0.02;
  velocities[i * 3 + 1] = (Math.random() - 0.5) * 0.02;
  velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.02;
}

const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
  'position',
  new THREE.BufferAttribute(positions, 3) // 3 числа float на вершину
);

const material = new THREE.PointsMaterial({
  size:        0.06,
  color:       0x818cf8,   // індиго
  transparent: true,
  opacity:     0.85,
  sizeAttenuation: true     // розмір масштабується з глибиною
});

const points = new THREE.Points(geometry, material);
scene.add(points);

// ── Освітлення (необов'язкове для Points — вони його ігнорують) ──────────
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);

// ── Годинник ───────────────────────────────────────────────────
const clock = new THREE.Clock();

// ── Цикл анімації ──────────────────────────────────────────
function animate() {
  requestAnimationFrame(animate);

  const posArr = geometry.attributes.position.array;

  for (let i = 0; i < COUNT; i++) {
    const xi = i * 3;

    // Інтегруємо позицію
    posArr[xi    ] += velocities[xi    ];
    posArr[xi + 1] += velocities[xi + 1];
    posArr[xi + 2] += velocities[xi + 2];

    // Відскакуємо від стінок куба
    for (let axis = 0; axis < 3; axis++) {
      if (Math.abs(posArr[xi + axis]) > 5) {
        velocities[xi + axis] *= -1; // змінюємо напрямок на протилежний
      }
    }
  }

  // Повідомляємо Three.js, що позиції змінилися — ВАЖЛИВО!
  geometry.attributes.position.needsUpdate = true;

  // Повільно обертаємо всю хмару
  points.rotation.y += 0.001;

  renderer.render(scene, camera);
}

animate();
needsUpdate = true

Після прямої зміни типізованого масиву ви маєте встановити geometry.attributes.position.needsUpdate = true, щоб повідомити рендерер про повторне завантаження буфера на GPU. Якщо про це забути, частинки застигнуть або миготітимуть.

Тепер у вас 1 000 частинок кольору індиго, що відскакують усередині невидимого куба, і все це за один виклик відмалювання. Відкрийте DevTools → Performance, щоб переконатися, що частота кадрів тримається на рівні 60 fps.

🎉
Готово! 1 000 частинок, що відскакують, один виклик відмалювання, ~60 fps. Ви щойно створили свою першу симуляцію!

✨ Подивіться в дії

Перегляньте симуляції, створені саме за цим підходом — з більшою кількістю частинок, фізичними рушіями та шейдерами GLSL.

Симуляція Boids →

Наступні кроки

У вас є робоча симуляція на Three.js. Ось природний шлях подальшого навчання:

Або переходьте одразу до читання статті про SPH-рідини, щоб дізнатися математику, що стоїть за симуляціями частинок.

🛠

Експериментуйте в пісочниці

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

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