rayzee 7.11.0 → 7.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/rayzee.es.js +565 -545
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +5 -5
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Processor/ShaderBuilder.js +3 -0
- package/src/Stages/PathTracer.js +22 -0
- package/src/TSL/FinalWriteKernel.js +25 -6
- package/src/TSL/ShadeKernel.js +66 -2
- package/src/managers/DenoisingManager.js +6 -0
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ export class ShaderBuilder {
|
|
|
19
19
|
// Previous-frame texture nodes (sample from MRT RenderTarget)
|
|
20
20
|
this.prevColorTexNode = null;
|
|
21
21
|
this.prevAlbedoTexNode = null;
|
|
22
|
+
this.prevNormalDepthTexNode = null;
|
|
22
23
|
|
|
23
24
|
// Scene texture nodes cache (for in-place updates on model change)
|
|
24
25
|
this._sceneTextureNodes = null;
|
|
@@ -98,6 +99,7 @@ export class ShaderBuilder {
|
|
|
98
99
|
const readTextures = storageTextures.getReadTextures();
|
|
99
100
|
this.prevColorTexNode = texture( readTextures.color );
|
|
100
101
|
this.prevAlbedoTexNode = texture( readTextures.albedo );
|
|
102
|
+
this.prevNormalDepthTexNode = texture( readTextures.normalDepth );
|
|
101
103
|
|
|
102
104
|
const createArrayPlaceholder = () => {
|
|
103
105
|
|
|
@@ -137,6 +139,7 @@ export class ShaderBuilder {
|
|
|
137
139
|
|
|
138
140
|
this.prevColorTexNode = null;
|
|
139
141
|
this.prevAlbedoTexNode = null;
|
|
142
|
+
this.prevNormalDepthTexNode = null;
|
|
140
143
|
this._sceneTextureNodes = null;
|
|
141
144
|
|
|
142
145
|
}
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -46,6 +46,11 @@ export class PathTracer extends PathTracerStage {
|
|
|
46
46
|
// uniform — NOT baked — so DenoisingManager can toggle it without a (UI-freezing) kernel rebuild.
|
|
47
47
|
this._auxGBufferEnabled = false;
|
|
48
48
|
this._auxGBufferUniform = uniform( 0, 'uint' );
|
|
49
|
+
// Clean-aux mode: temporally accumulate + renormalize the aux NORMAL (like albedo) so a clean-aux
|
|
50
|
+
// OIDN model (calb_cnrm/high, alb_nrm/balanced) gets the prefiltered-ish normal it expects instead
|
|
51
|
+
// of the point-sampled per-frame value that leaks noise. Off for fast/ASVGF (they want the bump normal).
|
|
52
|
+
this._cleanAuxNormalEnabled = false;
|
|
53
|
+
this._cleanAuxNormalUniform = uniform( 0, 'uint' );
|
|
49
54
|
|
|
50
55
|
// CPU sizes per-bounce kernels from last frame's survivor curve; kernels bound on ENTERING_COUNT so over-sizing is safe. (indirect dispatch not viable — three.js doesn't sync compute-written indirect buffers across submissions)
|
|
51
56
|
this._useDynamicDispatch = true;
|
|
@@ -220,6 +225,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
220
225
|
|
|
221
226
|
this.shaderBuilder.prevColorTexNode.value = readTextures.color;
|
|
222
227
|
this.shaderBuilder.prevAlbedoTexNode.value = readTextures.albedo;
|
|
228
|
+
this.shaderBuilder.prevNormalDepthTexNode.value = readTextures.normalDepth;
|
|
223
229
|
|
|
224
230
|
}
|
|
225
231
|
|
|
@@ -410,6 +416,19 @@ export class PathTracer extends PathTracerStage {
|
|
|
410
416
|
|
|
411
417
|
}
|
|
412
418
|
|
|
419
|
+
// Clean-aux normal: when a clean-aux OIDN model is active (calb_cnrm/high, alb_nrm/balanced), FinalWrite
|
|
420
|
+
// temporally accumulates + renormalizes the aux normal so the model isn't fed per-frame point-sampled
|
|
421
|
+
// noise. DenoisingManager calls this on OIDN enable + quality change. Live uniform → value flip + reset.
|
|
422
|
+
setCleanAuxNormal( enabled ) {
|
|
423
|
+
|
|
424
|
+
enabled = !! enabled;
|
|
425
|
+
if ( this._cleanAuxNormalEnabled === enabled ) return;
|
|
426
|
+
this._cleanAuxNormalEnabled = enabled;
|
|
427
|
+
this._cleanAuxNormalUniform.value = enabled ? 1 : 0;
|
|
428
|
+
this.reset();
|
|
429
|
+
|
|
430
|
+
}
|
|
431
|
+
|
|
413
432
|
// UI-driven resize (Resolution dropdown) — parent bypasses _handleResize(), so hook here too.
|
|
414
433
|
setSize( width, height ) {
|
|
415
434
|
|
|
@@ -605,6 +624,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
605
624
|
|
|
606
625
|
const prevColor = this.shaderBuilder.prevColorTexNode;
|
|
607
626
|
const prevAlbedo = this.shaderBuilder.prevAlbedoTexNode;
|
|
627
|
+
const prevNormalDepth = this.shaderBuilder.prevNormalDepthTexNode;
|
|
608
628
|
const writeTex = this.storageTextures.getWriteTextures();
|
|
609
629
|
|
|
610
630
|
const counters = qm.getCounters();
|
|
@@ -907,10 +927,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
907
927
|
transparentBackground: this.transparentBackground,
|
|
908
928
|
prevAccumTexture: prevColor,
|
|
909
929
|
prevAlbedoTexture: prevAlbedo,
|
|
930
|
+
prevNormalDepthTexture: prevNormalDepth,
|
|
910
931
|
renderWidth: this._wfRenderWidth,
|
|
911
932
|
renderHeight: this._wfRenderHeight,
|
|
912
933
|
visMode: this.visMode,
|
|
913
934
|
auxGBufferEnabled: this._auxGBufferUniform,
|
|
935
|
+
cleanAuxNormalEnabled: this._cleanAuxNormalUniform,
|
|
914
936
|
} );
|
|
915
937
|
this._kernelManager.register( 'finalWrite',
|
|
916
938
|
// Per-pixel (w×h) — kernel averages the S sample-slots internally.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
Fn, wgslFn, float, vec2, vec4, int, uint, uvec2,
|
|
7
|
-
If, mix, select, texture, textureStore,
|
|
7
|
+
If, mix, select, texture, textureStore, length,
|
|
8
8
|
localId, workgroupId,
|
|
9
9
|
} from 'three/tsl';
|
|
10
10
|
|
|
@@ -32,15 +32,19 @@ export function buildFinalWriteKernel( params ) {
|
|
|
32
32
|
resolution, frame,
|
|
33
33
|
enableAccumulation, hasPreviousAccumulated, accumulationAlpha, cameraIsMoving,
|
|
34
34
|
transparentBackground,
|
|
35
|
-
prevAccumTexture, prevAlbedoTexture,
|
|
35
|
+
prevAccumTexture, prevAlbedoTexture, prevNormalDepthTexture,
|
|
36
36
|
renderWidth, renderHeight,
|
|
37
37
|
visMode,
|
|
38
38
|
// Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. Gated by a live uniform (1 = denoiser
|
|
39
39
|
// on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
|
|
40
40
|
auxGBufferEnabled,
|
|
41
|
+
// Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
|
|
42
|
+
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
|
|
43
|
+
cleanAuxNormalEnabled,
|
|
41
44
|
} = params;
|
|
42
45
|
|
|
43
46
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
47
|
+
const cleanAuxNormalOn = cleanAuxNormalEnabled.greaterThan( uint( 0 ) );
|
|
44
48
|
|
|
45
49
|
const computeFn = Fn( () => {
|
|
46
50
|
|
|
@@ -80,12 +84,27 @@ export function buildFinalWriteKernel( params ) {
|
|
|
80
84
|
finalColor.assign( mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) );
|
|
81
85
|
If( auxOn, () => {
|
|
82
86
|
|
|
83
|
-
// Albedo averages cleanly (it's a colour).
|
|
84
|
-
// unit normals collapses toward the flat geometric mean (worse at distance), which made
|
|
85
|
-
// OIDN smooth bump detail away. Keep this frame's point-sampled normal — it varies with
|
|
86
|
-
// the bump, which is exactly what OIDN's edge-stop needs to preserve it.
|
|
87
|
+
// Albedo averages cleanly (it's a colour).
|
|
87
88
|
finalAlbedo.assign( mix( texture( prevAlbedoTexture, prevUV, 0 ).xyz, finalAlbedo, accumulationAlpha ) );
|
|
88
89
|
|
|
90
|
+
// NORMAL: by default keep this frame's POINT-SAMPLED normal — it varies with the bump,
|
|
91
|
+
// which fast/ASVGF want to preserve edge detail. But a CLEAN-AUX OIDN model (calb_cnrm/high,
|
|
92
|
+
// alb_nrm/balanced) trusts the aux and is fed per-frame point-sampled NOISE → leaked noise
|
|
93
|
+
// (high) / over-smoothing (balanced). When cleanAuxNormalOn, temporally accumulate it like
|
|
94
|
+
// the colour. Average the RAW unit normals (decode 0.5+0.5 → [-1,1]) and RENORMALIZE — a
|
|
95
|
+
// plain encoded-space mix would bias toward the flat (0.5,0.5,1) mean and collapse detail.
|
|
96
|
+
// Depth (.w) stays this frame's value; guard the degenerate near-zero mean (opposing normals).
|
|
97
|
+
If( cleanAuxNormalOn, () => {
|
|
98
|
+
|
|
99
|
+
const prevN = texture( prevNormalDepthTexture, prevUV, 0 ).xyz.mul( 2.0 ).sub( 1.0 );
|
|
100
|
+
const curN = finalNormalDepth.xyz.mul( 2.0 ).sub( 1.0 ).toVar();
|
|
101
|
+
const mixedN = mix( prevN, curN, accumulationAlpha ).toVar();
|
|
102
|
+
const len = length( mixedN );
|
|
103
|
+
const avgN = select( len.greaterThan( 1e-4 ), mixedN.div( len ), curN );
|
|
104
|
+
finalNormalDepth.assign( vec4( avgN.mul( 0.5 ).add( 0.5 ), finalNormalDepth.w ) );
|
|
105
|
+
|
|
106
|
+
} );
|
|
107
|
+
|
|
89
108
|
} );
|
|
90
109
|
|
|
91
110
|
If( transparentBackground, () => {
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
readHitDistance, readHitBarycentrics, readHitNormal,
|
|
55
55
|
readHitMaterialIndex, readHitTriangleIndex,
|
|
56
56
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance,
|
|
57
|
-
writeGBuffer, readGBuffer, gbDecodeNormalDepth,
|
|
57
|
+
writeGBuffer, readGBuffer, gbDecodeNormalDepth, gbDecodeAlbedo,
|
|
58
58
|
readRayRadiance,
|
|
59
59
|
} from '../Processor/PackedRayBuffer.js';
|
|
60
60
|
|
|
@@ -320,6 +320,11 @@ export function buildShadeKernel( params ) {
|
|
|
320
320
|
const wantBackdrop = isBackdropView.and( showBackground ); // draw env image as backdrop
|
|
321
321
|
const wantEnvLight = isBackdropView.not().and( enableEnvironmentLight ); // env as light on redirected bounces
|
|
322
322
|
|
|
323
|
+
// Fix ①: OIDN clean-aux for a redirected ray that escapes to the environment (e.g. smooth
|
|
324
|
+
// glass over sky). Holds the clamped env colour this ray sees; written into the aux G-buffer
|
|
325
|
+
// before the miss Return below, but only when the pixel still carries the black bounce-0 default.
|
|
326
|
+
const escapedAuxAlbedo = vec3( 0.0 ).toVar();
|
|
327
|
+
|
|
323
328
|
If( wantBackdrop.or( wantEnvLight ), () => {
|
|
324
329
|
|
|
325
330
|
// Ground projection bends the backdrop-view env lookup onto a projected sphere+disk so the lower
|
|
@@ -358,6 +363,10 @@ export function buildShadeKernel( params ) {
|
|
|
358
363
|
|
|
359
364
|
} );
|
|
360
365
|
|
|
366
|
+
// Fix ①: remember the (clamped) env colour this escaped ray sees, so a redirected ray
|
|
367
|
+
// that never captured a surface aux can feed OIDN a meaningful albedo below.
|
|
368
|
+
escapedAuxAlbedo.assign( clamp( envColor, vec3( 0.0 ), vec3( 1.0 ) ) );
|
|
369
|
+
|
|
361
370
|
// MIS weight for implicit env hit — prevents double-counting with NEE
|
|
362
371
|
const envMisWeight = float( 1.0 ).toVar();
|
|
363
372
|
If( isBackdropView.not().and( useEnvMapIS ), () => {
|
|
@@ -425,6 +434,25 @@ export function buildShadeKernel( params ) {
|
|
|
425
434
|
|
|
426
435
|
} );
|
|
427
436
|
|
|
437
|
+
// Fix ①: fill the black bounce-0 default aux for a redirected ray that reached the
|
|
438
|
+
// environment without ever locking a surface guide (smooth glass / specular transmission
|
|
439
|
+
// over sky). Only when the stored albedo is still the black default (dot < 1e-4), so a real
|
|
440
|
+
// specular-surface aux written upstream is never clobbered. Normal = the escaped ray
|
|
441
|
+
// direction (varies per pixel → OIDN sees structure that tracks the refracted view);
|
|
442
|
+
// albedo = the clamped env colour it sees; depth kept at the primary hit.
|
|
443
|
+
If( auxOn
|
|
444
|
+
.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) )
|
|
445
|
+
.and( flags.bitAnd( uint( RAY_FLAG.REDIRECTED ) ).notEqual( uint( 0 ) ) ), () => {
|
|
446
|
+
|
|
447
|
+
const gPrev = readGBuffer( gBufferRW, pixelIndex );
|
|
448
|
+
If( dot( gbDecodeAlbedo( gPrev ), vec3( 1.0 ) ).lessThan( float( 1e-4 ) ), () => {
|
|
449
|
+
|
|
450
|
+
writeGBuffer( gBufferRW, pixelIndex, normalize( direction ), gbDecodeNormalDepth( gPrev ).w, escapedAuxAlbedo );
|
|
451
|
+
|
|
452
|
+
} );
|
|
453
|
+
|
|
454
|
+
} );
|
|
455
|
+
|
|
428
456
|
writeRayRadiance( rayBufferRW, rayID, currentRadiance );
|
|
429
457
|
writeRayDirFlags( rayBufferRW, rayID, direction, flags.bitAnd( uint( ~ RAY_FLAG.ACTIVE ) ) );
|
|
430
458
|
Return();
|
|
@@ -607,7 +635,23 @@ export function buildShadeKernel( params ) {
|
|
|
607
635
|
// (aux-extend) and may extend through specular surfaces (gap #9). Glass rays Return at the transparency
|
|
608
636
|
// block before that capture, so a glass-then-escape pixel keeps this default aux — megakernel parity
|
|
609
637
|
// (objectNormal/objectColor stay at their init for transmissive-then-miss).
|
|
610
|
-
|
|
638
|
+
//
|
|
639
|
+
// Fix ③: BLEND (semi-transparent) glass instead LOCKS its OWN normal + albedo here. Its per-frame
|
|
640
|
+
// stochastic alpha-skip-vs-opaque-shade outcome (MaterialTransmission.js:522-540) otherwise writes a
|
|
641
|
+
// DIFFERENT surface into the aux each frame (wall-behind on skip vs glass on opaque); with the normal
|
|
642
|
+
// not temporally averaged that becomes the speckle OIDN smears on — the visible artefact on
|
|
643
|
+
// alpha-transparent glass. A deterministic glass-surface guide every frame removes it. MASK / opaque
|
|
644
|
+
// materials keep the default + rely on the downstream aux-extend.
|
|
645
|
+
If( material.alphaMode.equal( int( 2 ) ), () => {
|
|
646
|
+
|
|
647
|
+
writeGBuffer( gBufferRW, pixelIndex, N, linearDepth, albedo.xyz );
|
|
648
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.AUX_LOCKED ) ) );
|
|
649
|
+
|
|
650
|
+
} ).Else( () => {
|
|
651
|
+
|
|
652
|
+
writeGBuffer( gBufferRW, pixelIndex, vec3( 0.0, 0.0, 1.0 ), linearDepth, vec3( 0.0 ) );
|
|
653
|
+
|
|
654
|
+
} );
|
|
611
655
|
|
|
612
656
|
} );
|
|
613
657
|
|
|
@@ -744,6 +788,26 @@ export function buildShadeKernel( params ) {
|
|
|
744
788
|
|
|
745
789
|
throughput.mulAssign( interaction.throughput );
|
|
746
790
|
|
|
791
|
+
// Fix ②: OIDN clean-aux for STOCHASTIC glass (rough / frosted / dispersive), whose refracted
|
|
792
|
+
// ray hits a different behind-surface every frame. Extending aux through it (the aux-extend on
|
|
793
|
+
// the opaque-hit path) captures that per-frame surface — albedo then temporally averaged, normal
|
|
794
|
+
// point-sampled — an inconsistent guide OIDN smears on. Instead lock the glass surface's OWN
|
|
795
|
+
// normal + tint here (deterministic, spatially coherent). Smooth non-dispersive glass is left
|
|
796
|
+
// alone: its refraction is a per-pixel delta, so the behind-surface aux captured downstream is
|
|
797
|
+
// already stable. Gated to a genuine dielectric event (not alpha-cutout passthrough or SSS).
|
|
798
|
+
If( auxOn
|
|
799
|
+
.and( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ) )
|
|
800
|
+
.and( interaction.isTransmissive )
|
|
801
|
+
.and( interaction.isAlphaSkip.not() )
|
|
802
|
+
.and( interaction.isSubsurface.not() )
|
|
803
|
+
.and( material.roughness.greaterThan( 0.05 ).or( material.dispersion.greaterThan( 0.0 ) ) ), () => {
|
|
804
|
+
|
|
805
|
+
const primaryDepth = gbDecodeNormalDepth( readGBuffer( gBufferRW, pixelIndex ) ).w;
|
|
806
|
+
writeGBuffer( gBufferRW, pixelIndex, N, primaryDepth, albedo.xyz );
|
|
807
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.AUX_LOCKED ) ) );
|
|
808
|
+
|
|
809
|
+
} );
|
|
810
|
+
|
|
747
811
|
// reflection stays on same side, transmission pushes through
|
|
748
812
|
const reflectOffsetDir = select( interaction.entering, N, N.negate() );
|
|
749
813
|
const offsetDir = select( interaction.didReflect, reflectOffsetDir, direction );
|
|
@@ -368,6 +368,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
368
368
|
// MRT read-targets directly). When none are active the wavefront skips those writes entirely.
|
|
369
369
|
s.pathTracer?.setAuxGBufferEnabled?.( normalNeeded || !! this.denoiser?.enabled );
|
|
370
370
|
|
|
371
|
+
// Clean-aux normal: temporally accumulate the aux normal only for OIDN clean-aux models
|
|
372
|
+
// (balanced/high). 'fast' and the real-time denoisers keep the point-sampled bump normal.
|
|
373
|
+
s.pathTracer?.setCleanAuxNormal?.( !! this.denoiser?.enabled && this.denoiser?.quality !== 'fast' );
|
|
374
|
+
|
|
371
375
|
// Reclaim VRAM: free the big 2048² StorageTextures of any denoiser/G-buffer stage that ended up
|
|
372
376
|
// disabled (lazily re-created on the next dispatch after re-enable). Every strategy/denoiser
|
|
373
377
|
// toggle funnels through here after the enabled flags above are settled, so this is the one
|
|
@@ -610,6 +614,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
610
614
|
setOIDNQuality( quality ) {
|
|
611
615
|
|
|
612
616
|
this.denoiser?.updateQuality( quality );
|
|
617
|
+
// Clean-aux normal follows the model: balanced/high (clean-aux models) → accumulate; fast → keep bump.
|
|
618
|
+
this._stages.pathTracer?.setCleanAuxNormal?.( !! this.denoiser?.enabled && quality !== 'fast' );
|
|
613
619
|
|
|
614
620
|
}
|
|
615
621
|
|