rayzee 7.23.2 → 7.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -17
- package/dist/rayzee.es.js +162 -118
- package/dist/rayzee.umd.js +51 -51
- package/package.json +2 -2
- package/src/Passes/AIUpscaler.js +16 -8
- package/src/Passes/OIDNDenoiser.js +113 -54
- package/src/PathTracerApp.js +33 -0
- package/src/managers/DenoisingManager.js +18 -16
package/README.md
CHANGED
|
@@ -31,7 +31,9 @@ A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic —
|
|
|
31
31
|
- [engine.interactionManager](#engineinteractionmanager)
|
|
32
32
|
- [engine.transformManager](#enginetransformmanager)
|
|
33
33
|
- [Output Methods](#output-methods)
|
|
34
|
+
- [Render Resolution Reserve](#render-resolution-reserve)
|
|
34
35
|
- [Memory Monitoring](#memory-monitoring)
|
|
36
|
+
- [Logging](#logging)
|
|
35
37
|
- [Deterministic & Headless Rendering](#deterministic--headless-rendering)
|
|
36
38
|
- [Events](#events)
|
|
37
39
|
- [Advanced: Custom Pipeline Stages](#advanced-custom-pipeline-stages)
|
|
@@ -135,7 +137,7 @@ A single HTML file — no Node.js, no build step. Uses [ES module import maps](h
|
|
|
135
137
|
"three/tsl": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.tsl.js",
|
|
136
138
|
"three/webgpu": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.webgpu.js",
|
|
137
139
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/",
|
|
138
|
-
"oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.
|
|
140
|
+
"oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.4.0/dist/oidn.js",
|
|
139
141
|
"rayzee": "https://cdn.jsdelivr.net/npm/rayzee/dist/rayzee.es.js"
|
|
140
142
|
}
|
|
141
143
|
}
|
|
@@ -594,6 +596,36 @@ URL.revokeObjectURL(url);
|
|
|
594
596
|
|
|
595
597
|
---
|
|
596
598
|
|
|
599
|
+
### Render Resolution Reserve
|
|
600
|
+
|
|
601
|
+
Every compute `StorageTexture` and aux buffer is pre-allocated at one square dimension — the *reserve* — and `setCanvasSize()` refuses anything larger. The default is 2048, so 4K output needs the reserve raised first.
|
|
602
|
+
|
|
603
|
+
```js
|
|
604
|
+
engine.setReservedRenderResolution(4096) // raise to 4K (longest edge)
|
|
605
|
+
engine.setReservedRenderResolution(2048, { allowLower: true }) // lower, paying a rebuild, to reclaim VRAM
|
|
606
|
+
engine.getReservedRenderResolution() // the reserve actually in force
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
The request is **device-capped**: a 4096 reserve pins roughly 1.5 GB of MRT textures, so it is only granted on GPUs reporting ≥ 8 GB and a ≥ 1 GB `maxStorageBufferBindingSize`; weaker devices clamp to 2048. `MAX_RESERVABLE_RENDER_SIZE` (4096) is the ceiling on any request.
|
|
610
|
+
|
|
611
|
+
Raises are monotonic unless you pass `allowLower` — UI-driven callers ask for whatever the current view needs, and honouring every decrease made the reserve oscillate across preview↔render switches, paying a full kernel rebuild each time.
|
|
612
|
+
|
|
613
|
+
Callable at any point in the lifecycle:
|
|
614
|
+
|
|
615
|
+
- **Before `init()`** — recorded and applied during `init()`, after the device exists but before the stages are constructed, so they allocate at the raised size directly. The device gate cannot run without a device, so the return value here is the *request*, not the verdict.
|
|
616
|
+
- **After `init()`** — applied immediately, re-initialising the reserved GPU storage in place.
|
|
617
|
+
|
|
618
|
+
Either way the verdict arrives as a `reserved_render_size_changed` event (a plain string type, not an `EngineEvents` constant):
|
|
619
|
+
|
|
620
|
+
```js
|
|
621
|
+
engine.addEventListener('reserved_render_size_changed', e => console.log('reserve:', e.size));
|
|
622
|
+
engine.setReservedRenderResolution(4096);
|
|
623
|
+
await engine.init();
|
|
624
|
+
console.log(engine.getReservedRenderResolution()); // 4096, or 2048 if the device declined
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
597
629
|
### Memory Monitoring
|
|
598
630
|
|
|
599
631
|
Track GPU (VRAM) usage across the whole pipeline. Sizes are measured from live GPU resources (buffer `byteLength` + texture dimensions × format), so they are exact, not estimated.
|
|
@@ -612,6 +644,27 @@ The React app surfaces this as a `Memory: … | Peak: …` readout in the on-can
|
|
|
612
644
|
|
|
613
645
|
---
|
|
614
646
|
|
|
647
|
+
### Logging
|
|
648
|
+
|
|
649
|
+
Leveled, namespaced console output, shared with the engine's Web Workers. The default level is `info`, which hides per-mesh and per-texture detail; drop to `debug` to see it.
|
|
650
|
+
|
|
651
|
+
```js
|
|
652
|
+
import { Logger, createLogger, fmt, LOG_LEVELS } from 'rayzee';
|
|
653
|
+
|
|
654
|
+
Logger.setLevel('debug'); // 'silent' | 'error' | 'warn' | 'info' | 'debug'
|
|
655
|
+
Logger.getLevel();
|
|
656
|
+
Logger.isEnabled('debug'); // gate expensive message construction
|
|
657
|
+
Logger.only('bvh', 'gpu'); // restrict debug to these namespaces (implies setLevel('debug'))
|
|
658
|
+
Logger.only(); // clear the namespace filter
|
|
659
|
+
Logger.refresh(); // re-read the level from globals/localStorage
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
The chosen level persists in `localStorage` under `rayzeeLogLevel` (namespace filter: `rayzeeLogNamespaces`), so it survives a reload. The engine does not install a global itself — expose one from your host if you want console access without an import; the demo app does `globalThis.rayzee = { log: Logger, ... }`, which is what makes `rayzee.log.setLevel('debug')` work there.
|
|
663
|
+
|
|
664
|
+
`createLogger(namespace)` returns a channel with `error` / `warn` / `info` / `debug` plus `summary(headline, details)`, which prints one `info` line with the detail lines folded into a collapsed group. `fmt` holds the formatting helpers those summaries use — `n`, `ms`, `mb`, `px`, `count`, `list`. `LOG_LEVELS` is the name→severity map.
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
615
668
|
### Deterministic & Headless Rendering
|
|
616
669
|
|
|
617
670
|
For offline rendering, regression testing, and benchmarking — drive accumulation yourself instead of the rAF loop, and get bit-reproducible output.
|
|
@@ -645,6 +698,19 @@ WebGPU timestamp queries are the only true GPU metric here — `pipeline.getStat
|
|
|
645
698
|
|
|
646
699
|
`getKernelGPUTimings()` attributes each compute pass of the last resolved frame back to a wavefront kernel name. Durations are **summed per kernel across the frame**, so `extend` reports its whole per-frame cost over every bounce iteration, not one bounce. `unattributed` collects passes belonging to no registered kernel (other stages, denoisers), so `sum(kernels) + unattributed` reconciles with `total`.
|
|
647
700
|
|
|
701
|
+
Neither method can see the OIDN denoise — `oidn-web` submits on its own command encoders, outside
|
|
702
|
+
the stages three.js times. It carries its own profiler instead:
|
|
703
|
+
|
|
704
|
+
```js
|
|
705
|
+
engine.profileNextDenoise(); // arms one capture; per-denoise, not sticky
|
|
706
|
+
const { profile, runtime } = await engine.getDenoiseProfile();
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
`profile` is the per-layer GPU timing of that denoise; `runtime` reports the selected engine and
|
|
710
|
+
precision, the model, kernel capabilities, tile state and resource counts — which is the way to
|
|
711
|
+
confirm FP16 actually engaged on a given GPU rather than inferring it from the device's feature
|
|
712
|
+
list. Both need `timestamp-query`, and `getDenoiseProfile()` returns `null` when OIDN is not set up.
|
|
713
|
+
|
|
648
714
|
---
|
|
649
715
|
|
|
650
716
|
### Events
|
|
@@ -724,8 +790,12 @@ import {
|
|
|
724
790
|
MEMORY_CONSTANTS,
|
|
725
791
|
PRODUCTION_RENDER_CONFIG,
|
|
726
792
|
INTERACTIVE_RENDER_CONFIG,
|
|
793
|
+
MAX_RESERVABLE_RENDER_SIZE,
|
|
727
794
|
} from 'rayzee';
|
|
728
795
|
|
|
796
|
+
// Leveled/namespaced logging, shared with the workers
|
|
797
|
+
import { Logger, createLogger, fmt, LOG_LEVELS } from 'rayzee';
|
|
798
|
+
|
|
729
799
|
// Asset URL / cache namespace overrides
|
|
730
800
|
import { configureAssets, getAssetConfig } from 'rayzee';
|
|
731
801
|
|
|
@@ -767,7 +837,7 @@ import { setBindingAudit, getBindingAuditFindings, clearBindingAuditFindings } f
|
|
|
767
837
|
|
|
768
838
|
| Package | Purpose | Install needed? |
|
|
769
839
|
|---|---|---|
|
|
770
|
-
| `oidn-web` | Intel Open Image Denoise for high-quality final renders | Yes — `npm install oidn-web` |
|
|
840
|
+
| `oidn-web` | Intel Open Image Denoise for high-quality final renders | Yes — `npm install oidn-web` (**>=0.4.0**) |
|
|
771
841
|
| `onnxruntime-web` | AI-powered upscaling | No — loaded from CDN at runtime |
|
|
772
842
|
|
|
773
843
|
> **Note:** `onnxruntime-web` is also listed in `package.json` under `optionalDependencies` for bundler compatibility, but the engine's own runtime path always fetches it from a CDN (see `ortRuntimeUrl` / `ortWasmPaths` in [Configuring Assets](#configuring-assets-cdn-urls--cache-namespace)) rather than importing the installed package — installing it locally has no effect unless you also override those URLs to point at your own copy.
|
|
@@ -787,7 +857,7 @@ OIDN provides high-quality AI denoising for final renders. It runs automatically
|
|
|
787
857
|
```js
|
|
788
858
|
// After engine.init() completes
|
|
789
859
|
engine.denoisingManager.setOIDNEnabled(true);
|
|
790
|
-
engine.denoisingManager.setOIDNQuality('balance'); // 'fast' | 'balance' | 'high'
|
|
860
|
+
engine.denoisingManager.setOIDNQuality('balance'); // 'fast' | 'fast-clean' | 'balance' | 'high'
|
|
791
861
|
```
|
|
792
862
|
|
|
793
863
|
3. **Listen for progress** (optional)
|
|
@@ -801,11 +871,27 @@ OIDN provides high-quality AI denoising for final renders. It runs automatically
|
|
|
801
871
|
});
|
|
802
872
|
```
|
|
803
873
|
|
|
804
|
-
| Quality |
|
|
874
|
+
| Quality | Weights | Aux guide | Best for |
|
|
805
875
|
|---|---|---|---|
|
|
806
|
-
| `'fast'` |
|
|
807
|
-
| `'
|
|
808
|
-
| `'
|
|
876
|
+
| `'fast'` | 0.6 MB | point-sampled | Low sample counts — the default |
|
|
877
|
+
| `'fast-clean'` | 0.6 MB | accumulated | Converged frames, at `'fast'`'s cost |
|
|
878
|
+
| `'balance'` | 1.8 MB | accumulated | General use |
|
|
879
|
+
| `'high'` | 7.3 MB | accumulated | Final renders — used by `configureForMode('production')` |
|
|
880
|
+
|
|
881
|
+
`'fast'` and `'fast-clean'` are the same network size and cost the same to run; they differ only in
|
|
882
|
+
which auxiliary guide their weights expect. That makes the ordering **not** a simple quality ladder:
|
|
883
|
+
at 1 spp the accumulated guide has one sample, so `'fast-clean'` is fed something it was not trained
|
|
884
|
+
for and measures materially worse than `'fast'` (on a transmission-heavy scene, more than double the
|
|
885
|
+
RMSE). Once the guide converges it wins by a few percent — but `'high'` beats it there anyway. Pick
|
|
886
|
+
`'fast'` for previews and `'high'` for output; `'fast-clean'` is for the narrow case of denoising a
|
|
887
|
+
converged frame on a budget.
|
|
888
|
+
|
|
889
|
+
Denoise cost scales with frame area, and the tile tracks the frame so that a frame fitting inside one
|
|
890
|
+
tile pays no overlap padding — at 1024x1024 that is roughly 1.8x faster than tiling it. A cap
|
|
891
|
+
(default 1024) bounds the one-time activation allocation, so larger frames tile and stay
|
|
892
|
+
memory-bounded. Raise or lower it with
|
|
893
|
+
`engine.denoisingManager.denoiser.updateConfiguration({ tileSize: 2048 })`; the effective tile is
|
|
894
|
+
`min( max( width, height ), tileSize )`.
|
|
809
895
|
|
|
810
896
|
> **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.
|
|
811
897
|
|
|
@@ -851,21 +937,14 @@ Then load it via a script tag or import map instead:
|
|
|
851
937
|
<script type="importmap">
|
|
852
938
|
{
|
|
853
939
|
"imports": {
|
|
854
|
-
"oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.
|
|
940
|
+
"oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.4.0/dist/oidn.js"
|
|
855
941
|
}
|
|
856
942
|
}
|
|
857
943
|
</script>
|
|
858
944
|
```
|
|
859
945
|
|
|
860
|
-
**OIDN
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
```diff
|
|
864
|
-
- "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.3.5/+esm"
|
|
865
|
-
+ "oidn-web": "https://cdn.jsdelivr.net/npm/oidn-web@0.3.5/dist/oidn.js"
|
|
866
|
-
```
|
|
867
|
-
|
|
868
|
-
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.
|
|
946
|
+
**OIDN from a CDN**
|
|
947
|
+
Load the self-bundled `/dist/oidn.js` path rather than `/+esm` or `esm.sh` — it is a single pre-bundled ESM with no external imports, and it is the path this engine is tested against.
|
|
869
948
|
|
|
870
949
|
**Black screen / "WebGPU not supported"**
|
|
871
950
|
Your browser may not support WebGPU. Use Chrome 113+, Edge 113+, Safari 18+, or Firefox 141+. Ensure you're on HTTPS or localhost.
|