rayzee 5.6.1 → 5.7.1
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 +3 -0
- package/dist/assets/CDFWorker-BFQUr3By.js +2 -0
- package/dist/assets/CDFWorker-BFQUr3By.js.map +1 -0
- package/dist/rayzee.es.js +2374 -2347
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -43
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineEvents.js +3 -0
- package/src/PathTracerApp.js +32 -11
- package/src/Processor/EquirectHDRInfo.js +76 -16
- package/src/Processor/ShaderBuilder.js +1 -0
- package/src/Processor/Workers/CDFWorker.js +72 -11
- package/src/RenderSettings.js +10 -8
- package/src/Stages/Compositor.js +101 -0
- package/src/Stages/NormalDepth.js +37 -20
- package/src/TSL/Common.js +9 -0
- package/src/TSL/Environment.js +15 -6
- package/src/TSL/LightsIndirect.js +1 -1
- package/src/TSL/LightsSampling.js +5 -4
- package/src/TSL/PathTracer.js +2 -2
- package/src/TSL/PathTracerCore.js +6 -5
- package/src/managers/DenoisingManager.js +8 -18
- package/src/managers/EnvironmentManager.js +17 -0
- package/src/managers/TransformManager.js +9 -0
- package/src/managers/UniformManager.js +1 -0
- package/dist/assets/CDFWorker-2MoynL4F.js +0 -2
- package/dist/assets/CDFWorker-2MoynL4F.js.map +0 -1
- package/src/Stages/Display.js +0 -120
package/src/TSL/Common.js
CHANGED
|
@@ -99,6 +99,15 @@ export const powerHeuristic = wgslFn( `
|
|
|
99
99
|
}
|
|
100
100
|
` );
|
|
101
101
|
|
|
102
|
+
// Balance heuristic — optimal for MIS-compensated env map sampling (Karlík et al. 2019)
|
|
103
|
+
export const balanceHeuristic = wgslFn( `
|
|
104
|
+
fn balanceHeuristic( pdf1: f32, pdf2: f32 ) -> f32 {
|
|
105
|
+
|
|
106
|
+
return pdf1 / max( pdf1 + pdf2, ${MIN_PDF} );
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
` );
|
|
110
|
+
|
|
102
111
|
// Bayer matrix 4x4 dithering — exact port of GLSL
|
|
103
112
|
export const applyDithering = wgslFn( `
|
|
104
113
|
fn applyDithering( color: vec3f, uv: vec2f, ditheringAmount: f32, resolution: vec2f ) -> vec3f {
|
package/src/TSL/Environment.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, vec2, vec4, float, int, If, texture, sampler, dot, floor, fract, min, mix, clamp } from 'three/tsl';
|
|
1
|
+
import { Fn, wgslFn, vec2, vec4, float, int, If, texture, sampler, dot, sin, floor, fract, min, max, mix, clamp } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import { REC709_LUMINANCE_COEFFICIENTS } from './Common.js';
|
|
4
4
|
|
|
@@ -47,9 +47,9 @@ export const equirectDirectionPdf = /*@__PURE__*/ wgslFn( `
|
|
|
47
47
|
`, [ equirectDirectionToUv ] );
|
|
48
48
|
|
|
49
49
|
// Evaluate PDF for a given direction (for MIS)
|
|
50
|
-
// Exact implementation from three-gpu-pathtracer
|
|
51
50
|
// Returns vec4(color.rgb, pdf) since TSL cannot use inout params
|
|
52
|
-
|
|
51
|
+
// Uses MIS-compensated PDF (Karlík et al. 2019): max(0, lum - delta) / compensatedTotalSum
|
|
52
|
+
export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix, envTotalSum, envCompensationDelta, envResolution ] ) => {
|
|
53
53
|
|
|
54
54
|
const result = vec4( 0.0 ).toVar();
|
|
55
55
|
|
|
@@ -63,8 +63,13 @@ export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix,
|
|
|
63
63
|
const uv = equirectDirectionToUv( { direction, environmentMatrix } ).toVar();
|
|
64
64
|
const color = texture( environment, uv, 0 ).rgb.toVar();
|
|
65
65
|
|
|
66
|
+
// sin(theta) matches the CDF's solid-angle weighting (lum * sinTheta)
|
|
67
|
+
const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
|
|
66
68
|
const lum = dot( color, REC709_LUMINANCE_COEFFICIENTS ).toVar();
|
|
67
|
-
const
|
|
69
|
+
const weightedLum = lum.mul( sinTheta ).toVar();
|
|
70
|
+
// MIS Compensation: subtract delta to match the sharpened CDF
|
|
71
|
+
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) ).toVar();
|
|
72
|
+
const pdf = compensatedWeight.div( envTotalSum ).toVar();
|
|
68
73
|
|
|
69
74
|
const dirPdf = equirectDirectionPdf( { direction, environmentMatrix } ).toVar();
|
|
70
75
|
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf ).toVar();
|
|
@@ -86,6 +91,7 @@ export const sampleEquirectProbability = Fn( ( [
|
|
|
86
91
|
environmentMatrix,
|
|
87
92
|
environmentIntensity,
|
|
88
93
|
envTotalSum,
|
|
94
|
+
envCompensationDelta,
|
|
89
95
|
envResolution,
|
|
90
96
|
r,
|
|
91
97
|
colorOutput
|
|
@@ -132,9 +138,12 @@ export const sampleEquirectProbability = Fn( ( [
|
|
|
132
138
|
// Write color to output parameter (avoids redundant CDF texture lookups)
|
|
133
139
|
colorOutput.assign( color );
|
|
134
140
|
|
|
135
|
-
// Calculate PDF
|
|
141
|
+
// Calculate PDF — sin(theta) weighting + MIS Compensation (Karlík et al. 2019)
|
|
142
|
+
const sinTheta = sin( uv.y.mul( Math.PI ) ).toVar();
|
|
136
143
|
const lum = dot( color.div( environmentIntensity ), REC709_LUMINANCE_COEFFICIENTS ).toVar();
|
|
137
|
-
const
|
|
144
|
+
const weightedLum = lum.mul( sinTheta ).toVar();
|
|
145
|
+
const compensatedWeight = max( float( 0.0 ), weightedLum.sub( envCompensationDelta ) ).toVar();
|
|
146
|
+
const pdf = compensatedWeight.div( envTotalSum ).toVar();
|
|
138
147
|
|
|
139
148
|
const dirPdf = equirectDirectionPdf( { direction, environmentMatrix } ).toVar();
|
|
140
149
|
const finalPdf = float( envResolution.x ).mul( float( envResolution.y ) ).mul( pdf ).mul( dirPdf ).toVar();
|
|
@@ -273,7 +273,7 @@ export const calculateIndirectLighting = Fn( ( [
|
|
|
273
273
|
samplingInfo,
|
|
274
274
|
// Environment resources
|
|
275
275
|
envTexture, environmentIntensity, envMatrix,
|
|
276
|
-
envTotalSum, envResolution,
|
|
276
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
277
277
|
enableEnvironmentLight, useEnvMapIS,
|
|
278
278
|
] ) => {
|
|
279
279
|
|
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
EPSILON,
|
|
85
85
|
MIN_PDF,
|
|
86
86
|
powerHeuristic,
|
|
87
|
+
balanceHeuristic,
|
|
87
88
|
} from './Common.js';
|
|
88
89
|
import {
|
|
89
90
|
sampleEquirectProbability,
|
|
@@ -935,7 +936,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
935
936
|
// Environment resources
|
|
936
937
|
envTexture, environmentIntensity, envMatrix,
|
|
937
938
|
envCDFBuffer,
|
|
938
|
-
envTotalSum, envResolution,
|
|
939
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
939
940
|
enableEnvironmentLight,
|
|
940
941
|
] ) => {
|
|
941
942
|
|
|
@@ -1204,7 +1205,7 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1204
1205
|
// Sample direction + PDF + color from importance-sampled environment
|
|
1205
1206
|
const envSampleResult = sampleEquirectProbability(
|
|
1206
1207
|
envTexture, envCDFBuffer,
|
|
1207
|
-
envMatrix, environmentIntensity, envTotalSum, envResolution, envRandom, envColor
|
|
1208
|
+
envMatrix, environmentIntensity, envTotalSum, envCompensationDelta, envResolution, envRandom, envColor
|
|
1208
1209
|
).toVar();
|
|
1209
1210
|
|
|
1210
1211
|
const envDirection = envSampleResult.xyz.toVar();
|
|
@@ -1229,11 +1230,11 @@ export const calculateDirectLightingUnified = Fn( ( [
|
|
|
1229
1230
|
const brdfValue = evaluateMaterialResponse( viewDir, envDirection, hitNormal, material );
|
|
1230
1231
|
const bPdf = calculateMaterialPDF( viewDir, envDirection, hitNormal, material ).toVar();
|
|
1231
1232
|
|
|
1232
|
-
//
|
|
1233
|
+
// Balance heuristic for env MIS — optimal for MIS-compensated PDFs (Karlík et al. 2019).
|
|
1233
1234
|
// The implicit path uses material combinedPdf as prevBouncePdf at the miss check.
|
|
1234
1235
|
const misW = select(
|
|
1235
1236
|
bPdf.greaterThan( 0.0 ),
|
|
1236
|
-
|
|
1237
|
+
balanceHeuristic( { pdf1: envPdf, pdf2: bPdf } ),
|
|
1237
1238
|
float( 1.0 )
|
|
1238
1239
|
).toVar();
|
|
1239
1240
|
|
package/src/TSL/PathTracer.js
CHANGED
|
@@ -138,7 +138,7 @@ export const pathTracerMain = ( params ) => {
|
|
|
138
138
|
spotLightsBuffer, numSpotLights,
|
|
139
139
|
envTexture, environmentIntensity, envMatrix,
|
|
140
140
|
envCDFBuffer,
|
|
141
|
-
envTotalSum, envResolution,
|
|
141
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
142
142
|
enableEnvironmentLight, useEnvMapIS,
|
|
143
143
|
maxBounceCount, transmissiveBounces,
|
|
144
144
|
showBackground, transparentBackground, backgroundIntensity,
|
|
@@ -285,7 +285,7 @@ export const pathTracerMain = ( params ) => {
|
|
|
285
285
|
spotLightsBuffer, numSpotLights,
|
|
286
286
|
envTexture, environmentIntensity, envMatrix,
|
|
287
287
|
envCDFBuffer,
|
|
288
|
-
envTotalSum, envResolution,
|
|
288
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
289
289
|
enableEnvironmentLight, useEnvMapIS,
|
|
290
290
|
maxBounceCount, transmissiveBounces,
|
|
291
291
|
backgroundIntensity, showBackground, transparentBackground,
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
applySoftSuppressionRGB,
|
|
57
57
|
getMaterial,
|
|
58
58
|
powerHeuristic,
|
|
59
|
+
balanceHeuristic,
|
|
59
60
|
} from './Common.js';
|
|
60
61
|
import {
|
|
61
62
|
DirectionSample,
|
|
@@ -582,7 +583,7 @@ export const Trace = Fn( ( [
|
|
|
582
583
|
// Environment
|
|
583
584
|
envTexture, environmentIntensity, envMatrix,
|
|
584
585
|
envCDFBuffer,
|
|
585
|
-
envTotalSum, envResolution,
|
|
586
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
586
587
|
enableEnvironmentLight, useEnvMapIS,
|
|
587
588
|
// Rendering parameters
|
|
588
589
|
maxBounceCount, transmissiveBounces,
|
|
@@ -703,12 +704,12 @@ export const Trace = Fn( ( [
|
|
|
703
704
|
If( prevBouncePdf.greaterThan( 0.0 ).and( enableEnvironmentLight ).and( useEnvMapIS ), () => {
|
|
704
705
|
|
|
705
706
|
const envEval = sampleEquirect(
|
|
706
|
-
envTexture, rayDirection, envMatrix, envTotalSum, envResolution,
|
|
707
|
+
envTexture, rayDirection, envMatrix, envTotalSum, envCompensationDelta, envResolution,
|
|
707
708
|
);
|
|
708
709
|
const envPdf = envEval.w.toVar();
|
|
709
710
|
If( envPdf.greaterThan( 0.0 ), () => {
|
|
710
711
|
|
|
711
|
-
envMisWeight.assign(
|
|
712
|
+
envMisWeight.assign( balanceHeuristic( { pdf1: prevBouncePdf, pdf2: envPdf } ) );
|
|
712
713
|
|
|
713
714
|
} );
|
|
714
715
|
|
|
@@ -1001,7 +1002,7 @@ export const Trace = Fn( ( [
|
|
|
1001
1002
|
materialBuffer,
|
|
1002
1003
|
envTexture, environmentIntensity, envMatrix,
|
|
1003
1004
|
envCDFBuffer,
|
|
1004
|
-
envTotalSum, envResolution,
|
|
1005
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
1005
1006
|
enableEnvironmentLight,
|
|
1006
1007
|
);
|
|
1007
1008
|
|
|
@@ -1120,7 +1121,7 @@ export const Trace = Fn( ( [
|
|
|
1120
1121
|
rngState,
|
|
1121
1122
|
samplingInfo,
|
|
1122
1123
|
envTexture, environmentIntensity, envMatrix,
|
|
1123
|
-
envTotalSum, envResolution,
|
|
1124
|
+
envTotalSum, envCompensationDelta, envResolution,
|
|
1124
1125
|
enableEnvironmentLight, useEnvMapIS,
|
|
1125
1126
|
) );
|
|
1126
1127
|
throughput.mulAssign( indirectResult.throughput );
|
|
@@ -41,7 +41,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
41
41
|
this.pipeline = pipeline;
|
|
42
42
|
|
|
43
43
|
// Stage references — only used internally for orchestration
|
|
44
|
-
this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, adaptiveSampling, edgeFilter, ssrc, autoExposure,
|
|
44
|
+
this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, adaptiveSampling, edgeFilter, ssrc, autoExposure, compositor }
|
|
45
45
|
|
|
46
46
|
this._getExposure = getExposure;
|
|
47
47
|
this._getSaturation = getSaturation;
|
|
@@ -295,9 +295,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
|
-
* Enables/disables auto-exposure with proper exposure stacking management.
|
|
299
298
|
* @param {boolean} enabled
|
|
300
|
-
* @param {number} manualExposure -
|
|
299
|
+
* @param {number} manualExposure - Restored to renderer.toneMappingExposure when disabling.
|
|
301
300
|
*/
|
|
302
301
|
setAutoExposureEnabled( enabled, manualExposure ) {
|
|
303
302
|
|
|
@@ -306,19 +305,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
306
305
|
|
|
307
306
|
s.autoExposure.enabled = enabled;
|
|
308
307
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Neutralize Display manual exposure to avoid stacking
|
|
312
|
-
s.display?.setExposure( 1.0 );
|
|
313
|
-
|
|
314
|
-
} else {
|
|
308
|
+
// AutoExposure overwrites renderer.toneMappingExposure each frame; restore manual on disable.
|
|
309
|
+
if ( ! enabled && this.renderer ) {
|
|
315
310
|
|
|
316
|
-
|
|
317
|
-
if ( s.display && this.renderer ) {
|
|
318
|
-
|
|
319
|
-
this.renderer.toneMappingExposure = 1.0;
|
|
320
|
-
|
|
321
|
-
}
|
|
311
|
+
this.renderer.toneMappingExposure = manualExposure;
|
|
322
312
|
|
|
323
313
|
}
|
|
324
314
|
|
|
@@ -417,10 +407,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
417
407
|
|
|
418
408
|
} else {
|
|
419
409
|
|
|
420
|
-
// Re-render
|
|
421
|
-
if ( this.upscaler?.enabled && this._stages.
|
|
410
|
+
// Re-render compositor stage so WebGPU canvas has valid content
|
|
411
|
+
if ( this.upscaler?.enabled && this._stages.compositor && context ) {
|
|
422
412
|
|
|
423
|
-
this._stages.
|
|
413
|
+
this._stages.compositor.render( context );
|
|
424
414
|
|
|
425
415
|
}
|
|
426
416
|
|
|
@@ -280,6 +280,7 @@ export class EnvironmentManager {
|
|
|
280
280
|
|
|
281
281
|
this._updateCDFStorageBuffers();
|
|
282
282
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
283
|
+
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
283
284
|
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
284
285
|
return;
|
|
285
286
|
|
|
@@ -294,6 +295,7 @@ export class EnvironmentManager {
|
|
|
294
295
|
|
|
295
296
|
this._updateCDFStorageBuffers();
|
|
296
297
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
298
|
+
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
297
299
|
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
298
300
|
return;
|
|
299
301
|
|
|
@@ -313,6 +315,7 @@ export class EnvironmentManager {
|
|
|
313
315
|
|
|
314
316
|
this._updateCDFStorageBuffers();
|
|
315
317
|
this.uniforms.set( 'envTotalSum', this.equirectHdrInfo.totalSum );
|
|
318
|
+
this.uniforms.set( 'envCompensationDelta', this.equirectHdrInfo.compensationDelta );
|
|
316
319
|
this.uniforms.set( 'useEnvMapIS', 1 );
|
|
317
320
|
|
|
318
321
|
const { width, height } = this.equirectHdrInfo;
|
|
@@ -329,6 +332,7 @@ export class EnvironmentManager {
|
|
|
329
332
|
console.error( 'Error building environment CDF:', error );
|
|
330
333
|
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
331
334
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
335
|
+
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
332
336
|
|
|
333
337
|
}
|
|
334
338
|
|
|
@@ -375,6 +379,7 @@ export class EnvironmentManager {
|
|
|
375
379
|
|
|
376
380
|
this._updateCDFStorageBuffers();
|
|
377
381
|
this.uniforms.set( 'envTotalSum', 0.0 );
|
|
382
|
+
this.uniforms.set( 'envCompensationDelta', 0.0 );
|
|
378
383
|
this.uniforms.set( 'useEnvMapIS', 0 );
|
|
379
384
|
|
|
380
385
|
}
|
|
@@ -537,11 +542,23 @@ export class EnvironmentManager {
|
|
|
537
542
|
|
|
538
543
|
this.proceduralSkyRenderer = null;
|
|
539
544
|
this.simpleSkyRenderer = null;
|
|
545
|
+
|
|
546
|
+
this.envCDFStorageAttr?.dispose?.();
|
|
540
547
|
this.envCDFStorageAttr = null;
|
|
541
548
|
this.envCDFStorageNode = null;
|
|
549
|
+
|
|
550
|
+
// Dispose the HDRI environment texture unless it's the shared placeholder
|
|
551
|
+
// (the placeholder is handled separately just below).
|
|
552
|
+
if ( this.environmentTexture && this.environmentTexture !== this._envPlaceholder ) {
|
|
553
|
+
|
|
554
|
+
this.environmentTexture.dispose?.();
|
|
555
|
+
|
|
556
|
+
}
|
|
557
|
+
|
|
542
558
|
this._envPlaceholder?.dispose();
|
|
543
559
|
this._envPlaceholder = null;
|
|
544
560
|
this.environmentTexture = null;
|
|
561
|
+
this._previousHDRI = null;
|
|
545
562
|
|
|
546
563
|
}
|
|
547
564
|
|
|
@@ -437,6 +437,15 @@ export class TransformManager {
|
|
|
437
437
|
this._normalCache = null;
|
|
438
438
|
this._baselineComputed = false;
|
|
439
439
|
|
|
440
|
+
// Drop back-references to the owning app and shared resources so the
|
|
441
|
+
// PathTracerApp graph can be GC'd. Without this, _app pinned the entire
|
|
442
|
+
// engine (verified via heap snapshot retainer chain).
|
|
443
|
+
this._app = null;
|
|
444
|
+
this._orbitControls = null;
|
|
445
|
+
this._camera = null;
|
|
446
|
+
this._controls = null;
|
|
447
|
+
this._gizmoScene = null;
|
|
448
|
+
|
|
440
449
|
}
|
|
441
450
|
|
|
442
451
|
}
|
|
@@ -182,6 +182,7 @@ export class UniformManager {
|
|
|
182
182
|
u( 'environmentMatrix', new Matrix4(), 'mat4' );
|
|
183
183
|
ub( 'useEnvMapIS', DEFAULT_STATE.useImportanceSampledEnvironment );
|
|
184
184
|
u( 'envTotalSum', 0.0, 'float' );
|
|
185
|
+
u( 'envCompensationDelta', 0.0, 'float' );
|
|
185
186
|
u( 'envResolution', new Vector2( 1, 1 ), 'vec2' );
|
|
186
187
|
|
|
187
188
|
// Sun parameters
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
(function(){function e(e,t,n,r){let i=n,a=n+r-1;for(;i<a;){let n=i+a>>1;e[n]<t?i=n+1:a=n}return i-n}function t(t,n,r){let i=new Float32Array(n*r),a=new Float32Array(r),o=0,s=0;for(let e=0;e<r;e++){let r=0;for(let a=0;a<n;a++){let s=e*n+a,c=t[4*s],l=t[4*s+1],u=t[4*s+2],d=.2126*c+.7152*l+.0722*u;r+=d,o+=d,i[s]=r}if(r!==0)for(let t=e*n,a=e*n+n;t<a;t++)i[t]/=r;s+=r,a[e]=s}if(s!==0)for(let e=0,t=a.length;e<t;e++)a[e]/=s;let c=new Float32Array(r);for(let t=0;t<r;t++)c[t]=(e(a,(t+1)/r,0,r)+.5)/r;let l=new Float32Array(n*r);for(let t=0;t<r;t++)for(let r=0;r<n;r++){let a=t*n+r;l[a]=(e(i,(r+1)/n,t*n,n)+.5)/n}return{marginalData:c,conditionalData:l,totalSum:o}}self.onmessage=function(e){let{floatData:n,width:r,height:i}=e.data;try{let e=t(n,r,i);self.postMessage({marginalData:e.marginalData,conditionalData:e.conditionalData,totalSum:e.totalSum,width:r,height:i},[e.marginalData.buffer,e.conditionalData.buffer])}catch(e){self.postMessage({error:e.message})}}})();
|
|
2
|
-
//# sourceMappingURL=CDFWorker-2MoynL4F.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CDFWorker-2MoynL4F.js","names":[],"sources":["../../src/Processor/Workers/CDFWorker.js"],"sourcesContent":["/**\n * Web Worker for computing environment map CDF (Cumulative Distribution Function)\n * for importance sampling. Pure math — no Three.js dependencies.\n *\n * Input: { floatData: Float32Array, width, height }\n * Output: { marginalData: Float32Array, conditionalData: Float32Array, totalSum, width, height }\n */\n\nfunction binarySearchFindClosestIndexOf( array, targetValue, offset, count ) {\n\n\tlet lower = offset;\n\tlet upper = offset + count - 1;\n\n\twhile ( lower < upper ) {\n\n\t\tconst mid = ( lower + upper ) >> 1;\n\n\t\tif ( array[ mid ] < targetValue ) {\n\n\t\t\tlower = mid + 1;\n\n\t\t} else {\n\n\t\t\tupper = mid;\n\n\t\t}\n\n\t}\n\n\treturn lower - offset;\n\n}\n\nfunction buildCDF( floatData, width, height ) {\n\n\tconst cdfConditional = new Float32Array( width * height );\n\tconst cdfMarginal = new Float32Array( height );\n\n\tlet totalSumValue = 0.0;\n\tlet cumulativeWeightMarginal = 0.0;\n\n\t// Build conditional CDFs (per-row distribution)\n\tfor ( let y = 0; y < height; y ++ ) {\n\n\t\tlet cumulativeRowWeight = 0.0;\n\t\tfor ( let x = 0; x < width; x ++ ) {\n\n\t\t\tconst i = y * width + x;\n\t\t\tconst r = floatData[ 4 * i ];\n\t\t\tconst g = floatData[ 4 * i + 1 ];\n\t\t\tconst b = floatData[ 4 * i + 2 ];\n\n\t\t\t// Luminance (Rec. 709)\n\t\t\tconst weight = 0.2126 * r + 0.7152 * g + 0.0722 * b;\n\t\t\tcumulativeRowWeight += weight;\n\t\t\ttotalSumValue += weight;\n\n\t\t\tcdfConditional[ i ] = cumulativeRowWeight;\n\n\t\t}\n\n\t\t// Normalize row CDF to [0, 1]\n\t\tif ( cumulativeRowWeight !== 0 ) {\n\n\t\t\tfor ( let i = y * width, l = y * width + width; i < l; i ++ ) {\n\n\t\t\t\tcdfConditional[ i ] /= cumulativeRowWeight;\n\n\t\t\t}\n\n\t\t}\n\n\t\tcumulativeWeightMarginal += cumulativeRowWeight;\n\t\tcdfMarginal[ y ] = cumulativeWeightMarginal;\n\n\t}\n\n\t// Normalize marginal CDF to [0, 1]\n\tif ( cumulativeWeightMarginal !== 0 ) {\n\n\t\tfor ( let i = 0, l = cdfMarginal.length; i < l; i ++ ) {\n\n\t\t\tcdfMarginal[ i ] /= cumulativeWeightMarginal;\n\n\t\t}\n\n\t}\n\n\t// Invert marginal CDF\n\tconst marginalData = new Float32Array( height );\n\tfor ( let i = 0; i < height; i ++ ) {\n\n\t\tconst dist = ( i + 1 ) / height;\n\t\tconst row = binarySearchFindClosestIndexOf( cdfMarginal, dist, 0, height );\n\t\tmarginalData[ i ] = ( row + 0.5 ) / height;\n\n\t}\n\n\t// Invert conditional CDFs\n\tconst conditionalData = new Float32Array( width * height );\n\tfor ( let y = 0; y < height; y ++ ) {\n\n\t\tfor ( let x = 0; x < width; x ++ ) {\n\n\t\t\tconst i = y * width + x;\n\t\t\tconst dist = ( x + 1 ) / width;\n\t\t\tconst col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );\n\t\t\tconditionalData[ i ] = ( col + 0.5 ) / width;\n\n\t\t}\n\n\t}\n\n\treturn { marginalData, conditionalData, totalSum: totalSumValue };\n\n}\n\nself.onmessage = function ( e ) {\n\n\tconst { floatData, width, height } = e.data;\n\n\ttry {\n\n\t\tconst result = buildCDF( floatData, width, height );\n\n\t\t// Transfer arrays back zero-copy\n\t\tself.postMessage(\n\t\t\t{\n\t\t\t\tmarginalData: result.marginalData,\n\t\t\t\tconditionalData: result.conditionalData,\n\t\t\t\ttotalSum: result.totalSum,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t},\n\t\t\t[ result.marginalData.buffer, result.conditionalData.buffer ]\n\t\t);\n\n\t} catch ( error ) {\n\n\t\tself.postMessage( { error: error.message } );\n\n\t}\n\n};\n"],"mappings":"YAQA,SAAS,EAAgC,EAAO,EAAa,EAAQ,EAAQ,CAE5E,IAAI,EAAQ,EACR,EAAQ,EAAS,EAAQ,EAE7B,KAAQ,EAAQ,GAAQ,CAEvB,IAAM,EAAQ,EAAQ,GAAW,EAE5B,EAAO,GAAQ,EAEnB,EAAQ,EAAM,EAId,EAAQ,EAMV,OAAO,EAAQ,EAIhB,SAAS,EAAU,EAAW,EAAO,EAAS,CAE7C,IAAM,EAAiB,IAAI,aAAc,EAAQ,EAAQ,CACnD,EAAc,IAAI,aAAc,EAAQ,CAE1C,EAAgB,EAChB,EAA2B,EAG/B,IAAM,IAAI,EAAI,EAAG,EAAI,EAAQ,IAAO,CAEnC,IAAI,EAAsB,EAC1B,IAAM,IAAI,EAAI,EAAG,EAAI,EAAO,IAAO,CAElC,IAAM,EAAI,EAAI,EAAQ,EAChB,EAAI,EAAW,EAAI,GACnB,EAAI,EAAW,EAAI,EAAI,GACvB,EAAI,EAAW,EAAI,EAAI,GAGvB,EAAS,MAAS,EAAI,MAAS,EAAI,MAAS,EAClD,GAAuB,EACvB,GAAiB,EAEjB,EAAgB,GAAM,EAKvB,GAAK,IAAwB,EAE5B,IAAM,IAAI,EAAI,EAAI,EAAO,EAAI,EAAI,EAAQ,EAAO,EAAI,EAAG,IAEtD,EAAgB,IAAO,EAMzB,GAA4B,EAC5B,EAAa,GAAM,EAKpB,GAAK,IAA6B,EAEjC,IAAM,IAAI,EAAI,EAAG,EAAI,EAAY,OAAQ,EAAI,EAAG,IAE/C,EAAa,IAAO,EAOtB,IAAM,EAAe,IAAI,aAAc,EAAQ,CAC/C,IAAM,IAAI,EAAI,EAAG,EAAI,EAAQ,IAI5B,EAAc,IADF,EAAgC,GAD7B,EAAI,GAAM,EACsC,EAAG,EAAQ,CAC9C,IAAQ,EAKrC,IAAM,EAAkB,IAAI,aAAc,EAAQ,EAAQ,CAC1D,IAAM,IAAI,EAAI,EAAG,EAAI,EAAQ,IAE5B,IAAM,IAAI,EAAI,EAAG,EAAI,EAAO,IAAO,CAElC,IAAM,EAAI,EAAI,EAAQ,EAGtB,EAAiB,IADL,EAAgC,GAD7B,EAAI,GAAM,EACyC,EAAI,EAAO,EAAO,CACrD,IAAQ,EAMzC,MAAO,CAAE,eAAc,kBAAiB,SAAU,EAAe,CAIlE,KAAK,UAAY,SAAW,EAAI,CAE/B,GAAM,CAAE,YAAW,QAAO,UAAW,EAAE,KAEvC,GAAI,CAEH,IAAM,EAAS,EAAU,EAAW,EAAO,EAAQ,CAGnD,KAAK,YACJ,CACC,aAAc,EAAO,aACrB,gBAAiB,EAAO,gBACxB,SAAU,EAAO,SACjB,QACA,SACA,CACD,CAAE,EAAO,aAAa,OAAQ,EAAO,gBAAgB,OAAQ,CAC7D,OAEQ,EAAQ,CAEjB,KAAK,YAAa,CAAE,MAAO,EAAM,QAAS,CAAE"}
|
package/src/Stages/Display.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { vec4, vec3, uv, uniform, select, dot, mix } from 'three/tsl';
|
|
2
|
-
import { MeshBasicNodeMaterial, QuadMesh, TextureNode } from 'three/webgpu';
|
|
3
|
-
import { NoBlending } from 'three';
|
|
4
|
-
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
5
|
-
import { REC709_LUMINANCE_COEFFICIENTS } from '../TSL/Common.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Display — Terminal pipeline stage for WebGPU.
|
|
9
|
-
*
|
|
10
|
-
* Reads the final colour texture from the pipeline context (using a
|
|
11
|
-
* priority fallback chain), applies exposure, and renders to screen.
|
|
12
|
-
*
|
|
13
|
-
* When new post-processing stages are added between PathTracer and
|
|
14
|
-
* Display, the fallback chain automatically picks up the latest output
|
|
15
|
-
* without any wiring changes.
|
|
16
|
-
*/
|
|
17
|
-
export class Display extends RenderStage {
|
|
18
|
-
|
|
19
|
-
constructor( renderer, options = {} ) {
|
|
20
|
-
|
|
21
|
-
super( 'Display', {
|
|
22
|
-
...options,
|
|
23
|
-
executionMode: StageExecutionMode.ALWAYS
|
|
24
|
-
} );
|
|
25
|
-
|
|
26
|
-
this.renderer = renderer;
|
|
27
|
-
|
|
28
|
-
// Exposure uniform — linear multiplier (consistent with auto-exposure)
|
|
29
|
-
this.exposure = uniform( options.exposure ?? 1.0 );
|
|
30
|
-
|
|
31
|
-
// Pre-tonemapping saturation — compensates for ACES/AgX desaturation (1.0 = neutral)
|
|
32
|
-
this.saturation = uniform( options.saturation ?? 1.0 );
|
|
33
|
-
|
|
34
|
-
// Transparent background toggle
|
|
35
|
-
this._transparentBackground = uniform( 0, 'int' );
|
|
36
|
-
|
|
37
|
-
// Updatable texture node — swap .value each frame, no shader recompile
|
|
38
|
-
this._displayTexNode = new TextureNode();
|
|
39
|
-
|
|
40
|
-
const texSample = this._displayTexNode.sample( uv() );
|
|
41
|
-
|
|
42
|
-
// Build material once (TSL compiles on first render)
|
|
43
|
-
const exposed = texSample.xyz.mul( this.exposure );
|
|
44
|
-
|
|
45
|
-
// Saturation adjustment (before tonemapping): mix between luminance and color
|
|
46
|
-
const luma = dot( exposed, REC709_LUMINANCE_COEFFICIENTS );
|
|
47
|
-
let displayShader = mix( vec3( luma ), exposed, this.saturation );
|
|
48
|
-
|
|
49
|
-
// Alpha: pass through source alpha when transparent, otherwise 1.0
|
|
50
|
-
const outputAlpha = select( this._transparentBackground, texSample.w, 1.0 );
|
|
51
|
-
|
|
52
|
-
this.displayMaterial = new MeshBasicNodeMaterial();
|
|
53
|
-
this.displayMaterial.colorNode = vec4( displayShader, outputAlpha );
|
|
54
|
-
this.displayMaterial.blending = NoBlending;
|
|
55
|
-
this.displayMaterial.toneMapped = true;
|
|
56
|
-
|
|
57
|
-
this.displayQuad = new QuadMesh( this.displayMaterial );
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Resolve the best available output texture from the pipeline context.
|
|
63
|
-
* Later stages in the chain take priority; pathtracer:color is the
|
|
64
|
-
* baseline fallback that is always present.
|
|
65
|
-
*/
|
|
66
|
-
_resolveDisplayTexture( context ) {
|
|
67
|
-
|
|
68
|
-
return context.getTexture( 'bloom:output' )
|
|
69
|
-
|| context.getTexture( 'edgeFiltering:output' )
|
|
70
|
-
|| context.getTexture( 'asvgf:output' )
|
|
71
|
-
|| context.getTexture( 'ssrc:output' )
|
|
72
|
-
|| context.getTexture( 'pathtracer:color' );
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
render( context ) {
|
|
77
|
-
|
|
78
|
-
if ( ! this.enabled ) return;
|
|
79
|
-
|
|
80
|
-
const displayTexture = this._resolveDisplayTexture( context );
|
|
81
|
-
|
|
82
|
-
if ( ! displayTexture ) return;
|
|
83
|
-
|
|
84
|
-
// Swap texture reference (no shader recompilation)
|
|
85
|
-
this._displayTexNode.value = displayTexture;
|
|
86
|
-
|
|
87
|
-
// Render to screen
|
|
88
|
-
this.renderer.setRenderTarget( null );
|
|
89
|
-
this.displayQuad.render( this.renderer );
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
setExposure( value ) {
|
|
94
|
-
|
|
95
|
-
this.exposure.value = value;
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
setSaturation( value ) {
|
|
100
|
-
|
|
101
|
-
this.saturation.value = value;
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
setTransparentBackground( enabled ) {
|
|
106
|
-
|
|
107
|
-
this._transparentBackground.value = enabled ? 1 : 0;
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
dispose() {
|
|
112
|
-
|
|
113
|
-
this._displayTexNode?.dispose();
|
|
114
|
-
this.displayMaterial?.dispose();
|
|
115
|
-
// QuadMesh extends Mesh — no dispose method; material already disposed.
|
|
116
|
-
this.displayQuad = null;
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
}
|