Skip to main content
<CodeWithBhurtel/>
HomeLearnProjectsChallengesBlogAboutContactLogin
<CodeWithBhurtel/>

Learn through structured lessons, real projects, and live challenges. Free tutorials for HTML, CSS, JavaScript, and more.

Pages

  • Home
  • Learn
  • Blog
  • Projects
  • Challenges
  • About
  • Contact

Lessons

  • HTML
  • CSS
  • JavaScript

© 2026 CodeWithBhurtel. All rights reserved.

Privacy PolicyTerms & Conditions
Projects

Space Work: Interactive Three js

AdvancedHTML

Advanced. This project assumes you're already comfortable with core Three.js concepts like scenes, cameras, and materials, and it requires a working knowledge of shader programming in GLSL.

Space Work: Interactive Three js – project preview
Share this project
Follow me on social media

About this project

This project renders a fully interactive black hole simulation in the browser using Three.js and custom GLSL shaders. At the center sits a pure black event horizon surrounded by a glowing orange rim light shader that simulates light bending around the singularity. Around it, an accretion disk made of five thousand instanced particles orbits according to a simplified gravitational velocity model, with particles closer to the core moving faster than those farther out. A vertex shader handles the orbital motion, radial compression, and organic turbulence using simplex noise, while a fragment shader calculates a Doppler beaming effect that shifts colors between cool blue, warm orange, and hot white depending on whether the material is moving toward or away from the camera. The scene cycles automatically through three distinct states using GSAP animations, moving from a calm stable singularity into turbulent accretion and finally into a violent relativistic collapse, with the camera, rotation speed, and disk intensity all transitioning smoothly between each state.

What you will learn

Working through this project teaches you how to use InstancedMesh in Three.js to efficiently render thousands of objects without tanking performance. You'll get hands on experience writing custom vertex and fragment shaders in GLSL, including how to pass uniforms from JavaScript into your shaders and animate them over time. The project also covers simplex noise functions for generating organic, natural looking motion, and how to implement a Doppler shift effect by comparing an object's orbital direction against the camera's view direction. On the animation side, you'll learn how GSAP timelines can orchestrate complex multi property transitions across materials, camera position, and orbit controls simultaneously. You'll also pick up techniques for rim lighting shaders, additive blending for glow effects, and structuring a real time WebGL scene with OrbitControls for user interaction.

Understand the code

Read a plain-language overview or a file-by-file breakdown. Ask questions about the project below the overview.

This project creates an interactive 3D visualization of a celestial phenomenon, resembling a black hole or singularity surrounded by a dynamic accretion disk. Its primary purpose is to showcase a complex, animated space environment with a futuristic user interface (HUD). Key features include a central dark sphere, a glowing aura, thousands of orbiting particles forming a turbulent disk, and an interactive camera that allows users to explore the scene. The visualization also cycles through different "states" (e.g., "Stable Singularity," "Accretion Turbulence"), each with distinct visual characteristics and updated on-screen information.

The core of the project is built using the Three.js library for 3D rendering. It initializes a Scene, a PerspectiveCamera to view the scene, and a WebGLRenderer to display it on a canvas. User interaction is enabled through OrbitControls, which allow the camera to rotate around the central object and include an autoRotate feature for continuous motion.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(60, 30, 60);
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
// ...
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.autoRotate = true;

This snippet sets up the fundamental Three.js components: the scene where all 3D objects reside, the camera for viewing, the renderer to draw to the screen, and interactive orbit controls.

The central object consists of a dark SphereGeometry representing the black hole, and a slightly larger sphere with a custom ShaderMaterial that creates a glowing aura. This aura shader uses a rim lighting effect, where the glow is more intense at the edges of the sphere as viewed by the camera, giving it a volumetric appearance.

const bhMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
const bhGeo = new THREE.SphereGeometry(4, 64, 64);
coreGroup.add(new THREE.Mesh(bhGeo, bhMat));

const auraMat = new THREE.ShaderMaterial({
    uniforms: { uTime: { value: 0 }, uIntensity: { value: 1.0 } },
    vertexShader: `...`,
    fragmentShader: `
        uniform float uIntensity;
        varying vec3 vNormal;
        varying vec3 vView;
        void main() {
            float rim = pow(1.0 - max(dot(vNormal, vView), 0.0), 4.0);
            gl_FragColor = vec4(vec3(1.0, 0.45, 0.1) * rim * uIntensity * 5.0, 1.0);
        }
    `,
    side: THREE.BackSide, transparent: true, blending: THREE.AdditiveBlending
});

This code defines the central black hole as a dark sphere and an outer glowing aura using a custom shader that calculates a rim lighting effect based on the camera's view.

The accretion disk is a highly optimized InstancedMesh composed of 5000 small CylinderGeometry "streaks." Each streak's initial position and orientation are randomized to form a disk shape. A sophisticated ShaderMaterial controls the appearance and motion of these instances. The vertex shader calculates orbital paths, applies Simplex noise for turbulence and "morphing" effects, and dynamically colors the streaks based on their radius and a simulated Doppler shift as they orbit.

const instancedDisk = new THREE.InstancedMesh(streakGeo, diskMaterial, instanceCount);
const dummy = new THREE.Object3D();
for (let i = 0; i < instanceCount; i++) {
    const r = 5 + Math.pow(Math.random(), 1.3) * 40;
    const angle = Math.random() * Math.PI * 2;
    dummy.position.set(Math.cos(angle) * r, (Math.random() - 0.5) * (8 / r), Math.sin(angle) * r);
    dummy.lookAt(dummy.position.x + Math.sin(angle), dummy.position.y, dummy.position.z - Math.cos(angle));
    dummy.updateMatrix();
    instancedDisk.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedDisk);

This section efficiently creates thousands of small cylindrical "streaks" using an InstancedMesh to form the accretion disk. Each streak's initial position and orientation are randomized, and its movement and appearance are controlled by a complex shader.

The project features dynamic transitions between different states, defined in a config array. Every 10 seconds, a transition function is called, which uses the GSAP animation library to smoothly interpolate various parameters. These parameters include shader uniforms (like uMorph, uCompression, uIntensity, uOrbitScale) that control the disk's appearance and behavior, as well as camera position and auto-rotation speed. Concurrently, the on-screen HUD elements (title, status, velocity) are updated with new text and color values, providing a cohesive visual and informational shift.

function transition() {
    stateIdx = (stateIdx + 1) % config.length;
    const s = config[stateIdx];
    const tl = gsap.timeline({ defaults: { duration: 4.0, ease: "power2.inOut" } });
    tl.to(diskMaterial.uniforms.uMorph, { value: s.morph }, 0);
    tl.to(diskMaterial.uniforms.uCompression, { value: s.compress }, 0);
    // ... more animations ...
    gsap.to([mainTitle, statusText, '.val'], { opacity: 0, duration: 0.8, onComplete: () => {
        mainTitle.innerText = s.title;
        statusText.innerText = s.status;
        // ... update text and colors ...
        gsap.to([mainTitle, statusText, '.val'], { opacity: 1, duration: 1.2 });
    }});
}
setInterval(transition, 10000);

This transition function, triggered every 10 seconds, uses GSAP to smoothly animate the 3D scene's properties (like disk turbulence and intensity) and update the on-screen text to reflect the current state, creating a dynamic user experience.

Ask about this project

Have a question about how this project works? Ask below and get an answer based on the project code.

Code Breakdown

Structured technical breakdown for beginners.

No breakdown yet. Generate one to see a file-by-file explanation.

Comments

No comments yet. Sign in to leave a comment.

Sign in to leave a comment.