Основи Three.js — сцена, камера, рендерер
Three.js абстрагує WebGL у зручний для JS граф сцени. Цей урок крок за кроком проходить кожну ключову концепцію з нуля: налаштування рендерера, типи камер, геометрію, матеріали, освітлення та цикл анімації. Наприкінці ви матимете 3D-меш, який обертається і який ви повністю розумієте.
-
Базові HTML та JavaScript (змінні, функції,
const/let) - Знайомство з декартовими координатами (x, y, z)
- Попередні знання Three.js чи WebGL не потрібні
Створення 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.
Типи матеріалів, які варто знати:
-
MeshBasicMaterial— без освітлення. Завжди однаковий колір. Підходить для налагодження/каркасів. -
MeshStandardMaterial— PBR (metalness/roughness). Потребує джерел світла. -
MeshPhongMaterial— старіше затінення Блінна-Фонга. Швидке, менш точне. Потребує джерел світла. -
ShaderMaterial— повністю власний вершинний + фрагментний шейдер GLSL.
Додавання освітлення
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>
✅ Що ви побачите
Синій металевий куб, що повільно обертається на темному тлі. Швидкість обертання не залежить від частоти кадрів. Змініть розмір вікна — співвідношення сторін залишається коректним.