three-gpu-pathtracer 0.0.27 → 0.0.28
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 +0 -1
- package/build/index.module.js +5 -3
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +4 -2
- package/build/index.umd.cjs.map +1 -1
- package/package.json +7 -7
- package/src/core/PathTracingSceneGenerator.js +1 -1
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
- package/src/webgpu/API.md +167 -19
- package/src/webgpu/AtlasTexture.js +2 -3
- package/src/webgpu/EquirectHdrInfoNode.js +89 -48
- package/src/webgpu/LightsInfoNode.js +1 -1
- package/src/webgpu/MegaKernelPathTracer.js +10 -3
- package/src/webgpu/PathTracerBackend.js +172 -21
- package/src/webgpu/WaveFrontPathTracer.js +156 -71
- package/src/webgpu/WebGPUPathTracer.js +628 -48
- package/src/webgpu/compute/ComputeKernel.js +8 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +187 -36
- package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
- package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
- package/src/webgpu/compute/ZeroOutKernel.js +3 -1
- package/src/webgpu/compute/targets.js +55 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +67 -20
- package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
- package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -25
- package/src/webgpu/compute/wavefront/structs.js +63 -37
- package/src/webgpu/constants.js +2 -2
- package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
- package/src/webgpu/index.d.ts +41 -6
- package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
- package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +35 -13
- package/src/webgpu/nodes/aux.wgsl.js +61 -0
- package/src/webgpu/nodes/eon.wgsl.js +1 -1
- package/src/webgpu/nodes/material.wgsl.js +2 -1
- package/src/webgpu/nodes/medium.wgsl.js +165 -0
- package/src/webgpu/nodes/random.wgsl.js +2 -0
- package/src/webgpu/nodes/sampling.wgsl.js +44 -3
- package/src/webgpu/nodes/structs.wgsl.js +7 -1
- package/src/webgpu/upscale/FSRUpscaler.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-gpu-pathtracer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28",
|
|
4
4
|
"description": "Path tracing renderer and utilities for three.js built on top of three-mesh-bvh.",
|
|
5
5
|
"module": "src/index.js",
|
|
6
6
|
"main": "build/index.umd.cjs",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@monogrid/gainmap-js": "^3.0.5",
|
|
46
46
|
"@pmndrs/upscaler": "^0.3.0",
|
|
47
47
|
"@types/node": "^24.3.1",
|
|
48
|
-
"@types/three": "^0.
|
|
48
|
+
"@types/three": "^0.186.0",
|
|
49
49
|
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
|
50
50
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
|
51
51
|
"@webgpu/types": "^0.1.72",
|
|
@@ -56,22 +56,22 @@
|
|
|
56
56
|
"jsdoc": "^4.0.5",
|
|
57
57
|
"mediabunny": "^1.50.9",
|
|
58
58
|
"node-fetch": "^3.2.9",
|
|
59
|
-
"oidn-
|
|
59
|
+
"oidn-webgpu": "^0.6.0",
|
|
60
60
|
"pixelmatch": "^5.3.0",
|
|
61
61
|
"pngjs": "^6.0.0",
|
|
62
62
|
"process": "^0.11.10",
|
|
63
63
|
"puppeteer": "^24.42.0",
|
|
64
64
|
"rollup": "^2.70.0",
|
|
65
65
|
"simple-git": "^3.10.0",
|
|
66
|
-
"three": "^0.
|
|
67
|
-
"three-mesh-bvh": "^0.9.
|
|
66
|
+
"three": "^0.187.0",
|
|
67
|
+
"three-mesh-bvh": "^0.9.17",
|
|
68
68
|
"typescript": "^5.9.2",
|
|
69
69
|
"vite": "^8.2.2",
|
|
70
70
|
"yargs": "^17.5.1"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"three": ">=0.
|
|
74
|
-
"three-mesh-bvh": ">=0.9.
|
|
73
|
+
"three": ">=0.187.0",
|
|
74
|
+
"three-mesh-bvh": ">=0.9.17",
|
|
75
75
|
"xatlas-web": "^0.1.0"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
@@ -177,7 +177,7 @@ export class PathTracingSceneGenerator {
|
|
|
177
177
|
// generate the geometry
|
|
178
178
|
const result = staticGeometryGenerator.generate( geometry );
|
|
179
179
|
const materials = result.materials;
|
|
180
|
-
let needsMaterialIndexUpdate = result.changeType !== NO_CHANGE || this._materialUuids === null || this._materialUuids.length !== length;
|
|
180
|
+
let needsMaterialIndexUpdate = result.changeType !== NO_CHANGE || this._materialUuids === null || this._materialUuids.length !== materials.length;
|
|
181
181
|
if ( ! needsMaterialIndexUpdate ) {
|
|
182
182
|
|
|
183
183
|
for ( let i = 0, length = materials.length; i < length; i ++ ) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType,
|
|
1
|
+
import { DataTexture, RedFormat, LinearFilter, DataUtils, HalfFloatType, TextureSource, RepeatWrapping, RGBAFormat, FloatType, ClampToEdgeWrapping } from 'three';
|
|
2
2
|
import { toHalfFloatArray } from '../utils/TextureUtils.js';
|
|
3
3
|
|
|
4
4
|
function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
|
|
@@ -42,7 +42,7 @@ function colorToLuminance( r, g, b ) {
|
|
|
42
42
|
function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
|
|
43
43
|
|
|
44
44
|
const map = envMap.clone();
|
|
45
|
-
map.source = new
|
|
45
|
+
map.source = new TextureSource( { ...map.image } );
|
|
46
46
|
const { width, height, data } = map.image;
|
|
47
47
|
|
|
48
48
|
// TODO: is there a simple way to avoid cloning and adjusting the env map data here?
|
|
@@ -311,6 +311,8 @@ export class EquirectHdrInfoUniform {
|
|
|
311
311
|
|
|
312
312
|
this.totalSum = totalSumValue;
|
|
313
313
|
this.map = map;
|
|
314
|
+
this.cdfMarginal = cdfMarginal;
|
|
315
|
+
this.cdfConditional = cdfConditional;
|
|
314
316
|
|
|
315
317
|
}
|
|
316
318
|
|
package/src/webgpu/API.md
CHANGED
|
@@ -17,7 +17,7 @@ Uncorrelated pseudo random samples. Converges slowest but never shows structured
|
|
|
17
17
|
RANDOM_SOBOL: Object
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Sobol low discrepancy samples, scrambled per pixel. Converges faster than `RANDOM_PCG`.
|
|
20
|
+
Sobol low discrepancy samples, scrambled per pixel. Converges faster than `RANDOM_PCG`. The default.
|
|
21
21
|
|
|
22
22
|
### RANDOM_BLUE_DITHER
|
|
23
23
|
|
|
@@ -26,7 +26,7 @@ RANDOM_BLUE_DITHER: Object
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The same Sobol sequence for every pixel, offset by a blue noise dither so early frames read as
|
|
29
|
-
smooth grain rather than clumped noise.
|
|
29
|
+
smooth grain rather than clumped noise.
|
|
30
30
|
|
|
31
31
|
## Transmissive Background Modes
|
|
32
32
|
|
|
@@ -143,7 +143,7 @@ Runs Open Image Denoise over a path traced image. Pass one to
|
|
|
143
143
|
nor the network files become a dependency.
|
|
144
144
|
|
|
145
145
|
```js
|
|
146
|
-
import { initUNetFromURL } from 'oidn-
|
|
146
|
+
import { initUNetFromURL } from 'oidn-webgpu';
|
|
147
147
|
pathTracer.setDenoiser( new OIDNDenoiser( { initUNetFromURL, auxWeightsUrl } ) );
|
|
148
148
|
```
|
|
149
149
|
|
|
@@ -151,6 +151,17 @@ Weights come from the oidn-weights repository, where the "_small" and "_large" v
|
|
|
151
151
|
quality against download size and per tile cost.
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
### events
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
// Fired when a pass started by `update` fails, such as the weights failing to download, with the
|
|
158
|
+
// failure as the event's `error`. No other pass starts until a reset.
|
|
159
|
+
{ type: 'error' }
|
|
160
|
+
|
|
161
|
+
// Fired as each tile of a pass finishes, with the fraction of the pass done as `progress`.
|
|
162
|
+
{ type: 'progress' }
|
|
163
|
+
```
|
|
164
|
+
|
|
154
165
|
### .texture
|
|
155
166
|
|
|
156
167
|
```js
|
|
@@ -210,20 +221,17 @@ init( renderer: WebGPURenderer ): void
|
|
|
210
221
|
```
|
|
211
222
|
|
|
212
223
|
|
|
213
|
-
### .setScene
|
|
214
|
-
|
|
215
|
-
```js
|
|
216
|
-
setScene( scene: Scene, camera: Camera ): void
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
|
|
220
224
|
### .update
|
|
221
225
|
|
|
222
226
|
```js
|
|
223
|
-
update(
|
|
227
|
+
update(
|
|
228
|
+
target: Texture,
|
|
229
|
+
aux = null: StorageArrayTexture | null
|
|
230
|
+
): Texture | null
|
|
224
231
|
```
|
|
225
232
|
|
|
226
|
-
|
|
233
|
+
Unpacks the auxiliary buffers the path tracer accumulated and starts a pass. Safe to call
|
|
234
|
+
every frame.
|
|
227
235
|
|
|
228
236
|
|
|
229
237
|
### .denoise
|
|
@@ -261,6 +269,19 @@ the environment afterward require the matching `update*` function. Any change th
|
|
|
261
269
|
the accumulated image restarts it.
|
|
262
270
|
|
|
263
271
|
|
|
272
|
+
### events
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
// Fired once every pixel has reached `maxSamples` and an attached denoiser has finished. Fires
|
|
276
|
+
// again after a reset, or after `maxSamples` is raised, once the render has caught up.
|
|
277
|
+
{ type: 'complete' }
|
|
278
|
+
|
|
279
|
+
// Fired as the render advances. `stage` is "building", "compiling", "rendering" or "denoising", and
|
|
280
|
+
// `progress` is the fraction of it done, or null when unknown. "rendering" also gives the sample
|
|
281
|
+
// counts as `samples`, reported a few times a second.
|
|
282
|
+
{ type: 'progress' }
|
|
283
|
+
```
|
|
284
|
+
|
|
264
285
|
### .maxBounces
|
|
265
286
|
|
|
266
287
|
```js
|
|
@@ -277,20 +298,40 @@ resolve more indirect light at the cost of speed.
|
|
|
277
298
|
frameBudget: number = 250000
|
|
278
299
|
```
|
|
279
300
|
|
|
280
|
-
Number of path
|
|
301
|
+
Number of path steps dispatched per [WebGPUPathTracer#renderSample](WebGPUPathTracer#renderSample) call,
|
|
281
302
|
independent of resolution. Raising it trades frame rate for convergence speed.
|
|
282
303
|
|
|
283
304
|
|
|
305
|
+
### .frameTime
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
readonly frameTime: number
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
GPU time in milliseconds of a recent [WebGPUPathTracer#renderSample](WebGPUPathTracer#renderSample) call. Only measured
|
|
312
|
+
when the renderer is created with `trackTimestamp: true`.
|
|
313
|
+
|
|
314
|
+
|
|
284
315
|
### .maxTransparentBounces
|
|
285
316
|
|
|
286
317
|
```js
|
|
287
|
-
maxTransparentBounces: number =
|
|
318
|
+
maxTransparentBounces: number = 15
|
|
288
319
|
```
|
|
289
320
|
|
|
290
321
|
Maximum number of alpha tested surfaces a ray can pass through. Counted separately from
|
|
291
322
|
[WebGPUPathTracer#maxBounces](WebGPUPathTracer#maxBounces) so foliage and cutouts cannot exhaust the bounce budget.
|
|
292
323
|
|
|
293
324
|
|
|
325
|
+
### .maxSubsurfaceBounces
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
maxSubsurfaceBounces: number = 256
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Maximum number of scatter events a path can take inside a subsurface scattering volume. Counted
|
|
332
|
+
separately from [WebGPUPathTracer#maxBounces](WebGPUPathTracer#maxBounces). Paths that reach it end, darkening dense volumes.
|
|
333
|
+
|
|
334
|
+
|
|
294
335
|
### .maxSamples
|
|
295
336
|
|
|
296
337
|
```js
|
|
@@ -303,6 +344,16 @@ render has stopped, so it has no effect while this is `0`.
|
|
|
303
344
|
> [!NOTE]
|
|
304
345
|
> `0` renders indefinitely.
|
|
305
346
|
|
|
347
|
+
### .complete
|
|
348
|
+
|
|
349
|
+
```js
|
|
350
|
+
complete: boolean
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Whether every pixel has reached [WebGPUPathTracer#maxSamples](WebGPUPathTracer#maxSamples) and an attached denoiser
|
|
354
|
+
has finished, as of the last measurement. Always false while rendering indefinitely.
|
|
355
|
+
|
|
356
|
+
|
|
306
357
|
### .transmissiveBackground
|
|
307
358
|
|
|
308
359
|
```js
|
|
@@ -395,6 +446,17 @@ readonly lowResMode: boolean
|
|
|
395
446
|
Whether the tracer is currently rendering the low resolution preview.
|
|
396
447
|
|
|
397
448
|
|
|
449
|
+
### .compiling
|
|
450
|
+
|
|
451
|
+
```js
|
|
452
|
+
readonly compiling: boolean
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Whether the kernels are compiling, such as after the scene or material changes. Nothing new
|
|
456
|
+
renders until they finish and the canvas keeps the last frame, so an app may draw its own
|
|
457
|
+
preview in the meantime.
|
|
458
|
+
|
|
459
|
+
|
|
398
460
|
### .textureAtlas
|
|
399
461
|
|
|
400
462
|
```js
|
|
@@ -410,13 +472,13 @@ The atlas every scene texture is packed into for the kernels to sample.
|
|
|
410
472
|
minSamples: number = 1
|
|
411
473
|
```
|
|
412
474
|
|
|
413
|
-
|
|
475
|
+
Average samples per pixel the full resolution render must reach before it is faded in.
|
|
414
476
|
|
|
415
477
|
|
|
416
478
|
### .renderDelay
|
|
417
479
|
|
|
418
480
|
```js
|
|
419
|
-
renderDelay: number =
|
|
481
|
+
renderDelay: number = 16
|
|
420
482
|
```
|
|
421
483
|
|
|
422
484
|
Milliseconds to show the low resolution preview for after a reset.
|
|
@@ -425,7 +487,7 @@ Milliseconds to show the low resolution preview for after a reset.
|
|
|
425
487
|
### .fadeDuration
|
|
426
488
|
|
|
427
489
|
```js
|
|
428
|
-
fadeDuration: number =
|
|
490
|
+
fadeDuration: number = 250
|
|
429
491
|
```
|
|
430
492
|
|
|
431
493
|
Milliseconds taken to cross fade from the preview to the full render.
|
|
@@ -446,7 +508,39 @@ Whether to render a low resolution preview while the camera is moving.
|
|
|
446
508
|
lowResScale: number = 0.1
|
|
447
509
|
```
|
|
448
510
|
|
|
449
|
-
Resolution of the low resolution preview, as a fraction of the render size.
|
|
511
|
+
Resolution of the low resolution preview, as a fraction of the render size. Adjusted when
|
|
512
|
+
[WebGPUPathTracer#adaptiveDynamicRes](WebGPUPathTracer#adaptiveDynamicRes) is set. The preview never has more pixels than a
|
|
513
|
+
frame of [WebGPUPathTracer#frameBudget](WebGPUPathTracer#frameBudget) can render.
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
### .adaptiveDynamicRes
|
|
517
|
+
|
|
518
|
+
```js
|
|
519
|
+
adaptiveDynamicRes: boolean = true
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
Whether [WebGPUPathTracer#lowResScale](WebGPUPathTracer#lowResScale) is adjusted to the pixels a frame of
|
|
523
|
+
[WebGPUPathTracer#frameBudget](WebGPUPathTracer#frameBudget) can render.
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
### .adaptiveFrameBudget
|
|
527
|
+
|
|
528
|
+
```js
|
|
529
|
+
adaptiveFrameBudget: boolean = true
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Whether [WebGPUPathTracer#frameBudget](WebGPUPathTracer#frameBudget) is adjusted to keep each
|
|
533
|
+
[WebGPUPathTracer#renderSample](WebGPUPathTracer#renderSample) call near [WebGPUPathTracer#targetFrameTime](WebGPUPathTracer#targetFrameTime) of
|
|
534
|
+
GPU time. Requires a renderer created with `trackTimestamp: true`.
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
### .targetFrameTime
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
targetFrameTime: number = 16
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
GPU time in milliseconds per frame targeted by [WebGPUPathTracer#adaptiveFrameBudget](WebGPUPathTracer#adaptiveFrameBudget).
|
|
450
544
|
|
|
451
545
|
|
|
452
546
|
### .renderScale
|
|
@@ -469,6 +563,27 @@ Whether to track the canvas size automatically. Set to `false` to drive the rend
|
|
|
469
563
|
size with [WebGPUPathTracer#setSize](WebGPUPathTracer#setSize).
|
|
470
564
|
|
|
471
565
|
|
|
566
|
+
### .renderToCanvas
|
|
567
|
+
|
|
568
|
+
```js
|
|
569
|
+
renderToCanvas: boolean = true
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Whether [WebGPUPathTracer#renderSample](WebGPUPathTracer#renderSample) draws the result to the current render
|
|
573
|
+
target, the canvas by default. Set to `false` to only accumulate, as when baking to a
|
|
574
|
+
texture read through [WebGPUPathTracer#target](WebGPUPathTracer#target).
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
### .asyncCompile
|
|
578
|
+
|
|
579
|
+
```js
|
|
580
|
+
asyncCompile: boolean = false
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Whether to compile the kernels asynchronously so a compile doesn't stall the page. Nothing
|
|
584
|
+
new renders while [WebGPUPathTracer#compiling](WebGPUPathTracer#compiling) is true.
|
|
585
|
+
|
|
586
|
+
|
|
472
587
|
### .generateMissingAttributes
|
|
473
588
|
|
|
474
589
|
```js
|
|
@@ -539,7 +654,8 @@ setDenoiser( denoiser: OIDNDenoiser | null ): void
|
|
|
539
654
|
```
|
|
540
655
|
|
|
541
656
|
Attaches a denoiser, run once the render settles and displayed in place of the raw image.
|
|
542
|
-
Settings live on the instance. Pass null to remove it.
|
|
657
|
+
Settings live on the instance. Pass null to remove it. The render restarts when the auxiliary
|
|
658
|
+
buffers it needs are added or dropped.
|
|
543
659
|
|
|
544
660
|
|
|
545
661
|
### .setUpscaler
|
|
@@ -564,6 +680,28 @@ against. Call this once, then use the `update*` functions for later changes.
|
|
|
564
680
|
> [!NOTE]
|
|
565
681
|
> Geometry BVHs are built synchronously, so this blocks for large scenes.
|
|
566
682
|
|
|
683
|
+
### .setBVHWorker
|
|
684
|
+
|
|
685
|
+
```js
|
|
686
|
+
setBVHWorker( WorkerConstructor: function ): void
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
Sets the worker class [WebGPUPathTracer#setSceneAsync](WebGPUPathTracer#setSceneAsync) builds the mesh geometry BVHs
|
|
690
|
+
with, such as `GenerateMeshBVHWorker` from three-mesh-bvh. Several are created and disposed
|
|
691
|
+
per call so geometries build in parallel.
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
### .setSceneAsync
|
|
695
|
+
|
|
696
|
+
```js
|
|
697
|
+
async setSceneAsync( scene: Scene, camera: Camera ): Promise<void>
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
Like [WebGPUPathTracer#setScene](WebGPUPathTracer#setScene) but builds the mesh geometry BVHs in the workers set
|
|
701
|
+
with [WebGPUPathTracer#setBVHWorker](WebGPUPathTracer#setBVHWorker) so the page is not blocked. Skinned meshes are
|
|
702
|
+
still built synchronously.
|
|
703
|
+
|
|
704
|
+
|
|
567
705
|
### .setRandom
|
|
568
706
|
|
|
569
707
|
```js
|
|
@@ -708,6 +846,16 @@ Draws a heatmap of the per pixel sample counts, for spotting pixels that converg
|
|
|
708
846
|
> Measures on every call and draws with the previous result, since the readback lands a
|
|
709
847
|
> frame later.
|
|
710
848
|
|
|
849
|
+
### .renderAuxBuffer
|
|
850
|
+
|
|
851
|
+
```js
|
|
852
|
+
renderAuxBuffer( buffer = 'albedo': 'albedo' | 'normal' ): void
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
Draws the albedo or normal auxiliary buffer accumulated for the denoiser, for checking what
|
|
856
|
+
it is given. Nothing is drawn until a denoiser that uses them is attached.
|
|
857
|
+
|
|
858
|
+
|
|
711
859
|
### .dispose
|
|
712
860
|
|
|
713
861
|
```js
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
Matrix3,
|
|
10
10
|
} from 'three/webgpu';
|
|
11
11
|
|
|
12
|
-
const MAX_TEXTURE_SIZE = 4096;
|
|
13
12
|
const _prevClearColor = new Color();
|
|
14
13
|
const _prevMatrix = new Matrix3();
|
|
15
14
|
|
|
@@ -243,9 +242,9 @@ export class AtlasTexture {
|
|
|
243
242
|
|
|
244
243
|
}
|
|
245
244
|
|
|
246
|
-
// calculate the maximum dimension of the atlas
|
|
245
|
+
// calculate the maximum dimension of the atlas, up to what the device allows
|
|
247
246
|
let maxDim = textures.reduce( ( v, t ) => Math.max( v, t.width, t.height ), 1 );
|
|
248
|
-
maxDim = Math.min(
|
|
247
|
+
maxDim = Math.min( renderer.backend.device.limits.maxTextureDimension2D, maxDim );
|
|
249
248
|
|
|
250
249
|
const width = maxDim;
|
|
251
250
|
const height = maxDim;
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import { Matrix3 } from 'three/webgpu';
|
|
2
|
-
import { texture, sampler, uniform,
|
|
1
|
+
import { Matrix3, StorageBufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { texture, sampler, uniform, storage, PI } from 'three/tsl';
|
|
3
3
|
import { EquirectHdrInfoUniform } from '../uniforms/EquirectHdrInfoUniform.js';
|
|
4
4
|
import { wgslTagFn } from 'three-mesh-bvh/webgpu';
|
|
5
|
-
import {
|
|
6
|
-
import { equirectDirectionToUvFn, equirectUvToDirectionFn
|
|
7
|
-
|
|
8
|
-
// totalWeight includes sin( theta ), which cancels in the solid-angle conversion.
|
|
9
|
-
const equirectLuminancePdfFn = wgslFn( /* wgsl */ `
|
|
10
|
-
|
|
11
|
-
fn equirectLuminancePdf( luminance: f32, totalWeight: f32, resolution: vec2u ) -> f32 {
|
|
12
|
-
|
|
13
|
-
return f32( resolution.x * resolution.y ) * luminance / ( 2.0 * PI * PI * totalWeight );
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
`, [ constants ] );
|
|
5
|
+
import { environmentSampleStruct } from './nodes/structs.wgsl.js';
|
|
6
|
+
import { equirectDirectionToUvFn, equirectUvToDirectionFn } from './nodes/sampling.wgsl.js';
|
|
18
7
|
|
|
19
8
|
export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
20
9
|
|
|
@@ -22,13 +11,10 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
22
11
|
|
|
23
12
|
super();
|
|
24
13
|
|
|
25
|
-
// environment map
|
|
14
|
+
// environment map with a sampler, and the importance sampling CDFs for the default 1x1 map
|
|
26
15
|
this.mapNode = texture( this.map );
|
|
27
16
|
this.mapSampler = sampler( this.mapNode );
|
|
28
|
-
this.
|
|
29
|
-
this.marginalSampler = sampler( this.marginalNode );
|
|
30
|
-
this.conditionalNode = texture( this.conditionalWeights );
|
|
31
|
-
this.conditionalSampler = sampler( this.conditionalNode );
|
|
17
|
+
this.cdfNode = storage( new StorageBufferAttribute( new Float32Array( [ 0, 1, 0, 1 ] ), 1 ), 'float' ).toReadOnly();
|
|
32
18
|
|
|
33
19
|
// scalar parameters that assemble into the EnvironmentInfo struct in the shader
|
|
34
20
|
this.rotationNode = uniform( new Matrix3() );
|
|
@@ -53,17 +39,35 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
53
39
|
|
|
54
40
|
const {
|
|
55
41
|
mapNode,
|
|
56
|
-
|
|
57
|
-
conditionalNode,
|
|
42
|
+
cdfNode,
|
|
58
43
|
totalSumNode,
|
|
59
44
|
} = this;
|
|
60
45
|
|
|
61
46
|
// refresh values in place on the existing nodes so no rebuild is required
|
|
62
47
|
mapNode.value = this.map;
|
|
63
|
-
marginalNode.value = this.marginalWeights;
|
|
64
|
-
conditionalNode.value = this.conditionalWeights;
|
|
65
48
|
totalSumNode.value = this.totalSum;
|
|
66
49
|
|
|
50
|
+
// pack the row CDF and then each row's pixel CDF into one buffer, each led by a zero so a
|
|
51
|
+
// pixel's range is two neighboring entries
|
|
52
|
+
const { width, height } = this.map.image;
|
|
53
|
+
const { cdfMarginal, cdfConditional } = this;
|
|
54
|
+
const cdf = new Float32Array( height + 1 + height * ( width + 1 ) );
|
|
55
|
+
cdf.set( cdfMarginal, 1 );
|
|
56
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
57
|
+
|
|
58
|
+
cdf.set( cdfConditional.subarray( y * width, y * width + width ), height + 1 + y * ( width + 1 ) + 1 );
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cdfNode.value = new StorageBufferAttribute( cdf, 1 );
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
dispose() {
|
|
67
|
+
|
|
68
|
+
super.dispose();
|
|
69
|
+
this.cdfNode.value.dispose();
|
|
70
|
+
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
_initFns() {
|
|
@@ -71,15 +75,60 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
71
75
|
const {
|
|
72
76
|
mapNode,
|
|
73
77
|
mapSampler,
|
|
74
|
-
|
|
75
|
-
marginalSampler,
|
|
76
|
-
conditionalNode,
|
|
77
|
-
conditionalSampler,
|
|
78
|
+
cdfNode,
|
|
78
79
|
totalSumNode,
|
|
79
80
|
rotationNode,
|
|
80
81
|
intensityNode,
|
|
81
82
|
} = this;
|
|
82
83
|
|
|
84
|
+
// Binary searches the "count" pixel ranges from "offset" for the one holding "r", writing where
|
|
85
|
+
// "r" falls within it to "fraction". See:
|
|
86
|
+
// https://pbr-book.org/3ed-2018/Monte_Carlo_Integration/2D_Sampling_with_Multidimensional_Transformations#Piecewise-Constant2DDistributions
|
|
87
|
+
// https://github.com/blender/cycles/blob/main/src/kernel/light/background.h
|
|
88
|
+
const searchCdfFn = wgslTagFn/* wgsl */`
|
|
89
|
+
fn searchEnvCdf( r: f32, offset: u32, count: u32, fraction: ptr<function, f32> ) -> u32 {
|
|
90
|
+
|
|
91
|
+
var lower = 0u;
|
|
92
|
+
var upper = count - 1u;
|
|
93
|
+
while ( lower < upper ) {
|
|
94
|
+
|
|
95
|
+
let mid = ( lower + upper ) >> 1u;
|
|
96
|
+
if ( ${ cdfNode }[ offset + mid + 1u ] <= r ) {
|
|
97
|
+
|
|
98
|
+
lower = mid + 1u;
|
|
99
|
+
|
|
100
|
+
} else {
|
|
101
|
+
|
|
102
|
+
upper = mid;
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let start = ${ cdfNode }[ offset + lower ];
|
|
109
|
+
let end = ${ cdfNode }[ offset + lower + 1u ];
|
|
110
|
+
*fraction = select( 0.5, saturate( ( r - start ) / ( end - start ) ), end > start );
|
|
111
|
+
return lower;
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
// the solid angle pdf of a direction at "v" in the given pixel, which is sampled uniformly over its area
|
|
117
|
+
const getTexelPdfFn = wgslTagFn/* wgsl */`
|
|
118
|
+
fn getEnvTexelPdf( texel: vec2u, v: f32 ) -> f32 {
|
|
119
|
+
|
|
120
|
+
let resolution = textureDimensions( ${ mapNode } );
|
|
121
|
+
let rowOffset = resolution.y + 1u + texel.y * ( resolution.x + 1u );
|
|
122
|
+
let rowPdf = ${ cdfNode }[ texel.y + 1u ] - ${ cdfNode }[ texel.y ];
|
|
123
|
+
let pixelPdf = ${ cdfNode }[ rowOffset + texel.x + 1u ] - ${ cdfNode }[ rowOffset + texel.x ];
|
|
124
|
+
|
|
125
|
+
// the equirect projection stretches each pixel by 1 / sin( theta ) over the sphere
|
|
126
|
+
let sinTheta = sin( ${ PI } * v );
|
|
127
|
+
return select( 0.0, rowPdf * pixelPdf * f32( resolution.x * resolution.y ) / ( 2.0 * ${ PI } * ${ PI } * sinTheta ), sinTheta > 0.0 );
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
|
|
83
132
|
this.sampleColor = wgslTagFn/* wgsl */`
|
|
84
133
|
fn sampleEnv( direction: vec3f ) -> vec4f {
|
|
85
134
|
|
|
@@ -97,29 +146,22 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
97
146
|
|
|
98
147
|
var result: ${ environmentSampleStruct };
|
|
99
148
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
let
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
// search the CDFs: marginal picks the row (v), conditional picks the column (u), and the
|
|
150
|
+
// fractions place the sample within the pixel
|
|
151
|
+
let resolution = textureDimensions( ${ mapNode } );
|
|
152
|
+
var rowFraction = 0.0;
|
|
153
|
+
var columnFraction = 0.0;
|
|
154
|
+
let row = ${ searchCdfFn }( r.x, 0u, resolution.y, &rowFraction );
|
|
155
|
+
let column = ${ searchCdfFn }( r.y, resolution.y + 1u + row * ( resolution.x + 1u ), resolution.x, &columnFraction );
|
|
156
|
+
let texel = vec2u( column, row );
|
|
157
|
+
let uv = ( vec2f( texel ) + vec2f( columnFraction, rowFraction ) ) / vec2f( resolution );
|
|
105
158
|
|
|
106
159
|
let direction = ${ equirectUvToDirectionFn }( uv );
|
|
107
160
|
let color = textureSampleLevel( ${ mapNode }, ${ mapSampler }, uv, 0 ).rgb;
|
|
108
161
|
|
|
109
162
|
result.direction = transpose( ${ rotationNode } ) * direction;
|
|
110
163
|
result.color = color * ${ intensityNode };
|
|
111
|
-
|
|
112
|
-
if ( totalSum != 0.0 ) {
|
|
113
|
-
|
|
114
|
-
let lum = ${ luminanceFn }( color );
|
|
115
|
-
let resolution = textureDimensions( ${ mapNode } );
|
|
116
|
-
result.pdf = ${ equirectLuminancePdfFn }( lum, totalSum, resolution );
|
|
117
|
-
|
|
118
|
-
} else {
|
|
119
|
-
|
|
120
|
-
result.pdf = 0.0;
|
|
121
|
-
|
|
122
|
-
}
|
|
164
|
+
result.pdf = ${ getTexelPdfFn }( texel, uv.y );
|
|
123
165
|
|
|
124
166
|
return result;
|
|
125
167
|
|
|
@@ -147,11 +189,10 @@ export class EquirectHdrInfoNode extends EquirectHdrInfoUniform {
|
|
|
147
189
|
|
|
148
190
|
let rotatedDir = ${ rotationNode } * direction;
|
|
149
191
|
let mapUv = ${ equirectDirectionToUvFn }( rotatedDir );
|
|
150
|
-
let color = textureSampleLevel( ${ mapNode }, ${ mapSampler }, mapUv, 0 ).rgb;
|
|
151
|
-
let lum = ${ luminanceFn }( color );
|
|
152
192
|
let resolution = textureDimensions( ${ mapNode } );
|
|
193
|
+
let texel = min( vec2u( mapUv * vec2f( resolution ) ), resolution - 1u );
|
|
153
194
|
|
|
154
|
-
return ${
|
|
195
|
+
return ${ getTexelPdfFn }( texel, mapUv.y );
|
|
155
196
|
|
|
156
197
|
}
|
|
157
198
|
`;
|
|
@@ -36,7 +36,7 @@ export class LightsInfoNode extends LightsInfoUniformStruct {
|
|
|
36
36
|
// ies profiles packed into an atlas alongside their placement rects
|
|
37
37
|
this.iesAtlas = new AtlasTexture( { type: HalfFloatType } );
|
|
38
38
|
this.iesProfilesNode = texture( this.iesAtlas.texture );
|
|
39
|
-
this.iesInfoNode = uniformArray( this.iesAtlas.textureInfo, 'uvec4' );
|
|
39
|
+
this.iesInfoNode = uniformArray( this.iesAtlas.textureInfo, 'uvec4' ).setName( 'iesInfo' );
|
|
40
40
|
|
|
41
41
|
this._initFns();
|
|
42
42
|
|
|
@@ -69,7 +69,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
69
69
|
|
|
70
70
|
setBVHData( bvhData ) {
|
|
71
71
|
|
|
72
|
-
this.kernel.bvhData = bvhData;
|
|
72
|
+
this.kernel.context.bvhData = bvhData;
|
|
73
73
|
this.rebuild();
|
|
74
74
|
|
|
75
75
|
}
|
|
@@ -179,6 +179,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
179
179
|
kernel,
|
|
180
180
|
maxBounces,
|
|
181
181
|
maxTransparentBounces,
|
|
182
|
+
maxSubsurfaceBounces,
|
|
182
183
|
|
|
183
184
|
outputTarget,
|
|
184
185
|
sampleCountTarget,
|
|
@@ -193,6 +194,10 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
193
194
|
|
|
194
195
|
kernel.maxBounces = maxBounces;
|
|
195
196
|
kernel.maxTransparentBounces = maxTransparentBounces;
|
|
197
|
+
kernel.maxSubsurfaceBounces = maxSubsurfaceBounces;
|
|
198
|
+
|
|
199
|
+
// compile the kernel before its first dispatch
|
|
200
|
+
yield* this.compileKernels( [ kernel ] );
|
|
196
201
|
|
|
197
202
|
// number of tile cycles that have finished, ie. the sample count every pixel has reached
|
|
198
203
|
let completedSamples = 0;
|
|
@@ -201,6 +206,8 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
201
206
|
|
|
202
207
|
// skip the tile loop entirely once every pixel is finished
|
|
203
208
|
kernel.maxSamples = this.maxSamples;
|
|
209
|
+
kernel.auxTarget = this.auxTarget ?? this._auxPlaceholder;
|
|
210
|
+
kernel.accumulateAux = this.auxTarget ? 1 : 0;
|
|
204
211
|
if ( this.maxSamples !== 0 && completedSamples >= this.maxSamples ) {
|
|
205
212
|
|
|
206
213
|
yield;
|
|
@@ -210,7 +217,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
210
217
|
|
|
211
218
|
// split the frame into a near square grid of tiles no larger than the budget
|
|
212
219
|
this.getSize( _size );
|
|
213
|
-
const budgetTiles = Math.max( 1, Math.ceil( _size.x * _size.y / this.frameBudget ) );
|
|
220
|
+
const budgetTiles = lowResMode ? 1 : Math.max( 1, Math.ceil( _size.x * _size.y / this.frameBudget ) );
|
|
214
221
|
tiles.x = Math.ceil( Math.sqrt( budgetTiles ) );
|
|
215
222
|
tiles.y = Math.ceil( budgetTiles / tiles.x );
|
|
216
223
|
|
|
@@ -245,7 +252,7 @@ export class MegaKernelPathTracer extends PathTracerBackend {
|
|
|
245
252
|
for ( let y = 0; y < tiles.y; y ++ ) {
|
|
246
253
|
|
|
247
254
|
kernel.offset.set( x, y ).multiply( tileSize );
|
|
248
|
-
|
|
255
|
+
this.dispatch( kernel, dispatchSize );
|
|
249
256
|
|
|
250
257
|
// in low res mode the tiles past the first fall outside the target and do no work
|
|
251
258
|
completedTiles = Math.min( completedTiles + 1, tileCount );
|