rayzee 6.4.0 → 7.0.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/README.md +24 -5
- package/dist/rayzee.es.js +4953 -4225
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +157 -236
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +29 -13
- package/src/PathTracerApp.js +119 -26
- package/src/Pipeline/PipelineContext.js +1 -2
- package/src/Pipeline/RenderPipeline.js +1 -1
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/GeometryExtractor.js +22 -1
- package/src/Processor/KernelManager.js +277 -0
- package/src/Processor/PackedRayBuffer.js +265 -0
- package/src/Processor/QueueManager.js +173 -0
- package/src/Processor/SceneProcessor.js +1 -0
- package/src/Processor/ShaderBuilder.js +11 -316
- package/src/Processor/StorageTexturePool.js +29 -15
- package/src/Processor/TextureCreator.js +6 -0
- package/src/Processor/VRAMTracker.js +169 -0
- package/src/Processor/utils.js +11 -110
- package/src/RenderSettings.js +1 -3
- package/src/Stages/ASVGF.js +76 -20
- package/src/Stages/BilateralFilter.js +34 -10
- package/src/Stages/EdgeFilter.js +2 -3
- package/src/Stages/MotionVector.js +16 -9
- package/src/Stages/NormalDepth.js +17 -5
- package/src/Stages/PathTracer.js +671 -1456
- package/src/Stages/PathTracerStage.js +1451 -0
- package/src/Stages/SSRC.js +32 -15
- package/src/Stages/Variance.js +35 -12
- package/src/TSL/BVHTraversal.js +7 -1
- package/src/TSL/Common.js +12 -2
- package/src/TSL/CompactKernel.js +110 -0
- package/src/TSL/DebugKernel.js +98 -0
- package/src/TSL/Environment.js +13 -11
- package/src/TSL/ExtendKernel.js +75 -0
- package/src/TSL/FinalWriteKernel.js +121 -0
- package/src/TSL/GenerateKernel.js +109 -0
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/MaterialTransmission.js +32 -2
- package/src/TSL/PathTracerCore.js +43 -912
- package/src/TSL/ShadeKernel.js +873 -0
- package/src/TSL/Struct.js +5 -0
- package/src/TSL/Subsurface.js +232 -0
- package/src/TSL/patches.js +81 -4
- package/src/index.js +3 -0
- package/src/managers/CameraManager.js +1 -1
- package/src/managers/DenoisingManager.js +40 -75
- package/src/managers/EnvironmentManager.js +30 -39
- package/src/managers/MaterialDataManager.js +60 -1
- package/src/managers/OverlayManager.js +7 -22
- package/src/managers/UniformManager.js +1 -3
- package/src/managers/helpers/TileHelper.js +2 -2
- package/src/Stages/AdaptiveSampling.js +0 -483
- package/src/TSL/PathTracer.js +0 -384
- package/src/managers/TileManager.js +0 -298
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -58,6 +58,10 @@ export const ENGINE_DEFAULTS = {
|
|
|
58
58
|
afScreenPoint: { x: 0.5, y: 0.5 },
|
|
59
59
|
afSmoothingFactor: 0.15,
|
|
60
60
|
|
|
61
|
+
// Multi-sample pool: S=samplesPerPixel rays/pixel/frame, FinalWrite averages them; interactive-only (renderMode 0, ≤ cap), else S=1.
|
|
62
|
+
// Pixel cap (768²) bounds pool memory; covers the 512² default, excludes ≥768².
|
|
63
|
+
wavefrontMultiSampleMaxPixels: 589824,
|
|
64
|
+
|
|
61
65
|
enablePathTracer: true,
|
|
62
66
|
enableAccumulation: true,
|
|
63
67
|
pauseRendering: false,
|
|
@@ -65,18 +69,14 @@ export const ENGINE_DEFAULTS = {
|
|
|
65
69
|
bounces: 3,
|
|
66
70
|
samplesPerPixel: 1,
|
|
67
71
|
transmissiveBounces: 5,
|
|
72
|
+
maxSubsurfaceSteps: 8, // interactive default: low cap (bounded random-walk SSS)
|
|
68
73
|
samplingTechnique: 3,
|
|
69
74
|
enableEmissiveTriangleSampling: false,
|
|
70
75
|
emissiveBoost: 1.0,
|
|
71
76
|
|
|
72
|
-
adaptiveSampling: false,
|
|
73
|
-
adaptiveSamplingMin: 1,
|
|
74
|
-
adaptiveSamplingMax: 8,
|
|
75
|
-
adaptiveSamplingVarianceThreshold: 0.1,
|
|
76
77
|
temporalVarianceWeight: 0.6,
|
|
77
78
|
enableEarlyTermination: true,
|
|
78
79
|
earlyTerminationThreshold: 0.002,
|
|
79
|
-
showAdaptiveSamplingHelper: false,
|
|
80
80
|
performanceModeAdaptive: 'medium',
|
|
81
81
|
|
|
82
82
|
fireflyThreshold: 3.0,
|
|
@@ -84,8 +84,7 @@ export const ENGINE_DEFAULTS = {
|
|
|
84
84
|
renderTimeLimit: 30,
|
|
85
85
|
renderMode: 0,
|
|
86
86
|
enableAlphaShadows: false,
|
|
87
|
-
|
|
88
|
-
tilesHelper: false,
|
|
87
|
+
tilesHelper: true, // show OIDN denoise / AI upscale tile progress overlay
|
|
89
88
|
showLightHelper: false,
|
|
90
89
|
|
|
91
90
|
directionalLightIntensity: 0,
|
|
@@ -151,6 +150,12 @@ export const ENGINE_DEFAULTS = {
|
|
|
151
150
|
// only round-trip exactly when both sides agree.
|
|
152
151
|
export const ALBEDO_EPS = 0.01;
|
|
153
152
|
|
|
153
|
+
// Per-resolution compute StorageTextures are pre-allocated at this size and never
|
|
154
|
+
// resized (works around three.js r184 StorageTexture-resize bugs — see TSL/patches
|
|
155
|
+
// history). Render resolution must not exceed this on either axis; the engine warns
|
|
156
|
+
// and ignores larger requests.
|
|
157
|
+
export const MAX_STORAGE_TEXTURE_SIZE = 2048;
|
|
158
|
+
|
|
154
159
|
export const ASVGF_QUALITY_PRESETS = {
|
|
155
160
|
// phiColor / phiDepth are RELATIVE tolerances (fractions). Bigger = more
|
|
156
161
|
// permissive. gradientStrength = 0 keeps the adaptive-α boost off; the
|
|
@@ -356,8 +361,8 @@ export const TRIANGLE_DATA_LAYOUT = {
|
|
|
356
361
|
// Shared between CPU writers (TextureCreator, MaterialDataManager) and GPU readers (Common.js getMaterial).
|
|
357
362
|
export const MATERIAL_DATA_LAYOUT = {
|
|
358
363
|
|
|
359
|
-
SLOTS_PER_MATERIAL:
|
|
360
|
-
FLOATS_PER_MATERIAL:
|
|
364
|
+
SLOTS_PER_MATERIAL: 30, // vec4 slots per material
|
|
365
|
+
FLOATS_PER_MATERIAL: 120, // total floats per material (30 × 4)
|
|
361
366
|
|
|
362
367
|
// ── Flat float offsets (CPU side) ────────────────────────────────
|
|
363
368
|
// Used as: data[ materialIndex * FLOATS_PER_MATERIAL + offset ]
|
|
@@ -399,6 +404,14 @@ export const MATERIAL_DATA_LAYOUT = {
|
|
|
399
404
|
BUMP_TRANSFORM: 92,
|
|
400
405
|
DISPLACEMENT_TRANSFORM: 100,
|
|
401
406
|
|
|
407
|
+
// ── Subsurface scattering (3 slots appended after transforms) ────
|
|
408
|
+
// Slot 27: subsurfaceColor.rgb (scatter albedo) + subsurface weight
|
|
409
|
+
SUBSURFACE_COLOR: 108, SUBSURFACE: 111,
|
|
410
|
+
// Slot 28: subsurfaceRadius.rgb (mean free path) + radius scale
|
|
411
|
+
SUBSURFACE_RADIUS: 112, SUBSURFACE_RADIUS_SCALE: 115,
|
|
412
|
+
// Slot 29: anisotropy g (floats 117-119 reserved for future SSS)
|
|
413
|
+
SUBSURFACE_ANISOTROPY: 116,
|
|
414
|
+
|
|
402
415
|
// ── Vec4 slot indices (GPU/TSL side) ─────────────────────────────
|
|
403
416
|
// Used with getDatafromStorageBuffer( buf, matIdx, int(slot), int(SLOTS_PER_MATERIAL) )
|
|
404
417
|
SLOT: {
|
|
@@ -422,6 +435,9 @@ export const MATERIAL_DATA_LAYOUT = {
|
|
|
422
435
|
EMISSIVE_TRANSFORM_A: 21, EMISSIVE_TRANSFORM_B: 22,
|
|
423
436
|
BUMP_TRANSFORM_A: 23, BUMP_TRANSFORM_B: 24,
|
|
424
437
|
DISPLACEMENT_TRANSFORM_A: 25, DISPLACEMENT_TRANSFORM_B: 26,
|
|
438
|
+
SUBSURFACE_A: 27, // subsurfaceColor.rgb, subsurface weight
|
|
439
|
+
SUBSURFACE_B: 28, // subsurfaceRadius.rgb, subsurfaceRadiusScale
|
|
440
|
+
SUBSURFACE_C: 29, // subsurfaceAnisotropy, reserved
|
|
425
441
|
},
|
|
426
442
|
|
|
427
443
|
};
|
|
@@ -434,7 +450,7 @@ export const BVH_LEAF_MARKERS = {
|
|
|
434
450
|
|
|
435
451
|
// Texture processing constants
|
|
436
452
|
export const TEXTURE_CONSTANTS = {
|
|
437
|
-
PIXELS_PER_MATERIAL:
|
|
453
|
+
PIXELS_PER_MATERIAL: 30,
|
|
438
454
|
RGBA_COMPONENTS: 4,
|
|
439
455
|
VEC4_PER_TRIANGLE: 8,
|
|
440
456
|
VEC4_PER_BVH_NODE: 4,
|
|
@@ -454,8 +470,8 @@ export const DEFAULT_TEXTURE_MATRIX = [ 0, 0, 1, 1, 0, 0, 0, 1 ];
|
|
|
454
470
|
// 'interactive' — low-sample, bounded bounces, no offline denoising, controls enabled.
|
|
455
471
|
// 'production' — high-sample, deep bounces, OIDN enabled, controls disabled.
|
|
456
472
|
export const PRODUCTION_RENDER_CONFIG = {
|
|
457
|
-
maxSamples: 30, bounces: 20, transmissiveBounces: 8, samplesPerPixel: 1,
|
|
458
|
-
renderMode: 1, enableAlphaShadows: true,
|
|
473
|
+
maxSamples: 30, bounces: 20, transmissiveBounces: 8, maxSubsurfaceSteps: 64, samplesPerPixel: 1,
|
|
474
|
+
renderMode: 1, enableAlphaShadows: true,
|
|
459
475
|
enableOIDN: true, oidnQuality: 'balance',
|
|
460
476
|
interactionModeEnabled: false,
|
|
461
477
|
};
|
|
@@ -464,7 +480,7 @@ export const INTERACTIVE_RENDER_CONFIG = {
|
|
|
464
480
|
maxSamples: ENGINE_DEFAULTS.maxSamples, bounces: ENGINE_DEFAULTS.bounces,
|
|
465
481
|
samplesPerPixel: ENGINE_DEFAULTS.samplesPerPixel, renderMode: ENGINE_DEFAULTS.renderMode, enableAlphaShadows: ENGINE_DEFAULTS.enableAlphaShadows,
|
|
466
482
|
transmissiveBounces: ENGINE_DEFAULTS.transmissiveBounces,
|
|
467
|
-
|
|
483
|
+
maxSubsurfaceSteps: ENGINE_DEFAULTS.maxSubsurfaceSteps,
|
|
468
484
|
enableOIDN: false, oidnQuality: 'fast',
|
|
469
485
|
interactionModeEnabled: true,
|
|
470
486
|
};
|
package/src/PathTracerApp.js
CHANGED
|
@@ -11,14 +11,13 @@ import { MotionVector } from './Stages/MotionVector.js';
|
|
|
11
11
|
import { ASVGF } from './Stages/ASVGF.js';
|
|
12
12
|
import { Variance } from './Stages/Variance.js';
|
|
13
13
|
import { BilateralFilter } from './Stages/BilateralFilter.js';
|
|
14
|
-
import { AdaptiveSampling } from './Stages/AdaptiveSampling.js';
|
|
15
14
|
import { EdgeFilter } from './Stages/EdgeFilter.js';
|
|
16
15
|
import { AutoExposure } from './Stages/AutoExposure.js';
|
|
17
16
|
import { SSRC } from './Stages/SSRC.js';
|
|
18
17
|
import { Compositor } from './Stages/Compositor.js';
|
|
19
18
|
import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
20
19
|
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
21
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG } from './EngineDefaults.js';
|
|
20
|
+
import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, MAX_STORAGE_TEXTURE_SIZE } from './EngineDefaults.js';
|
|
22
21
|
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory } from './Processor/utils.js';
|
|
23
22
|
import { BuildTimer } from './Processor/BuildTimer.js';
|
|
24
23
|
import { InteractionManager } from './managers/InteractionManager.js';
|
|
@@ -310,7 +309,14 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
310
309
|
|
|
311
310
|
}
|
|
312
311
|
|
|
313
|
-
|
|
312
|
+
this._ensureVRAMWiring();
|
|
313
|
+
const mem = this.stages.pathTracer?.vramTracker?.measure();
|
|
314
|
+
updateStats( {
|
|
315
|
+
timeElapsed: this.completion.timeElapsed,
|
|
316
|
+
samples: getDisplaySamples( this.stages.pathTracer ),
|
|
317
|
+
memoryUsed: mem?.current ?? 0,
|
|
318
|
+
memoryPeak: mem?.peak ?? 0,
|
|
319
|
+
} );
|
|
314
320
|
|
|
315
321
|
// Check time limit
|
|
316
322
|
if ( this.completion.isTimeLimitReached( this.settings.get( 'renderLimitMode' ), this.settings.get( 'renderTimeLimit' ) ) ) {
|
|
@@ -740,12 +746,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
740
746
|
this.stages.pathTracer.setupMaterial();
|
|
741
747
|
timer.end( 'Material setup (TSL compile)' );
|
|
742
748
|
|
|
743
|
-
// Front-load
|
|
744
|
-
//
|
|
745
|
-
// build time moves the stall to this loading moment.
|
|
746
|
-
// - raster fallback: compileAsync yields to main thread (r184+).
|
|
749
|
+
// Front-load raster pipeline creation (compileAsync yields to main thread, r184+) so the first
|
|
750
|
+
// animate frame is snappy. Wavefront compute kernels compile lazily on their first dispatch.
|
|
747
751
|
timer.start( 'Pipeline precompile' );
|
|
748
|
-
this.stages.pathTracer.shaderBuilder.forceCompile( this.renderer );
|
|
749
752
|
try {
|
|
750
753
|
|
|
751
754
|
await this.renderer.compileAsync( this.meshScene, this.cameraManager.camera );
|
|
@@ -845,11 +848,31 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
845
848
|
// Resize
|
|
846
849
|
// ═══════════════════════════════════════════════════════════════
|
|
847
850
|
|
|
851
|
+
/**
|
|
852
|
+
* Guard against render resolutions the compute pipeline can't support.
|
|
853
|
+
* Per-resolution StorageTextures are pre-allocated at MAX_STORAGE_TEXTURE_SIZE
|
|
854
|
+
* and never resized, so a larger request would overflow them. Warn and skip.
|
|
855
|
+
* @returns {boolean} true if the size is renderable
|
|
856
|
+
*/
|
|
857
|
+
_isRenderSizeSupported( width, height ) {
|
|
858
|
+
|
|
859
|
+
if ( width > MAX_STORAGE_TEXTURE_SIZE || height > MAX_STORAGE_TEXTURE_SIZE ) {
|
|
860
|
+
|
|
861
|
+
console.warn( `[Rayzee] Render resolution ${width}×${height} exceeds the ${MAX_STORAGE_TEXTURE_SIZE}px limit (compute storage textures are pre-allocated at ${MAX_STORAGE_TEXTURE_SIZE}px). Ignoring resize — use a resolution ≤ ${MAX_STORAGE_TEXTURE_SIZE}.` );
|
|
862
|
+
return false;
|
|
863
|
+
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return true;
|
|
867
|
+
|
|
868
|
+
}
|
|
869
|
+
|
|
848
870
|
onResize() {
|
|
849
871
|
|
|
850
872
|
const width = this.canvas.clientWidth;
|
|
851
873
|
const height = this.canvas.clientHeight;
|
|
852
874
|
if ( width === 0 || height === 0 ) return;
|
|
875
|
+
if ( ! this._isRenderSizeSupported( width, height ) ) return;
|
|
853
876
|
|
|
854
877
|
this.renderer.setPixelRatio( 1.0 );
|
|
855
878
|
this.renderer.setSize( width, height, false );
|
|
@@ -878,6 +901,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
878
901
|
|
|
879
902
|
_applyRenderResize( renderWidth, renderHeight ) {
|
|
880
903
|
|
|
904
|
+
if ( ! this._isRenderSizeSupported( renderWidth, renderHeight ) ) return;
|
|
905
|
+
|
|
881
906
|
this.pipeline?.setSize( renderWidth, renderHeight );
|
|
882
907
|
this.denoisingManager?.setRenderSize( renderWidth, renderHeight );
|
|
883
908
|
this.needsReset = true;
|
|
@@ -889,6 +914,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
889
914
|
setCanvasSize( width, height ) {
|
|
890
915
|
|
|
891
916
|
if ( width === 0 || height === 0 ) return;
|
|
917
|
+
if ( ! this._isRenderSizeSupported( width, height ) ) return;
|
|
892
918
|
|
|
893
919
|
this.renderer.setPixelRatio( 1.0 );
|
|
894
920
|
this.renderer.setSize( width, height, false );
|
|
@@ -922,19 +948,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
922
948
|
maxBounces: config.bounces,
|
|
923
949
|
samplesPerPixel: config.samplesPerPixel,
|
|
924
950
|
transmissiveBounces: config.transmissiveBounces,
|
|
951
|
+
maxSubsurfaceSteps: config.maxSubsurfaceSteps,
|
|
925
952
|
}, { silent: true } );
|
|
926
953
|
|
|
927
954
|
this.stages.pathTracer?.setUniform( 'renderMode', parseInt( config.renderMode ) );
|
|
928
955
|
this.stages.pathTracer?.setUniform( 'enableAlphaShadows', config.enableAlphaShadows ?? false );
|
|
929
|
-
this.stages.pathTracer?.tileManager?.setTileCount( config.tiles );
|
|
930
|
-
|
|
931
|
-
const tileHelper = this.overlayManager?.getHelper( 'tiles' );
|
|
932
|
-
if ( tileHelper ) {
|
|
933
|
-
|
|
934
|
-
tileHelper.enabled = config.tilesHelper;
|
|
935
|
-
if ( ! config.tilesHelper ) tileHelper.hide();
|
|
936
|
-
|
|
937
|
-
}
|
|
938
956
|
|
|
939
957
|
this.stages.pathTracer?.updateCompletionThreshold?.();
|
|
940
958
|
|
|
@@ -957,6 +975,20 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
957
975
|
|
|
958
976
|
this.needsReset = false;
|
|
959
977
|
this.pauseRendering = false;
|
|
978
|
+
|
|
979
|
+
// Entering a final render starts a fresh peak window (Blender per-render semantics).
|
|
980
|
+
if ( isProduction ) {
|
|
981
|
+
|
|
982
|
+
const tracker = this.stages.pathTracer?.vramTracker;
|
|
983
|
+
if ( tracker ) {
|
|
984
|
+
|
|
985
|
+
tracker.measure();
|
|
986
|
+
tracker.resetPeak();
|
|
987
|
+
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
}
|
|
991
|
+
|
|
960
992
|
this.reset();
|
|
961
993
|
|
|
962
994
|
}
|
|
@@ -1066,6 +1098,69 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1066
1098
|
|
|
1067
1099
|
}
|
|
1068
1100
|
|
|
1101
|
+
/** The path tracer's VRAM tracker, or null before stages are built. */
|
|
1102
|
+
get vram() {
|
|
1103
|
+
|
|
1104
|
+
return this.stages.pathTracer?.vramTracker ?? null;
|
|
1105
|
+
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* On-demand current/peak GPU memory snapshot.
|
|
1110
|
+
* @returns {{ current: number, peak: number, byCategory: Object }} bytes
|
|
1111
|
+
*/
|
|
1112
|
+
getMemoryInfo() {
|
|
1113
|
+
|
|
1114
|
+
return this.stages.pathTracer?.vramTracker?.measure() ?? { current: 0, peak: 0, byCategory: {} };
|
|
1115
|
+
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Idempotent: registers the cross-stage texture provider and re-measures on
|
|
1119
|
+
// allocation events (scene/env load, resize) so peak is caught even while idle.
|
|
1120
|
+
_ensureVRAMWiring() {
|
|
1121
|
+
|
|
1122
|
+
if ( this._vramWired ) return;
|
|
1123
|
+
const tracker = this.stages.pathTracer?.vramTracker;
|
|
1124
|
+
if ( ! tracker ) return; // stages not ready yet
|
|
1125
|
+
|
|
1126
|
+
tracker.register( 'stages', () => this._collectStageTextures() );
|
|
1127
|
+
|
|
1128
|
+
const remeasure = () => tracker.measure();
|
|
1129
|
+
this._addTrackedListener( this, 'SceneRebuild', remeasure );
|
|
1130
|
+
this._addTrackedListener( this, 'EnvironmentLoaded', remeasure );
|
|
1131
|
+
this._addTrackedListener( this, 'resolution_changed', remeasure );
|
|
1132
|
+
|
|
1133
|
+
this._vramWired = true;
|
|
1134
|
+
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Direct StorageTexture/RenderTarget properties of every non-pathTracer stage
|
|
1138
|
+
// (denoiser/G-buffer/filter targets). The pathTracer's own buffers/textures are
|
|
1139
|
+
// registered explicitly; measure() dedupes by identity so overlaps don't double-count.
|
|
1140
|
+
_collectStageTextures() {
|
|
1141
|
+
|
|
1142
|
+
const out = [];
|
|
1143
|
+
const stages = this.stages || {};
|
|
1144
|
+
const pt = stages.pathTracer;
|
|
1145
|
+
|
|
1146
|
+
for ( const key in stages ) {
|
|
1147
|
+
|
|
1148
|
+
const stage = stages[ key ];
|
|
1149
|
+
if ( ! stage || stage === pt || typeof stage !== 'object' ) continue;
|
|
1150
|
+
|
|
1151
|
+
for ( const prop in stage ) {
|
|
1152
|
+
|
|
1153
|
+
const v = stage[ prop ];
|
|
1154
|
+
if ( v && ( v.isTexture || v.isRenderTarget ) ) out.push( v );
|
|
1155
|
+
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
return out;
|
|
1161
|
+
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1069
1164
|
// ═══════════════════════════════════════════════════════════════
|
|
1070
1165
|
// Materials (absorbed from MaterialsAPI)
|
|
1071
1166
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -1206,6 +1301,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1206
1301
|
maxBufferSize: adapterLimits.maxBufferSize,
|
|
1207
1302
|
maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
|
|
1208
1303
|
maxColorAttachmentBytesPerSample: 128,
|
|
1304
|
+
maxStorageBuffersPerShaderStage: Math.min( adapterLimits.maxStorageBuffersPerShaderStage, 10 ),
|
|
1209
1305
|
}
|
|
1210
1306
|
} );
|
|
1211
1307
|
|
|
@@ -1264,7 +1360,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1264
1360
|
this.pipeline.addStage( this.stages.asvgf );
|
|
1265
1361
|
this.pipeline.addStage( this.stages.variance );
|
|
1266
1362
|
this.pipeline.addStage( this.stages.bilateralFilter );
|
|
1267
|
-
this.pipeline.addStage( this.stages.adaptiveSampling );
|
|
1268
1363
|
this.pipeline.addStage( this.stages.edgeFilter );
|
|
1269
1364
|
this.pipeline.addStage( this.stages.autoExposure );
|
|
1270
1365
|
this.pipeline.addStage( this.stages.compositor );
|
|
@@ -1468,9 +1563,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1468
1563
|
|
|
1469
1564
|
_createStages() {
|
|
1470
1565
|
|
|
1471
|
-
const adaptiveSamplingMax = this.settings.get( 'adaptiveSamplingMax' );
|
|
1472
|
-
const useAdaptiveSampling = this.settings.get( 'useAdaptiveSampling' );
|
|
1473
|
-
|
|
1474
1566
|
this.stages.pathTracer = new PathTracer( this.renderer, this.scene, this.cameraManager.camera );
|
|
1475
1567
|
this.stages.normalDepth = new NormalDepth( this.renderer, {
|
|
1476
1568
|
pathTracer: this.stages.pathTracer
|
|
@@ -1482,10 +1574,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1482
1574
|
this.stages.asvgf = new ASVGF( this.renderer, { enabled: false } );
|
|
1483
1575
|
this.stages.variance = new Variance( this.renderer, { enabled: false } );
|
|
1484
1576
|
this.stages.bilateralFilter = new BilateralFilter( this.renderer, { enabled: false } );
|
|
1485
|
-
this.stages.adaptiveSampling = new AdaptiveSampling( this.renderer, {
|
|
1486
|
-
adaptiveSamplingMax,
|
|
1487
|
-
enabled: useAdaptiveSampling,
|
|
1488
|
-
} );
|
|
1489
1577
|
this.stages.edgeFilter = new EdgeFilter( this.renderer, { enabled: false } );
|
|
1490
1578
|
this.stages.autoExposure = new AutoExposure( this.renderer, { enabled: DEFAULT_STATE.autoExposure ?? false } );
|
|
1491
1579
|
|
|
@@ -1504,10 +1592,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1504
1592
|
camera: this.cameraManager.camera,
|
|
1505
1593
|
stages: {
|
|
1506
1594
|
pathTracer: this.stages.pathTracer,
|
|
1595
|
+
normalDepth: this.stages.normalDepth,
|
|
1596
|
+
motionVector: this.stages.motionVector,
|
|
1507
1597
|
asvgf: this.stages.asvgf,
|
|
1508
1598
|
variance: this.stages.variance,
|
|
1509
1599
|
bilateralFilter: this.stages.bilateralFilter,
|
|
1510
|
-
adaptiveSampling: this.stages.adaptiveSampling,
|
|
1511
1600
|
edgeFilter: this.stages.edgeFilter,
|
|
1512
1601
|
ssrc: this.stages.ssrc,
|
|
1513
1602
|
autoExposure: this.stages.autoExposure,
|
|
@@ -1522,6 +1611,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1522
1611
|
this.denoisingManager.setupDenoiser();
|
|
1523
1612
|
this.denoisingManager.setupUpscaler();
|
|
1524
1613
|
|
|
1614
|
+
// Seed G-buffer gating: NormalDepth/MotionVector start enabled (stage default)
|
|
1615
|
+
// but are only needed by real-time denoisers — idle them until one is active.
|
|
1616
|
+
this.denoisingManager._syncGBufferStages();
|
|
1617
|
+
|
|
1525
1618
|
// Set initial render resolution
|
|
1526
1619
|
const initW = this.canvas.clientWidth || 1;
|
|
1527
1620
|
const initH = this.canvas.clientHeight || 1;
|
|
@@ -42,7 +42,7 @@ export class PipelineContext {
|
|
|
42
42
|
accumulatedFrames: 0,
|
|
43
43
|
|
|
44
44
|
// Render modes
|
|
45
|
-
renderMode: 0, // 0 =
|
|
45
|
+
renderMode: 0, // 0 = interactive, 1 = production (full-frame in both)
|
|
46
46
|
interactionMode: false,
|
|
47
47
|
isComplete: false,
|
|
48
48
|
|
|
@@ -65,7 +65,6 @@ export class PipelineContext {
|
|
|
65
65
|
|
|
66
66
|
// Feature flags
|
|
67
67
|
enableASVGF: false,
|
|
68
|
-
enableAdaptiveSampling: false,
|
|
69
68
|
enableEdgeFiltering: false,
|
|
70
69
|
// Can be extended by stages as needed
|
|
71
70
|
};
|
|
@@ -22,7 +22,7 @@ import { EventDispatcher } from './EventDispatcher.js';
|
|
|
22
22
|
* // Add stages in execution order
|
|
23
23
|
* pipeline.addStage(new PathTracer(...));
|
|
24
24
|
* pipeline.addStage(new ASVGF(...));
|
|
25
|
-
* pipeline.addStage(new
|
|
25
|
+
* pipeline.addStage(new EdgeFilter(...));
|
|
26
26
|
*
|
|
27
27
|
* // Render all stages
|
|
28
28
|
* pipeline.render(writeBuffer);
|
|
@@ -28,7 +28,7 @@ export const StageExecutionMode = {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* CONDITIONAL - Stage decides execution via shouldExecute() method
|
|
31
|
-
* Use for: Stages with complex execution logic
|
|
31
|
+
* Use for: Stages with complex execution logic
|
|
32
32
|
*/
|
|
33
33
|
CONDITIONAL: 'conditional'
|
|
34
34
|
};
|
|
@@ -25,7 +25,6 @@ export class CameraOptimizer {
|
|
|
25
25
|
this.interactionQualitySettings = {
|
|
26
26
|
maxBounceCount: 1,
|
|
27
27
|
numRaysPerPixel: 1,
|
|
28
|
-
useAdaptiveSampling: false,
|
|
29
28
|
useEnvMapIS: false,
|
|
30
29
|
// pixelRatio: 0.25,
|
|
31
30
|
enableAccumulation: false,
|
|
@@ -306,7 +305,6 @@ export class CameraOptimizer {
|
|
|
306
305
|
'ultra-low': {
|
|
307
306
|
maxBounceCount: 1,
|
|
308
307
|
numRaysPerPixel: 1,
|
|
309
|
-
useAdaptiveSampling: false,
|
|
310
308
|
useEnvMapIS: false,
|
|
311
309
|
pixelRatio: 0.125,
|
|
312
310
|
enableAccumulation: false
|
|
@@ -314,7 +312,6 @@ export class CameraOptimizer {
|
|
|
314
312
|
'low': {
|
|
315
313
|
maxBounceCount: 1,
|
|
316
314
|
numRaysPerPixel: 1,
|
|
317
|
-
useAdaptiveSampling: false,
|
|
318
315
|
useEnvMapIS: false,
|
|
319
316
|
pixelRatio: 0.25,
|
|
320
317
|
enableAccumulation: false
|
|
@@ -322,7 +319,6 @@ export class CameraOptimizer {
|
|
|
322
319
|
'medium': {
|
|
323
320
|
maxBounceCount: 2,
|
|
324
321
|
numRaysPerPixel: 1,
|
|
325
|
-
useAdaptiveSampling: false,
|
|
326
322
|
useEnvMapIS: true,
|
|
327
323
|
pixelRatio: 0.5,
|
|
328
324
|
enableAccumulation: false
|
|
@@ -330,7 +326,6 @@ export class CameraOptimizer {
|
|
|
330
326
|
'high': {
|
|
331
327
|
maxBounceCount: 3,
|
|
332
328
|
numRaysPerPixel: 1,
|
|
333
|
-
useAdaptiveSampling: true,
|
|
334
329
|
useEnvMapIS: true,
|
|
335
330
|
pixelRatio: 0.75,
|
|
336
331
|
enableAccumulation: true
|
|
@@ -160,6 +160,7 @@ export class GeometryExtractor {
|
|
|
160
160
|
if ( newMaterial.iridescence > 0 ) this.sceneFeatures.hasIridescence = true;
|
|
161
161
|
if ( newMaterial.sheen > 0 ) this.sceneFeatures.hasSheen = true;
|
|
162
162
|
if ( newMaterial.transparent || newMaterial.opacity < 1.0 || newMaterial.alphaTest > 0 ) this.sceneFeatures.hasTransparency = true;
|
|
163
|
+
if ( newMaterial.subsurface > 0 ) this.sceneFeatures.hasSubsurface = true;
|
|
163
164
|
|
|
164
165
|
// Detect multi-lobe materials (require multi-lobe MIS for optimal sampling)
|
|
165
166
|
const featureCount = [
|
|
@@ -259,7 +260,13 @@ export class GeometryExtractor {
|
|
|
259
260
|
normalScale: { x: 1, y: 1 },
|
|
260
261
|
bumpScale: 1.0,
|
|
261
262
|
displacementScale: 1.0,
|
|
262
|
-
alphaTest: 0.0
|
|
263
|
+
alphaTest: 0.0,
|
|
264
|
+
// Subsurface scattering (no native MeshPhysicalMaterial equivalent)
|
|
265
|
+
subsurface: 0.0,
|
|
266
|
+
subsurfaceColor: new Color( 0xffffff ),
|
|
267
|
+
subsurfaceRadius: [ 1.0, 0.2, 0.1 ], // skin-like: red travels furthest
|
|
268
|
+
subsurfaceRadiusScale: 1.0,
|
|
269
|
+
subsurfaceAnisotropy: 0.0
|
|
263
270
|
};
|
|
264
271
|
|
|
265
272
|
}
|
|
@@ -390,6 +397,13 @@ export class GeometryExtractor {
|
|
|
390
397
|
iridescenceIOR: material.iridescenceIOR ?? defaults.iridescenceIOR,
|
|
391
398
|
iridescenceThicknessRange: material.iridescenceThicknessRange ?? defaults.iridescenceThicknessRange,
|
|
392
399
|
|
|
400
|
+
// Subsurface scattering (custom props; MeshPhysicalMaterial has none)
|
|
401
|
+
subsurface: material.subsurface ?? defaults.subsurface,
|
|
402
|
+
subsurfaceColor: material.subsurfaceColor ?? defaults.subsurfaceColor,
|
|
403
|
+
subsurfaceRadius: material.subsurfaceRadius ?? defaults.subsurfaceRadius,
|
|
404
|
+
subsurfaceRadiusScale: material.subsurfaceRadiusScale ?? defaults.subsurfaceRadiusScale,
|
|
405
|
+
subsurfaceAnisotropy: material.subsurfaceAnisotropy ?? defaults.subsurfaceAnisotropy,
|
|
406
|
+
|
|
393
407
|
// Specular properties (for compatibility)
|
|
394
408
|
specularIntensity: legacyMapping.specularIntensity ?? material.specularIntensity ?? defaults.specularIntensity,
|
|
395
409
|
specularColor: legacyMapping.specularColor ?? material.specularColor ?? defaults.specularColor,
|
|
@@ -522,6 +536,10 @@ export class GeometryExtractor {
|
|
|
522
536
|
// triangle extraction that stores directly in texture format
|
|
523
537
|
extractTrianglesInBatch( positions, normals, uvs, indices, triangleCount, materialIndex, meshIndex ) {
|
|
524
538
|
|
|
539
|
+
// Track per-material triangle count for sort-bin remap (item 41)
|
|
540
|
+
while ( this.materialTriangleCounts.length <= materialIndex ) this.materialTriangleCounts.push( 0 );
|
|
541
|
+
this.materialTriangleCounts[ materialIndex ] += triangleCount;
|
|
542
|
+
|
|
525
543
|
// Pre-allocate objects for positions, normals, and UVs
|
|
526
544
|
const posA = this._getVec3( 0 );
|
|
527
545
|
const posB = this._getVec3( 1 );
|
|
@@ -765,6 +783,7 @@ export class GeometryExtractor {
|
|
|
765
783
|
|
|
766
784
|
// Reset other arrays
|
|
767
785
|
this.materials = [];
|
|
786
|
+
this.materialTriangleCounts = []; // Per-material triangle count (for sort-bin remap, item 41)
|
|
768
787
|
this.meshes = [];
|
|
769
788
|
this.meshTriangleRanges = []; // Per-mesh { start, count } for TLAS/BLAS
|
|
770
789
|
this.maps = [];
|
|
@@ -789,6 +808,7 @@ export class GeometryExtractor {
|
|
|
789
808
|
hasIridescence: false,
|
|
790
809
|
hasSheen: false,
|
|
791
810
|
hasTransparency: false,
|
|
811
|
+
hasSubsurface: false,
|
|
792
812
|
hasMultiLobeMaterials: false, // Materials with 2+ BRDF lobes
|
|
793
813
|
hasMRTOutputs: true // Always enabled for ASVGF/adaptive sampling support
|
|
794
814
|
};
|
|
@@ -801,6 +821,7 @@ export class GeometryExtractor {
|
|
|
801
821
|
triangleData: this.getTriangleData(), // Texture-ready Float32Array format
|
|
802
822
|
triangleCount: this.getTriangleCount(),
|
|
803
823
|
materials: this.materials,
|
|
824
|
+
materialTriangleCounts: this.materialTriangleCounts,
|
|
804
825
|
meshes: this.meshes,
|
|
805
826
|
meshTriangleRanges: this.meshTriangleRanges, // Per-mesh { start, count } for TLAS/BLAS
|
|
806
827
|
maps: this.maps,
|