rayzee 11.5.0 → 11.7.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 +14 -14
- package/dist/{ArchiveImporter-29Nl1567.js → ArchiveImporter-DYcRXeKE.js} +2 -2
- package/dist/{BidirectionalIntegrator-CJay8tXK.js → BidirectionalIntegrator-BvjRpbTP.js} +289 -289
- package/dist/{EngineEvents-Ci8dVWCP.js → EngineEvents-CLckhYbL.js} +1 -0
- package/dist/{NeuralRetouch-KqGVPV7u.js → NeuralRetouch-ClaMiKYg.js} +1 -1
- package/dist/{NeuralSuperRes-BHxhYzlD.js → NeuralSuperRes-WNEXun9s.js} +2 -2
- package/dist/{PathTracer-Cldt0mXi.js → PathTracer-B9wZPPVL.js} +3197 -3167
- package/dist/{PhysicalSky-CUWrTcmT.js → PhysicalSky-DcjItqKi.js} +1 -1
- package/dist/{RayzeeRenderer-Dd-Dit0v.js → RayzeeRenderer-p-NeE6E2.js} +405 -437
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +5 -5
- package/dist/rayzee.es.js +1003 -1686
- package/dist/rayzee.umd.js +104 -143
- package/dist/{sceneCachePolicy--ta6moJu.js → sceneCachePolicy-B9w3BnaP.js} +2 -2
- package/package.json +2 -2
- package/src/EngineEvents.js +2 -0
- package/src/Passes/OIDNDenoiser.js +106 -100
- package/src/PathTracerApp.js +8 -19
- package/src/Pipeline/BindingAudit.js +2 -3
- package/src/Pipeline/PipelineContext.js +0 -1
- package/src/Pipeline/RenderPipeline.js +1 -2
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/PackedRayBuffer.js +2 -2
- package/src/Processor/StorageTexturePool.js +1 -1
- package/src/Processor/TextureReadback.js +18 -1
- package/src/RayzeeRenderer.js +5 -5
- package/src/Stages/DenoiserSettings.js +5 -81
- package/src/Stages/MotionVector.js +21 -215
- package/src/Stages/NormalDepth.js +5 -5
- package/src/Stages/PathTracer.js +3 -11
- package/src/Stages/PathTracerStage.js +4 -17
- package/src/TSL/FinalWriteKernel.js +2 -2
- package/src/TSL/ShadeKernel.js +3 -3
- package/src/index.js +1 -1
- package/src/managers/DenoisingManager.js +93 -187
- package/src/Stages/ASVGF.js +0 -932
- package/src/Stages/BilateralFilter.js +0 -486
- package/src/Stages/EdgeFilter.js +0 -545
- package/src/Stages/Variance.js +0 -443
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as e } from "./EngineEvents-
|
|
1
|
+
import { t as e } from "./EngineEvents-CLckhYbL.js";
|
|
2
2
|
import { a as t, i as n, r } from "./Logger-dDDGj9Av.js";
|
|
3
3
|
import { f as i, v as a, y as o } from "./BufferLayout-xPRH9jYD.js";
|
|
4
4
|
import { n as s } from "./EngineIssues-CIZnXHsF.js";
|
|
@@ -1594,7 +1594,7 @@ var vt = class {
|
|
|
1594
1594
|
samples: this.samples.slice()
|
|
1595
1595
|
};
|
|
1596
1596
|
}
|
|
1597
|
-
}, yt = "11.
|
|
1597
|
+
}, yt = "11.7.0", $ = 1e4, bt = 15e5;
|
|
1598
1598
|
function xt(e, t, n = $) {
|
|
1599
1599
|
return e >= n && t / bt < e / 3;
|
|
1600
1600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rayzee",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time WebGPU path tracing engine built on Three.js",
|
|
6
6
|
"main": "dist/rayzee.umd.js",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"three": ">=0.186.0"
|
|
61
61
|
},
|
|
62
62
|
"optionalDependencies": {
|
|
63
|
-
"oidn-web": ">=0.
|
|
63
|
+
"oidn-web": ">=0.5.0",
|
|
64
64
|
"onnxruntime-web": ">=1.0.0"
|
|
65
65
|
}
|
|
66
66
|
}
|
package/src/EngineEvents.js
CHANGED
|
@@ -14,6 +14,8 @@ export const EngineEvents = {
|
|
|
14
14
|
// Denoiser
|
|
15
15
|
DENOISING_START: 'engine:denoisingStart',
|
|
16
16
|
DENOISING_END: 'engine:denoisingEnd',
|
|
17
|
+
// The denoiser fetches and builds its network ({ loading }); a denoise asked for meanwhile waits for it.
|
|
18
|
+
DENOISER_LOADING: 'engine:denoiserLoading',
|
|
17
19
|
|
|
18
20
|
// Upscaler
|
|
19
21
|
UPSCALING_START: 'engine:upscalingStart',
|
|
@@ -4,9 +4,22 @@ import { createLogger, fmt } from '../utils/Logger.js';
|
|
|
4
4
|
|
|
5
5
|
const log = createLogger( 'oidn' );
|
|
6
6
|
|
|
7
|
-
// The tile alignment oidn-web's own
|
|
7
|
+
// The tile alignment oidn-web's own planner uses (its tileScheduler.js).
|
|
8
8
|
const OIDN_TILE_ALIGNMENT = 16;
|
|
9
9
|
|
|
10
|
+
// Mirrored around the frame: at the network's own boundary (zero padding, or oidn-web's repeated
|
|
11
|
+
// edge pixel) the outer pixels come out worse than not denoising at all. A multiple of 16 keeps
|
|
12
|
+
// the frame on the network's pooling grid, so pixels away from the edges denoise as before.
|
|
13
|
+
const OIDN_BORDER = 16;
|
|
14
|
+
|
|
15
|
+
/** The buffer oidn-web reads: the frame inside a mirrored border, both sides a multiple of 16. */
|
|
16
|
+
function oidnInputSize( width, height ) {
|
|
17
|
+
|
|
18
|
+
const align = n => Math.ceil( ( n + 2 * OIDN_BORDER ) / OIDN_TILE_ALIGNMENT ) * OIDN_TILE_ALIGNMENT;
|
|
19
|
+
return { width: align( width ), height: align( height ) };
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
let _initUNetFromBuffer = null;
|
|
11
24
|
async function getInitUNetFromBuffer() {
|
|
12
25
|
|
|
@@ -42,11 +55,8 @@ const MODEL_CONFIG = {
|
|
|
42
55
|
DEFAULT_OPTIONS: {
|
|
43
56
|
enableOIDN: true,
|
|
44
57
|
oidnQuality: 'fast',
|
|
45
|
-
// A cap, not a
|
|
46
|
-
//
|
|
47
|
-
// tileSize + 2*overlap (96px, 112 for _large): at 1024²/high, 4x512 tiles measured
|
|
48
|
-
// 422ms against 229ms for one 1024 tile. 1024 caps the one-time activation
|
|
49
|
-
// allocation at ~430MB; beyond it larger frames tile and stay bounded.
|
|
58
|
+
// A cap on the frame, not a tile size: a frame no larger in area than this square is one
|
|
59
|
+
// tile, with no overlap. The area bounds the network's activation memory.
|
|
50
60
|
tileSize: 1024
|
|
51
61
|
}
|
|
52
62
|
};
|
|
@@ -89,12 +99,13 @@ fn reduce( @builtin(global_invocation_id) gid: vec3<u32>,
|
|
|
89
99
|
@builtin(local_invocation_id) lid: vec3<u32>,
|
|
90
100
|
@builtin(workgroup_id) wid: vec3<u32> ) {
|
|
91
101
|
|
|
102
|
+
// x: frame pixels, y: frame width, z: buffer width, w: border. The border is not metered.
|
|
92
103
|
let total = params.x;
|
|
93
104
|
var sum = 0.0;
|
|
94
105
|
var i = gid.x;
|
|
95
106
|
loop {
|
|
96
107
|
if ( i >= total ) { break; }
|
|
97
|
-
let c = col[ i ].xyz;
|
|
108
|
+
let c = col[ ( i / params.y + params.w ) * params.z + i % params.y + params.w ].xyz;
|
|
98
109
|
let lum = 0.212671 * c.r + 0.71516 * c.g + 0.072169 * c.b;
|
|
99
110
|
sum = sum + log2( max( lum, 0.0 ) + 0.0001 );
|
|
100
111
|
i = i + WG * GROUPS;
|
|
@@ -155,7 +166,7 @@ struct UnpackParams {
|
|
|
155
166
|
tileY: u32,
|
|
156
167
|
tileW: u32,
|
|
157
168
|
tileH: u32,
|
|
158
|
-
|
|
169
|
+
border: u32,
|
|
159
170
|
pad1: u32,
|
|
160
171
|
pad2: u32,
|
|
161
172
|
};
|
|
@@ -173,7 +184,7 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
|
173
184
|
|
|
174
185
|
let px = P.tileX + gid.x;
|
|
175
186
|
let py = P.tileY + gid.y;
|
|
176
|
-
let si = py * P.srcWidth + px;
|
|
187
|
+
let si = ( py + P.border ) * P.srcWidth + px + P.border;
|
|
177
188
|
|
|
178
189
|
// scaleBuf is the autoexposure factor the input was multiplied by; dividing it out returns
|
|
179
190
|
// the renderer's units. Alpha rides through from the path tracer so a transparent background
|
|
@@ -186,13 +197,20 @@ const PACK_WG_SIZE = 8;
|
|
|
186
197
|
const PACK_WGSL = /* wgsl */`
|
|
187
198
|
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
188
199
|
@group(0) @binding(1) var<storage, read_write> dst: array<vec4<f32>>;
|
|
189
|
-
@group(0) @binding(2) var<uniform> size:
|
|
200
|
+
@group(0) @binding(2) var<uniform> size: vec4<u32>; // buffer w, h; frame w, h
|
|
201
|
+
|
|
202
|
+
fn mirror( i: i32, n: i32 ) -> i32 {
|
|
203
|
+
let m = abs( i );
|
|
204
|
+
return clamp( select( m, 2 * n - 2 - m, m >= n ), 0, n - 1 );
|
|
205
|
+
}
|
|
190
206
|
|
|
191
207
|
@compute @workgroup_size(${PACK_WG_SIZE}, ${PACK_WG_SIZE})
|
|
192
208
|
fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
193
209
|
|
|
194
210
|
if ( gid.x >= size.x || gid.y >= size.y ) { return; }
|
|
195
|
-
|
|
211
|
+
let p = vec2<i32>( gid.xy ) - ${OIDN_BORDER};
|
|
212
|
+
let s = vec2<i32>( mirror( p.x, i32( size.z ) ), mirror( p.y, i32( size.w ) ) );
|
|
213
|
+
dst[ gid.y * size.x + gid.x ] = textureLoad( src, s, 0 );
|
|
196
214
|
}
|
|
197
215
|
`;
|
|
198
216
|
|
|
@@ -223,8 +241,9 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
223
241
|
|
|
224
242
|
// Cached GPU storage buffers for texture→buffer copies (reused across denoise calls)
|
|
225
243
|
this._gpuInputBuffers = { color: null, albedo: null, normal: null };
|
|
244
|
+
// The frame the buffers hold; they are allocated at oidnInputSize() of it.
|
|
226
245
|
this._gpuInputBufferSize = { width: 0, height: 0 };
|
|
227
|
-
// Packs
|
|
246
|
+
// Packs the frame and its mirrored border; see _beginPack.
|
|
228
247
|
this._packPipeline = null;
|
|
229
248
|
this._packLayout = null;
|
|
230
249
|
this._packParams = null;
|
|
@@ -363,19 +382,15 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
363
382
|
*/
|
|
364
383
|
_resolveTileSize() {
|
|
365
384
|
|
|
366
|
-
|
|
367
|
-
if ( ! longest ) return this.maxTileSize;
|
|
385
|
+
if ( ! this._renderWidth || ! this._renderHeight ) return this.maxTileSize;
|
|
368
386
|
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if ( aligned <= this.maxTileSize ) return aligned;
|
|
387
|
+
// One tile for any frame no larger in area than the square the cap allows: oidn-web's memory
|
|
388
|
+
// follows the tile's area, not its longest side, so a wide frame needs no split. The cap is on
|
|
389
|
+
// the frame, so the border never splits a frame that fits.
|
|
390
|
+
const input = oidnInputSize( this._renderWidth, this._renderHeight );
|
|
391
|
+
const largest = oidnInputSize( this.maxTileSize, this.maxTileSize );
|
|
392
|
+
if ( input.width * input.height <= largest.width * largest.height ) return Math.max( input.width, input.height );
|
|
376
393
|
|
|
377
|
-
// The cap has to be aligned too, or a cap somebody picked by hand lands on the same slow
|
|
378
|
-
// path. Rounded down, so it never rises above what the caller asked for.
|
|
379
394
|
return Math.max( OIDN_TILE_ALIGNMENT, Math.floor( this.maxTileSize / OIDN_TILE_ALIGNMENT ) * OIDN_TILE_ALIGNMENT );
|
|
380
395
|
|
|
381
396
|
}
|
|
@@ -470,7 +485,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
470
485
|
|
|
471
486
|
const params = this.backendParamsGetter();
|
|
472
487
|
this.gpuDevice = params?.device ?? null;
|
|
473
|
-
backendParams = params?.device ? params : undefined;
|
|
488
|
+
backendParams = params?.device ? { device: params.device } : undefined;
|
|
474
489
|
|
|
475
490
|
}
|
|
476
491
|
|
|
@@ -648,8 +663,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
648
663
|
|
|
649
664
|
/**
|
|
650
665
|
* GPU-native execution path. Copies render target textures into GPU storage buffers
|
|
651
|
-
*
|
|
652
|
-
* oidn-web's well-tested GPUBuffer path.
|
|
666
|
+
* (GPU-only, no CPU roundtrip), then passes those buffers to oidn-web's GPUBuffer path.
|
|
653
667
|
*
|
|
654
668
|
* Note: oidn-web's GPUTexture input path produces NaN outputs — using GPUBuffer instead.
|
|
655
669
|
*/
|
|
@@ -688,71 +702,55 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
688
702
|
|
|
689
703
|
// Autoexposure and the pre-multiply it drives, both on the GPU and both queued behind the
|
|
690
704
|
// copies above. Nothing is awaited here — an await would drain the queue before the UNet.
|
|
691
|
-
|
|
692
|
-
this.
|
|
705
|
+
const input = oidnInputSize( width, height );
|
|
706
|
+
this._computeInputScale( device, width, height );
|
|
707
|
+
this._applyColorScale( device, input.width * input.height );
|
|
693
708
|
|
|
694
709
|
// A final denoise is shown tile by tile, so the tiles still to come must show this render
|
|
695
710
|
// rather than the blank or previous-view picture the output holds between runs.
|
|
696
711
|
if ( ! continuous && ! this._hasOutput ) {
|
|
697
712
|
|
|
698
|
-
this._unpackToTexture( this._gpuInputBuffers.color,
|
|
713
|
+
this._unpackToTexture( this._gpuInputBuffers.color, { x: 0, y: 0, width, height }, false );
|
|
699
714
|
|
|
700
715
|
}
|
|
701
716
|
|
|
702
|
-
|
|
717
|
+
const image = data => ( { data, width: input.width, height: input.height } );
|
|
703
718
|
const config = {
|
|
704
|
-
color:
|
|
705
|
-
albedo:
|
|
706
|
-
normal:
|
|
719
|
+
color: image( this._gpuInputBuffers.color ),
|
|
720
|
+
albedo: image( this._gpuInputBuffers.albedo ),
|
|
721
|
+
normal: image( this._gpuInputBuffers.normal )
|
|
707
722
|
};
|
|
708
723
|
|
|
709
724
|
return this._executeWithAbortGPU( config, continuous );
|
|
710
725
|
|
|
711
726
|
}
|
|
712
727
|
|
|
713
|
-
/** Copies the render's colour, albedo and normal into the
|
|
728
|
+
/** Copies the render's colour, albedo and normal into the buffers oidn-web reads, border included. @private */
|
|
714
729
|
_copyInputs( device, textures, width, height ) {
|
|
715
730
|
|
|
716
|
-
|
|
731
|
+
const input = oidnInputSize( width, height );
|
|
717
732
|
const encoder = device.createCommandEncoder( { label: 'oidn-tex-to-buf' } );
|
|
718
|
-
const
|
|
719
|
-
const pass = tightRowBytes % 256 === 0 ? null : this._beginPack( device, encoder, width, height );
|
|
720
|
-
|
|
721
|
-
const copyTex = ( tex, tightBuf ) => {
|
|
733
|
+
const pass = this._beginPack( device, encoder, input, width, height );
|
|
722
734
|
|
|
723
|
-
if ( pass ) {
|
|
724
|
-
|
|
725
|
-
pass.setBindGroup( 0, device.createBindGroup( {
|
|
726
|
-
layout: this._packLayout,
|
|
727
|
-
entries: [
|
|
728
|
-
{ binding: 0, resource: tex.createView() },
|
|
729
|
-
{ binding: 1, resource: { buffer: tightBuf } },
|
|
730
|
-
{ binding: 2, resource: { buffer: this._packParams } },
|
|
731
|
-
],
|
|
732
|
-
} ) );
|
|
733
|
-
pass.dispatchWorkgroups( Math.ceil( width / PACK_WG_SIZE ), Math.ceil( height / PACK_WG_SIZE ) );
|
|
734
|
-
|
|
735
|
-
} else {
|
|
736
|
-
|
|
737
|
-
encoder.copyTextureToBuffer(
|
|
738
|
-
{ texture: tex, mipLevel: 0 },
|
|
739
|
-
{ buffer: tightBuf, offset: 0, bytesPerRow: tightRowBytes, rowsPerImage: height },
|
|
740
|
-
{ width, height, depthOrArrayLayers: 1 }
|
|
741
|
-
);
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
copyTex( textures.color, this._gpuInputBuffers.color );
|
|
748
|
-
copyTex( textures.albedo, this._gpuInputBuffers.albedo );
|
|
749
735
|
// The normal stays [0,1]-encoded. This contradicts OIDN's API ("must be in the [-1,1]
|
|
750
736
|
// range") but is right for this port: OIDN's own getNormal (cpu_input_process.isph) does
|
|
751
737
|
// `value*0.5+0.5` before the network, and oidn-web omits that remap. Decoding to [-1,1]
|
|
752
738
|
// measured 0.878 -> 2.439 denoise ratio at 64 spp. Do not "fix" it.
|
|
753
|
-
|
|
739
|
+
for ( const name of [ 'color', 'albedo', 'normal' ] ) {
|
|
740
|
+
|
|
741
|
+
pass.setBindGroup( 0, device.createBindGroup( {
|
|
742
|
+
layout: this._packLayout,
|
|
743
|
+
entries: [
|
|
744
|
+
{ binding: 0, resource: textures[ name ].createView() },
|
|
745
|
+
{ binding: 1, resource: { buffer: this._gpuInputBuffers[ name ] } },
|
|
746
|
+
{ binding: 2, resource: { buffer: this._packParams } },
|
|
747
|
+
],
|
|
748
|
+
} ) );
|
|
749
|
+
pass.dispatchWorkgroups( Math.ceil( input.width / PACK_WG_SIZE ), Math.ceil( input.height / PACK_WG_SIZE ) );
|
|
754
750
|
|
|
755
|
-
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
pass.end();
|
|
756
754
|
device.queue.submit( [ encoder.finish() ] );
|
|
757
755
|
|
|
758
756
|
}
|
|
@@ -760,7 +758,6 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
760
758
|
/**
|
|
761
759
|
* Creates or recreates the GPU storage buffers used as oidn-web inputs.
|
|
762
760
|
* Reuses existing buffers if the resolution hasn't changed.
|
|
763
|
-
* Usage: COPY_DST (for copyTextureToBuffer) | STORAGE (for oidn-web WGSL read) | COPY_SRC
|
|
764
761
|
*/
|
|
765
762
|
_ensureGPUInputBuffers( width, height ) {
|
|
766
763
|
|
|
@@ -771,7 +768,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
771
768
|
this._destroyGPUInputBuffers();
|
|
772
769
|
|
|
773
770
|
const device = this.gpuDevice;
|
|
774
|
-
const
|
|
771
|
+
const input = oidnInputSize( width, height );
|
|
772
|
+
const byteSize = input.width * input.height * 16; // rgba32float
|
|
775
773
|
const usage = GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC;
|
|
776
774
|
|
|
777
775
|
this._gpuInputBuffers.color = device.createBuffer( { label: 'oidn-in-color', size: byteSize, usage } );
|
|
@@ -781,8 +779,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
781
779
|
|
|
782
780
|
}
|
|
783
781
|
|
|
784
|
-
/** Opens the compute pass that packs
|
|
785
|
-
_beginPack( device, encoder, width, height ) {
|
|
782
|
+
/** Opens the compute pass that packs a frame into its bordered buffer; one dispatch per texture follows. @private */
|
|
783
|
+
_beginPack( device, encoder, input, width, height ) {
|
|
786
784
|
|
|
787
785
|
if ( ! this._packPipeline ) {
|
|
788
786
|
|
|
@@ -803,12 +801,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
803
801
|
|
|
804
802
|
}
|
|
805
803
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
this._packParams = device.createBuffer( { label: 'oidn-pack-size', size: 8, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST } );
|
|
809
|
-
device.queue.writeBuffer( this._packParams, 0, new Uint32Array( [ width, height ] ) );
|
|
810
|
-
|
|
811
|
-
}
|
|
804
|
+
this._packParams ??= device.createBuffer( { label: 'oidn-pack-size', size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST } );
|
|
805
|
+
device.queue.writeBuffer( this._packParams, 0, new Uint32Array( [ input.width, input.height, width, height ] ) );
|
|
812
806
|
|
|
813
807
|
const pass = encoder.beginComputePass( { label: 'oidn-pack' } );
|
|
814
808
|
pass.setPipeline( this._packPipeline );
|
|
@@ -953,7 +947,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
953
947
|
}
|
|
954
948
|
|
|
955
949
|
/** Writes OIDN's autoexposure scale into `_inputScaleBuffer`. Never read back. */
|
|
956
|
-
_computeInputScale( device,
|
|
950
|
+
_computeInputScale( device, width, height ) {
|
|
957
951
|
|
|
958
952
|
if ( ! this._ensureScalePipelines( device ) ) return;
|
|
959
953
|
|
|
@@ -964,7 +958,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
964
958
|
|
|
965
959
|
}
|
|
966
960
|
|
|
967
|
-
|
|
961
|
+
const input = oidnInputSize( width, height );
|
|
962
|
+
device.queue.writeBuffer( this._lumParams, 0, new Uint32Array( [ width * height, width, input.width, OIDN_BORDER ] ) );
|
|
968
963
|
|
|
969
964
|
const encoder = device.createCommandEncoder( { label: 'oidn-autoexposure' } );
|
|
970
965
|
|
|
@@ -1078,12 +1073,11 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1078
1073
|
/**
|
|
1079
1074
|
* Writes a rectangle of the denoised buffer into the output picture, on the card.
|
|
1080
1075
|
*
|
|
1081
|
-
* @param {GPUBuffer} src - rgba32float,
|
|
1082
|
-
* @param {number}
|
|
1083
|
-
* @param {{x: number, y: number, width: number, height: number}} rect
|
|
1076
|
+
* @param {GPUBuffer} src - rgba32float, the bordered input size, scaled by the input autoexposure
|
|
1077
|
+
* @param {{x: number, y: number, width: number, height: number}} rect - in frame pixels
|
|
1084
1078
|
* @param {boolean} [denoised=true] - false for the raw render laid under a tiled run
|
|
1085
1079
|
*/
|
|
1086
|
-
_unpackToTexture( src,
|
|
1080
|
+
_unpackToTexture( src, rect, denoised = true ) {
|
|
1087
1081
|
|
|
1088
1082
|
const device = this.gpuDevice;
|
|
1089
1083
|
if ( ! device || ! this._ensureScalePipelines( device ) ) return;
|
|
@@ -1101,7 +1095,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1101
1095
|
|
|
1102
1096
|
try {
|
|
1103
1097
|
|
|
1104
|
-
|
|
1098
|
+
const srcWidth = oidnInputSize( this._gpuInputBufferSize.width, this._gpuInputBufferSize.height ).width;
|
|
1099
|
+
device.queue.writeBuffer( params, 0, new Uint32Array( [ srcWidth, x, y, width, height, OIDN_BORDER, 0, 0 ] ) );
|
|
1105
1100
|
|
|
1106
1101
|
const bindGroup = device.createBindGroup( {
|
|
1107
1102
|
label: 'oidn-output-unpack',
|
|
@@ -1174,13 +1169,23 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1174
1169
|
|
|
1175
1170
|
};
|
|
1176
1171
|
|
|
1177
|
-
this.state.abortController.signal
|
|
1172
|
+
const signal = this.state.abortController.signal;
|
|
1173
|
+
signal.addEventListener( 'abort', abortHandler, { once: true } );
|
|
1178
1174
|
|
|
1179
1175
|
abortDenoise = this.unet.tileExecute( {
|
|
1180
1176
|
...config,
|
|
1177
|
+
// Refreshes share the GPU with the render; the closing denoise has it to itself.
|
|
1178
|
+
scheduling: continuous ? 'animation-frame' : 'event-loop',
|
|
1179
|
+
error: ( reason ) => {
|
|
1180
|
+
|
|
1181
|
+
signal.removeEventListener( 'abort', abortHandler );
|
|
1182
|
+
abortDenoise = null;
|
|
1183
|
+
reject( reason );
|
|
1184
|
+
|
|
1185
|
+
},
|
|
1181
1186
|
done: async ( output ) => {
|
|
1182
1187
|
|
|
1183
|
-
|
|
1188
|
+
signal.removeEventListener( 'abort', abortHandler );
|
|
1184
1189
|
abortDenoise = null;
|
|
1185
1190
|
|
|
1186
1191
|
try {
|
|
@@ -1203,28 +1208,28 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1203
1208
|
progress: ( outputData, _tileData, tile ) => {
|
|
1204
1209
|
|
|
1205
1210
|
// oidn-web GPU path: tileData is null, but outputData holds the assembled
|
|
1206
|
-
// full-image buffer updated after each tile. The
|
|
1211
|
+
// full-image buffer updated after each tile. The unpack pass reads that tile's
|
|
1207
1212
|
// rectangle straight out of it, so there are no row-by-row buffer copies.
|
|
1208
1213
|
if ( ! outputData?.data || ! tile ) return;
|
|
1209
1214
|
|
|
1210
|
-
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const width = Math.min( tile.width,
|
|
1215
|
-
const height = Math.min( tile.height,
|
|
1215
|
+
// The tile in frame pixels, without the border.
|
|
1216
|
+
const { width: frameWidth, height: frameHeight } = this._gpuInputBufferSize;
|
|
1217
|
+
const x = Math.max( tile.x - OIDN_BORDER, 0 );
|
|
1218
|
+
const y = Math.max( tile.y - OIDN_BORDER, 0 );
|
|
1219
|
+
const width = Math.min( tile.x + tile.width - OIDN_BORDER, frameWidth ) - x;
|
|
1220
|
+
const height = Math.min( tile.y + tile.height - OIDN_BORDER, frameHeight ) - y;
|
|
1216
1221
|
if ( width <= 0 || height <= 0 ) return;
|
|
1217
1222
|
|
|
1218
|
-
const rect = { x
|
|
1223
|
+
const rect = { x, y, width, height };
|
|
1219
1224
|
|
|
1220
|
-
this._unpackToTexture( outputData.data,
|
|
1225
|
+
this._unpackToTexture( outputData.data, rect );
|
|
1221
1226
|
this._tilesWritten ++;
|
|
1222
1227
|
|
|
1223
1228
|
this.dispatchEvent( {
|
|
1224
1229
|
type: 'tileProgress',
|
|
1225
1230
|
tile: rect,
|
|
1226
|
-
imageWidth:
|
|
1227
|
-
imageHeight:
|
|
1231
|
+
imageWidth: frameWidth,
|
|
1232
|
+
imageHeight: frameHeight,
|
|
1228
1233
|
continuous
|
|
1229
1234
|
} );
|
|
1230
1235
|
|
|
@@ -1237,9 +1242,9 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1237
1242
|
|
|
1238
1243
|
/**
|
|
1239
1244
|
* Degenerate fallback when no per-tile progress was emitted: one authoritative full paint.
|
|
1240
|
-
* @param {{ data: GPUBuffer
|
|
1245
|
+
* @param {{ data: GPUBuffer }} output
|
|
1241
1246
|
*/
|
|
1242
|
-
_displayGPUOutput( { data: gpuBuffer
|
|
1247
|
+
_displayGPUOutput( { data: gpuBuffer } ) {
|
|
1243
1248
|
|
|
1244
1249
|
if ( ! this.gpuDevice ) {
|
|
1245
1250
|
|
|
@@ -1248,7 +1253,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1248
1253
|
|
|
1249
1254
|
}
|
|
1250
1255
|
|
|
1251
|
-
|
|
1256
|
+
const { width, height } = this._gpuInputBufferSize;
|
|
1257
|
+
this._unpackToTexture( gpuBuffer, { x: 0, y: 0, width, height } );
|
|
1252
1258
|
|
|
1253
1259
|
}
|
|
1254
1260
|
|
package/src/PathTracerApp.js
CHANGED
|
@@ -2,11 +2,7 @@ import { Box3, Vector3 } from 'three';
|
|
|
2
2
|
import { RayzeeRenderer, describeAdapter, RENDER_CHECKPOINT_VERSION } from './RayzeeRenderer.js';
|
|
3
3
|
import { NormalDepth } from './Stages/NormalDepth.js';
|
|
4
4
|
import { MotionVector } from './Stages/MotionVector.js';
|
|
5
|
-
import { ASVGF } from './Stages/ASVGF.js';
|
|
6
5
|
import { NRD } from './Stages/NRD.js';
|
|
7
|
-
import { Variance } from './Stages/Variance.js';
|
|
8
|
-
import { BilateralFilter } from './Stages/BilateralFilter.js';
|
|
9
|
-
import { EdgeFilter } from './Stages/EdgeFilter.js';
|
|
10
6
|
import { AutoExposure, AUTO_EXPOSURE_DEFAULTS } from './Stages/AutoExposure.js';
|
|
11
7
|
import { LocalExposure, LOCAL_EXPOSURE_DEFAULTS } from './Stages/LocalExposure.js';
|
|
12
8
|
import { PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, modePresetSettings } from './EngineDefaults.js';
|
|
@@ -151,17 +147,12 @@ export class PathTracerApp extends RayzeeRenderer {
|
|
|
151
147
|
const { renderer, stages } = this;
|
|
152
148
|
stages.normalDepth = new NormalDepth( renderer, { pathTracer: stages.pathTracer } );
|
|
153
149
|
stages.motionVector = new MotionVector( renderer, this.camera, { pathTracer: stages.pathTracer } );
|
|
154
|
-
stages.asvgf = new ASVGF( renderer, { enabled: false } );
|
|
155
150
|
stages.nrd = new NRD( renderer, { enabled: false, pathTracer: stages.pathTracer } );
|
|
156
|
-
stages.variance = new Variance( renderer, { enabled: false } );
|
|
157
|
-
stages.bilateralFilter = new BilateralFilter( renderer, { enabled: false } );
|
|
158
|
-
stages.edgeFilter = new EdgeFilter( renderer, { enabled: false } );
|
|
159
151
|
stages.autoExposure = new AutoExposure( renderer, { enabled: AUTO_EXPOSURE_DEFAULTS.autoExposure } );
|
|
160
152
|
stages.localExposure = new LocalExposure( renderer, { enabled: LOCAL_EXPOSURE_DEFAULTS.localExposure } );
|
|
161
153
|
|
|
162
154
|
return [
|
|
163
|
-
stages.normalDepth, stages.motionVector, stages.nrd, stages.
|
|
164
|
-
stages.variance, stages.bilateralFilter, stages.edgeFilter, stages.autoExposure, stages.localExposure,
|
|
155
|
+
stages.normalDepth, stages.motionVector, stages.nrd, stages.autoExposure, stages.localExposure,
|
|
165
156
|
];
|
|
166
157
|
|
|
167
158
|
}
|
|
@@ -252,7 +243,7 @@ export class PathTracerApp extends RayzeeRenderer {
|
|
|
252
243
|
this._addTrackedListener( this.timeline, EngineEvents.TIMELINE_CHANGED, ( e ) => this.dispatchEvent( e ) );
|
|
253
244
|
|
|
254
245
|
this._forwardEvents( this.denoisingManager, [
|
|
255
|
-
EngineEvents.DENOISING_START, EngineEvents.DENOISING_END,
|
|
246
|
+
EngineEvents.DENOISING_START, EngineEvents.DENOISING_END, EngineEvents.DENOISER_LOADING,
|
|
256
247
|
EngineEvents.UPSCALING_START, EngineEvents.UPSCALING_PROGRESS, EngineEvents.UPSCALING_END,
|
|
257
248
|
EngineEvents.RESOLUTION_CHANGED,
|
|
258
249
|
] );
|
|
@@ -318,7 +309,7 @@ export class PathTracerApp extends RayzeeRenderer {
|
|
|
318
309
|
|
|
319
310
|
_displaySources() {
|
|
320
311
|
|
|
321
|
-
return [ 'oidn:output', '
|
|
312
|
+
return [ 'oidn:output', 'nrd:output' ];
|
|
322
313
|
|
|
323
314
|
}
|
|
324
315
|
|
|
@@ -338,8 +329,10 @@ export class PathTracerApp extends RayzeeRenderer {
|
|
|
338
329
|
|
|
339
330
|
this.cameraManager.applyProjection( value );
|
|
340
331
|
// MotionVector unprojects through projectionMatrixInverse, which is meaningless once every pixel is its
|
|
341
|
-
// own direction:
|
|
342
|
-
if ( value === 'equirectangular' && this.denoisingManager?.requiresMotionVectors ) this.denoisingManager.setDenoiserStrategy( '
|
|
332
|
+
// own direction: hand the view to OIDN, which needs none, rather than leaving no denoiser.
|
|
333
|
+
if ( value === 'equirectangular' && this.denoisingManager?.requiresMotionVectors ) this.denoisingManager.setDenoiserStrategy( 'oidn' );
|
|
334
|
+
// OIDN's motion history is off in a panorama and back on outside one.
|
|
335
|
+
else this.denoisingManager?._syncGBufferStages?.();
|
|
343
336
|
|
|
344
337
|
},
|
|
345
338
|
};
|
|
@@ -844,7 +837,7 @@ export class PathTracerApp extends RayzeeRenderer {
|
|
|
844
837
|
|
|
845
838
|
}
|
|
846
839
|
|
|
847
|
-
// OIDN
|
|
840
|
+
// Live OIDN keeps full size: a resize drops its motion history and shows the raw render until it denoises again.
|
|
848
841
|
_interactionRenderScale() {
|
|
849
842
|
|
|
850
843
|
if ( this.denoisingManager?.continuousDenoise ) return 1;
|
|
@@ -1106,11 +1099,7 @@ export class PathTracerApp extends RayzeeRenderer {
|
|
|
1106
1099
|
pathTracer: this.stages.pathTracer,
|
|
1107
1100
|
normalDepth: this.stages.normalDepth,
|
|
1108
1101
|
motionVector: this.stages.motionVector,
|
|
1109
|
-
asvgf: this.stages.asvgf,
|
|
1110
1102
|
nrd: this.stages.nrd,
|
|
1111
|
-
variance: this.stages.variance,
|
|
1112
|
-
bilateralFilter: this.stages.bilateralFilter,
|
|
1113
|
-
edgeFilter: this.stages.edgeFilter,
|
|
1114
1103
|
autoExposure: this.stages.autoExposure,
|
|
1115
1104
|
compositor: this.stages.compositor,
|
|
1116
1105
|
},
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Two TextureNodes still holding the default EmptyTexture when a stage's kernel is first
|
|
5
5
|
* compiled can share one GPU binding, which then resolves to whichever is assigned last.
|
|
6
|
-
* Nothing throws — the aliased node reads a real texture, just not its own.
|
|
7
|
-
* shipped this way, reading ASVGF's history counter as its variance.
|
|
6
|
+
* Nothing throws — the aliased node reads a real texture, just not its own.
|
|
8
7
|
*
|
|
9
8
|
* Rule enforced: every TextureNode a kernel reads must hold its real texture before that
|
|
10
9
|
* kernel is first dispatched. StorageTexture-typed nodes are exempt — `textureLoad` codegen
|
|
11
10
|
* only emits the required `level` parameter while the node still holds EmptyTexture, so those
|
|
12
|
-
* are deliberately bound after compile
|
|
11
|
+
* are deliberately bound after compile.
|
|
13
12
|
*
|
|
14
13
|
* Off by default; the bench harness turns it on.
|
|
15
14
|
*/
|
|
@@ -23,8 +23,7 @@ import { ISSUE_CODES } from '../EngineIssues.js';
|
|
|
23
23
|
*
|
|
24
24
|
* // Add stages in execution order
|
|
25
25
|
* pipeline.addStage(new PathTracer(...));
|
|
26
|
-
* pipeline.addStage(new
|
|
27
|
-
* pipeline.addStage(new EdgeFilter(...));
|
|
26
|
+
* pipeline.addStage(new NRD(...));
|
|
28
27
|
*
|
|
29
28
|
* // Render all stages
|
|
30
29
|
* pipeline.render(writeBuffer);
|
|
@@ -12,7 +12,7 @@ export const StageExecutionMode = {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* PER_CYCLE - Execute only when tile rendering cycle completes
|
|
15
|
-
* Use for: Post-processing, denoisers, filters (
|
|
15
|
+
* Use for: Post-processing, denoisers, filters (NRD, OIDN)
|
|
16
16
|
*
|
|
17
17
|
* Complete cycle means:
|
|
18
18
|
* - Progressive mode (renderMode=0): Every frame
|
|
@@ -424,7 +424,7 @@ export const writeMediumStack = ( buf, id, stackDepth, transTraversals, ior1, io
|
|
|
424
424
|
), ior1, ior2, ior3 ) );
|
|
425
425
|
|
|
426
426
|
// Region 7: Beer-Lambert sigmaA (.xyz) of the active medium + DDFA feature-throughput (.w).
|
|
427
|
-
// .xyz absorption is gated on stackDepth>0; .w carries the OIDN/
|
|
427
|
+
// .xyz absorption is gated on stackDepth>0; .w carries the OIDN/NRD see-through aux tint (see below).
|
|
428
428
|
// Both writers RMW-preserve the other half so sigmaA (live in glass) and featTP never clobber each other.
|
|
429
429
|
export const readMediumSigmaA = ( buf, id ) => buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).xyz;
|
|
430
430
|
|
|
@@ -436,7 +436,7 @@ export const writeMediumSigmaA = ( buf, id, sigmaA ) => {
|
|
|
436
436
|
};
|
|
437
437
|
|
|
438
438
|
// DDFA (Deferred Denoising-Feature Aux) feature-throughput: a clean per-ray tint carried through smooth
|
|
439
|
-
// glass/mirror, committed into the OIDN/
|
|
439
|
+
// glass/mirror, committed into the OIDN/NRD albedo guide at the first diffuse-enough surface. Rides the
|
|
440
440
|
// otherwise-dead MEDIUM_SIGMA_A.w, packed 10/10/10 into 30 bits (top 2 bits zero ⇒ the float bit-pattern is
|
|
441
441
|
// always finite — never NaN/Inf). LDR [0,1] per channel; final albedo re-quantizes to unorm16 anyway.
|
|
442
442
|
const pack30 = ( tp ) => {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - 1 MRT RenderTarget (3 attachments) used as the readable copy
|
|
8
8
|
* - After each compute dispatch, copyTextureToTexture() transfers data
|
|
9
9
|
* from StorageTextures → RenderTarget textures
|
|
10
|
-
* - Downstream stages (
|
|
10
|
+
* - Downstream stages (NRD, display, OIDN) read from the RenderTarget
|
|
11
11
|
* - Previous-frame reads in the path tracer sample from RenderTarget textures
|
|
12
12
|
* via texture() (not storageTexture().toReadOnly())
|
|
13
13
|
*/
|
|
@@ -14,6 +14,23 @@ import { RenderTarget, FloatType, RGBAFormat, NearestFilter } from 'three';
|
|
|
14
14
|
import { QuadMesh, NodeMaterial } from 'three/webgpu';
|
|
15
15
|
import { texture as tslTexture, screenCoordinate, ivec2 } from 'three/tsl';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* An RGBA render target's pixels without the 256-byte row padding WebGPU copies them with.
|
|
19
|
+
* @returns {Promise<TypedArray>} `width * height * 4`, top row first
|
|
20
|
+
*/
|
|
21
|
+
export async function readPixels( renderer, target, width, height, index = 0 ) {
|
|
22
|
+
|
|
23
|
+
const data = await renderer.readRenderTargetPixelsAsync( target, 0, 0, width, height, index );
|
|
24
|
+
const row = width * 4;
|
|
25
|
+
const stride = Math.ceil( row * data.BYTES_PER_ELEMENT / 256 ) * 256 / data.BYTES_PER_ELEMENT;
|
|
26
|
+
if ( stride === row ) return data;
|
|
27
|
+
|
|
28
|
+
const tight = new data.constructor( row * height );
|
|
29
|
+
for ( let y = 0; y < height; y ++ ) tight.set( data.subarray( y * stride, y * stride + row ), y * row );
|
|
30
|
+
return tight;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
export class TextureReadback {
|
|
18
35
|
|
|
19
36
|
constructor( renderer ) {
|
|
@@ -48,7 +65,7 @@ export class TextureReadback {
|
|
|
48
65
|
|
|
49
66
|
try {
|
|
50
67
|
|
|
51
|
-
return await this.renderer
|
|
68
|
+
return await readPixels( this.renderer, this.draw( source, width, height ), width, height );
|
|
52
69
|
|
|
53
70
|
} finally {
|
|
54
71
|
|