rayzee 7.21.0 → 7.22.1
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 +75 -4
- package/dist/rayzee.es.js +634 -604
- package/dist/rayzee.umd.js +52 -52
- package/package.json +1 -1
- package/src/EngineDefaults.js +6 -0
- package/src/PathTracerApp.js +76 -0
- package/src/Processor/BVHBuilder.js +4 -6
- package/src/Processor/KernelManager.js +17 -70
- package/src/Processor/QueueManager.js +4 -3
- package/src/Processor/SceneProcessor.js +12 -4
- package/src/Processor/TreeletOptimizer.js +0 -7
- package/src/RenderSettings.js +41 -6
- package/src/Stages/NormalDepth.js +16 -13
- package/src/Stages/PathTracer.js +29 -31
- package/src/TSL/BVHTraversal.js +1 -67
- package/src/TSL/CameraRay.js +141 -0
- package/src/TSL/DebugKernel.js +4 -5
- package/src/TSL/ExtendKernel.js +11 -3
- package/src/TSL/FinalWriteKernel.js +1 -14
- package/src/TSL/GenerateKernel.js +11 -8
- package/src/TSL/SortGlobalKernels.js +52 -7
- package/src/managers/DenoisingManager.js +13 -0
- package/src/managers/EnvironmentManager.js +6 -3
- package/src/managers/UniformManager.js +8 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic —
|
|
|
22
22
|
- [Configuring Assets (CDN URLs & cache namespace)](#configuring-assets-cdn-urls--cache-namespace)
|
|
23
23
|
- [PathTracerApp](#pathtracerapp)
|
|
24
24
|
- [engine.cameraManager](#enginecameramanager)
|
|
25
|
+
- [Camera Projection (360° Panorama)](#camera-projection-360-panorama)
|
|
25
26
|
- [engine.lightManager](#enginelightmanager)
|
|
26
27
|
- [engine.animationManager](#engineanimationmanager)
|
|
27
28
|
- [Materials](#materials)
|
|
@@ -31,6 +32,7 @@ A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic —
|
|
|
31
32
|
- [engine.transformManager](#enginetransformmanager)
|
|
32
33
|
- [Output Methods](#output-methods)
|
|
33
34
|
- [Memory Monitoring](#memory-monitoring)
|
|
35
|
+
- [Deterministic & Headless Rendering](#deterministic--headless-rendering)
|
|
34
36
|
- [Events](#events)
|
|
35
37
|
- [Advanced: Custom Pipeline Stages](#advanced-custom-pipeline-stages)
|
|
36
38
|
- [All Exports](#all-exports)
|
|
@@ -367,7 +369,7 @@ Key settings:
|
|
|
367
369
|
| `environmentIntensity` | `number` | 1.0 | Environment light strength |
|
|
368
370
|
| `environmentRotation` | `number` | 270 | Environment Y-rotation (degrees) |
|
|
369
371
|
| `showBackground` | `boolean` | true | Show the environment as a visible backdrop for camera-miss rays (vs. a solid/transparent background) |
|
|
370
|
-
| `samplingTechnique` | `number` |
|
|
372
|
+
| `samplingTechnique` | `number` | 2 | Sampler: `0` PCG, `1` scrambled Halton, `2` Owen-scrambled Sobol |
|
|
371
373
|
| `fireflyThreshold` | `number` | 3.0 | Firefly clamping threshold |
|
|
372
374
|
| `transmissiveBounces` | `number` | 5 | Max bounces for transmissive materials |
|
|
373
375
|
| `maxSubsurfaceSteps` | `number` | 8 | Max random-walk steps for subsurface scattering (raised to 64 by `configureForMode('production')`) |
|
|
@@ -381,12 +383,15 @@ Key settings:
|
|
|
381
383
|
| `renderMode` | `number` | 0 | Internal preview(0)/production(1) flag driving accumulation & ASVGF behavior — normally set via `configureForMode()`, not written directly |
|
|
382
384
|
| `visMode` | `number` | 0 | Debug visualization mode (0 = off) |
|
|
383
385
|
| `environmentMode` | `string` | 'hdri' | Sky mode: `'hdri'` \| `'procedural'` \| `'gradient'` \| `'color'` — not routed through `engine.settings`; use `engine.environmentManager.setMode()` instead |
|
|
386
|
+
| `cameraProjection` | `string` | 'perspective' | `'perspective'` \| `'equirectangular'` — see [Camera Projection](#camera-projection-360-panorama) |
|
|
387
|
+
| `panoramaLonRange` | `[number, number]` | `[-180, 180]` | Panorama longitude sweep, degrees, left→right |
|
|
388
|
+
| `panoramaLatRange` | `[number, number]` | `[-90, 90]` | Panorama latitude sweep, degrees, bottom→top |
|
|
389
|
+
| `panoramaLevelHorizon` | `boolean` | true | Yaw-only panorama basis, so orbit pitch/roll can't tilt the horizon |
|
|
384
390
|
| `useAdaptiveSampling` | `boolean` | true | Whole-frame early-stop once convergence reaches `adaptiveStopFraction` |
|
|
385
|
-
| `noiseThreshold` | `number` | 0.02 |
|
|
386
|
-
| `darkNoiseFloor` | `number` | 0.003 | Absolute-error floor so dim pixels don't stall convergence |
|
|
391
|
+
| `noiseThreshold` | `number` | 0.02 | √-luminance-normalized per-pixel noise below which a pixel counts as converged |
|
|
387
392
|
| `adaptiveMinSamples` | `number` | 8 | Minimum samples before adaptive sampling can trigger |
|
|
388
393
|
| `adaptiveStopFraction` | `number` | 0.95 | Fraction of pixels that must converge before the frame retires |
|
|
389
|
-
| `usePixelFreeze` | `boolean` |
|
|
394
|
+
| `usePixelFreeze` | `boolean` | true | Per-pixel freeze (Tier-2): skip individually-converged pixels via active-list compaction |
|
|
390
395
|
| `pixelFreezeThreshold` | `number` | 0.02 | Relative-error threshold for a pixel to become a freeze candidate |
|
|
391
396
|
| `pixelFreezeStability` | `number` | 8 | Consecutive candidate frames required before a pixel freezes |
|
|
392
397
|
|
|
@@ -417,6 +422,30 @@ engine.cameraManager.setAutoFocusMode(mode) // 'auto' | 'manual'
|
|
|
417
422
|
engine.cameraManager.setAFScreenPoint(x, y) // Set normalized AF screen point (0-1)
|
|
418
423
|
```
|
|
419
424
|
|
|
425
|
+
### Camera Projection (360° Panorama)
|
|
426
|
+
|
|
427
|
+
Two camera models live behind the `cameraProjection` setting. Both branches are compiled into the same kernel, so switching writes a uniform and resets accumulation — it never recompiles WGSL.
|
|
428
|
+
|
|
429
|
+
```js
|
|
430
|
+
engine.settings.set('cameraProjection', 'equirectangular'); // 'perspective' (default) | 'equirectangular'
|
|
431
|
+
|
|
432
|
+
// Optional: crop the sweep. Degrees, [min, max].
|
|
433
|
+
engine.settings.set('panoramaLonRange', [-90, 90]); // VR180
|
|
434
|
+
engine.settings.set('panoramaLatRange', [0, 90]); // upper hemisphere only
|
|
435
|
+
engine.settings.set('panoramaLevelHorizon', true); // default — orbit pitch won't tilt the panorama
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
The mapping puts camera-forward at the image centre, the zenith at the top row, and yaw-right at increasing u. Full-sphere output is 2:1 — **size the canvas accordingly** (`engine.setCanvasSize(w, w / 2)`); the engine renders whatever aspect you give it and will stretch the sphere otherwise. A cropped range changes the natural aspect to match `lonRange / latRange`.
|
|
439
|
+
|
|
440
|
+
Depth of field still works: the lens plane is built from each ray's own frame, not the camera's, so bokeh stays round across the whole sweep.
|
|
441
|
+
|
|
442
|
+
Two features are incompatible with a non-frustum camera and the engine switches them off for you when panorama is enabled:
|
|
443
|
+
|
|
444
|
+
- **ASVGF** falls back to the `edgeaware` denoiser — ASVGF's motion vectors unproject through the projection matrix, which is meaningless when every pixel is its own direction.
|
|
445
|
+
- **Auto-focus** switches to `'manual'` — it raycasts via `Raycaster.setFromCamera`, which only understands a frustum.
|
|
446
|
+
|
|
447
|
+
Read the outcome back rather than duplicating the rule (`engine.denoisingManager.denoiserStrategy`, `engine.cameraManager.autoFocusMode`). Neither is restored automatically when you switch back to `'perspective'`.
|
|
448
|
+
|
|
420
449
|
### engine.lightManager
|
|
421
450
|
|
|
422
451
|
Light CRUD, visual helpers, and GPU sync.
|
|
@@ -496,6 +525,7 @@ Denoiser strategy, ASVGF, OIDN, upscaler, and auto-exposure.
|
|
|
496
525
|
```js
|
|
497
526
|
// Strategy
|
|
498
527
|
engine.denoisingManager.setStrategy('asvgf', 'medium') // 'none' | 'asvgf' | 'edgeaware'
|
|
528
|
+
engine.denoisingManager.denoiserStrategy // read back the active strategy (derived from stage state)
|
|
499
529
|
engine.denoisingManager.setASVGFEnabled(true, 'medium')
|
|
500
530
|
engine.denoisingManager.applyASVGFPreset('high') // 'low' | 'medium' | 'high'
|
|
501
531
|
engine.denoisingManager.setAutoExposure(true)
|
|
@@ -582,6 +612,41 @@ The React app surfaces this as a `Memory: … | Peak: …` readout in the on-can
|
|
|
582
612
|
|
|
583
613
|
---
|
|
584
614
|
|
|
615
|
+
### Deterministic & Headless Rendering
|
|
616
|
+
|
|
617
|
+
For offline rendering, regression testing, and benchmarking — drive accumulation yourself instead of the rAF loop, and get bit-reproducible output.
|
|
618
|
+
|
|
619
|
+
```js
|
|
620
|
+
engine.setDeterministicMode(true); // pin everything wall-clock- or readback-dependent
|
|
621
|
+
const samples = await engine.renderFrames(256, {
|
|
622
|
+
reset: true, // restart accumulation from sample 0
|
|
623
|
+
yieldEvery: 8, // yield to the event loop every N passes (0 disables)
|
|
624
|
+
onProgress: n => console.log(n),
|
|
625
|
+
});
|
|
626
|
+
const blob = await engine.screenshot();
|
|
627
|
+
engine.setDeterministicMode(false); // restore the previous configuration
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
The RNG is already pure — `hash(pixel, rayIndex, frame)`, no clock, no `Math.random()` in any shader. What varies run to run is *which* uniforms and dispatch grids are live on frame k, so `setDeterministicMode` disables adaptive sampling, per-pixel freeze, the readback-driven per-bounce early exit and dynamic dispatch sizing, interaction mode, auto-focus, and auto-exposure, and pins the sampler's seed axis to the accumulation frame. It also forces `renderLimitMode` to `'frames'` — a wall-clock render limit retires at a run-dependent sample count. It leaves the rAF loop stopped; `renderFrames` is the drive.
|
|
631
|
+
|
|
632
|
+
- `engine.isDeterministic` — whether output is currently bit-reproducible.
|
|
633
|
+
- `setDeterministicMode(true, { pinDispatch: false })` keeps the two readback-driven dispatch heuristics active. Output is then *not* reproducible; this exists so performance measurements reflect shipping behaviour rather than a configuration production never runs.
|
|
634
|
+
- `renderFrames` awaits `engine.stages.pathTracer.blueNoiseReady` first — until the STBN atlases land the sampler reads a constant-0.5 placeholder that bakes permanently into the accumulation buffer. It raises `maxSamples` if needed, and throws if something retires the render early.
|
|
635
|
+
|
|
636
|
+
#### GPU timing
|
|
637
|
+
|
|
638
|
+
```js
|
|
639
|
+
engine.enableGPUTiming(true); // off by default — the queries themselves cost time
|
|
640
|
+
const { compute, render, total } = await engine.getGPUTimings();
|
|
641
|
+
const { kernels, unattributed, frame } = await engine.getKernelGPUTimings();
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
WebGPU timestamp queries are the only true GPU metric here — `pipeline.getStats()` times command *encoding* on the CPU and stays flat while GPU cost doubles. Both methods return `null` when the device lacks `timestamp-query` or timing was never enabled.
|
|
645
|
+
|
|
646
|
+
`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
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
585
650
|
### Events
|
|
586
651
|
|
|
587
652
|
Subscribe to engine lifecycle events via `addEventListener`:
|
|
@@ -685,6 +750,12 @@ import {
|
|
|
685
750
|
|
|
686
751
|
// VRAM accounting (VRAMTracker is also reachable as engine.vram)
|
|
687
752
|
import { VRAMTracker, bufferBytes, textureBytes } from 'rayzee';
|
|
753
|
+
|
|
754
|
+
// Dev-only: texture-binding aliasing guard. Two TextureNodes still holding the default
|
|
755
|
+
// EmptyTexture when a kernel is first compiled can share one GPU binding — nothing throws,
|
|
756
|
+
// the aliased node just reads someone else's texture. Off by default; costs a per-stage
|
|
757
|
+
// snapshot when on. Intended for test harnesses, not production.
|
|
758
|
+
import { setBindingAudit, getBindingAuditFindings, clearBindingAuditFindings } from 'rayzee';
|
|
688
759
|
```
|
|
689
760
|
|
|
690
761
|
## Browser Requirements
|