rayzee 7.26.3 → 7.26.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 +2664 -2651
- package/dist/rayzee.umd.js +22 -22
- package/package.json +1 -1
- package/src/Processor/GeometryExtractor.js +48 -37
- package/src/TSL/LightsSampling.js +82 -39
- package/src/index.js +3 -0
- package/src/managers/LightManager.js +7 -0
- package/src/managers/UniformManager.js +30 -0
package/package.json
CHANGED
|
@@ -6,6 +6,53 @@ const log = createLogger( 'geometry' );
|
|
|
6
6
|
|
|
7
7
|
const MAX_TEXTURES_LIMIT = 128;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* glTF 2.0 alphaMode for a three.js material: 0 OPAQUE, 1 MASK, 2 BLEND.
|
|
11
|
+
*
|
|
12
|
+
* The single definition of this rule. alphaMode is the only alpha field the shader reads
|
|
13
|
+
* (MaterialTransmission takes the opaque fast path on 0 and gates its MASK branch on 1), so
|
|
14
|
+
* any control that changes alpha behaviour has to route through here or it does nothing.
|
|
15
|
+
*
|
|
16
|
+
* @param {import('three').Material} material
|
|
17
|
+
* @returns {0|1|2}
|
|
18
|
+
*/
|
|
19
|
+
export function deriveAlphaMode( material ) {
|
|
20
|
+
|
|
21
|
+
// Follow glTF 2.0 specification for alphaMode
|
|
22
|
+
// Check if material explicitly sets alphaMode (from glTF loader)
|
|
23
|
+
if ( material.userData?.gltfExtensions?.KHR_materials_unlit?.alphaMode ) {
|
|
24
|
+
|
|
25
|
+
const mode = material.userData.gltfExtensions.KHR_materials_unlit.alphaMode;
|
|
26
|
+
if ( mode === 'BLEND' ) return 2;
|
|
27
|
+
if ( mode === 'MASK' ) return 1;
|
|
28
|
+
return 0; // OPAQUE
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fallback logic based on material properties
|
|
33
|
+
if ( material.alphaTest > 0.0 ) {
|
|
34
|
+
|
|
35
|
+
return 1; // MASK - alphaTest takes priority
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ( material.transparent && material.opacity < 1.0 ) {
|
|
40
|
+
|
|
41
|
+
return 2; // BLEND - transparent with opacity < 1
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for alpha in diffuse texture
|
|
46
|
+
if ( material.map && material.map.format === RGBAFormat && material.transparent ) {
|
|
47
|
+
|
|
48
|
+
return 2; // BLEND - has alpha texture and transparent flag
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return 0; // OPAQUE
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
9
56
|
export class GeometryExtractor {
|
|
10
57
|
|
|
11
58
|
constructor() {
|
|
@@ -189,42 +236,6 @@ export class GeometryExtractor {
|
|
|
189
236
|
|
|
190
237
|
}
|
|
191
238
|
|
|
192
|
-
getMaterialAlphaMode( material ) {
|
|
193
|
-
|
|
194
|
-
// Follow glTF 2.0 specification for alphaMode
|
|
195
|
-
// Check if material explicitly sets alphaMode (from glTF loader)
|
|
196
|
-
if ( material.userData?.gltfExtensions?.KHR_materials_unlit?.alphaMode ) {
|
|
197
|
-
|
|
198
|
-
const mode = material.userData.gltfExtensions.KHR_materials_unlit.alphaMode;
|
|
199
|
-
if ( mode === 'BLEND' ) return 2;
|
|
200
|
-
if ( mode === 'MASK' ) return 1;
|
|
201
|
-
return 0; // OPAQUE
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Fallback logic based on material properties
|
|
206
|
-
if ( material.alphaTest > 0.0 ) {
|
|
207
|
-
|
|
208
|
-
return 1; // MASK - alphaTest takes priority
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if ( material.transparent && material.opacity < 1.0 ) {
|
|
213
|
-
|
|
214
|
-
return 2; // BLEND - transparent with opacity < 1
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Check for alpha in diffuse texture
|
|
219
|
-
if ( material.map && material.map.format === RGBAFormat && material.transparent ) {
|
|
220
|
-
|
|
221
|
-
return 2; // BLEND - has alpha texture and transparent flag
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return 0; // OPAQUE
|
|
226
|
-
|
|
227
|
-
}
|
|
228
239
|
|
|
229
240
|
getMaterialType( material ) {
|
|
230
241
|
|
|
@@ -424,7 +435,7 @@ export class GeometryExtractor {
|
|
|
424
435
|
// Transparency and alpha
|
|
425
436
|
transparent: material.transparent ? 1 : 0,
|
|
426
437
|
alphaTest: material.alphaTest ?? defaults.alphaTest,
|
|
427
|
-
alphaMode:
|
|
438
|
+
alphaMode: deriveAlphaMode( material ),
|
|
428
439
|
|
|
429
440
|
// Rendering properties
|
|
430
441
|
side: this.getMaterialSide( material ),
|
|
@@ -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
|
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,9 @@ export { EngineEvents } from './EngineEvents.js';
|
|
|
17
17
|
// Asset URL / cache namespace overrides (call before constructing PathTracerApp)
|
|
18
18
|
export { configureAssets, getAssetConfig } from './AssetConfig.js';
|
|
19
19
|
|
|
20
|
+
// glTF alphaMode derivation — the one definition, shared with hosts that edit materials
|
|
21
|
+
export { deriveAlphaMode } from './Processor/GeometryExtractor.js';
|
|
22
|
+
|
|
20
23
|
// Scene-level authoring metadata embedded in model files (glTF `extras`)
|
|
21
24
|
export { extractSceneMetadata, parseSceneMetadata } from './Processor/SceneMetadata.js';
|
|
22
25
|
|
|
@@ -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 = {
|
|
@@ -29,6 +29,9 @@ export class UniformManager {
|
|
|
29
29
|
/** @type {Set<string>} Uniforms that store boolean values as int 0/1 */
|
|
30
30
|
this._booleans = new Set();
|
|
31
31
|
|
|
32
|
+
/** @type {Set<string>} Uniforms already warned about an array value (warn once each) */
|
|
33
|
+
this._arrayWarned = new Set();
|
|
34
|
+
|
|
32
35
|
/** @type {Object} Light buffer uniformArray nodes */
|
|
33
36
|
this._lightBuffers = {};
|
|
34
37
|
|
|
@@ -64,6 +67,32 @@ export class UniformManager {
|
|
|
64
67
|
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
// Hosts wire UI controls straight to these, and slider components commonly emit [n]
|
|
71
|
+
// rather than n. A scalar uniform has no meaningful array value, and nothing downstream
|
|
72
|
+
// rejects one: the array lands in .value verbatim and poisons every later read. That is
|
|
73
|
+
// not hypothetical — maxBounces received [20], and PathTracer's `maxBounces +
|
|
74
|
+
// transmissiveBounces + maxSubsurfaceSteps` then evaluated to the STRING '2058', so the
|
|
75
|
+
// bounce loop ran to 2058 on any frame the survivor curve could not early-exit and hung
|
|
76
|
+
// the tab. Unwrap and warn once per uniform, so the caller is named rather than the
|
|
77
|
+
// symptom surfacing somewhere unrelated.
|
|
78
|
+
if ( Array.isArray( value ) && value.length === 1
|
|
79
|
+
&& ( this._booleans.has( name ) || typeof node.value === 'number' ) ) {
|
|
80
|
+
|
|
81
|
+
if ( ! this._arrayWarned.has( name ) ) {
|
|
82
|
+
|
|
83
|
+
this._arrayWarned.add( name );
|
|
84
|
+
console.warn(
|
|
85
|
+
`UniformManager: "${name}" was set to [${value[ 0 ]}] but expects a scalar — ` +
|
|
86
|
+
'unwrapping. Fix the caller; a slider handler is probably forwarding its ' +
|
|
87
|
+
'array value unchanged.'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
value = value[ 0 ];
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
67
96
|
if ( this._booleans.has( name ) ) {
|
|
68
97
|
|
|
69
98
|
node.value = value ? 1 : 0;
|
|
@@ -324,6 +353,7 @@ export class UniformManager {
|
|
|
324
353
|
|
|
325
354
|
this._uniforms.clear();
|
|
326
355
|
this._booleans.clear();
|
|
356
|
+
this._arrayWarned.clear();
|
|
327
357
|
this._lightBuffers = {};
|
|
328
358
|
|
|
329
359
|
}
|