rayzee 7.26.4 → 7.27.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/dist/rayzee.es.js +2525 -2509
- package/dist/rayzee.umd.js +17 -17
- package/package.json +1 -1
- package/src/PathTracerApp.js +3 -0
- package/src/Pipeline/CompletionTracker.js +25 -4
- package/src/TSL/LightsSampling.js +82 -39
- package/src/managers/LightManager.js +7 -0
package/package.json
CHANGED
package/src/PathTracerApp.js
CHANGED
|
@@ -393,6 +393,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
393
393
|
samples: this.stages.pathTracer.frameCount,
|
|
394
394
|
timeElapsed: this.completion.timeElapsed,
|
|
395
395
|
budgetOverrun: this.completion.budgetOverrun,
|
|
396
|
+
// null means isComplete was forced rather than earned (the reconcile below);
|
|
397
|
+
// a forced stop is closer to the ceiling than to convergence.
|
|
398
|
+
reason: this.completion.stopCondition( this.stages.pathTracer ) ?? 'samples',
|
|
396
399
|
};
|
|
397
400
|
|
|
398
401
|
this.dispatchEvent( { type: 'RenderComplete', ...completionInfo } );
|
|
@@ -83,12 +83,33 @@ export class CompletionTracker {
|
|
|
83
83
|
|
|
84
84
|
if ( ! pathTracer ) return false;
|
|
85
85
|
|
|
86
|
+
// Arms budgetOverrun, which stopCondition reads.
|
|
86
87
|
if ( this.isTimeLimitReached( pathTracer, renderLimitMode, renderTimeLimit ) ) return true;
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
return this.stopCondition( pathTracer ) !== null;
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Which stop condition is satisfied, or null to keep rendering — the one place the condition
|
|
95
|
+
* set lives, so "should we stop" and "why did we stop" cannot drift apart. The Tier-1
|
|
96
|
+
* convergence check must agree with PathTracer.render()'s own, else the app-level reconcile
|
|
97
|
+
* flips isComplete back off and keeps dispatching after the frame converged.
|
|
98
|
+
*
|
|
99
|
+
* Ceiling before convergence: a render that spent its whole budget reports the ceiling even
|
|
100
|
+
* when it also converged on that sample.
|
|
101
|
+
*
|
|
102
|
+
* @param {Object} pathTracer - The PathTracer stage
|
|
103
|
+
* @returns {'timeLimit'|'samples'|'converged'|null}
|
|
104
|
+
*/
|
|
105
|
+
stopCondition( pathTracer ) {
|
|
106
|
+
|
|
107
|
+
if ( this.budgetOverrun ) return 'timeLimit';
|
|
108
|
+
if ( ! pathTracer ) return null;
|
|
109
|
+
if ( pathTracer.frameCount >= pathTracer.completionThreshold ) return 'samples';
|
|
110
|
+
if ( pathTracer._isConvergedComplete?.() ) return 'converged';
|
|
111
|
+
|
|
112
|
+
return null;
|
|
92
113
|
|
|
93
114
|
}
|
|
94
115
|
|
|
@@ -330,6 +330,67 @@ export const samplePointLightWithAttenuation = Fn( ( [ light, rayOrigin, lightSe
|
|
|
330
330
|
|
|
331
331
|
} );
|
|
332
332
|
|
|
333
|
+
// Sun sampling. `angle` is angular diameter: 0 is a hard shadow, >0 cone-samples the disc.
|
|
334
|
+
//
|
|
335
|
+
// intensity is IRRADIANCE (Blender Sun "Strength"), already integrated over the disc, so it has
|
|
336
|
+
// to become radiance before NEE divides by the 1/solidAngle cone pdf. Passing it through raw
|
|
337
|
+
// scaled the sun BY its own solid angle — a real 0.53° sun measured ~15000x too dark.
|
|
338
|
+
export const sampleDirectionalLight = Fn( ( [ light, rayOrigin, lightSelectionPdf, uCos, uPhi ] ) => {
|
|
339
|
+
|
|
340
|
+
const ls_valid = tslBool( false ).toVar();
|
|
341
|
+
const ls_direction = vec3( 0.0, 1.0, 0.0 ).toVar();
|
|
342
|
+
const ls_emission = vec3( 0.0 ).toVar();
|
|
343
|
+
const ls_pdf = float( 0.0 ).toVar();
|
|
344
|
+
|
|
345
|
+
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
346
|
+
|
|
347
|
+
const w = normalize( light.direction ).toVar();
|
|
348
|
+
const emission = light.color.mul( light.intensity ).mul( sampleDirectionalGoboMask( light, rayOrigin ) ).toVar();
|
|
349
|
+
|
|
350
|
+
ls_direction.assign( w );
|
|
351
|
+
ls_pdf.assign( lightSelectionPdf );
|
|
352
|
+
|
|
353
|
+
If( light.angle.greaterThan( 0.0 ), () => {
|
|
354
|
+
|
|
355
|
+
const cosHalfAngle = cos( light.angle.mul( 0.5 ) ).toVar();
|
|
356
|
+
const cosTheta = mix( cosHalfAngle, float( 1.0 ), uCos ).toVar();
|
|
357
|
+
const sinTheta = sqrt( max( float( 0.0 ), float( 1.0 ).sub( cosTheta.mul( cosTheta ) ) ) ).toVar();
|
|
358
|
+
const phi = float( TWO_PI ).mul( uPhi ).toVar();
|
|
359
|
+
|
|
360
|
+
const u = normalize( cross(
|
|
361
|
+
select( abs( w.x ).greaterThan( 0.9 ), vec3( 0.0, 1.0, 0.0 ), vec3( 1.0, 0.0, 0.0 ) ),
|
|
362
|
+
w
|
|
363
|
+
) ).toVar();
|
|
364
|
+
const v = cross( w, u ).toVar();
|
|
365
|
+
|
|
366
|
+
ls_direction.assign( normalize(
|
|
367
|
+
w.mul( cosTheta ).add( u.mul( cos( phi ) ).add( v.mul( sin( phi ) ) ).mul( sinTheta ) )
|
|
368
|
+
) );
|
|
369
|
+
|
|
370
|
+
// Guard the division: (1 - cosHalfAngle) underflows to zero at tiny angles.
|
|
371
|
+
const solidAngle = float( TWO_PI ).mul( max( float( 1.0 ).sub( cosHalfAngle ), 1e-10 ) ).toVar();
|
|
372
|
+
emission.assign( emission.div( solidAngle ) );
|
|
373
|
+
ls_pdf.assign( lightSelectionPdf.div( solidAngle ) );
|
|
374
|
+
|
|
375
|
+
} );
|
|
376
|
+
|
|
377
|
+
ls_emission.assign( emission );
|
|
378
|
+
ls_valid.assign( tslBool( true ) );
|
|
379
|
+
|
|
380
|
+
} );
|
|
381
|
+
|
|
382
|
+
return LightSample( {
|
|
383
|
+
valid: ls_valid,
|
|
384
|
+
direction: ls_direction,
|
|
385
|
+
emission: ls_emission,
|
|
386
|
+
distance: float( 1e6 ),
|
|
387
|
+
pdf: ls_pdf,
|
|
388
|
+
lightType: int( LIGHT_TYPE_DIRECTIONAL ),
|
|
389
|
+
selectionTotalWeight: float( 0.0 ),
|
|
390
|
+
} );
|
|
391
|
+
|
|
392
|
+
} );
|
|
393
|
+
|
|
333
394
|
// =============================================================================
|
|
334
395
|
// Importance-Weighted Light Sampling
|
|
335
396
|
// =============================================================================
|
|
@@ -496,12 +557,16 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
496
557
|
|
|
497
558
|
If( light.intensity.greaterThan( 0.0 ), () => {
|
|
498
559
|
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
560
|
+
const dirSample = LightSample.wrap( sampleDirectionalLight(
|
|
561
|
+
light, rayOrigin, lightSelectionPdf, randomSeed.y,
|
|
562
|
+
getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 14 ) ), rngState, resolution, frame )
|
|
563
|
+
) );
|
|
564
|
+
r_valid.assign( dirSample.valid );
|
|
565
|
+
r_direction.assign( dirSample.direction );
|
|
566
|
+
r_emission.assign( dirSample.emission );
|
|
567
|
+
r_distance.assign( dirSample.distance );
|
|
568
|
+
r_pdf.assign( dirSample.pdf );
|
|
569
|
+
r_lightType.assign( dirSample.lightType );
|
|
505
570
|
sampled.assign( tslBool( true ) );
|
|
506
571
|
|
|
507
572
|
} );
|
|
@@ -601,39 +666,17 @@ export const sampleLightWithImportance = Fn( ( [
|
|
|
601
666
|
|
|
602
667
|
const light = DirectionalLight.wrap( getDirectionalLight( directionalLightsBuffer, selectedIdx ) );
|
|
603
668
|
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const u = normalize( cross(
|
|
616
|
-
select( abs( w.x ).greaterThan( 0.9 ), vec3( 0.0, 1.0, 0.0 ), vec3( 1.0, 0.0, 0.0 ) ),
|
|
617
|
-
w
|
|
618
|
-
) ).toVar();
|
|
619
|
-
const v = cross( w, u ).toVar();
|
|
620
|
-
|
|
621
|
-
direction.assign( normalize(
|
|
622
|
-
w.mul( cosTheta ).add( u.mul( cos( phi ) ).add( v.mul( sin( phi ) ) ).mul( sinTheta ) )
|
|
623
|
-
) );
|
|
624
|
-
// Guard division: (1.0 - cosHalfAngle) could be zero if angle is 0
|
|
625
|
-
const solidAngle = float( TWO_PI ).mul( max( float( 1.0 ).sub( cosHalfAngle ), 1e-10 ) );
|
|
626
|
-
dirPdf.assign( float( 1.0 ).div( solidAngle ) );
|
|
627
|
-
|
|
628
|
-
} );
|
|
629
|
-
|
|
630
|
-
const dirGoboMask = sampleDirectionalGoboMask( light, rayOrigin );
|
|
631
|
-
r_direction.assign( direction );
|
|
632
|
-
r_emission.assign( light.color.mul( light.intensity ).mul( dirGoboMask ) );
|
|
633
|
-
r_distance.assign( 1e6 );
|
|
634
|
-
r_pdf.assign( dirPdf.mul( pdf ) );
|
|
635
|
-
r_lightType.assign( int( LIGHT_TYPE_DIRECTIONAL ) );
|
|
636
|
-
r_valid.assign( tslBool( true ) );
|
|
669
|
+
const dirSample = LightSample.wrap( sampleDirectionalLight(
|
|
670
|
+
light, rayOrigin, pdf, randomSeed.y,
|
|
671
|
+
getRandomSample1D( pixelCoord, int( 0 ), dimBase.add( int( 14 ) ), rngState, resolution, frame )
|
|
672
|
+
) );
|
|
673
|
+
|
|
674
|
+
r_direction.assign( dirSample.direction );
|
|
675
|
+
r_emission.assign( dirSample.emission );
|
|
676
|
+
r_distance.assign( dirSample.distance );
|
|
677
|
+
r_pdf.assign( dirSample.pdf );
|
|
678
|
+
r_lightType.assign( dirSample.lightType );
|
|
679
|
+
r_valid.assign( dirSample.valid );
|
|
637
680
|
|
|
638
681
|
} );
|
|
639
682
|
|
|
@@ -324,6 +324,13 @@ export class LightManager extends EventDispatcher {
|
|
|
324
324
|
|
|
325
325
|
angle = MathUtils.radToDeg( light.angle );
|
|
326
326
|
|
|
327
|
+
} else if ( light.type === 'DirectionalLight' ) {
|
|
328
|
+
|
|
329
|
+
// Sun angular diameter. Lives on userData because three.js gives DirectionalLight no
|
|
330
|
+
// angle property, and an ad-hoc one would not survive the light.clone() in
|
|
331
|
+
// transferSceneLights. Reported here or the panel reads back 0 and the slider snaps home.
|
|
332
|
+
angle = MathUtils.radToDeg( light.userData?.angle ?? 0 );
|
|
333
|
+
|
|
327
334
|
}
|
|
328
335
|
|
|
329
336
|
const descriptor = {
|