rayzee 7.22.3 → 7.22.5
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/dist/rayzee.es.js +1574 -1466
- package/dist/rayzee.umd.js +43 -43
- package/package.json +1 -1
- package/src/EngineDefaults.js +20 -13
- package/src/PathTracerApp.js +13 -0
- package/src/Processor/QueueManager.js +6 -1
- package/src/RenderSettings.js +13 -1
- package/src/Stages/Compositor.js +132 -2
- package/src/Stages/PathTracer.js +192 -15
- package/src/TSL/FinalWriteKernel.js +61 -34
- package/src/TSL/GenerateKernel.js +4 -4
- package/src/TSL/PathTracerCore.js +5 -1
- package/src/TSL/ShadeKernel.js +4 -1
- package/src/managers/UniformManager.js +4 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
Fn, wgslFn, float, vec2, vec4, int, uint, uvec2,
|
|
12
|
-
If, mix, select, texture, textureStore, length,
|
|
12
|
+
If, mix, select, texture, textureStore, length,
|
|
13
13
|
localId, workgroupId,
|
|
14
14
|
} from 'three/tsl';
|
|
15
15
|
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
readRayRadiance, readGBuffer, gbDecodeNormalDepth, gbDecodeAlbedo,
|
|
18
18
|
} from '../Processor/PackedRayBuffer.js';
|
|
19
19
|
import { luminance } from './Common.js';
|
|
20
|
-
import { COUNTER } from '../Processor/QueueManager.js';
|
|
21
20
|
|
|
22
21
|
const WG_SIZE = 16;
|
|
23
22
|
|
|
@@ -49,12 +48,17 @@ export function buildFinalWriteKernel( params ) {
|
|
|
49
48
|
// Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
|
|
50
49
|
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
|
|
51
50
|
cleanAuxNormalEnabled,
|
|
52
|
-
// Tier-1 convergence early-stop: per-pixel Welford luminance second moment + converged
|
|
53
|
-
|
|
51
|
+
// Tier-1 convergence early-stop: per-pixel Welford luminance second moment + converged bit (stamped
|
|
52
|
+
// into streak bit 31; counted with 3×3 erosion by countConvergedDilated at the next frame start).
|
|
53
|
+
m2BufferRW, useAdaptiveSampling, noiseThreshold, adaptiveMinSamples,
|
|
54
54
|
|
|
55
|
-
// Tier-2 per-pixel freeze: streakBufferRW = per-pixel freeze-candidate streak (stamped
|
|
56
|
-
// frozenMaskRO = dilated frozen mask from buildActivePixels (pass-through gates on it, not streak).
|
|
55
|
+
// Tier-2 per-pixel freeze: streakBufferRW = per-pixel freeze-candidate streak in bits 0-30 (stamped
|
|
56
|
+
// here); frozenMaskRO = dilated frozen mask from buildActivePixels (pass-through gates on it, not streak).
|
|
57
57
|
usePixelFreeze, pixelFreezeThreshold, streakBufferRW, frozenMaskRO,
|
|
58
|
+
|
|
59
|
+
// Display-only convergence overlay (Compositor). Only keeps m2 updated so the overlay has a live
|
|
60
|
+
// error field with adaptive sampling off — the counter and the freeze streak stay under convOn.
|
|
61
|
+
convergenceOverlay,
|
|
58
62
|
} = params;
|
|
59
63
|
|
|
60
64
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
@@ -62,6 +66,9 @@ export function buildFinalWriteKernel( params ) {
|
|
|
62
66
|
// useAdaptiveSampling is registered via UniformManager.ub() → an int 0/1 uniform, so compare against int(0).
|
|
63
67
|
const convOn = useAdaptiveSampling.greaterThan( int( 0 ) );
|
|
64
68
|
const adaptiveOn = usePixelFreeze.greaterThan( int( 0 ) );
|
|
69
|
+
// The m2/relErr block feeds three consumers: the frame early-stop, the per-pixel freeze, and the debug
|
|
70
|
+
// overlay. Any one of them being on has to keep it live.
|
|
71
|
+
const statsOn = convOn.or( adaptiveOn ).or( convergenceOverlay.greaterThan( int( 0 ) ) );
|
|
65
72
|
|
|
66
73
|
const computeFn = Fn( () => {
|
|
67
74
|
|
|
@@ -139,13 +146,10 @@ export function buildFinalWriteKernel( params ) {
|
|
|
139
146
|
|
|
140
147
|
} );
|
|
141
148
|
|
|
142
|
-
//
|
|
143
|
-
// so
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
// The m2 write runs every frame (incl. frame 0, where alpha==1 self-inits it → no explicit clear).
|
|
147
|
-
// Sits AFTER the accumulation mix (finalColor is the mean) and BEFORE the visMode-11 mutation.
|
|
148
|
-
If( convOn, () => {
|
|
149
|
+
// Per-pixel running second moment of luminance under the SAME global 1/(frame+1) alpha as the
|
|
150
|
+
// colour, so sampleVar = E[L²]-E[L]². Must sit after the accumulation mix (finalColor is the mean)
|
|
151
|
+
// and before the visMode-11 mutation. Frame 0 self-inits m2 via alpha==1 — no explicit clear.
|
|
152
|
+
If( statsOn, () => {
|
|
149
153
|
|
|
150
154
|
const sampleLum = luminance( sampleColor.xyz );
|
|
151
155
|
const prevM2 = m2BufferRW.element( pixelId ).toVar();
|
|
@@ -155,44 +159,67 @@ export function buildFinalWriteKernel( params ) {
|
|
|
155
159
|
const meanLum = luminance( finalColor ).toVar();
|
|
156
160
|
const sampleVar = m2.sub( meanLum.mul( meanLum ) ).max( float( 0 ) );
|
|
157
161
|
const varOfMean = sampleVar.div( float( frame ).add( 1.0 ) );
|
|
158
|
-
// absSE = absolute standard error of the mean luminance; relErr = its ratio to the mean.
|
|
159
|
-
// Combined criterion: bright pixels converge on relErr<threshold; dark/dim pixels (where relErr
|
|
160
|
-
// stays high forever) converge on absSE<absFloor, since their absolute noise is imperceptible.
|
|
161
162
|
const absSE = varOfMean.sqrt().toVar();
|
|
162
163
|
const relErr = absSE.div( meanLum.add( float( 1e-4 ) ) ); // Tier-2 freeze predicate reads this
|
|
163
164
|
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
// on their small absolute noise instead of stalling forever on a pure relative bar.
|
|
165
|
+
// √luminance normalization below 1 loosens the bar in shadows, so dark pixels converge on their
|
|
166
|
+
// small absolute noise instead of stalling forever on a pure relative one.
|
|
167
167
|
const convNorm = select( meanLum.lessThan( float( 1.0 ) ), meanLum.sqrt(), meanLum );
|
|
168
168
|
const converged = absSE.div( convNorm.add( float( 1e-4 ) ) ).lessThan( noiseThreshold );
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
// Must cover adaptiveOn: nested under convOn alone, freeze neither updated its streak nor ran
|
|
171
|
+
// its frame-0 clear, so buildActivePixels kept applying the last run's mask across resets.
|
|
172
|
+
If( convOn.or( adaptiveOn ), () => {
|
|
171
173
|
|
|
172
|
-
|
|
174
|
+
// Streak word: bit 31 = converged, bit 30 = hit geometry, bits 0-29 = freeze streak. Both flags
|
|
175
|
+
// are read by countConvergedDilated, which erodes them 3×3 before counting — a pointwise count is
|
|
176
|
+
// blind to undiscovered energy, which has zero variance and so passes any threshold.
|
|
177
|
+
const eligible = convOn.and( frame.greaterThanEqual( uint( adaptiveMinSamples ) ) ).and( converged );
|
|
178
|
+
const prevWord = streakBufferRW.element( pixelId ).toVar();
|
|
179
|
+
const oldStreak = prevWord.bitAnd( uint( 0x3FFFFFFF ) ).toVar();
|
|
180
|
+
const newStreak = oldStreak.toVar();
|
|
173
181
|
|
|
174
|
-
|
|
182
|
+
// Sticky within a run, else silhouette pixels flicker in and out of the subject denominator under
|
|
183
|
+
// AA jitter. Alpha is the only hit/miss signal valid here: the aux G-buffer is denoiser-gated and
|
|
184
|
+
// the ray flags do not survive to FinalWrite.
|
|
185
|
+
const hitGeometry = sampleColor.w.greaterThan( float( 0.5 ) );
|
|
186
|
+
const wasGeometry = prevWord.bitAnd( uint( 0x40000000 ) ).notEqual( uint( 0 ) );
|
|
187
|
+
const geometryBit = select(
|
|
188
|
+
frame.equal( uint( 0 ) ),
|
|
189
|
+
select( hitGeometry, uint( 0x40000000 ), uint( 0 ) ),
|
|
190
|
+
select( hitGeometry.or( wasGeometry ), uint( 0x40000000 ), uint( 0 ) )
|
|
191
|
+
).toVar();
|
|
175
192
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
// relErr-only, no absolute floor — a floor bakes dim regions dark once it freezes them.
|
|
194
|
+
// Monotonic: streak>=K freezes for the run, so the global alpha stays exact.
|
|
195
|
+
If( adaptiveOn, () => {
|
|
179
196
|
|
|
180
|
-
|
|
197
|
+
If( frame.equal( uint( 0 ) ), () => {
|
|
181
198
|
|
|
182
|
-
|
|
183
|
-
streakBufferRW.element( pixelId ).assign( uint( 0 ) );
|
|
199
|
+
newStreak.assign( uint( 0 ) );
|
|
184
200
|
|
|
185
|
-
|
|
201
|
+
} );
|
|
186
202
|
|
|
187
|
-
|
|
203
|
+
If( wasFrozen.not().and( frame.greaterThan( uint( 0 ) ) ), () => {
|
|
188
204
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
205
|
+
// meanLum>0: an all-zero pixel has zero variance and passes any bar, but freezing skips its
|
|
206
|
+
// rays so it could never then discover its energy (−39% mean luminance on Veach bidir). The
|
|
207
|
+
// whole-frame count deliberately has no such guard — true black must count as converged there.
|
|
208
|
+
const freezeCandidate = frame.greaterThanEqual( uint( adaptiveMinSamples ) )
|
|
209
|
+
.and( relErr.lessThan( pixelFreezeThreshold ) )
|
|
210
|
+
.and( meanLum.greaterThan( float( 0 ) ) );
|
|
211
|
+
newStreak.assign( select( freezeCandidate, oldStreak.add( uint( 1 ) ), uint( 0 ) ) );
|
|
212
|
+
|
|
213
|
+
} );
|
|
193
214
|
|
|
194
215
|
} );
|
|
195
216
|
|
|
217
|
+
streakBufferRW.element( pixelId ).assign(
|
|
218
|
+
newStreak
|
|
219
|
+
.bitOr( select( eligible, uint( 0x80000000 ), uint( 0 ) ) )
|
|
220
|
+
.bitOr( geometryBit )
|
|
221
|
+
);
|
|
222
|
+
|
|
196
223
|
} );
|
|
197
224
|
|
|
198
225
|
} );
|
|
@@ -88,10 +88,10 @@ export function buildGenerateKernel( params ) {
|
|
|
88
88
|
// pdf inits to 0 = prevBouncePdf (megakernel parity PathTracerCore.js:556). The bounce>0 env/emissive
|
|
89
89
|
// MIS gate skips until an opaque scatter writes a real combinedPdf; free bounces preserve it.
|
|
90
90
|
writeRayThroughputPdf( rayBufferRW, rayID, vec4( 1.0, 1.0, 1.0, 0.0 ).xyz, float( 0.0 ) );
|
|
91
|
-
// Alpha inits to 1
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ),
|
|
91
|
+
// Alpha inits to 1 unconditionally (megakernel parity: PathTracerCore.js:554). Shade zeroes it only on
|
|
92
|
+
// env-escape-without-opaque, so the lane always carries coverage — which the convergence gate reads to
|
|
93
|
+
// tell subject from background. Output is unaffected: FinalWrite forces alpha 1 when transparent-bg is off.
|
|
94
|
+
writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), float( 1.0 ) ) );
|
|
95
95
|
|
|
96
96
|
If( auxOn, () => {
|
|
97
97
|
|
|
@@ -356,7 +356,11 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
356
356
|
rrProb.mulAssign( depthFactor );
|
|
357
357
|
|
|
358
358
|
const minProb = select( mc.isEmissive, float( 0.04 ), float( 0.02 ) );
|
|
359
|
-
|
|
359
|
+
// Upper clamp is load-bearing, not cosmetic: pathContribution scales with throughput,
|
|
360
|
+
// which prior RR compensation inflates well past 1, so rrProb could exceed 1. The path
|
|
361
|
+
// then always survives while throughput is still divided by rrProb — a plain energy
|
|
362
|
+
// sink that compounds with depth (albedo-1 concave furnace read 0.955 instead of 1.0).
|
|
363
|
+
rrProb.assign( clamp( rrProb, minProb, 1.0 ) );
|
|
360
364
|
|
|
361
365
|
const rrSample = getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame );
|
|
362
366
|
result.assign( select( rrSample.lessThan( rrProb ), rrProb, float( 0.0 ) ) );
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -469,7 +469,10 @@ export function buildShadeKernel( params ) {
|
|
|
469
469
|
// geometry (megakernel parity: PathTracerCore.js:784). A secondary bounce off an opaque
|
|
470
470
|
// surface that escapes to env keeps alpha 1 (HAS_HIT_OPAQUE set), so glass-in-front-of-an-
|
|
471
471
|
// object stays opaque while glass-in-front-of-sky exports see-through.
|
|
472
|
-
|
|
472
|
+
// Ungated (was transparentBackground-only) so the alpha lane always carries coverage: it is the
|
|
473
|
+
// only hit/miss signal valid at FinalWrite. The image is unchanged — FinalWrite forces alpha 1
|
|
474
|
+
// when transparent-bg is off.
|
|
475
|
+
If( flags.bitAnd( uint( RAY_FLAG.HAS_HIT_OPAQUE ) ).equal( uint( 0 ) ), () => {
|
|
473
476
|
|
|
474
477
|
currentRadiance.w.assign( 0.0 );
|
|
475
478
|
|
|
@@ -186,6 +186,10 @@ export class UniformManager {
|
|
|
186
186
|
u( 'pixelFreezeThreshold', DEFAULT_STATE.pixelFreezeThreshold, 'float' );
|
|
187
187
|
u( 'pixelFreezeStability', DEFAULT_STATE.pixelFreezeStability, 'int' );
|
|
188
188
|
|
|
189
|
+
// Convergence debug overlay: keeps FinalWrite's m2 estimate alive when adaptive sampling is off,
|
|
190
|
+
// so the Compositor overlay reads a live error field instead of a stale buffer.
|
|
191
|
+
ub( 'convergenceOverlay', DEFAULT_STATE.convergenceOverlay );
|
|
192
|
+
|
|
189
193
|
// Accumulation
|
|
190
194
|
ub( 'enableAccumulation', true );
|
|
191
195
|
u( 'accumulationAlpha', 0.0, 'float' );
|