rayzee 5.4.1 → 5.4.2
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 +907 -867
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +41 -41
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Processor/GeometryExtractor.js +19 -2
- package/src/Stages/PathTracer.js +13 -0
- package/src/TSL/BVHTraversal.js +19 -11
- package/src/TSL/LightsDirect.js +13 -0
- package/src/TSL/TextureSampling.js +1 -1
- package/src/managers/MaterialDataManager.js +93 -3
package/package.json
CHANGED
|
@@ -618,7 +618,22 @@ export class GeometryExtractor {
|
|
|
618
618
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 0 ] = normalA.x;
|
|
619
619
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 1 ] = normalA.y;
|
|
620
620
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 2 ] = normalA.z;
|
|
621
|
-
|
|
621
|
+
// Repurposed padding: opaque-blocker fast-path flag for shadow rays.
|
|
622
|
+
// 1.0 = surface fully blocks light (no alpha, transmission, or transparency) →
|
|
623
|
+
// traceShadowRay can skip the 7-slot getShadowMaterial fetch.
|
|
624
|
+
// 0.0 = requires full material evaluation.
|
|
625
|
+
{
|
|
626
|
+
|
|
627
|
+
const mat = this.materials[ materialIndex ];
|
|
628
|
+
const isOpaqueBlocker = mat
|
|
629
|
+
&& ( mat.alphaMode | 0 ) === 0
|
|
630
|
+
&& ( mat.transparent | 0 ) === 0
|
|
631
|
+
&& ( mat.transmission || 0 ) === 0
|
|
632
|
+
&& ( mat.opacity ?? 1 ) >= 1
|
|
633
|
+
? 1.0 : 0.0;
|
|
634
|
+
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 3 ] = isOpaqueBlocker;
|
|
635
|
+
|
|
636
|
+
}
|
|
622
637
|
|
|
623
638
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 0 ] = normalB.x;
|
|
624
639
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 1 ] = normalB.y;
|
|
@@ -628,7 +643,9 @@ export class GeometryExtractor {
|
|
|
628
643
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 0 ] = normalC.x;
|
|
629
644
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 1 ] = normalC.y;
|
|
630
645
|
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 2 ] = normalC.z;
|
|
631
|
-
|
|
646
|
+
// Repurposed padding: per-triangle side flag (0=front, 1=back, 2=double).
|
|
647
|
+
// Lets BVH traversal do side culling without a material-buffer read per hit.
|
|
648
|
+
this.triangleData[ offset + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 3 ] = this.materials[ materialIndex ]?.side ?? 0;
|
|
632
649
|
|
|
633
650
|
// UVs and material index (2 vec4s = 8 floats)
|
|
634
651
|
// First vec4: uvA.x, uvA.y, uvB.x, uvB.y
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -114,6 +114,19 @@ export class PathTracer extends RenderStage {
|
|
|
114
114
|
// Initialize material data manager
|
|
115
115
|
this.materialData = new MaterialDataManager( this.sdfs );
|
|
116
116
|
this.materialData.callbacks.onReset = () => this.reset();
|
|
117
|
+
// Triangle data carries the per-triangle `side` flag (NORMAL_C.w). The
|
|
118
|
+
// authoritative CPU array is triangleStorageAttr.array (not sdfs.triangleData,
|
|
119
|
+
// which isn't populated on the PathTracerApp build path). The patch mutates
|
|
120
|
+
// the array in place — only a dirty flag is needed for GPU re-upload.
|
|
121
|
+
this.materialData.callbacks.getTriangleData = () => ( {
|
|
122
|
+
array: this.triangleStorageAttr?.array,
|
|
123
|
+
count: this.triangleCount,
|
|
124
|
+
} );
|
|
125
|
+
this.materialData.callbacks.onTriangleDataChanged = () => {
|
|
126
|
+
|
|
127
|
+
if ( this.triangleStorageAttr ) this.triangleStorageAttr.needsUpdate = true;
|
|
128
|
+
|
|
129
|
+
};
|
|
117
130
|
|
|
118
131
|
// Initialize environment manager
|
|
119
132
|
this.environment = new EnvironmentManager( this.scene, this.uniforms );
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -224,28 +224,34 @@ export const traverseBVH = Fn( ( [
|
|
|
224
224
|
const u = triResult.y;
|
|
225
225
|
const v = triResult.z;
|
|
226
226
|
|
|
227
|
-
// Fetch normals
|
|
227
|
+
// Fetch normals for side-culling (3 reads). Slot 7 (uvData2,
|
|
228
|
+
// carries matIdx + meshIndex) is deferred to post-traversal —
|
|
229
|
+
// it's only needed for the one winning triangle, not per candidate.
|
|
230
|
+
// normalCData.w carries the per-triangle side flag (0/1/2).
|
|
228
231
|
const nA = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 3 ), int( TRI_STRIDE ) ).xyz;
|
|
229
232
|
const nB = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 4 ), int( TRI_STRIDE ) ).xyz;
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
const matIdx = int( uvData2.z );
|
|
233
|
+
const normalCData = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 5 ), int( TRI_STRIDE ) );
|
|
234
|
+
const nC = normalCData.xyz;
|
|
235
|
+
const side = int( normalCData.w ).toVar();
|
|
234
236
|
|
|
235
237
|
// Interpolate normal
|
|
236
238
|
const w = float( 1.0 ).sub( u ).sub( v );
|
|
237
239
|
const normal = normalize( nA.mul( w ).add( nB.mul( u ) ).add( nC.mul( v ) ) ).toVar();
|
|
238
240
|
|
|
239
|
-
// Side culling
|
|
240
|
-
|
|
241
|
+
// Side culling (inline; per-mesh visibility is at the BLAS-pointer level).
|
|
242
|
+
// 0=front (reject back-facing), 1=back (reject front-facing), 2=double (pass).
|
|
243
|
+
const rayDotNormal = rayDirection.dot( normal );
|
|
244
|
+
const sidePass = side.equal( int( 2 ) )
|
|
245
|
+
.or( side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
|
|
246
|
+
.or( side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) ) );
|
|
247
|
+
If( sidePass, () => {
|
|
241
248
|
|
|
242
249
|
closestHit.didHit.assign( true );
|
|
243
250
|
closestHit.dst.assign( t );
|
|
244
251
|
closestHit.normal.assign( normal );
|
|
245
|
-
closestHit.materialIndex.assign( matIdx );
|
|
246
|
-
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
247
252
|
|
|
248
|
-
// Defer hitPoint
|
|
253
|
+
// Defer materialIndex/meshIndex/hitPoint/UV to post-traversal
|
|
254
|
+
// (all re-derived from closestTriIdx with a single uvData2 fetch below).
|
|
249
255
|
closestTriIdx.assign( triIndex );
|
|
250
256
|
closestU.assign( u );
|
|
251
257
|
closestV.assign( v );
|
|
@@ -324,7 +330,7 @@ export const traverseBVH = Fn( ( [
|
|
|
324
330
|
|
|
325
331
|
} );
|
|
326
332
|
|
|
327
|
-
// Deferred: compute hitPoint and
|
|
333
|
+
// Deferred: compute hitPoint, UVs, and fetch matIdx/meshIndex once for the final closest hit
|
|
328
334
|
If( closestHit.didHit, () => {
|
|
329
335
|
|
|
330
336
|
closestHit.hitPoint.assign( ray.origin.add( ray.direction.mul( closestHit.dst ) ) );
|
|
@@ -335,6 +341,8 @@ export const traverseBVH = Fn( ( [
|
|
|
335
341
|
closestHit.uv.assign(
|
|
336
342
|
uvData1.xy.mul( w ).add( uvData1.zw.mul( closestU ) ).add( uvData2.xy.mul( closestV ) )
|
|
337
343
|
);
|
|
344
|
+
closestHit.materialIndex.assign( int( uvData2.z ) );
|
|
345
|
+
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
338
346
|
closestHit.triangleIndex.assign( closestTriIdx );
|
|
339
347
|
|
|
340
348
|
} );
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -130,6 +130,19 @@ export const traceShadowRay = Fn( ( [
|
|
|
130
130
|
|
|
131
131
|
} );
|
|
132
132
|
|
|
133
|
+
// Opaque fast-path: check the per-triangle blocker flag (NORMAL_A.w, set at
|
|
134
|
+
// extraction time when alphaMode/transparent/transmission/opacity all indicate
|
|
135
|
+
// a fully opaque surface). Short-circuits the 7-slot getShadowMaterial fetch
|
|
136
|
+
// and the entire alpha/transmission/transparent decision tree below.
|
|
137
|
+
const TRI_STRIDE_SR = int( 8 );
|
|
138
|
+
const blocker = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 3 ), TRI_STRIDE_SR ).w;
|
|
139
|
+
If( blocker.greaterThan( 0.5 ), () => {
|
|
140
|
+
|
|
141
|
+
transmittance.assign( 0.0 );
|
|
142
|
+
Break();
|
|
143
|
+
|
|
144
|
+
} );
|
|
145
|
+
|
|
133
146
|
// Fetch material for the hit surface (thin reader: 7 slots instead of 27)
|
|
134
147
|
const shadowMaterial = ShadowMaterial.wrap( getShadowMaterial( shadowHit.materialIndex, materialBuffer ) );
|
|
135
148
|
|
|
@@ -281,7 +281,7 @@ export const processBump = Fn( ( [ bumpMaps, currentNormal, material, uvCache ]
|
|
|
281
281
|
|
|
282
282
|
const result = currentNormal.toVar();
|
|
283
283
|
|
|
284
|
-
If( material.bumpMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
284
|
+
If( material.bumpMapIndex.greaterThanEqual( int( 0 ) ).and( material.bumpScale.greaterThan( 0.0 ) ), () => {
|
|
285
285
|
|
|
286
286
|
// Approximate texel size
|
|
287
287
|
const texelSize = vec2( 1.0 / 1024.0 ).toVar();
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
|
|
10
10
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
11
11
|
import { storage } from 'three/tsl';
|
|
12
|
-
import {
|
|
12
|
+
import { MATERIAL_DATA_LAYOUT as M, TRIANGLE_DATA_LAYOUT as T } from '../EngineDefaults.js';
|
|
13
13
|
|
|
14
14
|
const PIXELS_PER_MATERIAL = M.SLOTS_PER_MATERIAL;
|
|
15
|
+
// Per-triangle float offsets used by _patchTriangleSideForMaterial / _patchTriangleBlockerForMaterial.
|
|
16
|
+
const TRI_MAT_IDX_OFFSET = T.UV_C_MAT_OFFSET + 2; // uvData2.z in shader
|
|
17
|
+
const TRI_SIDE_OFFSET = T.NORMAL_C_OFFSET + 3; // normalCData.w in shader
|
|
18
|
+
const TRI_BLOCKER_OFFSET = T.NORMAL_A_OFFSET + 3; // nA.w in shader (opaque-blocker fast path)
|
|
19
|
+
|
|
20
|
+
// Material properties that affect the shadow-ray opaque-blocker flag.
|
|
21
|
+
const BLOCKER_PROPS = new Set( [ 'transmission', 'transparent', 'opacity', 'alphaMode' ] );
|
|
15
22
|
|
|
16
23
|
export class MaterialDataManager {
|
|
17
24
|
|
|
@@ -41,7 +48,7 @@ export class MaterialDataManager {
|
|
|
41
48
|
|
|
42
49
|
/**
|
|
43
50
|
* Optional callbacks set by the owning stage.
|
|
44
|
-
* @type {{ onReset?: Function, onFeaturesChanged?: Function }}
|
|
51
|
+
* @type {{ onReset?: Function, onFeaturesChanged?: Function, getTriangleData?: Function, onTriangleDataChanged?: Function }}
|
|
45
52
|
*/
|
|
46
53
|
this.callbacks = {};
|
|
47
54
|
|
|
@@ -275,7 +282,11 @@ export class MaterialDataManager {
|
|
|
275
282
|
case 'clearcoat': data[ stride + M.CLEARCOAT ] = value; break;
|
|
276
283
|
case 'clearcoatRoughness': data[ stride + M.CLEARCOAT_ROUGHNESS ] = value; break;
|
|
277
284
|
case 'opacity': data[ stride + M.OPACITY ] = value; break;
|
|
278
|
-
case 'side': data[ stride + M.SIDE ] = value;
|
|
285
|
+
case 'side': data[ stride + M.SIDE ] = value;
|
|
286
|
+
// Side is also mirrored into per-triangle data (NORMAL_C.w) so BVH
|
|
287
|
+
// traversal can do side culling without reading the material buffer.
|
|
288
|
+
this._patchTriangleSideForMaterial( materialIndex, value );
|
|
289
|
+
break;
|
|
279
290
|
case 'transparent': data[ stride + M.TRANSPARENT ] = value; break;
|
|
280
291
|
case 'alphaTest': data[ stride + M.ALPHA_TEST ] = value; break;
|
|
281
292
|
case 'alphaMode': data[ stride + M.ALPHA_MODE ] = value; break;
|
|
@@ -304,6 +315,13 @@ export class MaterialDataManager {
|
|
|
304
315
|
|
|
305
316
|
this.materialStorageAttr.needsUpdate = true;
|
|
306
317
|
|
|
318
|
+
// Recompute triangle-data opaque-blocker flag when any input to it changes.
|
|
319
|
+
if ( BLOCKER_PROPS.has( property ) ) {
|
|
320
|
+
|
|
321
|
+
this._recomputeOpaqueBlockerForMaterial( materialIndex );
|
|
322
|
+
|
|
323
|
+
}
|
|
324
|
+
|
|
307
325
|
const featureProperties = [ 'transmission', 'clearcoat', 'sheen', 'iridescence', 'dispersion', 'transparent', 'opacity', 'alphaTest' ];
|
|
308
326
|
if ( featureProperties.includes( property ) ) {
|
|
309
327
|
|
|
@@ -414,6 +432,10 @@ export class MaterialDataManager {
|
|
|
414
432
|
data[ stride + M.CLEARCOAT_ROUGHNESS ] = materialData.clearcoatRoughness ?? 0;
|
|
415
433
|
data[ stride + M.OPACITY ] = materialData.opacity ?? 1;
|
|
416
434
|
data[ stride + M.SIDE ] = materialData.side ?? 0;
|
|
435
|
+
// Mirror side into per-triangle data so BVH traversal avoids a material-buffer read.
|
|
436
|
+
this._patchTriangleSideForMaterial( materialIndex, materialData.side ?? 0 );
|
|
437
|
+
// Recompute shadow-ray opaque-blocker flag (reads alphaMode/transparent/transmission/opacity from buffer).
|
|
438
|
+
this._recomputeOpaqueBlockerForMaterial( materialIndex );
|
|
417
439
|
data[ stride + M.TRANSPARENT ] = materialData.transparent ?? 0;
|
|
418
440
|
data[ stride + M.ALPHA_TEST ] = materialData.alphaTest ?? 0;
|
|
419
441
|
data[ stride + M.ALPHA_MODE ] = materialData.alphaMode ?? 0;
|
|
@@ -657,6 +679,74 @@ export class MaterialDataManager {
|
|
|
657
679
|
|
|
658
680
|
}
|
|
659
681
|
|
|
682
|
+
/**
|
|
683
|
+
* Rewrite the per-triangle `side` flag (NORMAL_C.w) for every triangle whose
|
|
684
|
+
* materialIndex matches. Linear over triangles because there's no reverse
|
|
685
|
+
* index — side edits are a rare UI action so the scan cost is acceptable.
|
|
686
|
+
* @private
|
|
687
|
+
*/
|
|
688
|
+
/**
|
|
689
|
+
* Re-derive the shadow-ray opaque-blocker flag for a material from its
|
|
690
|
+
* current buffer values and patch NORMAL_A.w on every matching triangle.
|
|
691
|
+
* Kept in sync with the blocker definition in GeometryExtractor.
|
|
692
|
+
* @private
|
|
693
|
+
*/
|
|
694
|
+
_recomputeOpaqueBlockerForMaterial( materialIndex ) {
|
|
695
|
+
|
|
696
|
+
const matBuf = this.materialStorageAttr?.array;
|
|
697
|
+
if ( ! matBuf ) return;
|
|
698
|
+
|
|
699
|
+
const matStride = materialIndex * M.FLOATS_PER_MATERIAL;
|
|
700
|
+
const alphaMode = matBuf[ matStride + M.ALPHA_MODE ] | 0;
|
|
701
|
+
const transparent = matBuf[ matStride + M.TRANSPARENT ] | 0;
|
|
702
|
+
const transmission = matBuf[ matStride + M.TRANSMISSION ] || 0;
|
|
703
|
+
const opacity = matBuf[ matStride + M.OPACITY ] ?? 1;
|
|
704
|
+
const isOpaqueBlocker = ( alphaMode === 0 && transparent === 0 && transmission === 0 && opacity >= 1 ) ? 1.0 : 0.0;
|
|
705
|
+
|
|
706
|
+
this._patchTriangleFlagForMaterial( materialIndex, TRI_BLOCKER_OFFSET, isOpaqueBlocker );
|
|
707
|
+
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Generic helper: patch a single per-triangle float at `triOffset` for every
|
|
712
|
+
* triangle whose materialIndex matches, then fire onTriangleDataChanged.
|
|
713
|
+
* @private
|
|
714
|
+
*/
|
|
715
|
+
_patchTriangleFlagForMaterial( materialIndex, triOffset, value ) {
|
|
716
|
+
|
|
717
|
+
const triInfo = this.callbacks.getTriangleData?.();
|
|
718
|
+
const triData = triInfo?.array;
|
|
719
|
+
const triCount = triInfo?.count | 0;
|
|
720
|
+
if ( ! triData || triCount === 0 ) return;
|
|
721
|
+
|
|
722
|
+
const stride = T.FLOATS_PER_TRIANGLE;
|
|
723
|
+
let patched = 0;
|
|
724
|
+
for ( let i = 0; i < triCount; i ++ ) {
|
|
725
|
+
|
|
726
|
+
const base = i * stride;
|
|
727
|
+
if ( triData[ base + TRI_MAT_IDX_OFFSET ] === materialIndex ) {
|
|
728
|
+
|
|
729
|
+
triData[ base + triOffset ] = value;
|
|
730
|
+
patched ++;
|
|
731
|
+
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if ( patched > 0 && this.callbacks.onTriangleDataChanged ) {
|
|
737
|
+
|
|
738
|
+
this.callbacks.onTriangleDataChanged();
|
|
739
|
+
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
_patchTriangleSideForMaterial( materialIndex, sideValue ) {
|
|
745
|
+
|
|
746
|
+
this._patchTriangleFlagForMaterial( materialIndex, TRI_SIDE_OFFSET, sideValue );
|
|
747
|
+
|
|
748
|
+
}
|
|
749
|
+
|
|
660
750
|
// ===== DISPOSAL =====
|
|
661
751
|
|
|
662
752
|
dispose() {
|