npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rayzee

v6.2.0

Published

Real-time WebGPU path tracing engine built on Three.js

Readme

Rayzee Engine

npm minzipped size downloads jsDelivr

A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic — use it with React, Vue, vanilla JS, or any other setup.

Installation

npm install rayzee three

three (>=0.183.0) is a required peer dependency.

Getting Started

Vanilla JS with Vite

  1. Create a project

    npm create vite@latest my-raytracer -- --template vanilla
    cd my-raytracer
    npm install rayzee three
  2. Set up the HTML

    <!-- index.html -->
    <body style="margin: 0; overflow: hidden;">
      <canvas id="viewport"></canvas>
      <script type="module" src="/main.js"></script>
    </body>
  3. Write the code

    // main.js
    import { PathTracerApp, EngineEvents } from 'rayzee';
    
    const canvas = document.getElementById('viewport');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    
    const engine = new PathTracerApp(canvas);
    await engine.init();
    
    // Load a 3D model (place .glb in public/ folder)
    await engine.loadModel('/scene.glb');
    
    // Or load an environment map
    // await engine.loadEnvironment('/environment.hdr');
    
    // Start rendering
    engine.animate();
    
    // Listen for events
    engine.addEventListener(EngineEvents.RENDER_COMPLETE, () => {
      console.log('Frame rendered');
    });
    
    // Tweak settings
    engine.settings.set('bounces', 8);
    engine.settings.set('exposure', 1.2);
    
    // Use namespaced APIs and direct methods
    engine.cameraManager.switchCamera(0);
    engine.lightManager.add('PointLight');
    
    // Capture the current frame as a Blob (host handles save/upload)
    const blob = await engine.screenshot();
  4. Run

    npm run dev

Vanilla JS (no bundler)

A single HTML file — no Node.js, no build step. Uses ES module import maps to resolve the pre-built ESM bundle and its dependencies from a CDN.

<!DOCTYPE html>
<html>
<head>
  <title>Rayzee Path Tracer</title>
  <style>body { margin: 0; overflow: hidden; background: #111; }</style>
  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.webgpu.js",
      "three/tsl": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.tsl.js",
      "three/webgpu": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.webgpu.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
      "oidn-web": "https://cdn.jsdelivr.net/npm/[email protected]/dist/oidn.js",
      "rayzee": "https://cdn.jsdelivr.net/npm/rayzee/dist/rayzee.es.js"
    }
  }
  </script>
</head>
<body>
  <canvas id="viewport"></canvas>
  <script type="module">
    import { PathTracerApp } from 'rayzee';

    const canvas = document.getElementById('viewport');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const engine = new PathTracerApp(canvas);
    await engine.init();
    // Replace with your own model URL
    await engine.loadModel('https://your-cdn.com/scene.glb');
    engine.animate();

    window.addEventListener('resize', () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      engine.onResize();
    });
  </script>
</body>
</html>

Serve with any static server (ES modules require HTTP, not file://):

npx serve .

Note: The import map approach loads dependencies from a CDN, so initial load is slower than a bundled build. For production, use the Vite setup above.

React

import { useRef, useEffect } from 'react';
import { PathTracerApp } from 'rayzee';

export default function Viewport({ modelUrl }) {
  const canvasRef = useRef(null);
  const engineRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;

    const engine = new PathTracerApp(canvas);
    engineRef.current = engine;

    (async () => {
      await engine.init();
      if (modelUrl) await engine.loadModel(modelUrl);
      engine.animate();
    })();

    return () => engine.dispose();
  }, [modelUrl]);

  return <canvas ref={canvasRef} style={{ width: '100%', height: '100vh' }} />;
}

No special build config is needed — models and HDRs are loaded via URL at runtime.

Integrating Alongside an Existing Three.js App

If your app already has a WebGL/WebGPU rasterized view and you want to add a path-traced mode on demand, run rayzee on its own separate canvas (WebGL and WebGPU can't share one) and toggle visibility.

import { PathTracerApp } from 'rayzee';

// 1. WebGPU detection
if (!navigator.gpu || !(await navigator.gpu.requestAdapter())) return;

// 2. Overlay canvas (hidden until toggled on)
const ptCanvas = document.createElement('canvas');
Object.assign(ptCanvas.style, { position: 'absolute', inset: 0, display: 'none' });
container.appendChild(ptCanvas);

let engine = null;
async function togglePathTrace(on) {
  if (on && !engine) {
    ptCanvas.width = container.clientWidth;
    ptCanvas.height = container.clientHeight;
    engine = new PathTracerApp(ptCanvas, { autoResize: false });
    await engine.init();
    await engine.loadEnvironment('/env.hdr');             // required for realistic lighting
    await engine.loadObject3D(yourScene);                 // rayzee takes ownership — pass a clone if the host still renders it
    engine.animate();
  }
  ptCanvas.style.display = on ? 'block' : 'none';
  hostCanvas.style.display = on ? 'none' : 'block';
  on ? engine?.resume() : engine?.pause();                // pause the inactive renderer to avoid GPU contention
}

Key constraints:

  • loadObject3D takes ownership of the passed Object3D (sets it as the active model, disposes the previous one). If your host app continues to render the same scene graph, pass scene.clone(true) — deep-cloning shares geometry/texture data, so memory cost is small. Clone once on first toggle, not on every switch.
  • Rayzee ignores onBeforeCompile. It reads PBR material properties (albedo, roughness, metalness, …) directly into its own GPU buffers; custom shader injection on the host material has no effect on the path-traced view.
  • Always load an environment. Path tracing without an env map produces a black background and no indirect lighting.
  • three is a peer dep on both sides. Vite/webpack dedupe automatically. For script-tag setups, load one copy of three globally.

Vite tip

When rayzee is installed from npm, its pre-built dist/rayzee.es.js uses worker and import.meta.url patterns that Vite's dep pre-bundler re-parses incorrectly. Exclude it:

// vite.config.js
export default defineConfig({
  optimizeDeps: { exclude: ['rayzee'] },
});

API Reference

Configuring Assets (CDN URLs & cache namespace)

By default, the engine loads STBN blue-noise atlases, GLTF Draco/KTX2 decoders, OIDN denoiser weights, ONNX upscaler models, and the onnxruntime-web bundle from upstream CDNs. If you're self-hosting, embedding the engine alongside a different consumer of the same caches, or operating offline, override them once before constructing PathTracerApp:

import { configureAssets } from 'rayzee';

configureAssets({
  // STBN atlases (PNG, decoded as Float textures)
  stbnScalarAtlas: '/assets/stbn_scalar_atlas.png',
  stbnVec2Atlas:   '/assets/stbn_vec2_atlas.png',

  // onnxruntime-web (loaded by AI upscaler worker via dynamic import)
  ortRuntimeUrl: '/ort/ort.webgpu.bundle.min.mjs',
  ortWasmPaths:  '/ort/',

  // GLTFLoader extension decoders
  dracoDecoderPath:   '/draco/',
  ktx2TranscoderPath: '/basis/',

  // Denoiser & upscaler weights
  oidnWeightsBaseUrl:    '/oidn-tzas/',
  upscalerModelBaseUrl:  '/upscaler-onnx/',

  // Prefix for engine-managed IndexedDB stores. Set to a unique value if multiple
  // apps embed the engine on the same origin to avoid cache collisions.
  cacheNamespace: 'my-app',
});

const engine = new PathTracerApp(canvas);
await engine.init();

All keys are optional — only what you pass is overridden. Call getAssetConfig() to read the current values.

PathTracerApp

The main engine class. Extends Three.js EventDispatcher. Related functionality is grouped into namespaced managers accessed via engine.cameraManager, engine.lightManager, etc., or as direct methods on the engine instance.

const engine = new PathTracerApp(canvas, options?)

| Parameter | Type | Description | |---|---|---| | canvas | HTMLCanvasElement | Rendering target | | options.autoResize | boolean | Auto-resize on window resize (default: true) | | options.container | HTMLElement | Single DOM parent the engine mounts auxiliary elements into — HUD overlay (tile borders, helpers) and denoiser canvas. Defaults to canvas.parentNode. |

The engine creates and mounts everything it needs (denoiser canvas, tile/HUD overlay) into a single parent on init(). Performance HUDs (e.g. stats-gl) are not bundled — listen to EngineEvents.FRAME and tick your own panel.

Lifecycle

await engine.init()           // Initialize WebGPU renderer and pipeline
engine.animate()              // Start the render loop
engine.pause()                // Pause rendering
engine.resume()               // Resume rendering
engine.reset()                // Reset accumulation (restart from sample 0)
engine.dispose()              // Clean up all resources
engine.wake()                 // Resume render loop if idle

Constructing a new PathTracerApp on a canvas that already has an active instance auto-disposes the prior one — safe under React StrictMode and HMR even without explicit cleanup, though engine.dispose() remains the recommended teardown path.

Loading Assets

await engine.loadModel(url)           // Load GLB/GLTF/FBX/OBJ/STL/PLY/DAE/3MF/USDZ/ZIP
await engine.loadObject3D(object3d)   // Load a Three.js Object3D directly
await engine.loadEnvironment(url)     // Load HDR/EXR environment map

Settings

engine.settings.set('bounces', 8)              // Set a single parameter
engine.settings.setMany({                      // Set multiple parameters at once
  bounces: 8,
  samplesPerPixel: 1,
  exposure: 1.0
})
engine.settings.get('bounces')                 // Read a parameter
engine.settings.getAll()                       // Get all current settings

Key settings:

| Setting | Type | Default | Description | |---|---|---|---| | bounces | number | 3 | Max ray bounce depth | | samplesPerPixel | number | 1 | Samples per pixel per frame | | maxSamples | number | 60 | Max accumulated samples before stopping | | exposure | number | 1.0 | Exposure value | | saturation | number | 1.2 | Color saturation | | enableEnvironment | boolean | true | Use environment lighting | | environmentIntensity | number | 1.0 | Environment light strength | | environmentRotation | number | 270 | Environment Y-rotation (degrees) | | fireflyThreshold | number | 3.0 | Firefly clamping threshold | | transmissiveBounces | number | 5 | Max bounces for transmissive materials | | enableDOF | boolean | false | Enable depth of field | | focusDistance | number | 0.8 | DOF focus distance | | aperture | number | 5.6 | DOF aperture (f-stop) | | focalLength | number | 50 | DOF focal length (mm) | | adaptiveSampling | boolean | false | Variance-guided sample distribution | | transparentBackground | boolean | false | Transparent canvas background | | interactionModeEnabled | boolean | true | Lower quality during camera movement for smoother navigation | | debugMode | number | 0 | Debug visualization mode (0 = off) | | environmentMode | string | 'hdri' | Sky mode: 'hdri' | 'procedural' | 'gradient' | 'color' |

See ENGINE_DEFAULTS for the full list with default values.

Rendering Modes

engine.configureForMode('production')   // High quality (tiled, 20 bounces, OIDN, controls disabled)
engine.configureForMode('interactive')  // Real-time navigation (3 bounces, controls enabled)

To pause rendering for image-viewing UI, set engine.pauseRendering = true and disable camera controls directly — the engine doesn't model viewport visibility.


engine.cameraManager

Camera switching, auto-focus, DOF, and direct Three.js access.

engine.cameraManager.active                  // The active PerspectiveCamera
engine.cameraManager.controls                // The OrbitControls instance
engine.cameraManager.switchCamera(index)      // Switch between scene cameras
engine.cameraManager.getNames()              // List available cameras
engine.cameraManager.focusOn(center)         // Focus orbit camera on a world-space point
engine.cameraManager.setAutoFocusMode(mode)  // 'auto' | 'manual'
engine.cameraManager.setAFScreenPoint(x, y)  // Set normalized AF screen point (0-1)

engine.lightManager

Light CRUD, visual helpers, and GPU sync.

engine.lightManager.add('PointLight')       // Add a light (PointLight, SpotLight, DirectionalLight, RectAreaLight)
engine.lightManager.remove(uuid)            // Remove by UUID
engine.lightManager.clear()                 // Remove all lights
engine.lightManager.getAll()                // Get all light descriptors
engine.lightManager.sync()                  // Re-upload light data to GPU
engine.lightManager.showHelpers(true)       // Toggle visual helpers

engine.animationManager

GLTF animation playback controls.

engine.animationManager.play(clipIndex)      // Play an animation clip
engine.animationManager.pause()              // Pause playback
engine.animationManager.resume()             // Resume playback
engine.animationManager.stop()               // Stop and reset
engine.animationManager.setSpeed(2)          // Set playback speed multiplier
engine.animationManager.setLoop(true)        // Enable/disable looping
engine.animationManager.clips                // Get available animation clips

Materials

Material property updates and texture transforms — accessed as direct methods on the engine.

engine.setMaterialProperty(index, property, value)  // Update a material property
engine.setTextureTransform(index, name, transform)   // Update texture transform
engine.reset()                        // Re-upload all material data to GPU
engine.stages.pathTracer.materialData.updateMaterial(index, mat)  // Replace a material
await engine.rebuildMaterials(scene)  // Full rebuild (after texture changes)

// Per-mesh visibility — recommended UUID-based API (handles lookup + sync internally)
engine.setMeshVisibilityByUuid(uuid, true)             // explicit set
engine.setMeshVisibilityByUuid(uuid, prev => !prev)    // toggle via updater fn
// Returns the new visibility state, or null if the mesh wasn't found.

// Lower-level — for callers that already have a meshIndex or have mutated object.visible directly
engine.setMeshVisibility(meshIndex, visible)
engine.updateAllMeshVisibility()                  // re-sync after manual object.visible mutations

// Read access to the active scene (returns the mesh-bearing scene)
engine.getScene()

engine.environmentManager

Environment maps, sky modes, and procedural generation.

engine.environmentManager.params             // Current environment parameters
engine.environmentManager.texture            // The loaded environment texture
await engine.loadEnvironment(url)            // Load HDR/EXR environment map (method on engine)
await engine.environmentManager.setEnvironmentMap(tex) // Set a custom environment texture
await engine.environmentManager.setMode(mode)   // 'hdri' | 'procedural' | 'gradient' | 'color'
await engine.environmentManager.generateProcedural() // Preetham-model sky
await engine.environmentManager.generateGradient()   // Gradient sky
await engine.environmentManager.generateSolid()      // Solid color sky
engine.environmentManager.markDirty()        // Flag environment for GPU re-upload

engine.denoisingManager

Denoiser strategy, ASVGF, OIDN, upscaler, adaptive sampling, and auto-exposure.

// Strategy
engine.denoisingManager.setStrategy('asvgf', 'medium')  // 'none' | 'asvgf' | 'ssrc' | 'edgeaware'
engine.denoisingManager.setASVGFEnabled(true, 'medium')
engine.denoisingManager.applyASVGFPreset('high')         // 'low' | 'medium' | 'high'
engine.denoisingManager.setAutoExposure(true)
engine.denoisingManager.setAdaptiveSampling(true)

// Fine-grained parameters
engine.denoisingManager.setASVGFParams({ temporalAlpha: 0.1, phiColor: 10 })
engine.denoisingManager.setSSRCParams({ temporalAlpha: 0.1, spatialRadius: 3 })
engine.denoisingManager.setEdgeAwareParams({ pixelEdgeSharpness: 1.0 })
engine.denoisingManager.setAutoExposureParams({ keyValue: 0.18 })
engine.denoisingManager.setAdaptiveSamplingParams({ varianceThreshold: 0.01 })

// OIDN & Upscaler
engine.denoisingManager.setOIDNEnabled(true)
engine.denoisingManager.setOIDNQuality('high')
engine.denoisingManager.setUpscalerEnabled(true)
engine.denoisingManager.setUpscalerScaleFactor(2)
engine.denoisingManager.setUpscalerQuality('high')

engine.interactionManager

Object picking and interaction modes.

engine.interactionManager.select(object)       // Programmatically select an object
engine.interactionManager.deselect()           // Deselect the current object
engine.interactionManager.toggleSelectMode()   // Toggle object selection mode
engine.interactionManager.disableMode()        // Disable selection mode and detach gizmo
engine.interactionManager.toggleFocusMode()    // Toggle click-to-focus DOF
engine.interactionManager.on(type, handler)    // Subscribe (returns unsubscribe function)

engine.transformManager

Transform gizmo controls.

engine.transformManager.setMode('translate') // 'translate' | 'rotate' | 'scale'
engine.transformManager.setSpace('world')    // 'world' | 'local'
engine.transformManager.controls             // Access the underlying TransformControls

Output Methods

Canvas output, screenshots, and scene statistics — accessed as direct methods on the engine.

engine.getCanvas()                    // Get the canvas with the final rendered image
const blob = await engine.screenshot()           // Capture frame as Blob (default 'image/png')
const jpg  = await engine.screenshot({ type: 'image/jpeg', quality: 0.9 })
engine.getStatistics()                // Triangle count, mesh count, etc.
engine.setCanvasSize(1920, 1080)      // Set explicit canvas dimensions
engine.onResize()                     // Trigger manual resize recalculation
engine.isComplete()                   // Check if rendering has converged
engine.getFrameCount()                // Get the current accumulated frame count

screenshot() returns a Blob for the host to save, upload, or display. To trigger a browser download:

const blob = await engine.screenshot();
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'), { href: url, download: 'render.png' });
a.click();
URL.revokeObjectURL(url);

Events

Subscribe to engine lifecycle events via addEventListener:

import { EngineEvents } from 'rayzee';

engine.addEventListener(EngineEvents.RENDER_COMPLETE, (e) => {
  console.log('Render complete');
});

| Event | Fired when | |---|---| | RENDER_COMPLETE | Rendering has converged | | RENDER_RESET | Accumulation buffer is reset | | FRAME | Fires once per animate() tick — hook external instrumentation (stats panels, telemetry) here | | DENOISING_START / DENOISING_END | Denoiser runs | | UPSCALING_START / UPSCALING_PROGRESS / UPSCALING_END | AI upscaler runs | | LOADING_UPDATE / LOADING_RESET | Asset loading progress | | STATS_UPDATE | Performance stats updated | | OBJECT_SELECTED / OBJECT_DESELECTED | Object selection changes | | OBJECT_DOUBLE_CLICKED | Object double-clicked | | OBJECT_TRANSFORM_START / OBJECT_TRANSFORM_END | Transform gizmo drag | | TRANSFORM_MODE_CHANGED | Gizmo mode changed | | SELECT_MODE_CHANGED | Selection mode toggled | | SETTING_CHANGED | A render setting is modified | | AUTO_FOCUS_UPDATED | Auto-focus recalculated | | AUTO_EXPOSURE_UPDATED | Auto-exposure recalculated | | AF_POINT_PLACED | Focus point placed on screen | | ANIMATION_STARTED / ANIMATION_PAUSED / ANIMATION_STOPPED / ANIMATION_FINISHED | Animation lifecycle | | VIDEO_RENDER_PROGRESS / VIDEO_RENDER_COMPLETE | Video export progress | | DISPOSE | Engine is being disposed (fires before teardown begins, so listeners can release their own references) |

Advanced: Custom Pipeline Stages

Build custom rendering stages by extending RenderStage:

import { RenderStage } from 'rayzee';

class MyCustomStage extends RenderStage {
  constructor() {
    super('my-stage');
  }

  render(context, writeBuffer) {
    const input = context.getTexture('pathtracer:color');
    // ... process input, write output
    context.setTexture('my-stage:output', this.outputTexture);
  }
}

All Exports

// Core
import { PathTracerApp, EngineEvents } from 'rayzee';

// Configuration & presets
import {
  ENGINE_DEFAULTS,
  ASVGF_QUALITY_PRESETS,
  CAMERA_PRESETS,
  CAMERA_RANGES,
  SKY_PRESETS,
  AUTO_FOCUS_MODES,
  AF_DEFAULTS,
  TRIANGLE_DATA_LAYOUT,
  BVH_LEAF_MARKERS,
  TEXTURE_CONSTANTS,
  DEFAULT_TEXTURE_MATRIX,
  MEMORY_CONSTANTS,
  PRODUCTION_RENDER_CONFIG,
  INTERACTIVE_RENDER_CONFIG,
} from 'rayzee';

// Asset URL / cache namespace overrides
import { configureAssets, getAssetConfig } from 'rayzee';

// Advanced: managers & pipeline
import {
  RenderSettings,
  CameraManager,
  LightManager,
  DenoisingManager,
  OverlayManager,
  AnimationManager,
  TransformManager,
  VideoRenderManager,
  InteractionManager,
  RenderPipeline,
  RenderStage,
  StageExecutionMode,
  PipelineContext,
} from 'rayzee';

Browser Requirements

  • WebGPU support (Chrome 113+, Edge 113+, Safari 18+, Firefox 141+)
  • Secure context (HTTPS or localhost)

Optional Dependencies

| Package | Purpose | Install needed? | |---|---|---| | oidn-web | Intel Open Image Denoise for high-quality final renders | Yes — npm install oidn-web | | onnxruntime-web | AI-powered upscaling | No — loaded from CDN at runtime |

Enabling OIDN (Intel Open Image Denoise)

OIDN provides high-quality AI denoising for final renders. It runs automatically after the render converges (reaches maxSamples).

  1. Install the package

    npm install oidn-web
  2. Enable in your app

    // After engine.init() completes
    engine.denoisingManager.setOIDNEnabled(true);
    engine.denoisingManager.setOIDNQuality('balance'); // 'fast' | 'balance' | 'high'
  3. Listen for progress (optional)

    engine.addEventListener(EngineEvents.DENOISING_START, () => {
      console.log('Denoising started');
    });
    engine.addEventListener(EngineEvents.DENOISING_END, () => {
      console.log('Denoising complete');
    });

| Quality | Model size | Speed | Best for | |---|---|---|---| | 'fast' | ~20 MB | Fastest | Quick previews | | 'balance' | ~50 MB | Moderate | General use (default) | | 'high' | ~100 MB | Slowest | Final quality renders |

Note: The neural network model is downloaded on first use. Subsequent runs use the browser cache. OIDN also works with configureForMode('production'), which enables it automatically alongside high-quality render settings.

Enabling the AI Upscaler

The upscaler runs ONNX super-resolution models via onnxruntime-web. Unlike OIDN, onnxruntime-web is lazily fetched from a CDN inside a Web Worker — no npm install or import map entry is needed.

engine.denoisingManager.setUpscalerEnabled(true);
engine.denoisingManager.setUpscalerQuality('fast');      // 'fast' | 'balanced' | 'quality'
engine.denoisingManager.setUpscalerScaleFactor(2);       // 2 | 4

engine.addEventListener(EngineEvents.UPSCALING_START,    () => console.log('Upscaling started'));
engine.addEventListener(EngineEvents.UPSCALING_PROGRESS, (e) => console.log('Upscaling', e));
engine.addEventListener(EngineEvents.UPSCALING_END,      () => console.log('Upscaling complete'));

| Quality | Model | 2× size | 4× size | |---|---|---|---| | 'fast' | SPAN | 1.6 MB | 1.6 MB | | 'balanced' | SRVGGNetCompact | 2.4 MB | 4.9 MB | | 'quality' | RRDBNet / MoSR | 67 MB | 16.5 MB |

Chaining with OIDN: Upscaling and OIDN can run together — on render completion, OIDN runs first, then its denoised output is fed into the upscaler. Enable both; no manual coordination required.

Troubleshooting

OIDN: Cannot find module './tza' (webpack) The oidn-web package uses dynamic imports that webpack cannot resolve. This does not affect Vite or other ESM-native bundlers. Add oidn-web to your webpack externals:

// webpack.config.js
module.exports = {
  externals: {
    'oidn-web': 'oidn-web'
  }
};

Then load it via a script tag or import map instead:

<script type="importmap">
{
  "imports": {
    "oidn-web": "https://cdn.jsdelivr.net/npm/[email protected]/dist/oidn.js"
  }
}
</script>

OIDN: TypeError: t.alea is not a function or Argument 'x' passed to 'conv2d' must be a Tensor ... got 'L' You're loading oidn-web via jsdelivr.net/npm/oidn-web/+esm or esm.sh/oidn-web. Don't — use the self-bundled /dist/oidn.js path instead:

- "oidn-web": "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
+ "oidn-web": "https://cdn.jsdelivr.net/npm/[email protected]/dist/oidn.js"

Why: oidn-web transitively depends on @tensorflow/tfjs-core, which does import * as t from "seedrandom" and calls t.alea(...). jsDelivr's /+esm CJS→ESM shim of seedrandom emits only export default (no named exports), so t.alea is undefined. Swapping to esm.sh trades that for a different issue: deep-path imports produce multiple tfjs-core instances, so a Tensor made in one module fails instanceof checks in another (got 'L'). The /dist/oidn.js file in the npm package is a single pre-bundled ESM with all of tfjs inlined — no external imports, one tfjs instance, same exports. Use it.

Black screen / "WebGPU not supported" Your browser may not support WebGPU. Use Chrome 113+, Edge 113+, Safari 18+, or Firefox 141+. Ensure you're on HTTPS or localhost.

Models not loading If serving locally, place files in your public/ folder and reference them with absolute paths (e.g., /scene.glb). For remote files, ensure the server allows CORS.

License

MIT