Basic Example
Create a Three.js scene with an animated procedural sky. Choose a setup:
- TypeScript + Vite — recommended for bundled applications
- Plain JavaScript + CDN — no build tools required
TypeScript + Vite
Step 1: Create a Vite Project
npm create vite@latest my-sky-project -- --template vanilla-ts
cd my-sky-projectStep 2: Install Three.js
npm install three@^0.185.0
npm install --save-dev @types/three@^0.185.0Step 3: Add the Library
Unzip
threejs-sky-pro.zipinto your root directory.Create a
threejs-sky-prosub-directory within yoursrcdirectory.Copy the contents of the
builddirectory into the directory you just created. Copy everything, includingdata/— the cloud-noise volumes load from there at runtime.Confirm the following file structure:
my-sky-project/
├── src/
│ ├── threejs-sky-pro/
│ │ ├── index.js ← Main library bundle
│ │ ├── index.js.map ← Source map
│ │ ├── index.d.ts ← TypeScript declarations
│ │ └── data/ ← Cloud-noise volumes
│ └── main.ts
├── index.html
└── package.jsonStep 4: Update index.html
Replace the contents of index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three.js Sky Pro</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script type="module" src="/src/main.ts"></script>
</body>
</html>Step 5: Add the Application Code
Replace src/main.ts with:
import * as THREE from "three/webgpu";
import { pass, vec4 } from "three/tsl";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { SkySystem, PRESETS } from "./threejs-sky-pro";
async function main() {
// Create renderer
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.body.appendChild(renderer.domElement);
await renderer.init();
// Create scene and camera
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
1_000_000,
);
camera.position.set(0, 100, 0);
// Drag to orbit, scroll to zoom. Target a point on the horizon so the
// initial view looks out at the sky rather than straight down.
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 100, -100);
controls.enableDamping = true;
controls.update();
// Create the sky. Its backdrop meshes are added to the scene for you.
const sky = await SkySystem.create({
renderer,
camera,
scene,
quality: "high",
});
// Apply a bundled look preset (pure look — doesn't change render cost).
await sky.applyPreset(PRESETS.partlyCloudy);
// Sun position: elevation and compass azimuth, in degrees.
sky.sun.setFromAngles(35, 120);
// Post chain: scene → fog/god rays → exposure → tone map → canvas.
const scenePass = pass(scene, camera);
let output = scenePass.getTextureNode("output");
output = sky.applyTo(output, scenePass);
output = vec4(output.rgb.mul(sky.atmosphere.exposure), output.a);
const postProcessing = new THREE.RenderPipeline(renderer, output);
// Handle window resize
window.addEventListener("resize", () => {
const w = window.innerWidth;
const h = window.innerHeight;
renderer.setSize(w, h);
camera.aspect = w / h;
camera.updateProjectionMatrix();
sky.resize(w, h);
});
// Compile shaders before starting animation
await renderer.compileAsync(scene, camera);
// Animation loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
controls.update();
sky.update(clock.getDelta());
postProcessing.render();
}
animate();
}
main();Night sky
The star panorama is not bundled. Without one, stars are disabled and the moon uses a flat tinted disc. This example keeps the sun up, so no starmap is needed. To add stars to a day/night cycle, load an equirectangular starmap and pass it to SkySystem.create as nightSky: { texture }. See Day/Night Cycle for setup and properly licensed starmaps.
Step 6: Run the Project
npm run devOpen the URL shown in the terminal, usually http://localhost:5173.
Plain JavaScript + CDN
This setup loads Three.js from a CDN through an import map.
Step 1: Create Project Files
Create a folder with the following structure:
my-sky-project/
├── lib/
│ └── threejs-sky-pro/
├── src/
│ └── main.js
└── index.htmlStep 2: Add the Library
Unzip
threejs-sky-pro.zipCopy the contents of the
builddirectory intolib/threejs-sky-pro. Copy everything, includingdata/— the cloud-noise volumes load from there at runtime.
Step 3: Create index.html
Use an import map to load Three.js and its addons from the same CDN version.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three.js Sky Pro</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
</style>
<!-- Import map for Three.js CDN -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.webgpu.min.js",
"three/webgpu": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.webgpu.min.js",
"three/tsl": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.tsl.min.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
}
}
</script>
</head>
<body>
<script type="module" src="./src/main.js"></script>
</body>
</html>Step 4: Create main.js
import * as THREE from "three/webgpu";
import { pass, vec4 } from "three/tsl";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { SkySystem, PRESETS } from "../lib/threejs-sky-pro/index.js";
async function main() {
// Create renderer
const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
document.body.appendChild(renderer.domElement);
await renderer.init();
// Create scene and camera
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
1_000_000,
);
camera.position.set(0, 100, 0);
// Drag to orbit, scroll to zoom. Target a point on the horizon so the
// initial view looks out at the sky rather than straight down.
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 100, -100);
controls.enableDamping = true;
controls.update();
// Create the sky. Its backdrop meshes are added to the scene for you.
const sky = await SkySystem.create({
renderer,
camera,
scene,
quality: "high",
});
// Apply a bundled look preset (pure look — doesn't change render cost).
await sky.applyPreset(PRESETS.partlyCloudy);
// Sun position: elevation and compass azimuth, in degrees.
sky.sun.setFromAngles(35, 120);
// Post chain: scene → fog/god rays → exposure → tone map → canvas.
const scenePass = pass(scene, camera);
let output = scenePass.getTextureNode("output");
output = sky.applyTo(output, scenePass);
output = vec4(output.rgb.mul(sky.atmosphere.exposure), output.a);
const postProcessing = new THREE.RenderPipeline(renderer, output);
// Handle window resize
window.addEventListener("resize", () => {
const w = window.innerWidth;
const h = window.innerHeight;
renderer.setSize(w, h);
camera.aspect = w / h;
camera.updateProjectionMatrix();
sky.resize(w, h);
});
// Compile shaders before starting animation
await renderer.compileAsync(scene, camera);
// Animation loop
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
controls.update();
sky.update(clock.getDelta());
postProcessing.render();
}
animate();
}
main();Night sky
The star panorama is not bundled. Without one, stars are disabled and the moon uses a flat tinted disc. This example keeps the sun up, so no starmap is needed. To add stars to a day/night cycle, load an equirectangular starmap and pass it to SkySystem.create as nightSky: { texture }. See Day/Night Cycle for setup and properly licensed starmaps.
Step 5: Serve the Files
Serve the files over HTTP because browsers block ES modules loaded through file://. Use one of these commands:
# Using Python
python3 -m http.server 8080
# Using Node.js (npx)
npx serve .
# Using PHP
php -S localhost:8080Open http://localhost:8080 in your browser.
You can also use a development server such as the VS Code Live Server extension.
CORS Note
Import maps with CDN URLs require the page to be served over HTTP/HTTPS. Opening index.html directly as a file won't work.
Result
The result is a daytime sky with drifting volumetric clouds. Drag to orbit and scroll to zoom.
Next Steps
- Presets — serializable look snapshots
- Day/Night Cycle — animate the sun
- Water Pro Integration — sky and cloud reflections on water
- Tuning Performance — performance vs fidelity
