Guides
Performance
Most of the frame goes to the cloud march at a fraction of the screen resolution. The bakes are throttled and time-sliced, so there's nothing sudden, and all of it is tunable.
#Measured
- median across presets on high
- ~75 fps
- clear and cirrus skies
- 110+ fps
- full-sky decks: dawn, cumulonimbus
- 30–45 fps
These are for an Apple M5, headless Chromium on ANGLE/Metal, 1280×720 at pixel ratio 1, in the full demo scene with the post pass. The GPU was shared with other jobs while measuring, so treat them as lower bounds. npm test prints the numbers for your machine.
#Where the time goes
| Work | When | Scales with |
|---|---|---|
| Cloud march | Every frame, at marchScale of the drawing buffer | marchSteps, lightSteps, cloud cover |
| Temporal resolve | Every frame, at historyScale | Resolution |
| Sky-view LUT | Throttled | Cheap |
| Cloud shadow map | Every shadowInterval s (dynamic) | shadowSize, shadowSteps |
| Env bake | Every envInterval s, spread over envSlices frames | envSize, envSteps |
| PMREM | With each env refresh | pmremSamples (reduced from three's 256) |
| Post pass (optional) | Every frame | raySteps, msaa, bloom |
#Levers, cheapest first
#Drop a tier
sky.setQualityLevel('medium');#Override the expensive fields
sky.setQualityLevel('high', { marchScale: 0.4, marchSteps: 44, raySteps: 8 });#Freeze the weather
sky.setCloudRenderingMode('static'); // re-bakes only when something changes#Cap the pixel ratio
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.5));
sky.resize();The march resolution is relative to the drawing buffer, so a 2× retina display costs four times as much as 1×.
#Adaptive quality
The library doesn't change tiers on its own. That's deliberate, because a tier change resets history and re-bakes. For adaptive quality, watch your own frame time and step down with hysteresis:
let slow = 0;
renderer.setAnimationLoop(t => {
const dt = timer.update(t).getDelta();
slow = dt > 1 / 45 ? slow + 1 : Math.max(0, slow - 2);
if (slow > 120 && sky.quality !== 'low') {
sky.setQualityLevel({ ultra: 'high', high: 'medium', medium: 'low' }[sky.quality]);
slow = 0;
}
sky.update(dt);
renderer.render(scene, camera);
});