rayzee 7.26.0 → 7.26.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "7.26.0",
3
+ "version": "7.26.2",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -57,6 +57,8 @@ const _appsByCanvas = new WeakMap();
57
57
  * - `app.settings` — {@link RenderSettings} (all render parameters)
58
58
  * - `app.stages` — Named pipeline stages for advanced control
59
59
  * - `app.sceneMeshes` — meshes backing the BVH, in buffer order (see {@link refitBVH})
60
+ * - `app.sceneModel` — root of the rendered model (a copy, for {@link loadObject3D})
61
+ * - `app.getSceneObject(id)` — the rendered root for an appended object's id
60
62
  *
61
63
  * Extends EventDispatcher for event-driven communication with stores/UI.
62
64
  */
@@ -626,8 +628,6 @@ export class PathTracerApp extends EventDispatcher {
626
628
  this.interactionManager?.deselect();
627
629
  this.transformManager?.detach?.();
628
630
 
629
- // Release the loaded model. If loaded via loadObject3D(), the caller owns it —
630
- // we only detach it from the scene. Otherwise dispose geometries/materials/textures.
631
631
  this.assetLoader?.releaseTargetModel();
632
632
 
633
633
  // Clear lights in the WebGPU light scene
@@ -665,7 +665,13 @@ export class PathTracerApp extends EventDispatcher {
665
665
  /**
666
666
  * Loads a Three.js Object3D directly into the path tracer scene.
667
667
  * Builds BVH from the object's meshes and uploads scene data.
668
- * @param {import('three').Object3D} object3d - The Object3D to render
668
+ *
669
+ * Renders a copy: `object3d` is never reparented, rewritten or disposed, so passing a
670
+ * subtree of a scene the host still renders is safe. Geometry/material/texture are shared
671
+ * by reference, and any ancestor transform is baked in. Later edits to `object3d` do not
672
+ * reach the render — mutate {@link sceneModel}, then {@link refitBVH}/{@link refitBLASes}.
673
+ *
674
+ * @param {import('three').Object3D} object3d - The Object3D to render; left untouched.
669
675
  * @param {string} [name='object3d'] - Display name for the object
670
676
  */
671
677
  async loadObject3D( object3d, name = 'object3d' ) {
@@ -1134,10 +1140,9 @@ export class PathTracerApp extends EventDispatcher {
1134
1140
  /** Tag the primary (replace-loaded) model as a removable scene object (read by the Outliner + removeSceneObject). Idempotent. */
1135
1141
  _tagPrimarySceneObject() {
1136
1142
 
1137
- const m = this.assetLoader?.targetModel;
1143
+ const m = this.sceneModel;
1138
1144
  if ( ! m ) return;
1139
1145
  m.userData.__rayzeeSceneObject = true;
1140
- m.userData.__rayzeeExternal = ( m === this.assetLoader._externalModel );
1141
1146
 
1142
1147
  }
1143
1148
 
@@ -1147,12 +1152,12 @@ export class PathTracerApp extends EventDispatcher {
1147
1152
  const scene = this.meshScene;
1148
1153
  if ( ! scene ) return;
1149
1154
  const floor = this.assetLoader?.floorPlane;
1150
- const primary = this.assetLoader?.targetModel;
1155
+ const primary = this.sceneModel;
1151
1156
  for ( const child of [ ...scene.children ] ) {
1152
1157
 
1153
1158
  if ( child === floor || child === primary ) continue;
1154
1159
  if ( ! child.userData?.__rayzeeSceneObject ) continue;
1155
- this.assetLoader.removeModelRoot( child, { external: !! child.userData.__rayzeeExternal } );
1160
+ this.assetLoader.removeModelRoot( child );
1156
1161
 
1157
1162
  }
1158
1163
 
@@ -1191,7 +1196,6 @@ export class PathTracerApp extends EventDispatcher {
1191
1196
 
1192
1197
  const { root } = await this.assetLoader.appendModel( url );
1193
1198
  root.userData.__rayzeeSceneObject = true;
1194
- root.userData.__rayzeeExternal = false;
1195
1199
  if ( name ) root.userData.__rayzeeName = name;
1196
1200
  await this._finishRebuildNoReframe( { type: 'ModelAdded', url, id: root.uuid } );
1197
1201
  return root.uuid;
@@ -1207,7 +1211,7 @@ export class PathTracerApp extends EventDispatcher {
1207
1211
 
1208
1212
  /**
1209
1213
  * Append a caller-owned Object3D to the current scene, then rebuild (no reframe).
1210
- * The caller retains ownership — removal only detaches it.
1214
+ * Appends a copy; the object passed in is never mutated. Same rules as {@link loadObject3D}.
1211
1215
  * @param {import('three').Object3D} object3d
1212
1216
  * @param {Object} [opts]
1213
1217
  * @param {string} [opts.name]
@@ -1227,7 +1231,6 @@ export class PathTracerApp extends EventDispatcher {
1227
1231
 
1228
1232
  const { root } = this.assetLoader.appendObject3D( object3d, name || 'object3d' );
1229
1233
  root.userData.__rayzeeSceneObject = true;
1230
- root.userData.__rayzeeExternal = true;
1231
1234
  if ( name ) root.userData.__rayzeeName = name;
1232
1235
  await this._finishRebuildNoReframe( { type: 'ModelAdded', id: root.uuid } );
1233
1236
  return root.uuid;
@@ -1254,7 +1257,7 @@ export class PathTracerApp extends EventDispatcher {
1254
1257
  const floor = this.assetLoader?.floorPlane;
1255
1258
  if ( floor && floor.uuid === id ) return false; // Ground is not deletable
1256
1259
 
1257
- const root = scene.children.find( c => c.uuid === id && c.userData?.__rayzeeSceneObject );
1260
+ const root = this.getSceneObject( id );
1258
1261
  if ( ! root ) return false;
1259
1262
 
1260
1263
  if ( this._loadingInProgress ) {
@@ -1269,13 +1272,13 @@ export class PathTracerApp extends EventDispatcher {
1269
1272
  this.interactionManager?.deselect();
1270
1273
  this.transformManager?.detach?.();
1271
1274
 
1272
- if ( root === this.assetLoader.targetModel ) {
1275
+ if ( root === this.sceneModel ) {
1273
1276
 
1274
1277
  this.assetLoader.releaseTargetModel();
1275
1278
 
1276
1279
  } else {
1277
1280
 
1278
- this.assetLoader.removeModelRoot( root, { external: !! root.userData.__rayzeeExternal } );
1281
+ this.assetLoader.removeModelRoot( root );
1279
1282
 
1280
1283
  }
1281
1284
 
@@ -1378,6 +1381,32 @@ export class PathTracerApp extends EventDispatcher {
1378
1381
 
1379
1382
  }
1380
1383
 
1384
+ /**
1385
+ * Root of the model actually being rendered. For {@link loadObject3D} this is the engine's
1386
+ * copy, not the object you passed — mutate this one, then {@link refitBVH}/{@link refitBLASes}.
1387
+ *
1388
+ * @returns {import('three').Object3D|null} Live reference, or null when nothing is loaded.
1389
+ */
1390
+ get sceneModel() {
1391
+
1392
+ return this.assetLoader?.targetModel ?? null;
1393
+
1394
+ }
1395
+
1396
+ /**
1397
+ * Resolve an id from {@link addModel}/{@link addModelFromObject3D} to the root being
1398
+ * rendered for it — for an appended Object3D, the engine's copy.
1399
+ *
1400
+ * @param {string} id
1401
+ * @returns {import('three').Object3D|null} Live reference, or null if no such object.
1402
+ */
1403
+ getSceneObject( id ) {
1404
+
1405
+ const root = this.meshScene?.children.find( c => c.uuid === id );
1406
+ return root?.userData?.__rayzeeSceneObject ? root : null;
1407
+
1408
+ }
1409
+
1381
1410
  /**
1382
1411
  * Update vertex positions for animation without full BVH rebuild.
1383
1412
  * O(N) bottom-up AABB refit instead of O(N log N) SAH rebuild.
@@ -2839,7 +2868,7 @@ export class PathTracerApp extends EventDispatcher {
2839
2868
  const animations = this.assetLoader?.animations || [];
2840
2869
  if ( animations.length > 0 ) {
2841
2870
 
2842
- const mixerRoot = this.assetLoader?.targetModel || this.meshScene;
2871
+ const mixerRoot = this.sceneModel || this.meshScene;
2843
2872
  this.animationManager.init( this.meshScene, mixerRoot, this._sdf.meshes, animations );
2844
2873
  this.animationManager.onFinished = () => {
2845
2874
 
@@ -1,4 +1,4 @@
1
- import { Box3, Vector3, RectAreaLight, Color, FloatType, LinearFilter, EquirectangularReflectionMapping,
1
+ import { Box3, BufferGeometry, Vector3, RectAreaLight, Color, FloatType, LinearFilter, EquirectangularReflectionMapping,
2
2
  TextureLoader, Texture, SRGBColorSpace, RepeatWrapping, Mesh, MeshStandardMaterial, MeshPhysicalMaterial,
3
3
  CircleGeometry, Points, PointsMaterial, LoadingManager, EventDispatcher
4
4
  } from 'three';
@@ -8,9 +8,10 @@ import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
8
8
  import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
9
9
  import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
10
10
  import { createMeshesFromMultiMaterialMesh } from 'three/addons/utils/SceneUtils.js';
11
+ import { clone as cloneWithSkeletons } from 'three/addons/utils/SkeletonUtils.js';
11
12
  import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
12
13
  import { unzipSync, zipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
13
- import { disposeObjectFromMemory, updateLoading } from './utils';
14
+ import { disposeEngineOwnedResources, disposeObjectFromMemory, updateLoading } from './utils';
14
15
  import { BuildTimer } from './BuildTimer.js';
15
16
  import { getAssetConfig } from '../AssetConfig.js';
16
17
  import { loadPBRTScene, pickEntryPath } from './PBRT/index.js';
@@ -36,6 +37,18 @@ const SUPPORTED_FORMATS = {
36
37
  // layers and the image assets they reference.
37
38
  const USD_LAYER_RE = /\.(usd|usda|usdc)$/i;
38
39
  const USD_IMAGE_RE = /\.(png|jpg|jpeg|avif)$/i;
40
+ // A throwaway stand-in for a geometry the engine must not mutate: the split's mergeGroups()
41
+ // reorders and disposes what it is given, but never writes the attributes.
42
+ function standInForSplit( source ) {
43
+
44
+ const geometry = new BufferGeometry();
45
+ for ( const name in source.attributes ) geometry.setAttribute( name, source.attributes[ name ] );
46
+ if ( source.index ) geometry.setIndex( source.index );
47
+ for ( const group of source.groups ) geometry.addGroup( group.start, group.count, group.materialIndex );
48
+ return geometry;
49
+
50
+ }
51
+
39
52
  /**
40
53
  * AssetLoader class - handles loading of 3D models, environment maps, and archives
41
54
  */
@@ -48,7 +61,6 @@ export class AssetLoader extends EventDispatcher {
48
61
  this.camera = camera;
49
62
  this.controls = controls;
50
63
  this.targetModel = null;
51
- this._externalModel = null;
52
64
  this.floorPlane = null;
53
65
  this.sceneScale = 1.0;
54
66
  this.loaderCache = {};
@@ -132,29 +144,58 @@ export class AssetLoader extends EventDispatcher {
132
144
  }
133
145
 
134
146
  /**
135
- * Releases the current targetModel. If it was supplied by the caller via
136
- * loadObject3D(), we only detach it from its parent — the caller still owns
137
- * that Object3D and may reuse it. Otherwise we disposeObjectFromMemory() to
138
- * free geometry/material/texture GPU resources.
147
+ * Deep-clones a caller-owned Object3D so the engine never mutates the host's tree.
148
+ * Geometry/material/texture ride along by reference, so release frees only what the engine
149
+ * allocated — see removeModelRoot().
150
+ *
151
+ * @param {import('three').Object3D} object3d - the caller's object; left untouched.
152
+ * @returns {import('three').Object3D} the engine-owned copy.
139
153
  */
140
- releaseTargetModel() {
154
+ _adoptExternalObject( object3d ) {
141
155
 
142
- this.sceneMetadata = null;
156
+ // Not Object3D.clone(): that leaves SkinnedMeshes bound to the source's bones.
157
+ let model;
158
+ try {
143
159
 
144
- if ( ! this.targetModel ) return;
160
+ model = cloneWithSkeletons( object3d );
145
161
 
146
- if ( this.targetModel === this._externalModel ) {
162
+ } catch ( error ) {
147
163
 
148
- this.targetModel.parent?.remove( this.targetModel );
164
+ // Object3D.copy() round-trips userData through JSON, so a back-reference throws.
165
+ throw new Error(
166
+ `Cannot render "${object3d.name || object3d.type}": its userData must be JSON-serializable.`,
167
+ { cause: error }
168
+ );
149
169
 
150
- } else {
170
+ }
151
171
 
152
- disposeObjectFromMemory( this.targetModel );
172
+ model.userData.__rayzeeExternal = true;
173
+
174
+ // Carried through as the scene-object id, unless a second copy already took it.
175
+ if ( ! this.scene?.children.some( c => c.uuid === object3d.uuid ) ) model.uuid = object3d.uuid;
176
+
177
+ // The copy sits under the engine's identity root, which keeps only a local transform.
178
+ if ( object3d.parent ) {
179
+
180
+ object3d.parent.updateWorldMatrix( true, false );
181
+ model.applyMatrix4( object3d.parent.matrixWorld );
153
182
 
154
183
  }
155
184
 
185
+ return model;
186
+
187
+ }
188
+
189
+ /** Releases the current targetModel. See removeModelRoot() for what gets freed. */
190
+ releaseTargetModel() {
191
+
192
+ this.sceneMetadata = null;
193
+
194
+ if ( ! this.targetModel ) return;
195
+
196
+ this.removeModelRoot( this.targetModel );
197
+
156
198
  this.targetModel = null;
157
- this._externalModel = null;
158
199
  // Drop the released model's animation clips so a later rebuild doesn't rebind
159
200
  // a mixer to disposed nodes. Every load path re-populates this.animations after.
160
201
  this.animations = [];
@@ -1180,21 +1221,32 @@ export class AssetLoader extends EventDispatcher {
1180
1221
 
1181
1222
  }
1182
1223
 
1183
- // Append a caller-owned Object3D without releasing prior models or reframing.
1224
+ // Append a copy of a caller-owned Object3D without releasing prior models or reframing.
1184
1225
  appendObject3D( object3d, name = 'object3d' ) {
1185
1226
 
1186
- object3d.name = object3d.name || name;
1187
- this._processAndParent( object3d );
1188
- return { root: object3d };
1227
+ const root = this._adoptExternalObject( object3d );
1228
+ root.name = object3d.name || name;
1229
+ this._processAndParent( root );
1230
+ return { root };
1189
1231
 
1190
1232
  }
1191
1233
 
1192
- // Detach + dispose an appended root. External (caller-owned) roots are only detached.
1193
- removeModelRoot( root, { external = false } = {} ) {
1234
+ // Detach + dispose a model root. An adopted root's geometry/materials belong to the
1235
+ // caller, so only the engine's own allocations go.
1236
+ removeModelRoot( root ) {
1194
1237
 
1195
1238
  if ( ! root ) return;
1196
- if ( external ) root.parent?.remove( root );
1197
- else disposeObjectFromMemory( root );
1239
+
1240
+ if ( root.userData.__rayzeeExternal ) {
1241
+
1242
+ disposeEngineOwnedResources( root );
1243
+ root.parent?.remove( root );
1244
+
1245
+ } else {
1246
+
1247
+ disposeObjectFromMemory( root );
1248
+
1249
+ }
1198
1250
 
1199
1251
  }
1200
1252
 
@@ -1510,17 +1562,18 @@ export class AssetLoader extends EventDispatcher {
1510
1562
 
1511
1563
  async loadObject3D( object3d, name = 'object3d' ) {
1512
1564
 
1513
- object3d.name = object3d.name || name;
1514
-
1515
1565
  this.releaseTargetModel();
1516
- this.targetModel = object3d;
1517
- this._externalModel = object3d;
1566
+
1567
+ const model = this._adoptExternalObject( object3d );
1568
+ model.name = object3d.name || name;
1569
+
1570
+ this.targetModel = model;
1518
1571
 
1519
1572
  updateLoading( { isLoading: true, status: "Processing Data...", progress: 10 } );
1520
1573
  await this.onModelLoad( this.targetModel );
1521
1574
 
1522
- this.dispatchEvent( { type: 'load', model: object3d, filename: name } );
1523
- return object3d;
1575
+ this.dispatchEvent( { type: 'load', model, filename: name } );
1576
+ return model;
1524
1577
 
1525
1578
  }
1526
1579
 
@@ -1656,6 +1709,9 @@ export class AssetLoader extends EventDispatcher {
1656
1709
  processModelObjects( model ) {
1657
1710
 
1658
1711
  let visitedAreaLights = [];
1712
+ // Split after the walk: traverse() caches children.length, so splitting in place
1713
+ // shifts later siblings down a slot and skips one.
1714
+ const multiMaterialMeshes = [];
1659
1715
  model.traverse( ( object ) => {
1660
1716
 
1661
1717
  const userData = object.userData;
@@ -1709,19 +1765,29 @@ export class AssetLoader extends EventDispatcher {
1709
1765
  // Handle multi-material meshes
1710
1766
  if ( object.isMesh && Array.isArray( object.material ) ) {
1711
1767
 
1712
- console.log( 'Found multi-material mesh:', object.name );
1713
- const group = createMeshesFromMultiMaterialMesh( object );
1768
+ multiMaterialMeshes.push( object );
1714
1769
 
1715
- if ( object.parent ) {
1770
+ }
1716
1771
 
1717
- object.parent.add( group );
1718
- object.parent.remove( object );
1772
+ } );
1719
1773
 
1720
- }
1774
+ const shared = model.userData.__rayzeeExternal === true;
1721
1775
 
1722
- }
1776
+ for ( const object of multiMaterialMeshes ) {
1723
1777
 
1724
- } );
1778
+ if ( ! object.parent ) continue;
1779
+
1780
+ console.log( 'Found multi-material mesh:', object.name );
1781
+ if ( shared ) object.geometry = standInForSplit( object.geometry );
1782
+
1783
+ const group = createMeshesFromMultiMaterialMesh( object );
1784
+ // Fresh geometry per group; tag it so an adopted model's release can free it.
1785
+ for ( const child of group.children ) child.geometry.userData.__rayzeeOwned = true;
1786
+
1787
+ object.parent.add( group );
1788
+ object.parent.remove( object );
1789
+
1790
+ }
1725
1791
 
1726
1792
  }
1727
1793
 
@@ -183,29 +183,6 @@ export class GeometryExtractor {
183
183
  materialIndex = this.materials.length - 1;
184
184
  this._materialUuidMap.set( material.uuid, materialIndex );
185
185
 
186
- // Detect material features for shader optimization (strict > 0 check)
187
- if ( newMaterial.clearcoat > 0 ) this.sceneFeatures.hasClearcoat = true;
188
- if ( newMaterial.transmission > 0 ) this.sceneFeatures.hasTransmission = true;
189
- if ( newMaterial.dispersion > 0 ) this.sceneFeatures.hasDispersion = true;
190
- if ( newMaterial.iridescence > 0 ) this.sceneFeatures.hasIridescence = true;
191
- if ( newMaterial.sheen > 0 ) this.sceneFeatures.hasSheen = true;
192
- if ( newMaterial.transparent || newMaterial.opacity < 1.0 || newMaterial.alphaTest > 0 ) this.sceneFeatures.hasTransparency = true;
193
- if ( newMaterial.subsurface > 0 ) this.sceneFeatures.hasSubsurface = true;
194
-
195
- // Detect multi-lobe materials (require multi-lobe MIS for optimal sampling)
196
- const featureCount = [
197
- newMaterial.clearcoat > 0,
198
- newMaterial.transmission > 0,
199
- newMaterial.iridescence > 0,
200
- newMaterial.sheen > 0
201
- ].filter( Boolean ).length;
202
-
203
- if ( featureCount >= 2 ) {
204
-
205
- this.sceneFeatures.hasMultiLobeMaterials = true;
206
-
207
- }
208
-
209
186
  }
210
187
 
211
188
  return materialIndex;
@@ -846,19 +823,6 @@ export class GeometryExtractor {
846
823
  this._materialUuidMap = new Map();
847
824
  this._textureIndexCache = new WeakMap();
848
825
 
849
- // Reset scene-wide feature detection flags
850
- this.sceneFeatures = {
851
- hasClearcoat: false,
852
- hasTransmission: false,
853
- hasDispersion: false,
854
- hasIridescence: false,
855
- hasSheen: false,
856
- hasTransparency: false,
857
- hasSubsurface: false,
858
- hasMultiLobeMaterials: false, // Materials with 2+ BRDF lobes
859
- hasMRTOutputs: true // Always enabled for ASVGF/adaptive sampling support
860
- };
861
-
862
826
  }
863
827
 
864
828
  getExtractedData() {
@@ -888,8 +852,7 @@ export class GeometryExtractor {
888
852
  specularIntensityMaps: this.specularIntensityMaps,
889
853
  specularColorMaps: this.specularColorMaps,
890
854
  directionalLights: this.directionalLights,
891
- cameras: this.cameras,
892
- sceneFeatures: this.sceneFeatures // Scene-wide material feature flags
855
+ cameras: this.cameras
893
856
  };
894
857
 
895
858
  }
@@ -324,7 +324,6 @@ export class SceneProcessor {
324
324
 
325
325
  // Store other extracted data
326
326
  this.materials = extractedData.materials;
327
- this.materialCount = this.materials.length; // Store material count for feature scanning
328
327
  this.materialTriangleCounts = extractedData.materialTriangleCounts; // Per-material tri count for sort-bin remap
329
328
  this.meshes = extractedData.meshes;
330
329
  this.meshTriangleRanges = extractedData.meshTriangleRanges; // Per-mesh { start, count } for TLAS/BLAS
@@ -347,7 +346,6 @@ export class SceneProcessor {
347
346
  this.specularColorMaps = extractedData.specularColorMaps;
348
347
  this.directionalLights = extractedData.directionalLights;
349
348
  this.cameras = extractedData.cameras;
350
- this.sceneFeatures = extractedData.sceneFeatures; // Store material feature flags for shader optimization
351
349
 
352
350
  const duration = performance.now() - startTime;
353
351
  this._log( `Geometry extraction complete (${duration.toFixed( 2 )}ms)`, {
@@ -1157,7 +1155,6 @@ export class SceneProcessor {
1157
1155
 
1158
1156
  // Update material arrays (but keep existing triangle data)
1159
1157
  this.materials = extractedData.materials;
1160
- this.materialCount = this.materials.length; // Update material count
1161
1158
  this.meshes = extractedData.meshes; // Update mesh data
1162
1159
  this.maps = extractedData.maps;
1163
1160
  this.normalMaps = extractedData.normalMaps;
@@ -1166,7 +1163,6 @@ export class SceneProcessor {
1166
1163
  this.metalnessMaps = extractedData.metalnessMaps;
1167
1164
  this.emissiveMaps = extractedData.emissiveMaps;
1168
1165
  this.displacementMaps = extractedData.displacementMaps;
1169
- this.sceneFeatures = extractedData.sceneFeatures; // Update material feature flags
1170
1166
 
1171
1167
  // Bucket textures, remap material indices, regenerate raw material data, and
1172
1168
  // build the consolidated bucket arrays — same path as the initial build.
@@ -177,6 +177,17 @@ export function disposeObjectFromMemory( object, exeptions = [] ) {
177
177
 
178
178
  }
179
179
 
180
+ /** Frees only geometry the engine allocated inside an adopted model; the rest is the host's. */
181
+ export function disposeEngineOwnedResources( object ) {
182
+
183
+ object?.traverse( ( child ) => {
184
+
185
+ if ( child.geometry?.userData?.__rayzeeOwned ) child.geometry.dispose();
186
+
187
+ } );
188
+
189
+ }
190
+
180
191
 
181
192
 
182
193
  /**
@@ -453,9 +453,6 @@ export class PathTracerStage extends RenderStage {
453
453
  await this.sdfs.buildBVH( scene );
454
454
  this.cameras = this.sdfs.cameras;
455
455
 
456
- // Inject shader defines based on detected material features
457
- this.materialData.injectMaterialFeatureDefines();
458
-
459
456
  // Update uniforms with scene data
460
457
  this.updateSceneUniforms();
461
458
  this.updateLights();