rayzee 5.1.1 → 5.3.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 +2795 -2587
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -49
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +77 -2
- package/src/PathTracerApp.js +2 -0
- package/src/Processor/AssetLoader.js +31 -0
- package/src/Processor/GeometryExtractor.js +22 -67
- package/src/Processor/ShaderBuilder.js +9 -0
- package/src/Processor/TextureCreator.js +153 -17
- package/src/RenderSettings.js +1 -0
- package/src/TSL/BVHTraversal.js +9 -3
- package/src/TSL/Common.js +62 -28
- package/src/TSL/Debugger.js +1 -2
- package/src/TSL/EmissiveSampling.js +3 -4
- package/src/TSL/LightsCore.js +1 -1
- package/src/TSL/LightsDirect.js +112 -15
- package/src/TSL/LightsSampling.js +2 -3
- package/src/TSL/PathTracerCore.js +6 -5
- package/src/TSL/Struct.js +16 -0
- package/src/managers/MaterialDataManager.js +138 -150
- package/src/managers/UniformManager.js +1 -0
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -80,6 +80,7 @@ export const ENGINE_DEFAULTS = {
|
|
|
80
80
|
renderLimitMode: 'frames',
|
|
81
81
|
renderTimeLimit: 30,
|
|
82
82
|
renderMode: 0,
|
|
83
|
+
enableAlphaShadows: false,
|
|
83
84
|
tiles: 3,
|
|
84
85
|
tilesHelper: false,
|
|
85
86
|
showLightHelper: false,
|
|
@@ -337,6 +338,80 @@ export const TRIANGLE_DATA_LAYOUT = {
|
|
|
337
338
|
UV_C_MAT_OFFSET: 28
|
|
338
339
|
};
|
|
339
340
|
|
|
341
|
+
// Material data layout constants — single source of truth for material buffer offsets.
|
|
342
|
+
// Shared between CPU writers (TextureCreator, MaterialDataManager) and GPU readers (Common.js getMaterial).
|
|
343
|
+
export const MATERIAL_DATA_LAYOUT = {
|
|
344
|
+
|
|
345
|
+
SLOTS_PER_MATERIAL: 27, // vec4 slots per material
|
|
346
|
+
FLOATS_PER_MATERIAL: 108, // total floats per material (27 × 4)
|
|
347
|
+
|
|
348
|
+
// ── Flat float offsets (CPU side) ────────────────────────────────
|
|
349
|
+
// Used as: data[ materialIndex * FLOATS_PER_MATERIAL + offset ]
|
|
350
|
+
// Ordered for cache-line coherence: shadow/culling → BxDF core → maps → extended → transforms
|
|
351
|
+
|
|
352
|
+
// Slot 0: ior + transmission + thickness + emissiveIntensity [shadow]
|
|
353
|
+
IOR: 0, TRANSMISSION: 1, THICKNESS: 2, EMISSIVE_INTENSITY: 3,
|
|
354
|
+
// Slot 1: attenuationColor.rgb + attenuationDistance [shadow]
|
|
355
|
+
ATTENUATION_COLOR: 4, ATTENUATION_DISTANCE: 7,
|
|
356
|
+
// Slot 2: opacity + side + transparent + alphaTest [shadow + culling]
|
|
357
|
+
OPACITY: 8, SIDE: 9, TRANSPARENT: 10, ALPHA_TEST: 11,
|
|
358
|
+
// Slot 3: alphaMode + depthWrite + normalScale [shadow]
|
|
359
|
+
ALPHA_MODE: 12, DEPTH_WRITE: 13, NORMAL_SCALE: 14,
|
|
360
|
+
// Slot 4: color.rgb + metalness [BxDF core]
|
|
361
|
+
COLOR: 16, METALNESS: 19,
|
|
362
|
+
// Slot 5: emissive.rgb + roughness [BxDF core]
|
|
363
|
+
EMISSIVE: 20, ROUGHNESS: 23,
|
|
364
|
+
// Slot 6: map indices (albedo, normal, roughness, metalness) [maps]
|
|
365
|
+
ALBEDO_MAP_INDEX: 24, NORMAL_MAP_INDEX: 25, ROUGHNESS_MAP_INDEX: 26, METALNESS_MAP_INDEX: 27,
|
|
366
|
+
// Slot 7: map indices (emissive, bump) + clearcoat [maps]
|
|
367
|
+
EMISSIVE_MAP_INDEX: 28, BUMP_MAP_INDEX: 29, CLEARCOAT: 30, CLEARCOAT_ROUGHNESS: 31,
|
|
368
|
+
// Slot 8: dispersion + visible + sheen + sheenRoughness [extended BxDF]
|
|
369
|
+
DISPERSION: 32, VISIBLE: 33, SHEEN: 34, SHEEN_ROUGHNESS: 35,
|
|
370
|
+
// Slot 9: sheenColor.rgb + (reserved) [extended BxDF]
|
|
371
|
+
SHEEN_COLOR: 36,
|
|
372
|
+
// Slot 10: specularIntensity + specularColor.rgb [extended BxDF]
|
|
373
|
+
SPECULAR_INTENSITY: 40, SPECULAR_COLOR: 41,
|
|
374
|
+
// Slot 11: iridescence + iridescenceIOR + iridescenceThicknessRange [extended BxDF]
|
|
375
|
+
IRIDESCENCE: 44, IRIDESCENCE_IOR: 45, IRIDESCENCE_THICKNESS_RANGE: 46,
|
|
376
|
+
// Slot 12: bumpScale + displacementScale + displacementMapIndex + (padding)
|
|
377
|
+
BUMP_SCALE: 48, DISPLACEMENT_SCALE: 49, DISPLACEMENT_MAP_INDEX: 50,
|
|
378
|
+
|
|
379
|
+
// ── Transform float offsets (8 floats each: 7 matrix values + 1 padding) ──
|
|
380
|
+
ALBEDO_TRANSFORM: 52,
|
|
381
|
+
NORMAL_TRANSFORM: 60,
|
|
382
|
+
ROUGHNESS_TRANSFORM: 68,
|
|
383
|
+
METALNESS_TRANSFORM: 76,
|
|
384
|
+
EMISSIVE_TRANSFORM: 84,
|
|
385
|
+
BUMP_TRANSFORM: 92,
|
|
386
|
+
DISPLACEMENT_TRANSFORM: 100,
|
|
387
|
+
|
|
388
|
+
// ── Vec4 slot indices (GPU/TSL side) ─────────────────────────────
|
|
389
|
+
// Used with getDatafromStorageBuffer( buf, matIdx, int(slot), int(SLOTS_PER_MATERIAL) )
|
|
390
|
+
SLOT: {
|
|
391
|
+
IOR_TRANSMISSION: 0, // [shadow] ior, transmission, thickness, emissiveIntensity
|
|
392
|
+
ATTENUATION: 1, // [shadow] attenuationColor, attenuationDistance
|
|
393
|
+
OPACITY_ALPHA: 2, // [shadow+culling] opacity, side, transparent, alphaTest
|
|
394
|
+
ALPHA_MODE: 3, // [shadow] alphaMode, depthWrite, normalScale
|
|
395
|
+
COLOR_METALNESS: 4, // [BxDF] color.rgb, metalness
|
|
396
|
+
EMISSIVE_ROUGHNESS: 5, // [BxDF] emissive.rgb, roughness
|
|
397
|
+
MAP_INDICES_A: 6, // [maps] albedo, normal, roughness, metalness
|
|
398
|
+
MAP_INDICES_B: 7, // [maps] emissive, bump, clearcoat, clearcoatRoughness
|
|
399
|
+
DISPERSION_SHEEN: 8, // [extended] dispersion, visible, sheen, sheenRoughness
|
|
400
|
+
SHEEN_COLOR: 9, // [extended] sheenColor, reserved
|
|
401
|
+
SPECULAR: 10, // [extended] specularIntensity, specularColor
|
|
402
|
+
IRIDESCENCE: 11, // [extended] iridescence, iridescenceIOR, iridescenceThicknessRange
|
|
403
|
+
BUMP_DISPLACEMENT: 12, // bumpScale, displacementScale, displacementMapIndex
|
|
404
|
+
ALBEDO_TRANSFORM_A: 13, ALBEDO_TRANSFORM_B: 14,
|
|
405
|
+
NORMAL_TRANSFORM_A: 15, NORMAL_TRANSFORM_B: 16,
|
|
406
|
+
ROUGHNESS_TRANSFORM_A: 17, ROUGHNESS_TRANSFORM_B: 18,
|
|
407
|
+
METALNESS_TRANSFORM_A: 19, METALNESS_TRANSFORM_B: 20,
|
|
408
|
+
EMISSIVE_TRANSFORM_A: 21, EMISSIVE_TRANSFORM_B: 22,
|
|
409
|
+
BUMP_TRANSFORM_A: 23, BUMP_TRANSFORM_B: 24,
|
|
410
|
+
DISPLACEMENT_TRANSFORM_A: 25, DISPLACEMENT_TRANSFORM_B: 26,
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
};
|
|
414
|
+
|
|
340
415
|
// BVH node leaf markers
|
|
341
416
|
export const BVH_LEAF_MARKERS = {
|
|
342
417
|
TRIANGLE_LEAF: - 1, // Leaf containing triangle references
|
|
@@ -364,14 +439,14 @@ export const DEFAULT_TEXTURE_MATRIX = [ 0, 0, 1, 1, 0, 0, 0, 1 ];
|
|
|
364
439
|
// Render mode configurations
|
|
365
440
|
export const FINAL_RENDER_CONFIG = {
|
|
366
441
|
maxSamples: 30, bounces: 20, transmissiveBounces: 8, samplesPerPixel: 1,
|
|
367
|
-
renderMode: 1, tiles: 3, tilesHelper: false,
|
|
442
|
+
renderMode: 1, enableAlphaShadows: true, tiles: 3, tilesHelper: false,
|
|
368
443
|
enableOIDN: true, oidnQuality: 'balance',
|
|
369
444
|
interactionModeEnabled: false,
|
|
370
445
|
};
|
|
371
446
|
|
|
372
447
|
export const PREVIEW_RENDER_CONFIG = {
|
|
373
448
|
maxSamples: ENGINE_DEFAULTS.maxSamples, bounces: ENGINE_DEFAULTS.bounces,
|
|
374
|
-
samplesPerPixel: ENGINE_DEFAULTS.samplesPerPixel, renderMode: ENGINE_DEFAULTS.renderMode,
|
|
449
|
+
samplesPerPixel: ENGINE_DEFAULTS.samplesPerPixel, renderMode: ENGINE_DEFAULTS.renderMode, enableAlphaShadows: ENGINE_DEFAULTS.enableAlphaShadows,
|
|
375
450
|
transmissiveBounces: ENGINE_DEFAULTS.transmissiveBounces,
|
|
376
451
|
tiles: ENGINE_DEFAULTS.tiles, tilesHelper: ENGINE_DEFAULTS.tilesHelper,
|
|
377
452
|
enableOIDN: false, oidnQuality: 'fast',
|
package/src/PathTracerApp.js
CHANGED
|
@@ -730,6 +730,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
730
730
|
}, { silent: true } );
|
|
731
731
|
|
|
732
732
|
this.stages.pathTracer?.setUniform( 'renderMode', parseInt( config.renderMode ) );
|
|
733
|
+
this.stages.pathTracer?.setUniform( 'enableAlphaShadows', config.enableAlphaShadows ?? false );
|
|
733
734
|
this.stages.pathTracer?.tileManager?.setTileCount( config.tiles );
|
|
734
735
|
|
|
735
736
|
const tileHelper = this.overlayManager?.getHelper( 'tiles' );
|
|
@@ -1013,6 +1014,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1013
1014
|
|
|
1014
1015
|
this._sdf = new SceneProcessor();
|
|
1015
1016
|
this.assetLoader = new AssetLoader( this.meshScene, this.cameraManager.camera, this.cameraManager.controls );
|
|
1017
|
+
this.assetLoader.setRenderer( this.renderer );
|
|
1016
1018
|
this.assetLoader.createFloorPlane();
|
|
1017
1019
|
|
|
1018
1020
|
this.cameraManager.controls.addEventListener( 'change', () => {
|
|
@@ -4,6 +4,7 @@ import { Box3, Vector3, RectAreaLight, Color, FloatType, LinearFilter, Equirecta
|
|
|
4
4
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
5
5
|
import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
|
|
6
6
|
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
7
|
+
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
|
|
7
8
|
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
|
|
8
9
|
import { createMeshesFromMultiMaterialMesh } from 'three/addons/utils/SceneUtils.js';
|
|
9
10
|
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
|
|
@@ -41,6 +42,13 @@ export class AssetLoader extends EventDispatcher {
|
|
|
41
42
|
this.loaderCache = {};
|
|
42
43
|
this.uploadedFileInfo = null;
|
|
43
44
|
this.animations = [];
|
|
45
|
+
this.renderer = null;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setRenderer( renderer ) {
|
|
50
|
+
|
|
51
|
+
this.renderer = renderer;
|
|
44
52
|
|
|
45
53
|
}
|
|
46
54
|
|
|
@@ -698,8 +706,31 @@ export class AssetLoader extends EventDispatcher {
|
|
|
698
706
|
dracoLoader.setDecoderConfig( { type: 'js' } );
|
|
699
707
|
dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/v1/decoders/' );
|
|
700
708
|
|
|
709
|
+
const ktx2Loader = new KTX2Loader();
|
|
710
|
+
ktx2Loader.setTranscoderPath( 'https://cdn.jsdelivr.net/npm/three@0.183.2/examples/jsm/libs/basis/' );
|
|
711
|
+
|
|
712
|
+
if ( this.renderer ) {
|
|
713
|
+
|
|
714
|
+
ktx2Loader.detectSupport( this.renderer );
|
|
715
|
+
|
|
716
|
+
// Force RGBA output for Basis Universal textures. GPU-compressed
|
|
717
|
+
// texture arrays (CompressedArrayTexture) are blocked by a Three.js
|
|
718
|
+
// TSL limitation: the node compiler maintains global state that
|
|
719
|
+
// survives dispose(), so swapping texture array formats between
|
|
720
|
+
// DataArrayTexture and CompressedArrayTexture at runtime causes
|
|
721
|
+
// WGSL compilation failures (unresolved uniform bindings).
|
|
722
|
+
ktx2Loader.workerConfig = {
|
|
723
|
+
astcSupported: false, etc1Supported: false, etc2Supported: false,
|
|
724
|
+
dxtSupported: false, bptcSupported: false, pvrtcSupported: false,
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
this.loaderCache.ktx2 = ktx2Loader;
|
|
730
|
+
|
|
701
731
|
const loader = new GLTFLoader();
|
|
702
732
|
loader.setDRACOLoader( dracoLoader );
|
|
733
|
+
loader.setKTX2Loader( ktx2Loader );
|
|
703
734
|
loader.setMeshoptDecoder( MeshoptDecoder );
|
|
704
735
|
|
|
705
736
|
this.loaderCache.gltf = loader;
|
|
@@ -542,55 +542,29 @@ export class GeometryExtractor {
|
|
|
542
542
|
for ( let i = 0; i < triangleCount; i ++ ) {
|
|
543
543
|
|
|
544
544
|
const i3 = i * 3;
|
|
545
|
+
const idxA = indices ? indices[ i3 + 0 ] : i3 + 0;
|
|
546
|
+
const idxB = indices ? indices[ i3 + 1 ] : i3 + 1;
|
|
547
|
+
const idxC = indices ? indices[ i3 + 2 ] : i3 + 2;
|
|
545
548
|
|
|
546
|
-
|
|
547
|
-
|
|
549
|
+
this.getVertex( positions, idxA, posA );
|
|
550
|
+
this.getVertex( positions, idxB, posB );
|
|
551
|
+
this.getVertex( positions, idxC, posC );
|
|
548
552
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
553
|
+
this.getVertex( normals, idxA, normalA );
|
|
554
|
+
this.getVertex( normals, idxB, normalB );
|
|
555
|
+
this.getVertex( normals, idxC, normalC );
|
|
552
556
|
|
|
553
|
-
|
|
554
|
-
this.getVertexFromIndices( normals, indices[ i3 + 1 ], normalB );
|
|
555
|
-
this.getVertexFromIndices( normals, indices[ i3 + 2 ], normalC );
|
|
557
|
+
if ( uvs ) {
|
|
556
558
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
this.getVertexFromIndices( uvs, indices[ i3 + 1 ], uvB );
|
|
561
|
-
this.getVertexFromIndices( uvs, indices[ i3 + 2 ], uvC );
|
|
562
|
-
|
|
563
|
-
} else {
|
|
564
|
-
|
|
565
|
-
uvA.set( 0, 0 );
|
|
566
|
-
uvB.set( 0, 0 );
|
|
567
|
-
uvC.set( 0, 0 );
|
|
568
|
-
|
|
569
|
-
}
|
|
559
|
+
this.getVertex( uvs, idxA, uvA );
|
|
560
|
+
this.getVertex( uvs, idxB, uvB );
|
|
561
|
+
this.getVertex( uvs, idxC, uvC );
|
|
570
562
|
|
|
571
563
|
} else {
|
|
572
564
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
this.getVertex( normals, i3 + 0, normalA );
|
|
578
|
-
this.getVertex( normals, i3 + 1, normalB );
|
|
579
|
-
this.getVertex( normals, i3 + 2, normalC );
|
|
580
|
-
|
|
581
|
-
if ( uvs ) {
|
|
582
|
-
|
|
583
|
-
this.getVertex( uvs, i3 + 0, uvA );
|
|
584
|
-
this.getVertex( uvs, i3 + 1, uvB );
|
|
585
|
-
this.getVertex( uvs, i3 + 2, uvC );
|
|
586
|
-
|
|
587
|
-
} else {
|
|
588
|
-
|
|
589
|
-
uvA.set( 0, 0 );
|
|
590
|
-
uvB.set( 0, 0 );
|
|
591
|
-
uvC.set( 0, 0 );
|
|
592
|
-
|
|
593
|
-
}
|
|
565
|
+
uvA.set( 0, 0 );
|
|
566
|
+
uvB.set( 0, 0 );
|
|
567
|
+
uvC.set( 0, 0 );
|
|
594
568
|
|
|
595
569
|
}
|
|
596
570
|
|
|
@@ -689,37 +663,18 @@ export class GeometryExtractor {
|
|
|
689
663
|
}
|
|
690
664
|
|
|
691
665
|
// Optimized attribute access methods
|
|
692
|
-
getVertexFromIndices( attribute, index, target ) {
|
|
693
|
-
|
|
694
|
-
if ( attribute.itemSize === 2 ) {
|
|
695
|
-
|
|
696
|
-
target.x = attribute.array[ index * 2 ];
|
|
697
|
-
target.y = attribute.array[ index * 2 + 1 ];
|
|
698
|
-
|
|
699
|
-
} else if ( attribute.itemSize === 3 ) {
|
|
700
|
-
|
|
701
|
-
target.x = attribute.array[ index * 3 ];
|
|
702
|
-
target.y = attribute.array[ index * 3 + 1 ];
|
|
703
|
-
target.z = attribute.array[ index * 3 + 2 ];
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
return target;
|
|
708
|
-
|
|
709
|
-
}
|
|
710
|
-
|
|
711
666
|
getVertex( attribute, index, target ) {
|
|
712
667
|
|
|
713
668
|
if ( attribute.itemSize === 2 ) {
|
|
714
669
|
|
|
715
|
-
target.x = attribute.
|
|
716
|
-
target.y = attribute.
|
|
670
|
+
target.x = attribute.getX( index );
|
|
671
|
+
target.y = attribute.getY( index );
|
|
717
672
|
|
|
718
|
-
} else if ( attribute.itemSize
|
|
673
|
+
} else if ( attribute.itemSize >= 3 ) {
|
|
719
674
|
|
|
720
|
-
target.x = attribute.
|
|
721
|
-
target.y = attribute.
|
|
722
|
-
target.z = attribute.
|
|
675
|
+
target.x = attribute.getX( index );
|
|
676
|
+
target.y = attribute.getY( index );
|
|
677
|
+
target.z = attribute.getZ( index );
|
|
723
678
|
|
|
724
679
|
}
|
|
725
680
|
|
|
@@ -17,6 +17,7 @@ import { TextureNode } from 'three/webgpu';
|
|
|
17
17
|
import { LinearFilter, DataArrayTexture } from 'three';
|
|
18
18
|
import { pathTracerMain } from '../TSL/PathTracer.js';
|
|
19
19
|
import { setMeshVisibilityBuffer } from '../TSL/BVHTraversal.js';
|
|
20
|
+
import { setShadowAlbedoMaps, setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
|
|
20
21
|
import { BuildTimer } from './BuildTimer.js';
|
|
21
22
|
|
|
22
23
|
const WG_SIZE = 8;
|
|
@@ -192,6 +193,9 @@ export class ShaderBuilder {
|
|
|
192
193
|
// Set per-mesh visibility buffer (module-level in BVHTraversal.js, read during graph construction)
|
|
193
194
|
setMeshVisibilityBuffer( stage.meshVisibilityStorageNode );
|
|
194
195
|
|
|
196
|
+
// Set alpha-shadow uniform (module-level in LightsDirect.js, read at runtime)
|
|
197
|
+
setAlphaShadowsUniform( stage.uniforms.get( 'enableAlphaShadows' ) );
|
|
198
|
+
|
|
195
199
|
const envTex = texture( stage.environment.environmentTexture );
|
|
196
200
|
|
|
197
201
|
// Adaptive sampling texture
|
|
@@ -228,6 +232,11 @@ export class ShaderBuilder {
|
|
|
228
232
|
const emissiveMapsTex = mat.emissiveMaps ? texture( mat.emissiveMaps ) : createArrayPlaceholder();
|
|
229
233
|
const displacementMapsTex = mat.displacementMaps ? texture( mat.displacementMaps ) : createArrayPlaceholder();
|
|
230
234
|
|
|
235
|
+
// Set albedo texture array for alpha-aware shadow rays (module-level in LightsDirect.js).
|
|
236
|
+
// Always pass the texture node (real or placeholder) so alpha-cutout code is emitted
|
|
237
|
+
// into the shader at graph construction time. Runtime albedoMapIndex >= 0 guards sampling.
|
|
238
|
+
setShadowAlbedoMaps( albedoMapsTex );
|
|
239
|
+
|
|
231
240
|
const result = {
|
|
232
241
|
triStorage, bvhStorage, matStorage, emissiveTriStorage, lightBVHStorage,
|
|
233
242
|
envTex, adaptiveSamplingTex, marginalCDFStorage, conditionalCDFStorage,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace } from "three";
|
|
2
|
-
import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX } from '../EngineDefaults.js';
|
|
2
|
+
import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
3
3
|
|
|
4
4
|
// Canvas pooling for efficient reuse of canvas elements
|
|
5
5
|
class CanvasPool {
|
|
@@ -496,8 +496,11 @@ export class TextureCreator {
|
|
|
496
496
|
const cached = this.textureCache.get( cacheKey );
|
|
497
497
|
if ( cached ) return cached;
|
|
498
498
|
|
|
499
|
+
// Normalize non-drawable images (KTX2 CompressedTexture RGBA, DataTexture)
|
|
500
|
+
const { normalized, bitmapsToClose } = await this._normalizeTexturesForProcessing( textures );
|
|
501
|
+
|
|
499
502
|
// Select optimal processing strategy
|
|
500
|
-
const strategy = this.selectProcessingStrategy(
|
|
503
|
+
const strategy = this.selectProcessingStrategy( normalized );
|
|
501
504
|
let result;
|
|
502
505
|
|
|
503
506
|
try {
|
|
@@ -505,19 +508,19 @@ export class TextureCreator {
|
|
|
505
508
|
switch ( strategy.method ) {
|
|
506
509
|
|
|
507
510
|
case 'worker-direct':
|
|
508
|
-
result = await this.processWithWorkerDirect(
|
|
511
|
+
result = await this.processWithWorkerDirect( normalized );
|
|
509
512
|
break;
|
|
510
513
|
case 'worker-chunked':
|
|
511
|
-
result = await this.processWithWorkerChunked(
|
|
514
|
+
result = await this.processWithWorkerChunked( normalized, strategy.chunkSize );
|
|
512
515
|
break;
|
|
513
516
|
case 'main-batch':
|
|
514
|
-
result = await this.processOnMainThreadBatch(
|
|
517
|
+
result = await this.processOnMainThreadBatch( normalized, strategy.batchSize );
|
|
515
518
|
break;
|
|
516
519
|
case 'main-streaming':
|
|
517
|
-
result = await this.processOnMainThreadStreaming(
|
|
520
|
+
result = await this.processOnMainThreadStreaming( normalized );
|
|
518
521
|
break;
|
|
519
522
|
default:
|
|
520
|
-
result = await this.processOnMainThreadSync(
|
|
523
|
+
result = await this.processOnMainThreadSync( normalized );
|
|
521
524
|
|
|
522
525
|
}
|
|
523
526
|
|
|
@@ -533,7 +536,11 @@ export class TextureCreator {
|
|
|
533
536
|
} catch ( error ) {
|
|
534
537
|
|
|
535
538
|
console.warn( 'Texture processing failed, trying fallback:', error );
|
|
536
|
-
return await this.processOnMainThreadSync(
|
|
539
|
+
return await this.processOnMainThreadSync( normalized );
|
|
540
|
+
|
|
541
|
+
} finally {
|
|
542
|
+
|
|
543
|
+
for ( const bmp of bitmapsToClose ) bmp.close();
|
|
537
544
|
|
|
538
545
|
}
|
|
539
546
|
|
|
@@ -858,9 +865,9 @@ export class TextureCreator {
|
|
|
858
865
|
*/
|
|
859
866
|
createMaterialRawData( materials ) {
|
|
860
867
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const dataLengthPerMaterial =
|
|
868
|
+
// Layout is defined by MATERIAL_DATA_LAYOUT in EngineDefaults.js.
|
|
869
|
+
// The inline array below must match that layout exactly (positional order = canonical layout).
|
|
870
|
+
const dataLengthPerMaterial = MATERIAL_DATA_LAYOUT.FLOATS_PER_MATERIAL;
|
|
864
871
|
const totalMaterials = materials.length;
|
|
865
872
|
|
|
866
873
|
const size = totalMaterials * dataLengthPerMaterial;
|
|
@@ -879,19 +886,34 @@ export class TextureCreator {
|
|
|
879
886
|
const bumpMapMatrices = mat.bumpMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
880
887
|
const displacementMapMatrices = mat.displacementMapMatrices ?? DEFAULT_TEXTURE_MATRIX;
|
|
881
888
|
|
|
889
|
+
// Slot order: shadow/culling → BxDF core → maps → extended → displacement → transforms
|
|
890
|
+
// Must match MATERIAL_DATA_LAYOUT in EngineDefaults.js exactly.
|
|
882
891
|
const materialData = [
|
|
883
|
-
|
|
884
|
-
mat.emissive.r, mat.emissive.g, mat.emissive.b, mat.roughness,
|
|
892
|
+
// Slot 0: shadow core (ior, transmission, thickness, emissiveIntensity)
|
|
885
893
|
mat.ior, mat.transmission, mat.thickness, mat.emissiveIntensity,
|
|
894
|
+
// Slot 1: shadow (attenuationColor, attenuationDistance)
|
|
886
895
|
mat.attenuationColor.r, mat.attenuationColor.g, mat.attenuationColor.b, mat.attenuationDistance,
|
|
896
|
+
// Slot 2: shadow + culling (opacity, side, transparent, alphaTest)
|
|
897
|
+
mat.opacity, mat.side, mat.transparent, mat.alphaTest,
|
|
898
|
+
// Slot 3: shadow (alphaMode, depthWrite, normalScale)
|
|
899
|
+
mat.alphaMode, mat.depthWrite, mat.normalScale?.x ?? 1, mat.normalScale?.y ?? 1,
|
|
900
|
+
// Slot 4: BxDF core (color, metalness)
|
|
901
|
+
mat.color.r, mat.color.g, mat.color.b, mat.metalness,
|
|
902
|
+
// Slot 5: BxDF core (emissive, roughness)
|
|
903
|
+
mat.emissive.r, mat.emissive.g, mat.emissive.b, mat.roughness,
|
|
904
|
+
// Slot 6: map indices A (albedo, normal, roughness, metalness)
|
|
905
|
+
mat.map, mat.normalMap, mat.roughnessMap, mat.metalnessMap,
|
|
906
|
+
// Slot 7: map indices B (emissive, bump, clearcoat, clearcoatRoughness)
|
|
907
|
+
mat.emissiveMap, mat.bumpMap, mat.clearcoat, mat.clearcoatRoughness,
|
|
908
|
+
// Slot 8: extended BxDF (dispersion, visible, sheen, sheenRoughness)
|
|
887
909
|
mat.dispersion, mat.visible, mat.sheen, mat.sheenRoughness,
|
|
910
|
+
// Slot 9: extended BxDF (sheenColor, reserved)
|
|
888
911
|
mat.sheenColor.r, mat.sheenColor.g, mat.sheenColor.b, 1,
|
|
912
|
+
// Slot 10: extended BxDF (specularIntensity, specularColor)
|
|
889
913
|
mat.specularIntensity, mat.specularColor.r, mat.specularColor.g, mat.specularColor.b,
|
|
914
|
+
// Slot 11: extended BxDF (iridescence)
|
|
890
915
|
mat.iridescence, mat.iridescenceIOR, mat.iridescenceThicknessRange[ 0 ], mat.iridescenceThicknessRange[ 1 ],
|
|
891
|
-
|
|
892
|
-
mat.emissiveMap, mat.bumpMap, mat.clearcoat, mat.clearcoatRoughness,
|
|
893
|
-
mat.opacity, mat.side, mat.transparent, mat.alphaTest,
|
|
894
|
-
mat.alphaMode, mat.depthWrite, mat.normalScale?.x ?? 1, mat.normalScale?.y ?? 1,
|
|
916
|
+
// Slot 12: displacement
|
|
895
917
|
mat.bumpScale, mat.displacementScale, mat.displacementMap, 0,
|
|
896
918
|
mapMatrix[ 0 ], mapMatrix[ 1 ], mapMatrix[ 2 ], mapMatrix[ 3 ],
|
|
897
919
|
mapMatrix[ 4 ], mapMatrix[ 5 ], mapMatrix[ 6 ], 1,
|
|
@@ -1243,6 +1265,109 @@ export class TextureCreator {
|
|
|
1243
1265
|
|
|
1244
1266
|
}
|
|
1245
1267
|
|
|
1268
|
+
// ── KTX2 / DataTexture normalization ────────────────────────────────
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Normalize textures so every entry has a drawable `.image`.
|
|
1272
|
+
* - RGBA CompressedTexture (KTX2 Basis → RGBA): pixel data from mipmaps[0]
|
|
1273
|
+
* - GPU-compressed texture (BC7/ASTC/ETC2): warning (should be pre-decompressed)
|
|
1274
|
+
* - DataTexture (raw RGBA pixels): pixel data from image.data
|
|
1275
|
+
* - Regular texture: passed through as-is
|
|
1276
|
+
*
|
|
1277
|
+
* Bitmap creation is parallelized via Promise.all.
|
|
1278
|
+
*/
|
|
1279
|
+
async _normalizeTexturesForProcessing( textures ) {
|
|
1280
|
+
|
|
1281
|
+
const normalized = [];
|
|
1282
|
+
const bitmapsToClose = [];
|
|
1283
|
+
const bitmapJobs = []; // { index, promise }
|
|
1284
|
+
|
|
1285
|
+
for ( const tex of textures ) {
|
|
1286
|
+
|
|
1287
|
+
if ( ! tex?.image ) continue;
|
|
1288
|
+
|
|
1289
|
+
// RGBA CompressedTexture (KTX2 Basis transcode wraps output as CompressedTexture)
|
|
1290
|
+
if ( tex.isCompressedTexture && tex.format === RGBAFormat && tex.mipmaps?.[ 0 ]?.data ) {
|
|
1291
|
+
|
|
1292
|
+
const mip = tex.mipmaps[ 0 ];
|
|
1293
|
+
const idx = normalized.length;
|
|
1294
|
+
normalized.push( null ); // placeholder — filled after Promise.all
|
|
1295
|
+
bitmapJobs.push( { index: idx, promise: _rawPixelsToBitmap( mip.data, mip.width, mip.height ) } );
|
|
1296
|
+
continue;
|
|
1297
|
+
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// True GPU-compressed texture in a mixed group — can't extract pixels on CPU.
|
|
1301
|
+
// All-compressed groups are handled by the CompressedArrayTexture path upstream.
|
|
1302
|
+
if ( tex.isCompressedTexture ) {
|
|
1303
|
+
|
|
1304
|
+
console.warn( '[TextureCreator] GPU-compressed texture in mixed group — using placeholder' );
|
|
1305
|
+
normalized.push( null );
|
|
1306
|
+
continue;
|
|
1307
|
+
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// DataTexture with raw pixel array
|
|
1311
|
+
if ( tex.image.data && ! ( tex.image instanceof HTMLImageElement ) &&
|
|
1312
|
+
! ( tex.image instanceof HTMLCanvasElement ) &&
|
|
1313
|
+
! ( typeof ImageBitmap !== 'undefined' && tex.image instanceof ImageBitmap ) ) {
|
|
1314
|
+
|
|
1315
|
+
const idx = normalized.length;
|
|
1316
|
+
normalized.push( null );
|
|
1317
|
+
bitmapJobs.push( { index: idx, promise: _rawPixelsToBitmap( tex.image.data, tex.image.width, tex.image.height ) } );
|
|
1318
|
+
continue;
|
|
1319
|
+
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
normalized.push( tex );
|
|
1323
|
+
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Resolve all bitmap conversions in parallel
|
|
1327
|
+
if ( bitmapJobs.length > 0 ) {
|
|
1328
|
+
|
|
1329
|
+
const results = await Promise.allSettled( bitmapJobs.map( j => j.promise ) );
|
|
1330
|
+
|
|
1331
|
+
for ( let i = 0; i < bitmapJobs.length; i ++ ) {
|
|
1332
|
+
|
|
1333
|
+
const { index } = bitmapJobs[ i ];
|
|
1334
|
+
const result = results[ i ];
|
|
1335
|
+
|
|
1336
|
+
if ( result.status === 'fulfilled' ) {
|
|
1337
|
+
|
|
1338
|
+
const bitmap = result.value;
|
|
1339
|
+
bitmapsToClose.push( bitmap );
|
|
1340
|
+
normalized[ index ] = { image: bitmap };
|
|
1341
|
+
|
|
1342
|
+
} else {
|
|
1343
|
+
|
|
1344
|
+
console.warn( '[TextureCreator] Failed to create ImageBitmap:', result.reason );
|
|
1345
|
+
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// Replace any remaining nulls (failed conversions) with a 1x1 white placeholder
|
|
1353
|
+
// to preserve array indexing alignment with material texture indices.
|
|
1354
|
+
for ( let i = 0; i < normalized.length; i ++ ) {
|
|
1355
|
+
|
|
1356
|
+
if ( normalized[ i ] === null ) {
|
|
1357
|
+
|
|
1358
|
+
const placeholder = new Uint8ClampedArray( [ 255, 255, 255, 255 ] );
|
|
1359
|
+
const bitmap = await createImageBitmap( new ImageData( placeholder, 1, 1 ) );
|
|
1360
|
+
bitmapsToClose.push( bitmap );
|
|
1361
|
+
normalized[ i ] = { image: bitmap };
|
|
1362
|
+
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
return { normalized, bitmapsToClose };
|
|
1368
|
+
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1246
1371
|
createFallbackTexture() {
|
|
1247
1372
|
|
|
1248
1373
|
const data = new Uint8Array( [ 255, 255, 255, 255 ] );
|
|
@@ -1276,3 +1401,14 @@ export class TextureCreator {
|
|
|
1276
1401
|
}
|
|
1277
1402
|
|
|
1278
1403
|
}
|
|
1404
|
+
|
|
1405
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
1406
|
+
|
|
1407
|
+
/** Convert raw RGBA pixel data to an ImageBitmap (zero-copy Uint8ClampedArray view). */
|
|
1408
|
+
function _rawPixelsToBitmap( data, width, height ) {
|
|
1409
|
+
|
|
1410
|
+
const clamped = new Uint8ClampedArray( data.buffer, data.byteOffset, data.byteLength );
|
|
1411
|
+
return createImageBitmap( new ImageData( clamped, width, height ) );
|
|
1412
|
+
|
|
1413
|
+
}
|
|
1414
|
+
|
package/src/RenderSettings.js
CHANGED
|
@@ -31,6 +31,7 @@ const SETTING_ROUTES = {
|
|
|
31
31
|
anamorphicRatio: { uniform: 'anamorphicRatio', reset: true },
|
|
32
32
|
samplingTechnique: { uniform: 'samplingTechnique', reset: true },
|
|
33
33
|
fireflyThreshold: { uniform: 'fireflyThreshold', reset: true },
|
|
34
|
+
enableAlphaShadows: { uniform: 'enableAlphaShadows', reset: true },
|
|
34
35
|
enableEmissiveTriangleSampling: { uniform: 'enableEmissiveTriangleSampling', reset: true },
|
|
35
36
|
emissiveBoost: { uniform: 'emissiveBoost', reset: true },
|
|
36
37
|
visMode: { uniform: 'visMode', reset: true },
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from 'three/tsl';
|
|
27
27
|
|
|
28
28
|
import { Ray, HitInfo } from './Struct.js';
|
|
29
|
-
import { getDatafromStorageBuffer, MATERIAL_SLOTS } from './Common.js';
|
|
29
|
+
import { getDatafromStorageBuffer, MATERIAL_SLOTS, MATERIAL_SLOT } from './Common.js';
|
|
30
30
|
import { RandomPointInCircle } from './Random.js';
|
|
31
31
|
|
|
32
32
|
// ================================================================================
|
|
@@ -136,7 +136,7 @@ const fastRayAABBDst = wgslFn( `
|
|
|
136
136
|
// Per-mesh visibility handled at BLAS-pointer level; material visibility always 1.
|
|
137
137
|
export const passesSideCulling = Fn( ( [ materialIndex, rayDirection, normal, materialBuffer ] ) => {
|
|
138
138
|
|
|
139
|
-
const sideData = getDatafromStorageBuffer( materialBuffer, materialIndex, int(
|
|
139
|
+
const sideData = getDatafromStorageBuffer( materialBuffer, materialIndex, int( MATERIAL_SLOT.OPACITY_ALPHA ), int( MATERIAL_SLOTS ) );
|
|
140
140
|
const side = int( sideData.g );
|
|
141
141
|
const rayDotNormal = rayDirection.dot( normal );
|
|
142
142
|
const doubleSide = side.equal( int( 2 ) );
|
|
@@ -268,7 +268,7 @@ export const traverseBVH = Fn( ( [
|
|
|
268
268
|
} );
|
|
269
269
|
|
|
270
270
|
// If we found a very close hit, we can terminate early
|
|
271
|
-
If( closestHit.didHit.and( closestHit.dst.lessThan(
|
|
271
|
+
If( closestHit.didHit.and( closestHit.dst.lessThan( 0.001 ) ), () => {
|
|
272
272
|
|
|
273
273
|
Break();
|
|
274
274
|
|
|
@@ -451,6 +451,12 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
451
451
|
closestHit.hitPoint.assign( ray.origin.add( ray.direction.mul( triResult.x ) ) );
|
|
452
452
|
closestHit.normal.assign( normalize( cross( pB.sub( pA ), pC.sub( pA ) ) ) );
|
|
453
453
|
|
|
454
|
+
// Store barycentrics + triangle index for deferred UV computation.
|
|
455
|
+
// Actual UV interpolation happens in traceShadowRay only when
|
|
456
|
+
// the material needs alpha testing — zero overhead for opaque hits.
|
|
457
|
+
closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
|
|
458
|
+
closestHit.triangleIndex.assign( triIndex );
|
|
459
|
+
|
|
454
460
|
// Shadow ray only needs any hit — skip remaining triangles in leaf
|
|
455
461
|
Break();
|
|
456
462
|
|