Three.js · Астрономія
⏱ ~60 хв🟡 Середній рівеньThree.js r160 · WebGL

Створіть 3D Сонячну систему у Three.js

Текстуровані планети, кеплерівські орбіти, емісивне Сонце, кільця та постобробка UnrealBloom — усе з'єднано в одному HTML-файлі з Three.js r160.

1

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

Почніть зі стандартного шаблону Three.js — WebGLRenderer, що заповнює вікно, PerspectiveCamera та чорне тло, що імітує космос.

import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js'; import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/controls/OrbitControls.js'; import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/postprocessing/EffectComposer.js'; import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/postprocessing/UnrealBloomPass.js'; const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(devicePixelRatio); renderer.setSize(innerWidth, innerHeight); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 0.9; document.body.appendChild(renderer.domElement); const scene = new THREE.Scene(); scene.background = new THREE.Color(0x000008); // Зоряне поле — 10 000 випадкових точок const starGeo = new THREE.BufferGeometry(); const starPos = new Float32Array(30_000); for (let i = 0; i < 30_000; i++) starPos[i] = (Math.random() - 0.5) * 2000; starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3)); scene.add(new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 0.35 }))); const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 5000); camera.position.set(0, 80, 200); window.addEventListener('resize', () => { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); });
Установіть renderer.toneMapping = THREE.ACESFilmicToneMapping — це підсилює контраст середніх тонів і саме це використовує більшість ігор. Без цього bloom виглядає вицвілим.
2

Сонце з емісивним сяйвом

Сонце випромінює власне світло. Використайте PointLight у початку координат і MeshStandardMaterial з високим emissiveIntensity, щоб воно залишалося яскравим незалежно від інших джерел світла.

const loader = new THREE.TextureLoader(); // Сонце const sunTex = loader.load('https://cdn.jsdelivr.net/gh/mrdoob/three.js@r160/examples/textures/planets/sun.jpg'); const sunMat = new THREE.MeshStandardMaterial({ map: sunTex, emissiveMap: sunTex, emissive: new THREE.Color(1.0, 0.7, 0.1), emissiveIntensity: 2.5, }); const sun = new THREE.Mesh(new THREE.SphereGeometry(16, 32, 32), sunMat); scene.add(sun); // Світло від Сонця const sunLight = new THREE.PointLight(0xfff4cc, 4, 2000, 1.5); scene.add(sunLight); // Розсіяне світло для підсвічування scene.add(new THREE.AmbientLight(0x111122, 1.5));
map та emissiveMap використовують одну й ту саму текстуру. Емісивний канал змушує поверхню світитися незалежно від точкового джерела світла — це важливо, коли камера далеко від джерела світла.
3

Текстуровані планети та кільце Сатурна

Кожна планета — це SphereGeometry, згрупована зі своїм орбітальним опорним об'єктом (pivot), щоб обертання відбувалося навколо початку координат Сонця.

// Дані планет: [ name, radius(scene), texturePath, axialTilt, ringInner?, ringOuter? ] const PLANETS = [ { name:'Mercury', r:2.2, sma:28, period:0.24, tilt:0.034, tex:'/assets/textures/mercury.jpg' }, { name:'Venus', r:5.4, sma:52, period:0.62, tilt:177.4, tex:'/assets/textures/venus.jpg' }, { name:'Earth', r:5.7, sma:80, period:1.00, tilt:23.4, tex:'/assets/textures/earth.jpg' }, { name:'Mars', r:3.0, sma:112, period:1.88, tilt:25.2, tex:'/assets/textures/mars.jpg' }, { name:'Jupiter', r:11, sma:175, period:11.86, tilt:3.1, tex:'/assets/textures/jupiter.jpg' }, { name:'Saturn', r:9.5, sma:245, period:29.46, tilt:26.7, tex:'/assets/textures/saturn.jpg', ringInner:11.5, ringOuter:20 }, { name:'Uranus', r:7, sma:315, period:84.01, tilt:97.8, tex:'/assets/textures/uranus.jpg' }, { name:'Neptune', r:6.8, sma:375, period:164.8, tilt:28.3, tex:'/assets/textures/neptune.jpg' }, ]; const planets = PLANETS.map(p => { const pivot = new THREE.Object3D(); scene.add(pivot); const mesh = new THREE.Mesh( new THREE.SphereGeometry(p.r, 32, 32), new THREE.MeshStandardMaterial({ map: loader.load(p.tex), roughness:0.9 }) ); mesh.rotation.z = THREE.MathUtils.degToRad(p.tilt); mesh.position.x = p.sma; pivot.add(mesh); if (p.ringInner) { const ringGeo = new THREE.RingGeometry(p.ringInner, p.ringOuter, 64); // UV-координати RingGeometry треба виправити для текстури — переприв'язуємо v до радіальної відстані const pos = ringGeo.attributes.position; const uv = ringGeo.attributes.uv; for (let i = 0; i < pos.count; i++) { const dist = Math.sqrt(pos.getX(i)**2 + pos.getZ(i)**2); uv.setXY(i, (dist - p.ringInner)/(p.ringOuter - p.ringInner), 0); } const ring = new THREE.Mesh(ringGeo, new THREE.MeshBasicMaterial({ map: loader.load('/assets/textures/saturn-rings.png'), side: THREE.DoubleSide, transparent:true, opacity:0.85 })); ring.rotation.x = Math.PI / 2.2; ring.position.x = p.sma; pivot.add(ring); } return { pivot, mesh, ...p }; });
4

Кеплерівські орбітальні шляхи

Намалюйте еліптичні лінії орбіт і анімуйте планети, використовуючи період Кеплера. Масштабуйте 1 земний рік до 1 реальної секунди, щоб Сонячна система завжди рухалася.

// Малюємо еліпс орбіти (коловий для простоти — задайте ексцентриситет для точності) function addOrbitLine(sma) { const curve = new THREE.EllipseCurve(0, 0, sma, sma, 0, Math.PI * 2); const pts = curve.getPoints(256).map(p => new THREE.Vector3(p.x, 0, p.y)); const geo = new THREE.BufferGeometry().setFromPoints(pts); scene.add(new THREE.Line(geo, new THREE.LineBasicMaterial({ color: 0x334155, transparent:true, opacity:0.5 }))); } planets.forEach(p => addOrbitLine(p.sma)); // Цикл анімації const YEAR_SECS = 1.0; // 1 земний рік = 1 секунда екранного часу let last = performance.now(); function animate() { requestAnimationFrame(animate); const now = performance.now(); const dt = Math.min((now - last) / 1000, 0.05); last = now; planets.forEach(p => { // Кутова швидкість: ω = 2π / T (T у роках) p.pivot.rotation.y += (Math.PI * 2 / p.period) * dt * YEAR_SECS; p.mesh.rotation.y += dt * 0.5; // обертання навколо власної осі }); sun.rotation.y += dt * 0.1; controls.update(); composer.render(); } animate();
Третій закон Кеплера: T² ∝ a³. Співвідношення періодів закладене у p.period (у земних роках). Множте на YEAR_SECS, щоб керувати швидкістю анімації.
5

OrbitControls, підписи та повзунок швидкості

Додайте OrbitControls для навігації мишею та простий HTML-оверлей з повзунком швидкості й назвами планет.

const controls = new OrbitControls(camera, renderer.domElement); controls.minDistance = 20; controls.maxDistance = 1500; controls.enableDamping = true; controls.dampingFactor = 0.05; // --- Інтерфейс повзунка швидкості --- const slider = document.createElement('input'); slider.type = 'range'; slider.min = '0'; slider.max = '5'; slider.step = '0.01'; slider.value = '1'; Object.assign(slider.style, { position:'fixed', bottom:'24px', left:'50%', transform:'translateX(-50%)', width:'200px', accentColor:'#22c55e', zIndex:'10' }); document.body.appendChild(slider); // --- Підписи за допомогою CSS2DRenderer --- import { CSS2DRenderer, CSS2DObject } from 'https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/renderers/CSS2DRenderer.js'; const labelRenderer = new CSS2DRenderer(); labelRenderer.setSize(innerWidth, innerHeight); Object.assign(labelRenderer.domElement.style, { position:'fixed',top:'0',left:'0',pointerEvents:'none' }); document.body.appendChild(labelRenderer.domElement); planets.forEach(p => { const div = document.createElement('div'); div.textContent = p.name; div.style.cssText = 'color: var(--color-muted);font-size:11px;font-family:sans-serif;text-shadow:0 1px 3px #000'; const label = new CSS2DObject(div); label.position.set(0, p.r + 2, 0); p.mesh.add(label); });
6

Постобробка UnrealBloom

З'єднайте EffectComposer з UnrealBloomPass, щоб Сонце та інші яскраві об'єкти отримали м'який ореол сяйва.

const composer = new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); const bloom = new UnrealBloomPass( new THREE.Vector2(innerWidth, innerHeight), 1.2, // сила 0.4, // радіус 0.85 // поріг — світяться лише пікселі, яскравіші за це ); composer.addPass(bloom); // Обробляємо зміну розміру вікна також для композера window.addEventListener('resize', () => { composer.setSize(innerWidth, innerHeight); }); // Замініть renderer.render() на composer.render() у циклі // (вже зроблено на Кроці 4 — composer.render() всередині animate()) // --- Порада: вибірковий bloom --- // Щоб світилося лише Сонце (а не тьмяні планети): // 1. Рендеримо сцену двічі — раз з bloom, раз чисто // 2. Використовуємо шари: sun.layers.enable(1); прохід bloom рендерить лише шар 1 // Див. приклад selective-bloom у Three.js для повного налаштування
Поріг має значення: установіть threshold близько 1.0, щоб його перевищувало лише емісивне Сонце. Якщо задати 0, уся сцена світиться й усе виглядає туманно. Поєднуйте з ACESFilmicToneMapping для найкращого результату.

Часті запитання

Чого я навчуся в цьому уроці?

Покроковий урок: створіть інтерактивну 3D Сонячну систему у Three.js з текстурованими планетами, кеплерівськими орбітами, OrbitControls та постобробкою UnrealBloom.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Сцена, рендерер та камера, Сонце з емісивним сяйвом, Текстуровані планети та кільце Сатурна, Кеплерівські орбітальні шляхи, OrbitControls, підписи та інтерфейс, Постобробка UnrealBloom.

Які інструменти й технології використовуються?

Цей урок використовує: WebGL.

Скільки часу займає цей урок?

Цей урок займає приблизно 60 хв.