Three.js Project — Build a 3D Solar System Explorer
In this tutorial, you will learn about Three.js Project. We cover key concepts, practical examples, and best practices to help you master this topic.
This Three.js project tutorial builds a solar system explorer with orbiting planets, click interaction, info panels, and optimized rendering using all major Three.js concepts.
What You'll Learn
By the end of this project, you will apply scene graph hierarchies for orbital motion, PBR materials for realistic surfaces, multiple light types, raycaster for planet selection, animation loops with Clock, and performance optimization with instancing for star fields.
Project Overview
The solar system explorer lets users orbit a camera around the system, click planets to see information, watch realistic orbital motion with different speeds per planet, and explore a star field background.
flowchart TD
A[Scene Setup] --> B[Sun with PointLight]
B --> C[Planet Orbit Groups]
C --> D[Each Planet: Mesh + Label + Orbit Path]
D --> E[Animation Loop]
E --> F[Raycaster Interaction]
F --> G[Info Panel Update]
A --> H[Star Field Background]
Step 1: Scene and Camera Setup
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x000008);
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 20, 30);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
Expected output: A dark space background with a camera positioned to see the entire solar system.
Step 2: Star Field With Points
var starCount = 10000;
var starPositions = new Float32Array(starCount * 3);
var starColors = new Float32Array(starCount * 3);
var color = new THREE.Color();
for (var i = 0; i < starCount; i++) {
var radius = 200 + Math.random() * 300;
var theta = Math.random() * Math.PI * 2;
var phi = Math.acos(2 * Math.random() - 1);
starPositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
starPositions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
starPositions[i * 3 + 2] = radius * Math.cos(phi);
color.setHSL(0.6 + Math.random() * 0.3, 0.5, 0.5 + Math.random() * 0.5);
starColors[i * 3] = color.r;
starColors[i * 3 + 1] = color.g;
starColors[i * 3 + 2] = color.b;
}
var starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
starGeo.setAttribute('color', new THREE.BufferAttribute(starColors, 3));
var starMat = new THREE.PointsMaterial({
size: 0.5,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
var stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
Step 3: Solar System Hierarchy
var solarSystem = new THREE.Group();
scene.add(solarSystem);
// Sun
var sunMat = new THREE.MeshBasicMaterial({ color: 0xffdd44 });
var sun = new THREE.Mesh(new THREE.SphereGeometry(3, 48, 48), sunMat);
solarSystem.add(sun);
var sunLight = new THREE.PointLight(0xffffff, 2, 100);
solarSystem.add(sunLight);
// Planet data
var planetData = [
{ name: 'Mercury', size: 0.4, distance: 5, color: 0xaaaaaa, speed: 4 },
{ name: 'Venus', size: 0.7, distance: 8, color: 0xe8cda0, speed: 2.5 },
{ name: 'Earth', size: 0.8, distance: 11, color: 0x4488ff, speed: 2 },
{ name: 'Mars', size: 0.5, distance: 14, color: 0xcc5544, speed: 1.5 },
{ name: 'Jupiter', size: 1.8, distance: 18, color: 0xd4a574, speed: 0.8 },
{ name: 'Saturn', size: 1.5, distance: 22, color: 0xe8d5a0, speed: 0.6 },
{ name: 'Uranus', size: 1.0, distance: 26, color: 0x88ccff, speed: 0.4 },
{ name: 'Neptune', size: 0.9, distance: 30, color: 0x3366ff, speed: 0.3 }
];
var planets = [];
planetData.forEach(function(data) {
var orbitGroup = new THREE.Group();
solarSystem.add(orbitGroup);
var planet = new THREE.Mesh(
new THREE.SphereGeometry(data.size, 32, 32),
new THREE.MeshStandardMaterial({
color: data.color,
roughness: 0.7,
metalness: 0.1
})
);
planet.position.x = data.distance;
planet.userData = { name: data.name, info: data.name + ' planet' };
orbitGroup.add(planet);
planets.push({
mesh: planet,
orbitGroup: orbitGroup,
speed: data.speed,
data: data
});
});
Step 4: Complete Project
<!DOCTYPE html>
<html>
<head>
<title>Solar System Explorer</title>
<style>
body { margin: 0; overflow: hidden; background: #000; font-family: sans-serif; }
#info-panel { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.85); color: white; padding: 15px 25px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); min-width: 250px; text-align: center; pointer-events: none; }
#info-panel h3 { margin: 0 0 4px; color: #ffdd44; }
#info-panel p { margin: 0; font-size: 13px; color: #aaa; }
#hint { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); color: rgba(255,255,255,0.5); font-size: 14px; }
</style>
</head>
<body>
<div id="hint">Click a planet for information</div>
<div id="info-panel"><h3>Solar System Explorer</h3><p>Click on any planet to learn more</p></div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x000008);
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 20, 35);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
var controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
var ambient = new THREE.AmbientLight(0x111122);
scene.add(ambient);
var stars = new THREE.Points(
new THREE.BufferGeometry(),
new THREE.PointsMaterial({ color: 0xffffff, size: 0.3 })
);
var starPos = new Float32Array(6000 * 3);
for (var i = 0; i < 6000; i++) {
var r = 200 + Math.random() * 300;
var th = Math.random() * Math.PI * 2;
var ph = Math.acos(2 * Math.random() - 1);
starPos[i * 3] = r * Math.sin(ph) * Math.cos(th);
starPos[i * 3 + 1] = r * Math.sin(ph) * Math.sin(th);
starPos[i * 3 + 2] = r * Math.cos(ph);
}
stars.geometry.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
scene.add(stars);
var solarSystem = new THREE.Group();
scene.add(solarSystem);
var sunMat = new THREE.MeshBasicMaterial({ color: 0xffdd44 });
var sun = new THREE.Mesh(new THREE.SphereGeometry(3, 48, 48), sunMat);
solarSystem.add(sun);
var sunGlow = new THREE.PointLight(0xffcc44, 2, 60);
solarSystem.add(sunGlow);
var planets = [];
var planetData = [
{ name: 'Mercury', sz: 0.35, dist: 5, col: 0xaaaaaa, spd: 4 },
{ name: 'Venus', sz: 0.6, dist: 8, col: 0xe8cda0, spd: 2.5 },
{ name: 'Earth', sz: 0.7, dist: 11, col: 0x4488ff, spd: 2 },
{ name: 'Mars', sz: 0.45, dist: 14, col: 0xcc5544, spd: 1.5 },
{ name: 'Jupiter', sz: 1.6, dist: 18, col: 0xd4a574, spd: 0.8 },
{ name: 'Saturn', sz: 1.3, dist: 22, col: 0xe8d5a0, spd: 0.6 },
{ name: 'Uranus', sz: 0.9, dist: 26, col: 0x88ccff, spd: 0.4 },
{ name: 'Neptune', sz: 0.8, dist: 30, col: 0x3366ff, spd: 0.3 }
];
planetData.forEach(function(d) {
var group = new THREE.Group();
solarSystem.add(group);
var mat = new THREE.MeshStandardMaterial({ color: d.col, roughness: 0.6, metalness: 0.1 });
var mesh = new THREE.Mesh(new THREE.SphereGeometry(d.sz, 24, 24), mat);
mesh.position.x = d.dist;
mesh.userData = { name: d.name };
group.add(mesh);
planets.push({ mesh: mesh, group: group, speed: d.spd });
var orbitPoints = [];
for (var a = 0; a <= 64; a++) {
var angle = (a / 64) * Math.PI * 2;
orbitPoints.push(new THREE.Vector3(Math.cos(angle) * d.dist, 0, Math.sin(angle) * d.dist));
}
var orbitLine = new THREE.Line(
new THREE.BufferGeometry().setFromPoints(orbitPoints),
new THREE.LineBasicMaterial({ color: 0x333355, transparent: true, opacity: 0.3 })
);
solarSystem.add(orbitLine);
});
var infoPanel = document.getElementById('info-panel');
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
window.addEventListener('click', function(e) {
mouse.x = (e.clientX / innerWidth) * 2 - 1;
mouse.y = -(e.clientY / innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var meshes = planets.map(function(p) { return p.mesh; });
var hits = raycaster.intersectObjects(meshes);
if (hits.length > 0) {
var name = hits[0].object.userData.name;
infoPanel.innerHTML = '<h3>' + name + '</h3><p>Distance from Sun: ' +
hits[0].object.position.length().toFixed(1) + ' units</p>';
}
});
var clock = new THREE.Clock();
function animate() {
var t = clock.getElapsedTime();
planets.forEach(function(p) {
p.group.rotation.y = t * p.speed * 0.2;
});
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
window.addEventListener("resize", function() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html>
What's Next
Explore D3.js for data-driven document visualization.
Selections — D3.js selections and data binding. Transitions — Animated transitions in D3.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro