rayzee 7.21.0 → 7.22.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 +75 -4
- package/dist/rayzee.es.js +633 -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 +1 -4
- package/src/Processor/KernelManager.js +17 -70
- package/src/Processor/QueueManager.js +4 -3
- package/src/Processor/SceneProcessor.js +10 -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/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -62,6 +62,12 @@ export const ENGINE_DEFAULTS = {
|
|
|
62
62
|
skyMieAnisotropy: 0.76,
|
|
63
63
|
skyPreset: 'clearMorning',
|
|
64
64
|
|
|
65
|
+
// Camera projection — 'perspective' | 'equirectangular'. Panorama ranges are UI-facing degrees.
|
|
66
|
+
cameraProjection: 'perspective',
|
|
67
|
+
panoramaLonRange: [ - 180, 180 ],
|
|
68
|
+
panoramaLatRange: [ - 90, 90 ],
|
|
69
|
+
panoramaLevelHorizon: true,
|
|
70
|
+
|
|
65
71
|
enableDOF: false,
|
|
66
72
|
fov: 55,
|
|
67
73
|
focusDistance: 0.8,
|
package/src/PathTracerApp.js
CHANGED
|
@@ -1722,6 +1722,80 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1722
1722
|
|
|
1723
1723
|
}
|
|
1724
1724
|
|
|
1725
|
+
/**
|
|
1726
|
+
* Per-kernel GPU milliseconds for the last resolved frame.
|
|
1727
|
+
*
|
|
1728
|
+
* {@link PathTracerApp#getGPUTimings} only reports the frame aggregate, which buries a change to
|
|
1729
|
+
* one kernel under everything else. The backend's timestamp query pool already retains one
|
|
1730
|
+
* duration per compute pass, keyed `c:<frameCalls>:<nodeId>:f<frame>` (three.js
|
|
1731
|
+
* `WebGPUTimestampQueryPool.resolveQueriesAsync` → `timestamps`); this attributes those back to
|
|
1732
|
+
* kernel names through `KernelManager`'s registry.
|
|
1733
|
+
*
|
|
1734
|
+
* Durations are SUMMED per kernel across the frame, so `extend` reports its whole per-frame cost
|
|
1735
|
+
* over every bounce iteration rather than one bounce. `unattributed` collects passes belonging to
|
|
1736
|
+
* no registered kernel (other stages, denoisers) so `sum(kernels) + unattributed` reconciles with
|
|
1737
|
+
* `total` — a gap there means a pass was missed, not that a kernel is free.
|
|
1738
|
+
*
|
|
1739
|
+
* The pool's map accumulates across frames and is never pruned, hence the newest-frame filter.
|
|
1740
|
+
*
|
|
1741
|
+
* Requires {@link PathTracerApp#enableGPUTiming}. Returns null when timestamps are unavailable.
|
|
1742
|
+
*
|
|
1743
|
+
* @returns {Promise<{kernels: Object<string, number>, total: number, unattributed: number, frame: number}|null>}
|
|
1744
|
+
*/
|
|
1745
|
+
async getKernelGPUTimings() {
|
|
1746
|
+
|
|
1747
|
+
const renderer = this.renderer;
|
|
1748
|
+
if ( ! renderer?.backend?.trackTimestamp ) return null;
|
|
1749
|
+
|
|
1750
|
+
await renderer.resolveTimestampsAsync( 'compute' );
|
|
1751
|
+
|
|
1752
|
+
const timestamps = renderer.backend.timestampQueryPool?.compute?.timestamps;
|
|
1753
|
+
if ( ! timestamps || timestamps.size === 0 ) return null;
|
|
1754
|
+
|
|
1755
|
+
const nameByNodeId = new Map();
|
|
1756
|
+
const kernelMap = this.stages?.pathTracer?._kernelManager?.kernels;
|
|
1757
|
+
if ( kernelMap ) {
|
|
1758
|
+
|
|
1759
|
+
for ( const [ name, node ] of kernelMap ) nameByNodeId.set( node.id, name );
|
|
1760
|
+
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
const parsed = [];
|
|
1764
|
+
let frame = - 1;
|
|
1765
|
+
|
|
1766
|
+
for ( const [ uid, ms ] of timestamps ) {
|
|
1767
|
+
|
|
1768
|
+
// 'c:<frameCalls>:<nodeId>:f<frame>'
|
|
1769
|
+
const parts = uid.split( ':' );
|
|
1770
|
+
if ( parts.length !== 4 ) continue;
|
|
1771
|
+
|
|
1772
|
+
const f = Number( parts[ 3 ].slice( 1 ) );
|
|
1773
|
+
if ( ! Number.isFinite( f ) ) continue;
|
|
1774
|
+
|
|
1775
|
+
parsed.push( { nodeId: Number( parts[ 2 ] ), f, ms } );
|
|
1776
|
+
if ( f > frame ) frame = f;
|
|
1777
|
+
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
const kernels = {};
|
|
1781
|
+
let total = 0;
|
|
1782
|
+
let unattributed = 0;
|
|
1783
|
+
|
|
1784
|
+
for ( const entry of parsed ) {
|
|
1785
|
+
|
|
1786
|
+
if ( entry.f !== frame ) continue;
|
|
1787
|
+
|
|
1788
|
+
total += entry.ms;
|
|
1789
|
+
const name = nameByNodeId.get( entry.nodeId );
|
|
1790
|
+
if ( name === undefined ) unattributed += entry.ms;
|
|
1791
|
+
else kernels[ name ] = ( kernels[ name ] ?? 0 ) + entry.ms;
|
|
1792
|
+
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
return { kernels, total, unattributed, frame };
|
|
1796
|
+
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1725
1799
|
// ═══════════════════════════════════════════════════════════════
|
|
1726
1800
|
// Output (absorbed from OutputAPI)
|
|
1727
1801
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -2298,6 +2372,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2298
2372
|
renderer: this.renderer,
|
|
2299
2373
|
resetCallback: () => this.reset(),
|
|
2300
2374
|
reconcileCompletion: () => this._reconcileCompletion(),
|
|
2375
|
+
denoisingManager: this.denoisingManager,
|
|
2376
|
+
cameraManager: this.cameraManager,
|
|
2301
2377
|
} );
|
|
2302
2378
|
|
|
2303
2379
|
this.renderer.toneMappingExposure = this.settings.get( 'exposure' ) ?? 1.0;
|
|
@@ -101,8 +101,6 @@ export class BVHBuilder {
|
|
|
101
101
|
this.treeletSize = 5;
|
|
102
102
|
this.treeletOptimizationPasses = 1;
|
|
103
103
|
this.treeletMinImprovement = 0.02;
|
|
104
|
-
this.maxTreeletDepth = 3;
|
|
105
|
-
this.maxTreeletsPerScene = 20;
|
|
106
104
|
this.treeletComplexityThreshold = 50000;
|
|
107
105
|
|
|
108
106
|
// Reinsertion optimization configuration
|
|
@@ -191,6 +189,7 @@ export class BVHBuilder {
|
|
|
191
189
|
if ( config.size !== undefined ) this.treeletSize = Math.max( 3, Math.min( 12, config.size ) );
|
|
192
190
|
if ( config.passes !== undefined ) this.treeletOptimizationPasses = Math.max( 1, Math.min( 3, config.passes ) );
|
|
193
191
|
if ( config.minImprovement !== undefined ) this.treeletMinImprovement = Math.max( 0.001, config.minImprovement );
|
|
192
|
+
if ( config.complexityThreshold !== undefined ) this.treeletComplexityThreshold = config.complexityThreshold;
|
|
194
193
|
|
|
195
194
|
}
|
|
196
195
|
|
|
@@ -595,12 +594,10 @@ export class BVHBuilder {
|
|
|
595
594
|
|
|
596
595
|
const isLargeScene = this.totalTriangles > this.treeletComplexityThreshold;
|
|
597
596
|
const adaptiveTreeletSize = isLargeScene ? 3 : this.treeletSize;
|
|
598
|
-
const adaptiveMaxTreelets = isLargeScene ? 10 : this.maxTreeletsPerScene;
|
|
599
597
|
|
|
600
598
|
const optimizer = new TreeletOptimizer( this.traversalCost, this.intersectionCost );
|
|
601
599
|
optimizer.setTreeletSize( adaptiveTreeletSize );
|
|
602
600
|
optimizer.setMinImprovement( this.treeletMinImprovement );
|
|
603
|
-
optimizer.setMaxTreelets( adaptiveMaxTreelets );
|
|
604
601
|
|
|
605
602
|
const optimizationStartTime = performance.now();
|
|
606
603
|
|
|
@@ -4,25 +4,14 @@
|
|
|
4
4
|
* Builds, caches, and dispatches individual compute nodes for the wavefront
|
|
5
5
|
* path tracing pipeline. Each kernel is a separate `Fn().compute()` node.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Dispatch grids are derived from each node's own `workgroupSize`, so the divisor can never
|
|
8
|
+
* drift from the size the kernel was compiled with — see `setDispatchForCount`.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
12
12
|
|
|
13
13
|
const log = createLogger( 'wavefront' );
|
|
14
14
|
|
|
15
|
-
/** Default workgroup sizes per kernel type */
|
|
16
|
-
const WORKGROUP_SIZES = {
|
|
17
|
-
generate: [ 16, 16, 1 ], // 2D screen-space
|
|
18
|
-
extend: [ 256, 1, 1 ], // 1D ray-parallel
|
|
19
|
-
shade: [ 256, 1, 1 ], // 1D ray-parallel (material-sorted when sort enabled)
|
|
20
|
-
connect: [ 256, 1, 1 ], // 1D shadow-ray-parallel
|
|
21
|
-
accumulate: [ 256, 1, 1 ], // 1D shadow-ray-parallel
|
|
22
|
-
compact: [ 256, 1, 1 ], // 1D ray-parallel
|
|
23
|
-
finalWrite: [ 16, 16, 1 ], // 2D screen-space
|
|
24
|
-
};
|
|
25
|
-
|
|
26
15
|
export class KernelManager {
|
|
27
16
|
|
|
28
17
|
/**
|
|
@@ -41,12 +30,6 @@ export class KernelManager {
|
|
|
41
30
|
*/
|
|
42
31
|
this.kernels = new Map();
|
|
43
32
|
|
|
44
|
-
/**
|
|
45
|
-
* Map of kernel name → workgroup size [x, y, z].
|
|
46
|
-
* @type {Map<string, number[]>}
|
|
47
|
-
*/
|
|
48
|
-
this.workgroupSizes = new Map();
|
|
49
|
-
|
|
50
33
|
/**
|
|
51
34
|
* Timing data for performance profiling.
|
|
52
35
|
* @type {Map<string, {compiledOnce: boolean, lastDispatchMs: number}>}
|
|
@@ -74,13 +57,6 @@ export class KernelManager {
|
|
|
74
57
|
this._firstDispatches = [];
|
|
75
58
|
this._firstDispatchFlush = null;
|
|
76
59
|
|
|
77
|
-
// Initialize workgroup sizes from defaults
|
|
78
|
-
for ( const [ name, wgSize ] of Object.entries( WORKGROUP_SIZES ) ) {
|
|
79
|
-
|
|
80
|
-
this.workgroupSizes.set( name, wgSize );
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
60
|
}
|
|
85
61
|
|
|
86
62
|
/**
|
|
@@ -177,61 +153,32 @@ export class KernelManager {
|
|
|
177
153
|
}
|
|
178
154
|
|
|
179
155
|
/**
|
|
180
|
-
*
|
|
156
|
+
* Size a 1D kernel's grid to cover `count` items, deriving the divisor from the workgroup size the
|
|
157
|
+
* kernel was actually registered with. Extend/Shade bound on ENTERING_COUNT rather than on the grid,
|
|
158
|
+
* so an under-sized grid silently drops the tail of the active list — never hardcode the divisor.
|
|
181
159
|
* @param {string} name - Kernel name
|
|
182
|
-
* @param {number
|
|
160
|
+
* @param {number} count - Number of items to cover
|
|
183
161
|
*/
|
|
184
|
-
|
|
162
|
+
setDispatchForCount( name, count ) {
|
|
185
163
|
|
|
186
164
|
const node = this.kernels.get( name );
|
|
187
165
|
if ( ! node ) return;
|
|
188
|
-
node.dispatchSize = count;
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Calculate 2D dispatch dimensions for a screen-space kernel.
|
|
194
|
-
* @param {number} width - Render width in pixels
|
|
195
|
-
* @param {number} height - Render height in pixels
|
|
196
|
-
* @param {string} kernelName - Kernel name for WG size lookup
|
|
197
|
-
* @returns {number[]} [dispatchX, dispatchY, 1]
|
|
198
|
-
*/
|
|
199
|
-
calcScreenDispatch( width, height, kernelName ) {
|
|
200
|
-
|
|
201
|
-
const wg = this.workgroupSizes.get( kernelName ) || [ 16, 16, 1 ];
|
|
202
|
-
return [
|
|
203
|
-
Math.ceil( width / wg[ 0 ] ),
|
|
204
|
-
Math.ceil( height / wg[ 1 ] ),
|
|
205
|
-
1
|
|
206
|
-
];
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Calculate 1D dispatch dimensions for a ray-parallel kernel.
|
|
212
|
-
* @param {number} rayCount - Number of rays to process
|
|
213
|
-
* @param {string} kernelName - Kernel name for WG size lookup
|
|
214
|
-
* @returns {number[]} [dispatchX, 1, 1]
|
|
215
|
-
*/
|
|
216
|
-
calcRayDispatch( rayCount, kernelName ) {
|
|
217
|
-
|
|
218
|
-
const wg = this.workgroupSizes.get( kernelName ) || [ 256, 1, 1 ];
|
|
219
|
-
return [
|
|
220
|
-
Math.ceil( rayCount / wg[ 0 ] ),
|
|
221
|
-
1,
|
|
222
|
-
1
|
|
223
|
-
];
|
|
166
|
+
node.dispatchSize = [ Math.ceil( count / node.workgroupSize[ 0 ] ), 1, 1 ];
|
|
224
167
|
|
|
225
168
|
}
|
|
226
169
|
|
|
227
170
|
/**
|
|
228
|
-
*
|
|
229
|
-
* @param {string} name
|
|
230
|
-
* @
|
|
171
|
+
* Size a 2D screen-space kernel's grid, deriving both divisors from the registered workgroup size.
|
|
172
|
+
* @param {string} name - Kernel name
|
|
173
|
+
* @param {number} width - Width in pixels
|
|
174
|
+
* @param {number} height - Height in pixels
|
|
231
175
|
*/
|
|
232
|
-
|
|
176
|
+
setDispatchForGrid( name, width, height ) {
|
|
233
177
|
|
|
234
|
-
|
|
178
|
+
const node = this.kernels.get( name );
|
|
179
|
+
if ( ! node ) return;
|
|
180
|
+
const wg = node.workgroupSize;
|
|
181
|
+
node.dispatchSize = [ Math.ceil( width / wg[ 0 ] ), Math.ceil( height / wg[ 1 ] ), 1 ];
|
|
235
182
|
|
|
236
183
|
}
|
|
237
184
|
|
|
@@ -17,9 +17,10 @@ export const COUNTER = {
|
|
|
17
17
|
// per-frame count of pixels whose Tier-1 relative-error dropped below threshold; zeroed at frame start by
|
|
18
18
|
// initActiveIndices, incremented in FinalWrite, read back async to drive the whole-frame convergence early-stop.
|
|
19
19
|
CONVERGED_COUNT: 2,
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
20
|
+
// Reserved hole — was FROZEN_COUNT, a write-only counter no reader ever consumed. Kept as a hole so
|
|
21
|
+
// slot indices stay stable across the JS enum and every kernel that stores by index.
|
|
22
|
+
RESERVED_3: 3,
|
|
23
|
+
// Tier-2 per-pixel freeze: bounce-0 active count (maxRays − frozen), read back to size next frame's grid.
|
|
23
24
|
ACTIVE_PIXEL_COUNT: 4,
|
|
24
25
|
COUNT: 5,
|
|
25
26
|
};
|
|
@@ -51,6 +51,8 @@ export class SceneProcessor {
|
|
|
51
51
|
treeletSize: 7, // 7 nodes gives 315 topologies for optimal enumeration
|
|
52
52
|
treeletOptimizationPasses: 1,
|
|
53
53
|
treeletMinImprovement: 0.01, // Minimum SAH improvement threshold
|
|
54
|
+
// Above this triangle count the builder drops treelets to size 3.
|
|
55
|
+
treeletComplexityThreshold: 50000,
|
|
54
56
|
...options
|
|
55
57
|
};
|
|
56
58
|
|
|
@@ -151,7 +153,8 @@ export class SceneProcessor {
|
|
|
151
153
|
enabled: this.config.enableTreeletOptimization,
|
|
152
154
|
size: this.config.treeletSize,
|
|
153
155
|
passes: this.config.treeletOptimizationPasses,
|
|
154
|
-
minImprovement: this.config.treeletMinImprovement
|
|
156
|
+
minImprovement: this.config.treeletMinImprovement,
|
|
157
|
+
complexityThreshold: this.config.treeletComplexityThreshold
|
|
155
158
|
} );
|
|
156
159
|
|
|
157
160
|
// Create and configure texture creator
|
|
@@ -436,7 +439,8 @@ export class SceneProcessor {
|
|
|
436
439
|
enabled: originalTreeletEnabled !== false,
|
|
437
440
|
size: this.config.treeletSize,
|
|
438
441
|
passes: this.config.treeletOptimizationPasses,
|
|
439
|
-
minImprovement: this.config.treeletMinImprovement
|
|
442
|
+
minImprovement: this.config.treeletMinImprovement,
|
|
443
|
+
complexityThreshold: this.config.treeletComplexityThreshold
|
|
440
444
|
},
|
|
441
445
|
reinsertionOptimization: {
|
|
442
446
|
enabled: this.bvhBuilder.enableReinsertionOptimization,
|
|
@@ -1267,7 +1271,8 @@ export class SceneProcessor {
|
|
|
1267
1271
|
enabled: this.config.enableTreeletOptimization,
|
|
1268
1272
|
size: this.config.treeletSize,
|
|
1269
1273
|
passes: this.config.treeletOptimizationPasses,
|
|
1270
|
-
minImprovement: this.config.treeletMinImprovement
|
|
1274
|
+
minImprovement: this.config.treeletMinImprovement,
|
|
1275
|
+
complexityThreshold: this.config.treeletComplexityThreshold
|
|
1271
1276
|
} );
|
|
1272
1277
|
|
|
1273
1278
|
}
|
|
@@ -1953,7 +1958,8 @@ export class SceneProcessor {
|
|
|
1953
1958
|
enabled: treeletEnabled,
|
|
1954
1959
|
size: this.config.treeletSize,
|
|
1955
1960
|
passes: this.config.treeletOptimizationPasses,
|
|
1956
|
-
minImprovement: this.config.treeletMinImprovement
|
|
1961
|
+
minImprovement: this.config.treeletMinImprovement,
|
|
1962
|
+
complexityThreshold: this.config.treeletComplexityThreshold
|
|
1957
1963
|
},
|
|
1958
1964
|
reinsertionOptimization: {
|
|
1959
1965
|
enabled: this.bvhBuilder.enableReinsertionOptimization,
|
|
@@ -545,13 +545,6 @@ export class TreeletOptimizer {
|
|
|
545
545
|
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
setMaxTreelets() {
|
|
549
|
-
|
|
550
|
-
// No-op — we process all valid treelets now.
|
|
551
|
-
// Kept for API compatibility with BVHBuilder.
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
548
|
getStatistics() {
|
|
556
549
|
|
|
557
550
|
return { ...this.stats };
|
package/src/RenderSettings.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventDispatcher, Color } from 'three';
|
|
1
|
+
import { EventDispatcher, Color, Vector2, MathUtils } from 'three';
|
|
2
2
|
import { ENGINE_DEFAULTS } from './EngineDefaults.js';
|
|
3
3
|
import { EngineEvents } from './EngineEvents.js';
|
|
4
4
|
|
|
@@ -32,6 +32,7 @@ const SETTING_ROUTES = {
|
|
|
32
32
|
enableGroundCatcher: { uniform: 'enableGroundCatcher', reset: true },
|
|
33
33
|
groundCatcherHeight: { uniform: 'groundCatcherHeight', reset: true },
|
|
34
34
|
globalIlluminationIntensity: { uniform: 'globalIlluminationIntensity', reset: true },
|
|
35
|
+
panoramaLevelHorizon: { uniform: 'panoLevelHorizon', reset: true },
|
|
35
36
|
enableDOF: { uniform: 'enableDOF', reset: true },
|
|
36
37
|
focusDistance: { uniform: 'focusDistance', reset: false },
|
|
37
38
|
focalLength: { uniform: 'focalLength', reset: true },
|
|
@@ -56,6 +57,9 @@ const SETTING_ROUTES = {
|
|
|
56
57
|
|
|
57
58
|
// ── Multi-stage / special handling ────────────────────────────
|
|
58
59
|
|
|
60
|
+
cameraProjection: { handler: 'handleCameraProjection', reset: true },
|
|
61
|
+
panoramaLonRange: { handler: 'handlePanoramaLonRange', reset: true },
|
|
62
|
+
panoramaLatRange: { handler: 'handlePanoramaLatRange', reset: true },
|
|
59
63
|
interactionModeEnabled: { handler: 'handleInteractionModeEnabled', reset: false },
|
|
60
64
|
maxSamples: { handler: 'handleMaxSamples', reset: false },
|
|
61
65
|
transparentBackground: { handler: 'handleTransparentBackground' },
|
|
@@ -122,13 +126,15 @@ export class RenderSettings extends EventDispatcher {
|
|
|
122
126
|
* @param {Object} params.stages - Pipeline stages { pathTracer, compositor, autoExposure, ... }
|
|
123
127
|
* @param {Function} params.resetCallback - Called to reset accumulation
|
|
124
128
|
* @param {Function} [params.reconcileCompletion] - Called when completion limits change
|
|
129
|
+
* @param {Object} [params.denoisingManager] - Needed to force ASVGF off under panorama
|
|
130
|
+
* @param {Object} [params.cameraManager] - Needed to force auto-focus manual under panorama
|
|
125
131
|
*/
|
|
126
|
-
bind(
|
|
132
|
+
bind( params ) {
|
|
127
133
|
|
|
128
|
-
this._pathTracer = stages.pathTracer;
|
|
129
|
-
this._resetCallback = resetCallback;
|
|
134
|
+
this._pathTracer = params.stages.pathTracer;
|
|
135
|
+
this._resetCallback = params.resetCallback;
|
|
130
136
|
this._delegates = {};
|
|
131
|
-
this._handlers = this._buildHandlers(
|
|
137
|
+
this._handlers = this._buildHandlers( params );
|
|
132
138
|
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -136,10 +142,39 @@ export class RenderSettings extends EventDispatcher {
|
|
|
136
142
|
* Builds handler functions for multi-stage settings that can't
|
|
137
143
|
* be routed with a simple uniform forward.
|
|
138
144
|
*/
|
|
139
|
-
_buildHandlers( stages, renderer, reconcileCompletion ) {
|
|
145
|
+
_buildHandlers( { stages, renderer, reconcileCompletion, denoisingManager, cameraManager } ) {
|
|
146
|
+
|
|
147
|
+
// UniformManager copies into the existing node, so one scratch vector serves every write.
|
|
148
|
+
const panoScratch = new Vector2();
|
|
149
|
+
const setPanoRange = ( uniform, value ) => {
|
|
150
|
+
|
|
151
|
+
panoScratch.set( value?.[ 0 ] ?? 0, value?.[ 1 ] ?? 0 ).multiplyScalar( MathUtils.DEG2RAD );
|
|
152
|
+
stages.pathTracer?.setUniform( uniform, panoScratch );
|
|
153
|
+
|
|
154
|
+
};
|
|
140
155
|
|
|
141
156
|
return {
|
|
142
157
|
|
|
158
|
+
handleCameraProjection: ( value ) => {
|
|
159
|
+
|
|
160
|
+
const isPanorama = value === 'equirectangular';
|
|
161
|
+
stages.pathTracer?.setUniform( 'cameraProjection', isPanorama ? 1 : 0 );
|
|
162
|
+
|
|
163
|
+
if ( ! isPanorama ) return;
|
|
164
|
+
|
|
165
|
+
// ASVGF is driven entirely by MotionVector, which unprojects through
|
|
166
|
+
// projectionMatrixInverse — meaningless once every pixel is its own direction.
|
|
167
|
+
// Fall back to the spatial-only denoiser rather than leaving no strategy.
|
|
168
|
+
if ( denoisingManager?.denoiserStrategy === 'asvgf' ) denoisingManager.setDenoiserStrategy( 'edgeaware' );
|
|
169
|
+
// Auto-focus raycasts via Raycaster.setFromCamera, which only knows the frustum.
|
|
170
|
+
cameraManager?.setAutoFocusMode( 'manual' );
|
|
171
|
+
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
handlePanoramaLonRange: ( value ) => setPanoRange( 'panoLonRange', value ),
|
|
175
|
+
|
|
176
|
+
handlePanoramaLatRange: ( value ) => setPanoRange( 'panoLatRange', value ),
|
|
177
|
+
|
|
143
178
|
handleTransparentBackground: ( value ) => {
|
|
144
179
|
|
|
145
180
|
stages.pathTracer?.setUniform( 'transparentBackground', value );
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, vec3, vec4, float, int, uint, uvec2, uniform,
|
|
1
|
+
import { Fn, vec2, vec3, vec4, float, int, uint, uvec2, uniform, min, storage, If,
|
|
2
2
|
textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, StorageTexture } from 'three/webgpu';
|
|
4
4
|
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
@@ -6,6 +6,7 @@ import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
|
6
6
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
7
7
|
import { Ray, HitInfo, RayTracingMaterial, UVCache } from '../TSL/Struct.js';
|
|
8
8
|
import { traverseBVH } from '../TSL/BVHTraversal.js';
|
|
9
|
+
import { cameraRayDirection } from '../TSL/CameraRay.js';
|
|
9
10
|
import { getMaterial } from '../TSL/Common.js';
|
|
10
11
|
import { computeUVCache, processNormal, processBump, buildBucketTextureNodes, refreshBucketTextureNodes, setMaterialBucketTextures } from '../TSL/TextureSampling.js';
|
|
11
12
|
|
|
@@ -48,6 +49,12 @@ export class NormalDepth extends RenderStage {
|
|
|
48
49
|
|
|
49
50
|
this.cameraWorldMatrix = uniform( new Matrix4(), 'mat4' );
|
|
50
51
|
this.cameraProjectionMatrixInverse = uniform( new Matrix4(), 'mat4' );
|
|
52
|
+
// Mirrored from the path tracer each frame — these rays must use the same camera model
|
|
53
|
+
// as the colour buffer or the denoiser's normal/depth edge-stops fight the image.
|
|
54
|
+
this.cameraProjection = uniform( 0, 'int' );
|
|
55
|
+
this.panoLonRange = uniform( new Vector2(), 'vec2' );
|
|
56
|
+
this.panoLatRange = uniform( new Vector2(), 'vec2' );
|
|
57
|
+
this.panoLevelHorizon = uniform( 1, 'int' );
|
|
51
58
|
this.resolutionWidth = uniform( options.width || 1 );
|
|
52
59
|
this.resolutionHeight = uniform( options.height || 1 );
|
|
53
60
|
|
|
@@ -218,20 +225,12 @@ export class NormalDepth extends RenderStage {
|
|
|
218
225
|
|
|
219
226
|
If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
|
|
220
227
|
|
|
221
|
-
// Pixel center → NDC, Y negated for Three.js WebGPU.
|
|
222
|
-
const ndcX = float( gx ).add( 0.5 ).div( resW ).mul( 2.0 ).sub( 1.0 );
|
|
223
|
-
const ndcY = float( gy ).add( 0.5 ).div( resH ).mul( 2.0 ).sub( 1.0 ).negate();
|
|
224
|
-
const ndcPos = vec3( ndcX, ndcY, 1.0 );
|
|
225
|
-
|
|
226
228
|
// No jitter — deterministic per-pixel ray so the temporal gate
|
|
227
229
|
// sees stable per-pixel normals across frames.
|
|
228
|
-
const
|
|
229
|
-
const rayDirWorld =
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
camWorldMat[ 1 ].xyz,
|
|
233
|
-
camWorldMat[ 2 ].xyz
|
|
234
|
-
).mul( rayDirCS.xyz.div( rayDirCS.w ) )
|
|
230
|
+
const uv = vec2( float( gx ).add( 0.5 ).div( resW ), float( gy ).add( 0.5 ).div( resH ) );
|
|
231
|
+
const rayDirWorld = cameraRayDirection(
|
|
232
|
+
uv, camWorldMat, camProjInvMat,
|
|
233
|
+
this.cameraProjection, this.panoLonRange, this.panoLatRange, this.panoLevelHorizon
|
|
235
234
|
);
|
|
236
235
|
const rayOrigin = vec3( camWorldMat[ 3 ] );
|
|
237
236
|
|
|
@@ -307,6 +306,10 @@ export class NormalDepth extends RenderStage {
|
|
|
307
306
|
|
|
308
307
|
this.cameraWorldMatrix.value.copy( pt.uniforms.get( 'cameraWorldMatrix' ).value );
|
|
309
308
|
this.cameraProjectionMatrixInverse.value.copy( pt.uniforms.get( 'cameraProjectionMatrixInverse' ).value );
|
|
309
|
+
this.cameraProjection.value = pt.uniforms.get( 'cameraProjection' ).value;
|
|
310
|
+
this.panoLonRange.value.copy( pt.uniforms.get( 'panoLonRange' ).value );
|
|
311
|
+
this.panoLatRange.value.copy( pt.uniforms.get( 'panoLatRange' ).value );
|
|
312
|
+
this.panoLevelHorizon.value = pt.uniforms.get( 'panoLevelHorizon' ).value;
|
|
310
313
|
|
|
311
314
|
}
|
|
312
315
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -31,6 +31,13 @@ import {
|
|
|
31
31
|
|
|
32
32
|
const log = createLogger( 'wavefront' );
|
|
33
33
|
|
|
34
|
+
// Shared by the 1D index-list kernels built inline below.
|
|
35
|
+
const LIST_WG_SIZE = 256;
|
|
36
|
+
|
|
37
|
+
// Resized per bounce iteration, each from its own registered workgroup size. Unregistered entries
|
|
38
|
+
// (the sort passes when _sortMaterials is off) are skipped by setDispatchForCount.
|
|
39
|
+
const BOUNCE_KERNELS = [ 'extend', 'shade', 'globalHist', 'globalScatter', 'compact', 'compactCopyback' ];
|
|
40
|
+
|
|
34
41
|
export class PathTracer extends PathTracerStage {
|
|
35
42
|
|
|
36
43
|
constructor( renderer, scene, camera, options = {} ) {
|
|
@@ -350,7 +357,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
350
357
|
|
|
351
358
|
// reset counters → compact non-frozen pixel IDs → publish count → list-driven 1D generate.
|
|
352
359
|
const genSized = Math.min( maxRays, Math.ceil( activeBounce0 * 1.5 ) + 1024 );
|
|
353
|
-
km.
|
|
360
|
+
km.setDispatchForCount( 'generateList', genSized );
|
|
354
361
|
km.dispatch( 'resetFrameCounters' );
|
|
355
362
|
km.dispatch( 'buildActivePixels' );
|
|
356
363
|
km.dispatch( 'seedEnter' );
|
|
@@ -402,23 +409,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
402
409
|
}
|
|
403
410
|
|
|
404
411
|
const sized = Math.min( maxRays, Math.ceil( entering * 1.5 ) + 1024 );
|
|
405
|
-
const
|
|
406
|
-
km.setDispatchCount( 'extend', wg );
|
|
407
|
-
km.setDispatchCount( 'shade', wg );
|
|
408
|
-
km.setDispatchCount( 'globalHist', wg );
|
|
409
|
-
km.setDispatchCount( 'globalScatter', wg );
|
|
410
|
-
km.setDispatchCount( 'compact', wg );
|
|
411
|
-
km.setDispatchCount( 'compactCopyback', wg );
|
|
412
|
+
for ( const k of BOUNCE_KERNELS ) km.setDispatchForCount( k, sized );
|
|
412
413
|
|
|
413
414
|
} else {
|
|
414
415
|
|
|
415
416
|
km.dispatch( 'enterFull' );
|
|
416
|
-
const
|
|
417
|
-
km.setDispatchCount( 'extend', full );
|
|
418
|
-
km.setDispatchCount( 'shade', full );
|
|
419
|
-
km.setDispatchCount( 'compact', full );
|
|
420
|
-
km.setDispatchCount( 'globalHist', full );
|
|
421
|
-
km.setDispatchCount( 'globalScatter', full );
|
|
417
|
+
for ( const k of BOUNCE_KERNELS ) km.setDispatchForCount( k, maxRays );
|
|
422
418
|
|
|
423
419
|
}
|
|
424
420
|
|
|
@@ -935,7 +931,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
935
931
|
|
|
936
932
|
} );
|
|
937
933
|
this._kernelManager.register( 'initActiveIndices',
|
|
938
|
-
initFn().compute( [ Math.ceil( ( this._chunkRows * w ) /
|
|
934
|
+
initFn().compute( [ Math.ceil( ( this._chunkRows * w ) / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
939
935
|
);
|
|
940
936
|
|
|
941
937
|
const genParams = {
|
|
@@ -948,6 +944,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
948
944
|
frame: this.seedFrame,
|
|
949
945
|
cameraWorldMatrix: this.cameraWorldMatrix,
|
|
950
946
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
947
|
+
cameraProjection: this.cameraProjection,
|
|
948
|
+
panoLonRange: this.panoLonRange,
|
|
949
|
+
panoLatRange: this.panoLatRange,
|
|
950
|
+
panoLevelHorizon: this.panoLevelHorizon,
|
|
951
951
|
enableDOF: this.enableDOF,
|
|
952
952
|
focalLength: this.focalLength,
|
|
953
953
|
aperture: this.aperture,
|
|
@@ -977,13 +977,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
977
977
|
const freezeK = this.pixelFreezeStability;
|
|
978
978
|
const resetFrameFn = Fn( () => {
|
|
979
979
|
|
|
980
|
-
// ACTIVE is per-chunk (zeroed before every chunk's scatter). CONVERGED
|
|
981
|
-
// summed across chunks in FinalWrite → zero
|
|
980
|
+
// ACTIVE is per-chunk (zeroed before every chunk's scatter). CONVERGED is a per-FRAME counter
|
|
981
|
+
// summed across chunks in FinalWrite → zero it only on the first chunk of the frame.
|
|
982
982
|
atomicStore( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), uint( 0 ) );
|
|
983
983
|
If( this._wfIsFirstChunk.greaterThan( uint( 0 ) ), () => {
|
|
984
984
|
|
|
985
985
|
atomicStore( counters.element( uint( COUNTER.CONVERGED_COUNT ) ), uint( 0 ) );
|
|
986
|
-
atomicStore( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 0 ) );
|
|
987
986
|
|
|
988
987
|
} );
|
|
989
988
|
|
|
@@ -1050,7 +1049,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1050
1049
|
|
|
1051
1050
|
} );
|
|
1052
1051
|
this._kernelManager.register( 'buildActivePixels',
|
|
1053
|
-
buildActiveFn().compute( [ Math.ceil( ( this._chunkRows * w ) /
|
|
1052
|
+
buildActiveFn().compute( [ Math.ceil( ( this._chunkRows * w ) / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1054
1053
|
);
|
|
1055
1054
|
|
|
1056
1055
|
const seedEnterFn = Fn( () => {
|
|
@@ -1073,7 +1072,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1073
1072
|
counters,
|
|
1074
1073
|
} );
|
|
1075
1074
|
this._kernelManager.register( 'generateList',
|
|
1076
|
-
genListFn().compute( [ Math.ceil( maxRays /
|
|
1075
|
+
genListFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1077
1076
|
);
|
|
1078
1077
|
|
|
1079
1078
|
const freshBvh = this.bvhStorageNode;
|
|
@@ -1108,7 +1107,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1108
1107
|
const extFn = buildExtendKernel( {
|
|
1109
1108
|
bvhBuffer: freshBvh,
|
|
1110
1109
|
triangleBuffer: freshTri,
|
|
1111
|
-
materialBuffer: freshMat,
|
|
1112
1110
|
rayBufferRO: pb.rayBuffer.ro,
|
|
1113
1111
|
hitBufferRW: pb.hitBuffer.rw,
|
|
1114
1112
|
activeIndicesRO: qm.getActiveReadRO(),
|
|
@@ -1292,7 +1290,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1292
1290
|
|
|
1293
1291
|
} );
|
|
1294
1292
|
this._kernelManager.register( 'compactCopyback',
|
|
1295
|
-
copyFn().compute( [ Math.ceil( maxRays /
|
|
1293
|
+
copyFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1296
1294
|
);
|
|
1297
1295
|
|
|
1298
1296
|
const fwFn = buildFinalWriteKernel( {
|
|
@@ -1324,7 +1322,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1324
1322
|
// Tier-2 freeze (stamp + pass-through)
|
|
1325
1323
|
usePixelFreeze: this.usePixelFreeze,
|
|
1326
1324
|
pixelFreezeThreshold: this.pixelFreezeThreshold,
|
|
1327
|
-
pixelFreezeStability: this.pixelFreezeStability,
|
|
1328
1325
|
streakBufferRW: streakRW,
|
|
1329
1326
|
frozenMaskRO, // dilated frozen mask (read-only; matches the active-list decision)
|
|
1330
1327
|
chunkRowBase: this._wfChunkRowBase,
|
|
@@ -1351,6 +1348,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1351
1348
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
1352
1349
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1353
1350
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1351
|
+
cameraProjection: this.cameraProjection,
|
|
1352
|
+
panoLonRange: this.panoLonRange,
|
|
1353
|
+
panoLatRange: this.panoLatRange,
|
|
1354
|
+
panoLevelHorizon: this.panoLevelHorizon,
|
|
1354
1355
|
enableDOF: this.enableDOF,
|
|
1355
1356
|
focalLength: this.focalLength,
|
|
1356
1357
|
aperture: this.aperture,
|
|
@@ -1396,10 +1397,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1396
1397
|
const w = this._wfRenderWidth.value;
|
|
1397
1398
|
const h = this._wfRenderHeight.value;
|
|
1398
1399
|
|
|
1399
|
-
this._kernelManager.
|
|
1400
|
-
Math.ceil( w / DEBUG_WG_SIZE ),
|
|
1401
|
-
Math.ceil( h / DEBUG_WG_SIZE ), 1
|
|
1402
|
-
] );
|
|
1400
|
+
this._kernelManager.setDispatchForGrid( 'debug', w, h );
|
|
1403
1401
|
|
|
1404
1402
|
}
|
|
1405
1403
|
|
|
@@ -1416,10 +1414,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1416
1414
|
this._wfMaxRayCount.value = chunkPixels;
|
|
1417
1415
|
|
|
1418
1416
|
const km = this._kernelManager;
|
|
1419
|
-
km.
|
|
1420
|
-
km.
|
|
1421
|
-
km.
|
|
1422
|
-
km.
|
|
1417
|
+
km.setDispatchForGrid( 'generate', w, rows );
|
|
1418
|
+
km.setDispatchForGrid( 'finalWrite', w, rows );
|
|
1419
|
+
km.setDispatchForCount( 'initActiveIndices', chunkPixels );
|
|
1420
|
+
km.setDispatchForCount( 'buildActivePixels', chunkPixels );
|
|
1423
1421
|
|
|
1424
1422
|
return chunkPixels;
|
|
1425
1423
|
|