rayzee 9.4.1 → 9.5.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 +226 -25
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
- package/dist/ColorManagement-CheBCPaW.js +1488 -0
- package/dist/EngineDefaults-DH3bI5t-.js +844 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-BnHghprG.js +97 -0
- package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
- package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
- package/dist/PathTracer-Ccl9KOuF.js +29729 -0
- package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
- package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
- package/dist/SolarLimb-ken-JbDK.js +721 -0
- package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
- package/dist/ViewTransforms-D0rTLBsX.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2266 -44125
- package/dist/rayzee.umd.js +485 -473
- package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/EngineDefaults.js +23 -14
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +617 -4016
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +322 -1746
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +4 -95
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +12 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +8 -7
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +12 -3
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +15 -55
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SimpleSky.js +1 -62
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/TextureCreator.js +1 -1
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3902 -0
- package/src/RenderSettings.js +49 -41
- package/src/Stages/ASVGF.js +5 -7
- package/src/Stages/AutoExposure.js +3 -12
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/NRD.js +9 -2
- package/src/Stages/NormalDepth.js +9 -6
- package/src/Stages/PathTracer.js +254 -38
- package/src/Stages/PathTracerStage.js +11 -16
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +2 -4
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +62 -24
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +19 -14
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +64 -17
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +90 -38
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +4 -0
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +881 -103
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +65 -60
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +35 -0
- package/src/index.js +3 -1
- package/src/integrators/BidirectionalIntegrator.js +832 -0
- package/src/managers/EnvironmentManager.js +94 -91
- package/src/managers/MaterialDataManager.js +42 -0
- package/src/managers/UniformManager.js +0 -1
- package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -311,8 +311,7 @@ function transmittanceSpectrum( coefficients, depth ) {
|
|
|
311
311
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
|
|
315
|
-
export const LIMB_DARKENING_EXPONENT = [ 610, 550, 465 ].map( l => - 0.023 + 0.292 / ( l / 1000 ) );
|
|
314
|
+
export { LIMB_DARKENING_EXPONENT } from './SolarLimb.js';
|
|
316
315
|
|
|
317
316
|
/** Solid angle of a cone of the given half-angle, without the cancellation `2π(1 − cos)` suffers. */
|
|
318
317
|
export function coneSolidAngle( halfAngle ) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { TreeletOptimizer } from './TreeletOptimizer.js';
|
|
2
1
|
import { ReinsertionOptimizer } from './ReinsertionOptimizer.js';
|
|
3
2
|
// Logger is worker-safe (globalThis only, storage access guarded), unlike Constants.js below.
|
|
4
3
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
@@ -104,23 +103,11 @@ export class BVHBuilder {
|
|
|
104
103
|
totalSplitAttempts: 0,
|
|
105
104
|
mortonSortTime: 0,
|
|
106
105
|
totalBuildTime: 0,
|
|
107
|
-
treeletOptimizationTime: 0,
|
|
108
|
-
treeletsProcessed: 0,
|
|
109
|
-
treeletsImproved: 0,
|
|
110
|
-
averageSAHImprovement: 0,
|
|
111
106
|
reinsertionOptimizationTime: 0,
|
|
112
107
|
reinsertionsApplied: 0,
|
|
113
108
|
reinsertionIterations: 0
|
|
114
109
|
};
|
|
115
110
|
|
|
116
|
-
// Off: repairs LBVH trees, so on this binned-SAH build it buys 0.005% tree SAH for 72%
|
|
117
|
-
// of build time.
|
|
118
|
-
this.enableTreeletOptimization = false;
|
|
119
|
-
this.treeletSize = 5;
|
|
120
|
-
this.treeletOptimizationPasses = 1;
|
|
121
|
-
this.treeletMinImprovement = 0.02;
|
|
122
|
-
this.treeletComplexityThreshold = 50000;
|
|
123
|
-
|
|
124
111
|
// Reinsertion optimization configuration
|
|
125
112
|
this.enableReinsertionOptimization = true;
|
|
126
113
|
this.reinsertionBatchSizeRatio = 0.02;
|
|
@@ -201,22 +188,6 @@ export class BVHBuilder {
|
|
|
201
188
|
|
|
202
189
|
}
|
|
203
190
|
|
|
204
|
-
setTreeletConfig( config ) {
|
|
205
|
-
|
|
206
|
-
if ( config.enabled !== undefined ) this.enableTreeletOptimization = config.enabled;
|
|
207
|
-
if ( config.size !== undefined ) this.treeletSize = Math.max( 3, Math.min( 12, config.size ) );
|
|
208
|
-
if ( config.passes !== undefined ) this.treeletOptimizationPasses = Math.max( 1, Math.min( 3, config.passes ) );
|
|
209
|
-
if ( config.minImprovement !== undefined ) this.treeletMinImprovement = Math.max( 0.001, config.minImprovement );
|
|
210
|
-
if ( config.complexityThreshold !== undefined ) this.treeletComplexityThreshold = config.complexityThreshold;
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
disableTreeletOptimization() {
|
|
215
|
-
|
|
216
|
-
this.enableTreeletOptimization = false;
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
191
|
setReinsertionConfig( config ) {
|
|
221
192
|
|
|
222
193
|
if ( config.enabled !== undefined ) this.enableReinsertionOptimization = config.enabled;
|
|
@@ -442,7 +413,7 @@ export class BVHBuilder {
|
|
|
442
413
|
|
|
443
414
|
worker.onmessage = ( e ) => {
|
|
444
415
|
|
|
445
|
-
const { bvhData, triangles: transferredTriangles, originalToBvh, error, progress,
|
|
416
|
+
const { bvhData, triangles: transferredTriangles, originalToBvh, error, progress, splitStats } = e.data;
|
|
446
417
|
|
|
447
418
|
if ( error ) {
|
|
448
419
|
|
|
@@ -459,9 +430,9 @@ export class BVHBuilder {
|
|
|
459
430
|
|
|
460
431
|
}
|
|
461
432
|
|
|
462
|
-
if (
|
|
433
|
+
if ( splitStats ) {
|
|
463
434
|
|
|
464
|
-
this.splitStats =
|
|
435
|
+
this.splitStats = splitStats;
|
|
465
436
|
|
|
466
437
|
}
|
|
467
438
|
|
|
@@ -493,12 +464,6 @@ export class BVHBuilder {
|
|
|
493
464
|
depth,
|
|
494
465
|
reportProgress: !! progressCallback,
|
|
495
466
|
sharedReorderBuffer,
|
|
496
|
-
treeletOptimization: {
|
|
497
|
-
enabled: this.enableTreeletOptimization,
|
|
498
|
-
size: this.treeletSize,
|
|
499
|
-
passes: this.treeletOptimizationPasses,
|
|
500
|
-
minImprovement: this.treeletMinImprovement
|
|
501
|
-
},
|
|
502
467
|
reinsertionOptimization: {
|
|
503
468
|
enabled: this.enableReinsertionOptimization,
|
|
504
469
|
batchSizeRatio: this.reinsertionBatchSizeRatio,
|
|
@@ -571,10 +536,6 @@ export class BVHBuilder {
|
|
|
571
536
|
totalSplitAttempts: 0,
|
|
572
537
|
mortonSortTime: 0,
|
|
573
538
|
totalBuildTime: 0,
|
|
574
|
-
treeletOptimizationTime: 0,
|
|
575
|
-
treeletsProcessed: 0,
|
|
576
|
-
treeletsImproved: 0,
|
|
577
|
-
averageSAHImprovement: 0,
|
|
578
539
|
reinsertionOptimizationTime: 0,
|
|
579
540
|
reinsertionsApplied: 0,
|
|
580
541
|
reinsertionIterations: 0,
|
|
@@ -608,58 +569,7 @@ export class BVHBuilder {
|
|
|
608
569
|
const root = this.buildNodeRecursive( 0, n, depth, progressCallback );
|
|
609
570
|
this.splitStats.sahBuildTime = performance.now() - sahStart;
|
|
610
571
|
|
|
611
|
-
// Phase 4:
|
|
612
|
-
if ( this.enableTreeletOptimization && this.totalTriangles > 1000 ) {
|
|
613
|
-
|
|
614
|
-
const isLargeScene = this.totalTriangles > this.treeletComplexityThreshold;
|
|
615
|
-
const adaptiveTreeletSize = isLargeScene ? 3 : this.treeletSize;
|
|
616
|
-
|
|
617
|
-
const optimizer = new TreeletOptimizer( this.traversalCost, this.intersectionCost );
|
|
618
|
-
optimizer.setTreeletSize( adaptiveTreeletSize );
|
|
619
|
-
optimizer.setMinImprovement( this.treeletMinImprovement );
|
|
620
|
-
|
|
621
|
-
const optimizationStartTime = performance.now();
|
|
622
|
-
|
|
623
|
-
for ( let pass = 0; pass < this.treeletOptimizationPasses; pass ++ ) {
|
|
624
|
-
|
|
625
|
-
const passCallback = progressCallback ? ( status ) => {
|
|
626
|
-
|
|
627
|
-
progressCallback( `Treelet optimization pass ${pass + 1}/${this.treeletOptimizationPasses}: ${status}` );
|
|
628
|
-
|
|
629
|
-
} : null;
|
|
630
|
-
|
|
631
|
-
try {
|
|
632
|
-
|
|
633
|
-
optimizer.optimizeBVH( root, passCallback );
|
|
634
|
-
|
|
635
|
-
} catch ( error ) {
|
|
636
|
-
|
|
637
|
-
log.error( `treelet optimizer failed in pass ${pass + 1}:`, error );
|
|
638
|
-
break;
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// optimizeBVH resets stats internally, so afterStats reflects this pass only
|
|
643
|
-
const afterStats = optimizer.getStatistics();
|
|
644
|
-
const passTime = performance.now() - optimizationStartTime;
|
|
645
|
-
if ( ( afterStats.treeletsImproved === 0 && pass > 0 ) || passTime > 15000 ) {
|
|
646
|
-
|
|
647
|
-
break;
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const treeletTime = performance.now() - optimizationStartTime;
|
|
654
|
-
this.splitStats.treeletOptimizationTime = treeletTime;
|
|
655
|
-
const treeletStats = optimizer.getStatistics();
|
|
656
|
-
this.splitStats.treeletsProcessed = treeletStats.treeletsProcessed;
|
|
657
|
-
this.splitStats.treeletsImproved = treeletStats.treeletsImproved;
|
|
658
|
-
this.splitStats.averageSAHImprovement = treeletStats.averageSAHImprovement;
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Phase 4b: Reinsertion optimization (Meister & Bittner)
|
|
572
|
+
// Phase 4: Reinsertion optimization (Meister & Bittner)
|
|
663
573
|
if ( this.enableReinsertionOptimization && this.totalTriangles > 1000 ) {
|
|
664
574
|
|
|
665
575
|
const reinsertionOptimizer = new ReinsertionOptimizer( this.traversalCost, this.intersectionCost );
|
|
@@ -728,7 +638,6 @@ export class BVHBuilder {
|
|
|
728
638
|
log.debug(
|
|
729
639
|
`${fmt.n( n )} tris → ${fmt.n( this.totalNodes )} nodes in ${fmt.ms( total )}` +
|
|
730
640
|
` | SAH ${s.sahSplits} objMed ${s.objectMedianSplits} spatMed ${s.spatialMedianSplits} failed ${s.failedSplits}` +
|
|
731
|
-
( s.treeletsProcessed ? ` | treelets ${s.treeletsImproved}/${s.treeletsProcessed} improved` : '' ) +
|
|
732
641
|
( s.reinsertionsApplied ? ` | reinsertions ${s.reinsertionsApplied}` : '' )
|
|
733
642
|
);
|
|
734
643
|
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An environment's sampling table: piecewise constant over cells of at most EXACT_TABLE_MAX_WIDTH × half that,
|
|
3
|
+
* so a direction's density is its cell's share, read back from the same cumulative sums it is drawn from.
|
|
4
|
+
* MIS-compensated (Karlík et al. 2019): each texel weighs what it has above the mean, and BSDF-sampled rays
|
|
5
|
+
* cover the rest. Drawn by inverting the sums, which keeps the samples' stratification (an alias table does not:
|
|
6
|
+
* it doubled a furnace's noise). Two guides per entry (Chen & Hsu's cutpoints, GUIDES_PER_ENTRY) land most
|
|
7
|
+
* draws on their entry at once. Pure math: CDFWorker runs it too.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const EXACT_TABLE_MAX_WIDTH = 1024;
|
|
11
|
+
|
|
12
|
+
// Every cell keeps this share of the mean, so light tracing or NEE alone covers the whole sphere.
|
|
13
|
+
const FLOOR = 1e-4;
|
|
14
|
+
|
|
15
|
+
/** Guides per running-sum entry: step g of n · GUIDES_PER_ENTRY points at the first entry above it. */
|
|
16
|
+
export const GUIDES_PER_ENTRY = 2;
|
|
17
|
+
|
|
18
|
+
/** Cells the table has for a width × height environment; TSL/Environment.js derives the same. */
|
|
19
|
+
export function exactTableSize( width, height ) {
|
|
20
|
+
|
|
21
|
+
const k = Math.max( 1, Math.ceil( width / EXACT_TABLE_MAX_WIDTH ) );
|
|
22
|
+
return { k, width: Math.ceil( width / k ), height: Math.ceil( height / k ) };
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// For each of m equal steps of an n-entry running sum, the first entry above the step's start; n − 1 past the end.
|
|
27
|
+
function cutpoints( cdf, offset, n, out, outOffset, m ) {
|
|
28
|
+
|
|
29
|
+
let x = 0;
|
|
30
|
+
for ( let g = 0; g < m; g ++ ) {
|
|
31
|
+
|
|
32
|
+
while ( x < n - 1 && cdf[ offset + x ] <= g / m ) x ++;
|
|
33
|
+
out[ outOffset + g ] = x;
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {Float32Array} floatData - RGBA, row 0 the nadir
|
|
41
|
+
* @param {{ filtered?: boolean }} [options] - filtered false: each texel weighs as itself (the physical sky's GPU twin)
|
|
42
|
+
* @returns {{ exactWidth: number, exactHeight: number, exactConditional: Float32Array, exactMarginal: Float32Array,
|
|
43
|
+
* exactRowGuide: Float32Array, exactMarginalGuide: Float32Array, radianceIntegral: number }}
|
|
44
|
+
* each row's running sum over its cells and the rows' over all, both ending at 1, and their guides (GUIDES_PER_ENTRY
|
|
45
|
+
* an entry); ∫ luminance dω.
|
|
46
|
+
*/
|
|
47
|
+
export function buildExactEnvironmentTable( floatData, width, height, { filtered = true } = {} ) {
|
|
48
|
+
|
|
49
|
+
const { k, width: w, height: h } = exactTableSize( width, height );
|
|
50
|
+
const lum = new Float32Array( width * height );
|
|
51
|
+
let raw = 0;
|
|
52
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
53
|
+
|
|
54
|
+
const sinTheta = Math.sin( Math.PI * ( y + 0.5 ) / height );
|
|
55
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
56
|
+
|
|
57
|
+
const i = 4 * ( y * width + x );
|
|
58
|
+
const l = 0.2126 * floatData[ i ] + 0.7152 * floatData[ i + 1 ] + 0.0722 * floatData[ i + 2 ];
|
|
59
|
+
lum[ y * width + x ] = Number.isFinite( l ) && l > 0 ? l : 0;
|
|
60
|
+
raw += lum[ y * width + x ] * sinTheta;
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Each texel weighs as the bilinear filter's mean over it — 1/8, 6/8, 1/8 of its neighbours along each axis — so
|
|
67
|
+
// a cell has weight wherever the filtered map has light, and a sharp texel's neighbours only their share of it.
|
|
68
|
+
const TAP = [ 1 / 8, 6 / 8, 1 / 8 ];
|
|
69
|
+
const eachTexel = ( visit ) => {
|
|
70
|
+
|
|
71
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
72
|
+
|
|
73
|
+
const sinTheta = Math.sin( Math.PI * ( y + 0.5 ) / height );
|
|
74
|
+
const row = Math.floor( y / k ) * w;
|
|
75
|
+
const ys = [ Math.max( y - 1, 0 ), y, Math.min( y + 1, height - 1 ) ];
|
|
76
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
77
|
+
|
|
78
|
+
let value = lum[ y * width + x ];
|
|
79
|
+
if ( filtered ) {
|
|
80
|
+
|
|
81
|
+
const xs = [ ( x + width - 1 ) % width, x, ( x + 1 ) % width ];
|
|
82
|
+
value = 0;
|
|
83
|
+
for ( let j = 0; j < 3; j ++ ) for ( let i = 0; i < 3; i ++ ) value += TAP[ j ] * TAP[ i ] * lum[ ys[ j ] * width + xs[ i ] ];
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
visit( row + Math.floor( x / k ), value * sinTheta );
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
let filteredTotal = 0;
|
|
96
|
+
eachTexel( ( cell, weight ) => void ( filteredTotal += weight ) );
|
|
97
|
+
const mean = filteredTotal / ( width * height );
|
|
98
|
+
const cells = new Float64Array( w * h );
|
|
99
|
+
let compensated = 0;
|
|
100
|
+
eachTexel( ( cell, weight ) => {
|
|
101
|
+
|
|
102
|
+
cells[ cell ] += Math.max( weight - mean, 0 );
|
|
103
|
+
compensated += Math.max( weight - mean, 0 );
|
|
104
|
+
|
|
105
|
+
} );
|
|
106
|
+
// A flat map has nothing above its mean: then the raw weights.
|
|
107
|
+
if ( ! ( compensated > 0 ) ) {
|
|
108
|
+
|
|
109
|
+
cells.fill( 0 );
|
|
110
|
+
eachTexel( ( cell, weight ) => void ( cells[ cell ] += weight ) );
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const floor = filteredTotal > 0 ? FLOOR * filteredTotal / ( w * h ) : 0;
|
|
115
|
+
const exactConditional = new Float32Array( w * h );
|
|
116
|
+
const exactMarginal = new Float32Array( h );
|
|
117
|
+
const rowSums = new Float64Array( h );
|
|
118
|
+
let total = 0;
|
|
119
|
+
for ( let y = 0; y < h; y ++ ) {
|
|
120
|
+
|
|
121
|
+
let sum = 0;
|
|
122
|
+
for ( let x = 0; x < w; x ++ ) sum += cells[ y * w + x ] + floor;
|
|
123
|
+
let cumulative = 0;
|
|
124
|
+
for ( let x = 0; x < w; x ++ ) {
|
|
125
|
+
|
|
126
|
+
cumulative += cells[ y * w + x ] + floor;
|
|
127
|
+
exactConditional[ y * w + x ] = sum > 0 ? ( x === w - 1 ? 1 : cumulative / sum ) : 0;
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
rowSums[ y ] = sum;
|
|
132
|
+
total += sum;
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let cumulative = 0;
|
|
137
|
+
for ( let y = 0; y < h; y ++ ) {
|
|
138
|
+
|
|
139
|
+
cumulative += rowSums[ y ];
|
|
140
|
+
exactMarginal[ y ] = total > 0 ? ( y === h - 1 ? 1 : cumulative / total ) : 0;
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const G = GUIDES_PER_ENTRY;
|
|
145
|
+
const exactRowGuide = new Float32Array( G * w * h );
|
|
146
|
+
const exactMarginalGuide = new Float32Array( G * h );
|
|
147
|
+
for ( let y = 0; y < h; y ++ ) cutpoints( exactConditional, y * w, w, exactRowGuide, G * y * w, G * w );
|
|
148
|
+
cutpoints( exactMarginal, 0, h, exactMarginalGuide, 0, G * h );
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
exactWidth: w, exactHeight: h, exactConditional, exactMarginal, exactRowGuide, exactMarginalGuide,
|
|
152
|
+
radianceIntegral: 2 * Math.PI * Math.PI * raw / ( width * height ),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The table as the CDF texture holds it, ( w + 1 ) × h RGBA floats: texel ( x, y ) holds row y's entry x — its running
|
|
159
|
+
* sum, the one below it (0 at the first), and the guides of steps 2x and 2x + 1 — and texel ( w, y ) the rows' entry y
|
|
160
|
+
* likewise. TSL/Environment.js reads this layout.
|
|
161
|
+
*/
|
|
162
|
+
export function packExactTable( { exactWidth: w, exactHeight: h, exactConditional, exactMarginal, exactRowGuide, exactMarginalGuide } ) {
|
|
163
|
+
|
|
164
|
+
const stride = w + 1;
|
|
165
|
+
const data = new Float32Array( 4 * stride * h );
|
|
166
|
+
for ( let y = 0; y < h; y ++ ) {
|
|
167
|
+
|
|
168
|
+
let o = 4 * y * stride;
|
|
169
|
+
for ( let x = 0, i = y * w; x < w; x ++, i ++, o += 4 ) {
|
|
170
|
+
|
|
171
|
+
data[ o ] = exactConditional[ i ];
|
|
172
|
+
data[ o + 1 ] = x > 0 ? exactConditional[ i - 1 ] : 0;
|
|
173
|
+
data[ o + 2 ] = exactRowGuide[ 2 * i ];
|
|
174
|
+
data[ o + 3 ] = exactRowGuide[ 2 * i + 1 ];
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
data[ o ] = exactMarginal[ y ];
|
|
179
|
+
data[ o + 1 ] = y > 0 ? exactMarginal[ y - 1 ] : 0;
|
|
180
|
+
data[ o + 2 ] = exactMarginalGuide[ 2 * y ];
|
|
181
|
+
data[ o + 3 ] = exactMarginalGuide[ 2 * y + 1 ];
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { data, width: stride, height: h };
|
|
186
|
+
|
|
187
|
+
}
|
|
@@ -1,44 +1,7 @@
|
|
|
1
1
|
import { DataUtils, HalfFloatType, FloatType, SRGBColorSpace } from 'three';
|
|
2
2
|
import CDFWorker from './Workers/CDFWorker.js?worker&inline';
|
|
3
3
|
import { createWorker } from '../Platform.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Binary search to find the closest index
|
|
7
|
-
*/
|
|
8
|
-
function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
|
|
9
|
-
|
|
10
|
-
let lower = offset;
|
|
11
|
-
let upper = offset + count - 1;
|
|
12
|
-
|
|
13
|
-
while ( lower < upper ) {
|
|
14
|
-
|
|
15
|
-
const mid = ( lower + upper ) >> 1;
|
|
16
|
-
|
|
17
|
-
if ( array[ mid ] < targetValue ) {
|
|
18
|
-
|
|
19
|
-
lower = mid + 1;
|
|
20
|
-
|
|
21
|
-
} else {
|
|
22
|
-
|
|
23
|
-
upper = mid;
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return lower - offset;
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Calculate luminance from RGB values
|
|
35
|
-
*/
|
|
36
|
-
function colorToLuminance( r, g, b ) {
|
|
37
|
-
|
|
38
|
-
// https://en.wikipedia.org/wiki/Relative_luminance
|
|
39
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
40
|
-
|
|
41
|
-
}
|
|
4
|
+
import { buildExactEnvironmentTable } from './EnvironmentExactTable.js';
|
|
42
5
|
|
|
43
6
|
/**
|
|
44
7
|
* sRGB to linear conversion (IEC 61966-2-1 transfer function)
|
|
@@ -154,27 +117,24 @@ export function extractFloatData( envMap ) {
|
|
|
154
117
|
}
|
|
155
118
|
|
|
156
119
|
/**
|
|
157
|
-
* EquirectHDRInfo -
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* Outputs Float32Arrays consumed directly by StorageInstancedBufferAttribute
|
|
161
|
-
* on the GPU — no intermediate DataTexture or HalfFloat conversion needed.
|
|
162
|
-
*
|
|
163
|
-
* Supports two modes:
|
|
164
|
-
* - `updateFrom(hdr)`: synchronous, runs on main thread
|
|
165
|
-
* - `updateFromAsync(hdr)`: offloads CDF math to a Web Worker
|
|
120
|
+
* EquirectHDRInfo - an equirectangular environment's sampling table (EnvironmentExactTable.js), built on the
|
|
121
|
+
* main thread (`updateFrom`) or in CDFWorker (`updateFromAsync`). `totalSum` is the map's ∫ luminance dω, 0
|
|
122
|
+
* when there is nothing to sample.
|
|
166
123
|
*/
|
|
167
124
|
export class EquirectHDRInfo {
|
|
168
125
|
|
|
169
126
|
constructor() {
|
|
170
127
|
|
|
171
|
-
// Placeholder data matching the default storage buffer sizes in PathTracer
|
|
172
|
-
this.marginalData = new Float32Array( [ 0, 1 ] );
|
|
173
|
-
this.conditionalData = new Float32Array( [ 0, 0, 1, 1 ] );
|
|
174
128
|
this.totalSum = 0;
|
|
175
|
-
this.compensationDelta = 0;
|
|
176
129
|
this.width = 0;
|
|
177
130
|
this.height = 0;
|
|
131
|
+
this.exactConditional = null;
|
|
132
|
+
this.exactMarginal = null;
|
|
133
|
+
this.exactRowGuide = null;
|
|
134
|
+
this.exactMarginalGuide = null;
|
|
135
|
+
this.exactWidth = 0;
|
|
136
|
+
this.exactHeight = 0;
|
|
137
|
+
this.radianceIntegral = 0;
|
|
178
138
|
|
|
179
139
|
this._worker = null;
|
|
180
140
|
|
|
@@ -182,8 +142,10 @@ export class EquirectHDRInfo {
|
|
|
182
142
|
|
|
183
143
|
dispose() {
|
|
184
144
|
|
|
185
|
-
this.
|
|
186
|
-
this.
|
|
145
|
+
this.exactConditional = null;
|
|
146
|
+
this.exactMarginal = null;
|
|
147
|
+
this.exactRowGuide = null;
|
|
148
|
+
this.exactMarginalGuide = null;
|
|
187
149
|
|
|
188
150
|
if ( this._worker ) {
|
|
189
151
|
|
|
@@ -194,28 +156,28 @@ export class EquirectHDRInfo {
|
|
|
194
156
|
|
|
195
157
|
}
|
|
196
158
|
|
|
159
|
+
_adopt( table, width, height ) {
|
|
160
|
+
|
|
161
|
+
Object.assign( this, table );
|
|
162
|
+
this.width = width;
|
|
163
|
+
this.height = height;
|
|
164
|
+
this.totalSum = table.radianceIntegral;
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
197
168
|
/**
|
|
198
|
-
* Synchronous
|
|
169
|
+
* Synchronous build on the main thread (fallback path).
|
|
199
170
|
*/
|
|
200
171
|
updateFrom( hdr ) {
|
|
201
172
|
|
|
202
173
|
const { floatData, width, height } = extractFloatData( hdr );
|
|
203
|
-
|
|
204
|
-
const result = EquirectHDRInfo.computeCDF( floatData, width, height );
|
|
205
|
-
|
|
206
|
-
this.marginalData = result.marginalData;
|
|
207
|
-
this.conditionalData = result.conditionalData;
|
|
208
|
-
this.totalSum = result.totalSum;
|
|
209
|
-
this.compensationDelta = result.compensationDelta;
|
|
210
|
-
this.width = width;
|
|
211
|
-
this.height = height;
|
|
174
|
+
this._adopt( buildExactEnvironmentTable( floatData, width, height ), width, height );
|
|
212
175
|
|
|
213
176
|
}
|
|
214
177
|
|
|
215
178
|
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* then the pure-math CDF computation runs off-thread.
|
|
179
|
+
* The build offloaded to a Web Worker. Float extraction (HalfFloat → Float32) runs on the main thread
|
|
180
|
+
* (it needs three's DataUtils); the table's math runs off it.
|
|
219
181
|
* @returns {Promise<void>}
|
|
220
182
|
*/
|
|
221
183
|
async updateFromAsync( hdr ) {
|
|
@@ -253,12 +215,8 @@ export class EquirectHDRInfo {
|
|
|
253
215
|
|
|
254
216
|
} );
|
|
255
217
|
|
|
256
|
-
|
|
257
|
-
this.
|
|
258
|
-
this.totalSum = result.totalSum;
|
|
259
|
-
this.compensationDelta = result.compensationDelta;
|
|
260
|
-
this.width = result.width;
|
|
261
|
-
this.height = result.height;
|
|
218
|
+
const { width: w, height: h, ...table } = result;
|
|
219
|
+
this._adopt( table, w, h );
|
|
262
220
|
|
|
263
221
|
} finally {
|
|
264
222
|
|
|
@@ -273,155 +231,4 @@ export class EquirectHDRInfo {
|
|
|
273
231
|
|
|
274
232
|
}
|
|
275
233
|
|
|
276
|
-
/**
|
|
277
|
-
* Pure-math CDF computation. Used by both the sync path and CDFWorker.
|
|
278
|
-
* Static so it can be called without an instance.
|
|
279
|
-
*/
|
|
280
|
-
static computeCDF( floatData, width, height ) {
|
|
281
|
-
|
|
282
|
-
const numPixels = width * height;
|
|
283
|
-
|
|
284
|
-
// Pass 1: compute per-pixel luminance weighted by sin(theta) and raw total sum.
|
|
285
|
-
// sin(theta) compensates for the equirectangular projection: pixels near the poles
|
|
286
|
-
// cover less solid angle, so weighting by sin(theta) makes the CDF proportional to
|
|
287
|
-
// luminance per solid angle rather than luminance per pixel.
|
|
288
|
-
const pixelWeights = new Float32Array( numPixels );
|
|
289
|
-
let rawTotalSum = 0.0;
|
|
290
|
-
|
|
291
|
-
for ( let y = 0; y < height; y ++ ) {
|
|
292
|
-
|
|
293
|
-
const sinTheta = Math.sin( Math.PI * ( y + 0.5 ) / height );
|
|
294
|
-
|
|
295
|
-
for ( let x = 0; x < width; x ++ ) {
|
|
296
|
-
|
|
297
|
-
const i = y * width + x;
|
|
298
|
-
const w = colorToLuminance(
|
|
299
|
-
floatData[ 4 * i ],
|
|
300
|
-
floatData[ 4 * i + 1 ],
|
|
301
|
-
floatData[ 4 * i + 2 ],
|
|
302
|
-
) * sinTheta;
|
|
303
|
-
pixelWeights[ i ] = w;
|
|
304
|
-
rawTotalSum += w;
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// MIS Compensation (Karlík et al. 2019, Eq. 14)
|
|
311
|
-
// With equal sample allocation (c_I = 0.5): delta = 2*(1 - 0.5)*meanWeight = meanWeight
|
|
312
|
-
// Subtracting mean sharpens the env map PDF, reducing oversampling
|
|
313
|
-
// of dim regions already well-covered by BSDF sampling.
|
|
314
|
-
const meanWeight = rawTotalSum / numPixels;
|
|
315
|
-
let compensatedTotalSum = 0.0;
|
|
316
|
-
|
|
317
|
-
for ( let i = 0; i < numPixels; i ++ ) {
|
|
318
|
-
|
|
319
|
-
pixelWeights[ i ] = Math.max( 0, pixelWeights[ i ] - meanWeight );
|
|
320
|
-
compensatedTotalSum += pixelWeights[ i ];
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Fall back to raw weights if compensation zeroed everything (uniform env map)
|
|
325
|
-
const useCompensation = compensatedTotalSum > 0;
|
|
326
|
-
const totalSumValue = useCompensation ? compensatedTotalSum : rawTotalSum;
|
|
327
|
-
const compensationDelta = useCompensation ? meanWeight : 0;
|
|
328
|
-
|
|
329
|
-
if ( ! useCompensation ) {
|
|
330
|
-
|
|
331
|
-
for ( let y = 0; y < height; y ++ ) {
|
|
332
|
-
|
|
333
|
-
const sinTheta = Math.sin( Math.PI * ( y + 0.5 ) / height );
|
|
334
|
-
|
|
335
|
-
for ( let x = 0; x < width; x ++ ) {
|
|
336
|
-
|
|
337
|
-
const i = y * width + x;
|
|
338
|
-
pixelWeights[ i ] = colorToLuminance(
|
|
339
|
-
floatData[ 4 * i ],
|
|
340
|
-
floatData[ 4 * i + 1 ],
|
|
341
|
-
floatData[ 4 * i + 2 ],
|
|
342
|
-
) * sinTheta;
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Pass 2: build conditional and marginal CDFs from (compensated) weights
|
|
351
|
-
const cdfConditional = new Float32Array( numPixels );
|
|
352
|
-
const cdfMarginal = new Float32Array( height );
|
|
353
|
-
|
|
354
|
-
let cumulativeWeightMarginal = 0.0;
|
|
355
|
-
|
|
356
|
-
for ( let y = 0; y < height; y ++ ) {
|
|
357
|
-
|
|
358
|
-
let cumulativeRowWeight = 0.0;
|
|
359
|
-
for ( let x = 0; x < width; x ++ ) {
|
|
360
|
-
|
|
361
|
-
const i = y * width + x;
|
|
362
|
-
cumulativeRowWeight += pixelWeights[ i ];
|
|
363
|
-
cdfConditional[ i ] = cumulativeRowWeight;
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Normalize row CDF to [0, 1]
|
|
368
|
-
if ( cumulativeRowWeight !== 0 ) {
|
|
369
|
-
|
|
370
|
-
for ( let i = y * width, l = y * width + width; i < l; i ++ ) {
|
|
371
|
-
|
|
372
|
-
cdfConditional[ i ] /= cumulativeRowWeight;
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
cumulativeWeightMarginal += cumulativeRowWeight;
|
|
379
|
-
cdfMarginal[ y ] = cumulativeWeightMarginal;
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Normalize marginal CDF to [0, 1]
|
|
384
|
-
if ( cumulativeWeightMarginal !== 0 ) {
|
|
385
|
-
|
|
386
|
-
for ( let i = 0, l = cdfMarginal.length; i < l; i ++ ) {
|
|
387
|
-
|
|
388
|
-
cdfMarginal[ i ] /= cumulativeWeightMarginal;
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Create inverted CDF arrays (Float32 directly for storage buffers)
|
|
395
|
-
const marginalData = new Float32Array( height );
|
|
396
|
-
const conditionalData = new Float32Array( numPixels );
|
|
397
|
-
|
|
398
|
-
// Invert marginal CDF
|
|
399
|
-
for ( let i = 0; i < height; i ++ ) {
|
|
400
|
-
|
|
401
|
-
const dist = ( i + 1 ) / height;
|
|
402
|
-
const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
|
|
403
|
-
|
|
404
|
-
marginalData[ i ] = ( row + 0.5 ) / height;
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Invert conditional CDFs
|
|
409
|
-
for ( let y = 0; y < height; y ++ ) {
|
|
410
|
-
|
|
411
|
-
for ( let x = 0; x < width; x ++ ) {
|
|
412
|
-
|
|
413
|
-
const i = y * width + x;
|
|
414
|
-
const dist = ( x + 1 ) / width;
|
|
415
|
-
const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
|
|
416
|
-
|
|
417
|
-
conditionalData[ i ] = ( col + 0.5 ) / width;
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return { marginalData, conditionalData, totalSum: totalSumValue, compensationDelta };
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
234
|
}
|