rayzee 4.8.4 → 4.8.7
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 +141 -82
- package/dist/assets/AIUpscalerWorker-D58dcMrY.js +2 -0
- package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +1 -0
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js +2 -0
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js.map +1 -0
- package/dist/assets/BVHSubtreeWorker-C02ZWVeG.js +2 -0
- package/dist/assets/BVHSubtreeWorker-C02ZWVeG.js.map +1 -0
- package/dist/assets/BVHWorker-DobVXMda.js +2 -0
- package/dist/assets/BVHWorker-DobVXMda.js.map +1 -0
- package/dist/assets/CDFWorker-2MoynL4F.js +2 -0
- package/dist/assets/CDFWorker-2MoynL4F.js.map +1 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js +2 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -0
- package/dist/rayzee.es.js +922 -871
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +43 -43
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Passes/AIUpscaler.js +1 -2
- package/src/PathTracerApp.js +36 -3
- package/src/Processor/AssetLoader.js +2 -2
- package/src/Processor/BVHBuilder.js +1 -2
- package/src/Processor/EquirectHDRInfo.js +1 -2
- package/src/Processor/LightSerializer.js +26 -7
- package/src/Processor/ParallelBVHBuilder.js +8 -9
- package/src/Processor/SceneProcessor.js +3 -4
- package/src/Processor/TextureCreator.js +1 -2
- package/src/Processor/Workers/AIUpscalerWorker.js +21 -7
- package/src/README.md +1 -2
- package/src/Stages/PathTracer.js +7 -6
- package/src/TSL/LightsCore.js +12 -2
- package/src/TSL/LightsSampling.js +8 -6
- package/src/managers/LightManager.js +1 -1
- package/src/managers/UniformManager.js +2 -2
- package/src/Processor/createWorker.js +0 -38
package/package.json
CHANGED
package/src/Passes/AIUpscaler.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { EventDispatcher, ACESFilmicToneMapping } from 'three';
|
|
2
|
-
import { createWorker } from '../Processor/createWorker.js';
|
|
3
2
|
import { TONE_MAP_FNS, SRGB_GAMMA, applySaturation } from '../Processor/ToneMapCPU.js';
|
|
4
3
|
|
|
5
4
|
|
|
@@ -138,7 +137,7 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
138
137
|
// Create worker on first use
|
|
139
138
|
if ( ! this._worker ) {
|
|
140
139
|
|
|
141
|
-
this._worker =
|
|
140
|
+
this._worker = new Worker(
|
|
142
141
|
new URL( '../Processor/Workers/AIUpscalerWorker.js', import.meta.url ),
|
|
143
142
|
{ type: 'module' }
|
|
144
143
|
);
|
package/src/PathTracerApp.js
CHANGED
|
@@ -54,17 +54,16 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* @param {HTMLCanvasElement} canvas - Canvas element for rendering
|
|
57
|
-
* @param {HTMLCanvasElement} [denoiserCanvas] - Optional canvas for OIDN denoiser output
|
|
58
57
|
* @param {Object} [options] - Engine options
|
|
59
58
|
* @param {boolean} [options.autoResize=true] - Automatically listen for window resize events
|
|
60
59
|
* @param {HTMLElement} [options.statsContainer] - DOM element to append the stats panel to (defaults to document.body)
|
|
61
60
|
*/
|
|
62
|
-
constructor( canvas,
|
|
61
|
+
constructor( canvas, options = {} ) {
|
|
63
62
|
|
|
64
63
|
super();
|
|
65
64
|
|
|
66
65
|
this.canvas = canvas;
|
|
67
|
-
this.denoiserCanvas =
|
|
66
|
+
this.denoiserCanvas = null;
|
|
68
67
|
this._autoResize = options.autoResize !== false;
|
|
69
68
|
this._statsContainer = options.statsContainer || null;
|
|
70
69
|
|
|
@@ -295,6 +294,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
295
294
|
// ── Managers ──
|
|
296
295
|
this.cameraManager = new CameraManager( this._camera, this._controls, this._interactionManager );
|
|
297
296
|
this.lightManager = new LightManager( this.scene, this._sceneHelpers, this.stages.pathTracer );
|
|
297
|
+
this._createDenoiserCanvas();
|
|
298
298
|
this._setupDenoisingManager();
|
|
299
299
|
this._setupOverlayManager();
|
|
300
300
|
|
|
@@ -640,6 +640,14 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
640
640
|
this.overlayManager?.dispose();
|
|
641
641
|
this._sceneHelpers?.clear();
|
|
642
642
|
this.denoisingManager?.dispose();
|
|
643
|
+
|
|
644
|
+
if ( this.denoiserCanvas?.parentNode ) {
|
|
645
|
+
|
|
646
|
+
this.denoiserCanvas.parentNode.removeChild( this.denoiserCanvas );
|
|
647
|
+
this.denoiserCanvas = null;
|
|
648
|
+
|
|
649
|
+
}
|
|
650
|
+
|
|
643
651
|
this.pipeline?.dispose();
|
|
644
652
|
this._interactionManager?.dispose();
|
|
645
653
|
this._controls?.dispose();
|
|
@@ -1102,6 +1110,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1102
1110
|
this._camera.aspect = width / height;
|
|
1103
1111
|
this._camera.updateProjectionMatrix();
|
|
1104
1112
|
|
|
1113
|
+
if ( this.denoiserCanvas ) {
|
|
1114
|
+
|
|
1115
|
+
this.denoiserCanvas.style.width = `${width}px`;
|
|
1116
|
+
this.denoiserCanvas.style.height = `${height}px`;
|
|
1117
|
+
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1105
1120
|
// Overlay helpers always render at display resolution
|
|
1106
1121
|
const dpr = window.devicePixelRatio || 1;
|
|
1107
1122
|
this.overlayManager?.setSize(
|
|
@@ -1955,6 +1970,24 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1955
1970
|
|
|
1956
1971
|
}
|
|
1957
1972
|
|
|
1973
|
+
_createDenoiserCanvas() {
|
|
1974
|
+
|
|
1975
|
+
if ( this.denoiserCanvas ) return; // guard against double init
|
|
1976
|
+
|
|
1977
|
+
const parent = this.canvas.parentNode;
|
|
1978
|
+
if ( ! parent ) return; // headless / detached canvas — skip
|
|
1979
|
+
|
|
1980
|
+
const dc = document.createElement( 'canvas' );
|
|
1981
|
+
dc.width = this.canvas.width;
|
|
1982
|
+
dc.height = this.canvas.height;
|
|
1983
|
+
dc.style.width = `${this.canvas.clientWidth}px`;
|
|
1984
|
+
dc.style.height = `${this.canvas.clientHeight}px`;
|
|
1985
|
+
|
|
1986
|
+
parent.insertBefore( dc, this.canvas );
|
|
1987
|
+
this.denoiserCanvas = dc;
|
|
1988
|
+
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1958
1991
|
_setupDenoisingManager() {
|
|
1959
1992
|
|
|
1960
1993
|
this.denoisingManager = new DenoisingManager( {
|
|
@@ -1200,7 +1200,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1200
1200
|
|
|
1201
1201
|
if ( object.isRectAreaLight && ! visitedAreaLights.includes( object.uuid ) ) {
|
|
1202
1202
|
|
|
1203
|
-
object.
|
|
1203
|
+
visitedAreaLights.push( object.uuid );
|
|
1204
1204
|
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
@@ -1214,7 +1214,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1214
1214
|
|
|
1215
1215
|
const light = new RectAreaLight(
|
|
1216
1216
|
new Color( ...userData.color ),
|
|
1217
|
-
userData.intensity
|
|
1217
|
+
userData.intensity,
|
|
1218
1218
|
userData.width,
|
|
1219
1219
|
userData.height
|
|
1220
1220
|
);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { TreeletOptimizer } from './TreeletOptimizer.js';
|
|
2
2
|
import { ReinsertionOptimizer } from './ReinsertionOptimizer.js';
|
|
3
|
-
import { createWorker } from './createWorker.js';
|
|
4
3
|
|
|
5
4
|
// Inline copy of TRIANGLE_DATA_LAYOUT (mirrors Constants.js).
|
|
6
5
|
// Cannot import Constants.js because BVHBuilder runs inside BVHWorker
|
|
@@ -400,7 +399,7 @@ export class BVHBuilder {
|
|
|
400
399
|
|
|
401
400
|
try {
|
|
402
401
|
|
|
403
|
-
const worker =
|
|
402
|
+
const worker = new Worker(
|
|
404
403
|
new URL( './Workers/BVHWorker.js', import.meta.url ),
|
|
405
404
|
{ type: 'module' }
|
|
406
405
|
);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DataUtils, HalfFloatType, FloatType } from 'three';
|
|
2
|
-
import { createWorker } from './createWorker.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Binary search to find the closest index
|
|
@@ -185,7 +184,7 @@ export class EquirectHDRInfo {
|
|
|
185
184
|
// Reuse worker across calls; create on first use
|
|
186
185
|
if ( ! this._worker ) {
|
|
187
186
|
|
|
188
|
-
this._worker =
|
|
187
|
+
this._worker = new Worker(
|
|
189
188
|
new URL( './Workers/CDFWorker.js', import.meta.url ),
|
|
190
189
|
{ type: 'module' }
|
|
191
190
|
);
|
|
@@ -65,21 +65,35 @@ export class LightSerializer {
|
|
|
65
65
|
|
|
66
66
|
if ( light.intensity <= 0.0 ) return; // Skip zero intensity lights
|
|
67
67
|
|
|
68
|
-
// Convert world position to direction
|
|
69
68
|
light.updateMatrixWorld();
|
|
70
69
|
const position = light.getWorldPosition( new Vector3() );
|
|
71
70
|
|
|
71
|
+
// Compute direction toward the light source.
|
|
72
|
+
// Three.js convention: light shines from position toward target.
|
|
73
|
+
// For shadow rays we need the reverse: direction from target toward position.
|
|
74
|
+
let direction;
|
|
75
|
+
if ( light.target ) {
|
|
76
|
+
|
|
77
|
+
light.target.updateMatrixWorld();
|
|
78
|
+
const targetPos = light.target.getWorldPosition( new Vector3() );
|
|
79
|
+
direction = position.sub( targetPos ).normalize();
|
|
80
|
+
|
|
81
|
+
} else {
|
|
82
|
+
|
|
83
|
+
direction = position.normalize();
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
// Calculate importance for sorting
|
|
73
88
|
const importance = this.calculateLightImportance( light, 'directional' );
|
|
74
89
|
|
|
75
90
|
// Get angle parameter from light (default to 0 for sharp shadows)
|
|
76
|
-
// You can add this as a custom property to your DirectionalLight
|
|
77
91
|
const angle = light.userData.angle || light.angle || 0.0; // In radians
|
|
78
92
|
|
|
79
93
|
// Store in cache with importance
|
|
80
94
|
this.directionalLightCache.push( {
|
|
81
95
|
data: [
|
|
82
|
-
|
|
96
|
+
direction.x, direction.y, direction.z, // direction toward light (3)
|
|
83
97
|
light.color.r, light.color.g, light.color.b, // color (3)
|
|
84
98
|
light.intensity, // intensity (1)
|
|
85
99
|
angle // angular diameter in radians (1)
|
|
@@ -139,7 +153,9 @@ export class LightSerializer {
|
|
|
139
153
|
data: [
|
|
140
154
|
position.x, position.y, position.z, // position (3)
|
|
141
155
|
light.color.r, light.color.g, light.color.b, // color (3)
|
|
142
|
-
light.intensity // intensity (1)
|
|
156
|
+
light.intensity, // intensity (1)
|
|
157
|
+
light.distance || 0.0, // cutoff distance (0 = infinite) (1)
|
|
158
|
+
light.decay !== undefined ? light.decay : 2.0 // decay exponent (1)
|
|
143
159
|
],
|
|
144
160
|
importance: importance,
|
|
145
161
|
light: light
|
|
@@ -167,7 +183,10 @@ export class LightSerializer {
|
|
|
167
183
|
direction.x, direction.y, direction.z, // direction (3)
|
|
168
184
|
light.color.r, light.color.g, light.color.b, // color (3)
|
|
169
185
|
light.intensity, // intensity (1)
|
|
170
|
-
light.angle || Math.PI / 4 // cone half-angle in radians (1)
|
|
186
|
+
light.angle || Math.PI / 4, // cone half-angle in radians (1)
|
|
187
|
+
light.penumbra || 0.0, // penumbra [0,1] (1)
|
|
188
|
+
light.distance || 0.0, // cutoff distance (0 = infinite) (1)
|
|
189
|
+
light.decay !== undefined ? light.decay : 2.0 // decay exponent (1)
|
|
171
190
|
],
|
|
172
191
|
importance: importance,
|
|
173
192
|
light: light
|
|
@@ -244,8 +263,8 @@ export class LightSerializer {
|
|
|
244
263
|
// Divide flat array lengths by per-light stride to get actual light counts
|
|
245
264
|
const directionalCount = Math.floor( this.lightData.directional.length / 8 );
|
|
246
265
|
const areaCount = Math.floor( this.lightData.rectArea.length / 13 );
|
|
247
|
-
const pointCount = Math.floor( this.lightData.point.length /
|
|
248
|
-
const spotCount = Math.floor( this.lightData.spot.length /
|
|
266
|
+
const pointCount = Math.floor( this.lightData.point.length / 9 );
|
|
267
|
+
const spotCount = Math.floor( this.lightData.spot.length / 14 );
|
|
249
268
|
|
|
250
269
|
// Update light counts in shader defines
|
|
251
270
|
material.defines.MAX_DIRECTIONAL_LIGHTS = directionalCount;
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
* a cycle that Vite cannot resolve.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createWorker } from './createWorker.js';
|
|
11
|
-
|
|
12
10
|
const FPT = 32; // FLOATS_PER_TRIANGLE
|
|
13
11
|
const PARALLEL_THRESHOLD = 50000;
|
|
14
12
|
const MAX_PARALLEL_WORKERS = 8;
|
|
@@ -36,11 +34,6 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
|
|
|
36
34
|
|
|
37
35
|
return new Promise( ( resolve, reject ) => {
|
|
38
36
|
|
|
39
|
-
const coordinatorWorker = createWorker(
|
|
40
|
-
new URL( './Workers/BVHWorker.js', import.meta.url ),
|
|
41
|
-
{ type: 'module' }
|
|
42
|
-
);
|
|
43
|
-
|
|
44
37
|
try {
|
|
45
38
|
|
|
46
39
|
// Allocate SharedArrayBuffers
|
|
@@ -54,6 +47,12 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
|
|
|
54
47
|
const sharedMortonCodes = new SharedArrayBuffer( triangleCount * 4 );
|
|
55
48
|
const sharedReorderBuffer = new SharedArrayBuffer( triangleCount * FPT * 4 );
|
|
56
49
|
|
|
50
|
+
// Phase 1: Coordinator worker
|
|
51
|
+
const coordinatorWorker = new Worker(
|
|
52
|
+
new URL( './Workers/BVHWorker.js', import.meta.url ),
|
|
53
|
+
{ type: 'module' }
|
|
54
|
+
);
|
|
55
|
+
|
|
57
56
|
let phase1Stats = null;
|
|
58
57
|
const allWorkers = [ coordinatorWorker ];
|
|
59
58
|
|
|
@@ -300,7 +299,7 @@ function handlePhase2(
|
|
|
300
299
|
const bucket = workerTaskBuckets[ w ];
|
|
301
300
|
if ( bucket.length === 0 ) continue;
|
|
302
301
|
|
|
303
|
-
const subtreeWorker =
|
|
302
|
+
const subtreeWorker = new Worker(
|
|
304
303
|
new URL( './Workers/BVHSubtreeWorker.js', import.meta.url ),
|
|
305
304
|
{ type: 'module' }
|
|
306
305
|
);
|
|
@@ -408,7 +407,7 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
|
|
|
408
407
|
|
|
409
408
|
try {
|
|
410
409
|
|
|
411
|
-
const worker =
|
|
410
|
+
const worker = new Worker(
|
|
412
411
|
new URL( './Workers/BVHWorker.js', import.meta.url ),
|
|
413
412
|
{ type: 'module' }
|
|
414
413
|
);
|
|
@@ -11,7 +11,6 @@ import { EmissiveTriangleBuilder } from './EmissiveTriangleBuilder.js';
|
|
|
11
11
|
import { updateLoading } from '../Processor/utils.js';
|
|
12
12
|
import { BuildTimer } from './BuildTimer.js';
|
|
13
13
|
import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
14
|
-
import { createWorker } from './createWorker.js';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* SceneProcessor - Processes scene geometry into GPU-ready data:
|
|
@@ -666,7 +665,7 @@ export class SceneProcessor {
|
|
|
666
665
|
// Spin up the pool
|
|
667
666
|
for ( let i = 0; i < poolSize; i ++ ) {
|
|
668
667
|
|
|
669
|
-
const worker =
|
|
668
|
+
const worker = new Worker(
|
|
670
669
|
new URL( './Workers/BVHWorker.js', import.meta.url ),
|
|
671
670
|
{ type: 'module' }
|
|
672
671
|
);
|
|
@@ -1137,7 +1136,7 @@ export class SceneProcessor {
|
|
|
1137
1136
|
// Lazy-create worker
|
|
1138
1137
|
if ( ! this._refitWorker ) {
|
|
1139
1138
|
|
|
1140
|
-
this._refitWorker =
|
|
1139
|
+
this._refitWorker = new Worker(
|
|
1141
1140
|
new URL( './Workers/BVHRefitWorker.js', import.meta.url ),
|
|
1142
1141
|
{ type: 'module' }
|
|
1143
1142
|
);
|
|
@@ -1526,7 +1525,7 @@ export class SceneProcessor {
|
|
|
1526
1525
|
( entry.triOffset + entry.triCount ) * FPT
|
|
1527
1526
|
);
|
|
1528
1527
|
|
|
1529
|
-
const worker =
|
|
1528
|
+
const worker = new Worker(
|
|
1530
1529
|
new URL( './Workers/BVHWorker.js', import.meta.url ),
|
|
1531
1530
|
{ type: 'module' }
|
|
1532
1531
|
);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace } from "three";
|
|
2
2
|
import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX } from '../EngineDefaults.js';
|
|
3
|
-
import { createWorker } from './createWorker.js';
|
|
4
3
|
|
|
5
4
|
// Canvas pooling for efficient reuse of canvas elements
|
|
6
5
|
class CanvasPool {
|
|
@@ -596,7 +595,7 @@ export class TextureCreator {
|
|
|
596
595
|
|
|
597
596
|
try {
|
|
598
597
|
|
|
599
|
-
const worker =
|
|
598
|
+
const worker = new Worker(
|
|
600
599
|
new URL( './Workers/TexturesWorker.js', import.meta.url ),
|
|
601
600
|
{ type: 'module' }
|
|
602
601
|
);
|
|
@@ -14,11 +14,24 @@
|
|
|
14
14
|
* { type: 'error', message, id? }
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Loaded lazily via CDN to avoid bundling the 69 MB onnxruntime-web package
|
|
18
|
+
const ORT_CDN_URL = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs';
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
let ort = null;
|
|
21
|
+
|
|
22
|
+
async function getOrt() {
|
|
23
|
+
|
|
24
|
+
if ( ort ) return ort;
|
|
25
|
+
|
|
26
|
+
ort = await import( /* @vite-ignore */ ORT_CDN_URL );
|
|
27
|
+
|
|
28
|
+
// WASM paths for CDN delivery — WebGPU EP still uses WASM for lightweight shape ops
|
|
29
|
+
ort.env.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/';
|
|
30
|
+
ort.env.logLevel = 'error';
|
|
31
|
+
|
|
32
|
+
return ort;
|
|
33
|
+
|
|
34
|
+
}
|
|
22
35
|
|
|
23
36
|
const IDB_NAME = 'ai-upscaler-models';
|
|
24
37
|
const IDB_STORE = 'models';
|
|
@@ -127,9 +140,9 @@ async function loadModel( url, sessionOptions ) {
|
|
|
127
140
|
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
const modelBuffer = await fetchModel( url );
|
|
143
|
+
const [ modelBuffer, ortLib ] = await Promise.all( [ fetchModel( url ), getOrt() ] );
|
|
131
144
|
|
|
132
|
-
session = await
|
|
145
|
+
session = await ortLib.InferenceSession.create( modelBuffer, sessionOptions );
|
|
133
146
|
currentModelUrl = url;
|
|
134
147
|
|
|
135
148
|
// Detect GPU and recommend tile size based on device type
|
|
@@ -170,9 +183,10 @@ async function loadModel( url, sessionOptions ) {
|
|
|
170
183
|
|
|
171
184
|
async function inferTile( tileData, width, height, id ) {
|
|
172
185
|
|
|
186
|
+
const ortLib = await getOrt();
|
|
173
187
|
const inputName = session.inputNames[ 0 ];
|
|
174
188
|
const outputName = session.outputNames[ 0 ];
|
|
175
|
-
const inputTensor = new
|
|
189
|
+
const inputTensor = new ortLib.Tensor( 'float32', tileData, [ 1, 3, height, width ] );
|
|
176
190
|
|
|
177
191
|
const results = await session.run( { [ inputName ]: inputTensor } );
|
|
178
192
|
const outputData = results[ outputName ].data;
|
package/src/README.md
CHANGED
|
@@ -20,13 +20,12 @@ engine.animate();
|
|
|
20
20
|
## Constructor
|
|
21
21
|
|
|
22
22
|
```js
|
|
23
|
-
new PathTracerApp(canvas,
|
|
23
|
+
new PathTracerApp(canvas, options?)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
| Param | Type | Description |
|
|
27
27
|
|-------|------|-------------|
|
|
28
28
|
| `canvas` | `HTMLCanvasElement` | Render target |
|
|
29
|
-
| `denoiserCanvas` | `HTMLCanvasElement \| null` | Optional canvas for OIDN output |
|
|
30
29
|
| `options.autoResize` | `boolean` | Auto-listen for window resize (default `true`) |
|
|
31
30
|
|
|
32
31
|
## API Reference
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -26,8 +26,8 @@ import { LightSerializer } from '../Processor/LightSerializer';
|
|
|
26
26
|
// Constants
|
|
27
27
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
28
28
|
|
|
29
|
-
// Blue noise
|
|
30
|
-
|
|
29
|
+
// Blue noise (loaded at runtime from CDN — not inlined to keep bundle small)
|
|
30
|
+
const blueNoiseImage = 'https://assets.rayzee.atulmourya.com/noise/simple_bluenoise.png';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Data layout constants
|
|
@@ -361,6 +361,7 @@ export class PathTracer extends RenderStage {
|
|
|
361
361
|
setupBlueNoise() {
|
|
362
362
|
|
|
363
363
|
const loader = new TextureLoader();
|
|
364
|
+
loader.setCrossOrigin( 'anonymous' );
|
|
364
365
|
loader.load( blueNoiseImage, ( texture ) => {
|
|
365
366
|
|
|
366
367
|
texture.minFilter = NearestFilter;
|
|
@@ -572,11 +573,11 @@ export class PathTracer extends RenderStage {
|
|
|
572
573
|
|
|
573
574
|
}
|
|
574
575
|
|
|
575
|
-
// Point lights (
|
|
576
|
+
// Point lights (9 floats per light)
|
|
576
577
|
if ( this.pointLightsData && this.pointLightsData.length > 0 ) {
|
|
577
578
|
|
|
578
579
|
this.pointLightsBufferNode.array = Array.from( this.pointLightsData );
|
|
579
|
-
this.numPointLights.value = Math.floor( this.pointLightsData.length /
|
|
580
|
+
this.numPointLights.value = Math.floor( this.pointLightsData.length / 9 );
|
|
580
581
|
|
|
581
582
|
} else {
|
|
582
583
|
|
|
@@ -584,11 +585,11 @@ export class PathTracer extends RenderStage {
|
|
|
584
585
|
|
|
585
586
|
}
|
|
586
587
|
|
|
587
|
-
// Spot lights (
|
|
588
|
+
// Spot lights (14 floats per light)
|
|
588
589
|
if ( this.spotLightsData && this.spotLightsData.length > 0 ) {
|
|
589
590
|
|
|
590
591
|
this.spotLightsBufferNode.array = Array.from( this.spotLightsData );
|
|
591
|
-
this.numSpotLights.value = Math.floor( this.spotLightsData.length /
|
|
592
|
+
this.numSpotLights.value = Math.floor( this.spotLightsData.length / 14 );
|
|
592
593
|
|
|
593
594
|
} else {
|
|
594
595
|
|
package/src/TSL/LightsCore.js
CHANGED
|
@@ -40,6 +40,8 @@ export const PointLight = struct( {
|
|
|
40
40
|
position: 'vec3',
|
|
41
41
|
color: 'vec3',
|
|
42
42
|
intensity: 'float',
|
|
43
|
+
distance: 'float', // cutoff distance (0 = infinite)
|
|
44
|
+
decay: 'float', // decay exponent (2 = physically correct)
|
|
43
45
|
} );
|
|
44
46
|
|
|
45
47
|
export const SpotLight = struct( {
|
|
@@ -48,6 +50,9 @@ export const SpotLight = struct( {
|
|
|
48
50
|
color: 'vec3',
|
|
49
51
|
intensity: 'float',
|
|
50
52
|
angle: 'float', // cone half-angle in radians
|
|
53
|
+
penumbra: 'float', // penumbra factor [0,1]
|
|
54
|
+
distance: 'float', // cutoff distance (0 = infinite)
|
|
55
|
+
decay: 'float', // decay exponent (2 = physically correct)
|
|
51
56
|
} );
|
|
52
57
|
|
|
53
58
|
export const LightSample = struct( {
|
|
@@ -135,7 +140,7 @@ export const getAreaLight = Fn( ( [ areaLightsBuffer, index ] ) => {
|
|
|
135
140
|
|
|
136
141
|
export const getPointLight = Fn( ( [ pointLightsBuffer, index ] ) => {
|
|
137
142
|
|
|
138
|
-
const baseIndex = index.mul(
|
|
143
|
+
const baseIndex = index.mul( 9 );
|
|
139
144
|
return PointLight( {
|
|
140
145
|
position: vec3(
|
|
141
146
|
pointLightsBuffer.element( baseIndex ),
|
|
@@ -148,13 +153,15 @@ export const getPointLight = Fn( ( [ pointLightsBuffer, index ] ) => {
|
|
|
148
153
|
pointLightsBuffer.element( baseIndex.add( 5 ) ),
|
|
149
154
|
),
|
|
150
155
|
intensity: pointLightsBuffer.element( baseIndex.add( 6 ) ),
|
|
156
|
+
distance: pointLightsBuffer.element( baseIndex.add( 7 ) ),
|
|
157
|
+
decay: pointLightsBuffer.element( baseIndex.add( 8 ) ),
|
|
151
158
|
} );
|
|
152
159
|
|
|
153
160
|
} );
|
|
154
161
|
|
|
155
162
|
export const getSpotLight = Fn( ( [ spotLightsBuffer, index ] ) => {
|
|
156
163
|
|
|
157
|
-
const baseIndex = index.mul(
|
|
164
|
+
const baseIndex = index.mul( 14 );
|
|
158
165
|
return SpotLight( {
|
|
159
166
|
position: vec3(
|
|
160
167
|
spotLightsBuffer.element( baseIndex ),
|
|
@@ -173,6 +180,9 @@ export const getSpotLight = Fn( ( [ spotLightsBuffer, index ] ) => {
|
|
|
173
180
|
),
|
|
174
181
|
intensity: spotLightsBuffer.element( baseIndex.add( 9 ) ),
|
|
175
182
|
angle: spotLightsBuffer.element( baseIndex.add( 10 ) ),
|
|
183
|
+
penumbra: spotLightsBuffer.element( baseIndex.add( 11 ) ),
|
|
184
|
+
distance: spotLightsBuffer.element( baseIndex.add( 12 ) ),
|
|
185
|
+
decay: spotLightsBuffer.element( baseIndex.add( 13 ) ),
|
|
176
186
|
} );
|
|
177
187
|
|
|
178
188
|
} );
|
|
@@ -145,7 +145,7 @@ export const sampleRectAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
|
|
|
145
145
|
const cosAngle = dot( direction.negate(), lightNormal ).toVar();
|
|
146
146
|
|
|
147
147
|
ls_lightType.assign( int( LIGHT_TYPE_AREA ) );
|
|
148
|
-
ls_emission.assign( light.color.mul( light.intensity ) );
|
|
148
|
+
ls_emission.assign( light.color.mul( light.intensity ).div( PI ) );
|
|
149
149
|
ls_distance.assign( dist );
|
|
150
150
|
ls_direction.assign( direction );
|
|
151
151
|
// Guard division: ensure denominator is never zero
|
|
@@ -205,7 +205,7 @@ export const sampleCircAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
|
|
|
205
205
|
const cosAngle = dot( direction.negate(), lightNormal ).toVar();
|
|
206
206
|
|
|
207
207
|
ls_lightType.assign( int( LIGHT_TYPE_AREA ) );
|
|
208
|
-
ls_emission.assign( light.color.mul( light.intensity ) );
|
|
208
|
+
ls_emission.assign( light.color.mul( light.intensity ).div( PI ) );
|
|
209
209
|
ls_distance.assign( dist );
|
|
210
210
|
ls_direction.assign( direction );
|
|
211
211
|
// Guard division
|
|
@@ -258,9 +258,11 @@ export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, ruv, lightSel
|
|
|
258
258
|
|
|
259
259
|
If( ls_valid, () => {
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
// Penumbra: inner cone angle = outerAngle * (1 - penumbra)
|
|
262
|
+
// Clamp penumbraCosAngle > coneCosAngle to avoid smoothstep UB when penumbra = 0
|
|
263
|
+
const penumbraCosAngle = cos( light.angle.mul( float( 1.0 ).sub( light.penumbra ) ) ).max( coneCosAngle.add( 1e-5 ) ).toVar();
|
|
262
264
|
const coneAttenuation = getSpotAttenuation( { coneCosine: coneCosAngle, penumbraCosine: penumbraCosAngle, angleCosine: spotCosAngle } );
|
|
263
|
-
const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance:
|
|
265
|
+
const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance: light.distance, decayExponent: light.decay } );
|
|
264
266
|
|
|
265
267
|
ls_emission.assign( light.color.mul( light.intensity ).mul( distanceAttenuation ).mul( coneAttenuation ) );
|
|
266
268
|
|
|
@@ -297,8 +299,8 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
297
299
|
|
|
298
300
|
const lightDir = toLight.div( lightDist ).toVar();
|
|
299
301
|
|
|
300
|
-
// Calculate distance attenuation
|
|
301
|
-
const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance:
|
|
302
|
+
// Calculate distance attenuation using the light's actual distance and decay properties
|
|
303
|
+
const distanceAttenuation = getDistanceAttenuation( { lightDistance: lightDist, cutoffDistance: light.distance, decayExponent: light.decay } );
|
|
302
304
|
|
|
303
305
|
ls_lightType.assign( int( LIGHT_TYPE_POINT ) );
|
|
304
306
|
ls_direction.assign( lightDir );
|
|
@@ -166,7 +166,7 @@ export class LightManager extends EventDispatcher {
|
|
|
166
166
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
if ( light.isSpotLight && light.target ) {
|
|
169
|
+
if ( ( light.isSpotLight || light.isDirectionalLight ) && light.target ) {
|
|
170
170
|
|
|
171
171
|
const clonedTarget = new Object3D();
|
|
172
172
|
light.target.updateWorldMatrix( true, false );
|
|
@@ -203,8 +203,8 @@ export class UniformManager {
|
|
|
203
203
|
this._lightBuffers = {
|
|
204
204
|
directional: uniformArray( new Float32Array( 8 * 16 ), 'float' ),
|
|
205
205
|
area: uniformArray( new Float32Array( 13 * 16 ), 'float' ),
|
|
206
|
-
point: uniformArray( new Float32Array(
|
|
207
|
-
spot: uniformArray( new Float32Array(
|
|
206
|
+
point: uniformArray( new Float32Array( 9 * 16 ), 'float' ),
|
|
207
|
+
spot: uniformArray( new Float32Array( 14 * 16 ), 'float' ),
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
// Camera matrices
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-origin Worker utility.
|
|
3
|
-
*
|
|
4
|
-
* Browsers block `new Worker(url)` when the script is cross-origin (e.g. CDN).
|
|
5
|
-
* The workaround: create a same-origin blob that `import()`s the real script.
|
|
6
|
-
* This preserves the original URL as the base for relative imports inside the worker.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
function crossOriginWorker( url, options ) {
|
|
10
|
-
|
|
11
|
-
const blob = new Blob(
|
|
12
|
-
[ `import ${JSON.stringify( url.toString() )};` ],
|
|
13
|
-
{ type: 'application/javascript' }
|
|
14
|
-
);
|
|
15
|
-
return new Worker( URL.createObjectURL( blob ), { ...options, type: 'module' } );
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Creates a Worker, falling back to a blob-based proxy for cross-origin scripts.
|
|
21
|
-
* @param {URL} url - Worker script URL
|
|
22
|
-
* @param {WorkerOptions} [options] - Worker options (e.g. { type: 'module' })
|
|
23
|
-
* @returns {Worker}
|
|
24
|
-
*/
|
|
25
|
-
export function createWorker( url, options = {} ) {
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
|
|
29
|
-
return new Worker( url, options );
|
|
30
|
-
|
|
31
|
-
} catch ( e ) {
|
|
32
|
-
|
|
33
|
-
if ( e.name !== 'SecurityError' ) throw e;
|
|
34
|
-
return crossOriginWorker( url, options );
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
}
|