rayzee 9.4.2 → 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 +223 -21
- 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 -44079
- 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 +22 -8
- 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/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 +85 -33
- 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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pbrt textures the engine has no node for, baked on the CPU into an image it samples: the `mix` texture,
|
|
3
|
+
* an image texture's `scale` above 1 or `invert`, and a `mix` material whose amount is a texture.
|
|
4
|
+
*
|
|
5
|
+
* A layer is an image as pbrt evaluates it: linear values, top row first, `channels` 1 (a float texture reads
|
|
6
|
+
* the red channel) or 3, already scaled and inverted, and the uv mapping pbrt applies before the lookup
|
|
7
|
+
* (st = scale · uv + delta, image row from the top = 1 − t).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const MAX_BAKE_SIZE = 4096;
|
|
11
|
+
|
|
12
|
+
export const srgbToLinear = ( v ) => ( v <= 0.04045 ? v / 12.92 : ( ( v + 0.055 ) / 1.055 ) ** 2.4 );
|
|
13
|
+
export const linearToSRGB = ( v ) => ( v <= 0.0031308 ? v * 12.92 : 1.055 * v ** ( 1 / 2.4 ) - 0.055 );
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {{ data: ArrayLike<number>, width: number, height: number, channels?: number, topDown?: boolean }} pixels -
|
|
17
|
+
* 8-bit (0–255) or float values, `channels` per texel (4 if unsaid)
|
|
18
|
+
* @param {object} options
|
|
19
|
+
* @param {number} options.channels - 1 for a float texture, 3 for a spectrum one
|
|
20
|
+
* @param {(v: number) => number} [options.decode] - 8-bit value / 255 → linear; omitted for float data
|
|
21
|
+
* @param {number} [options.scale=1]
|
|
22
|
+
* @param {boolean} [options.invert=false]
|
|
23
|
+
* @param {{ su: number, sv: number, du: number, dv: number }} [options.mapping]
|
|
24
|
+
*/
|
|
25
|
+
export function makeLayer( pixels, { channels, decode = null, scale = 1, invert = false, mapping = null } ) {
|
|
26
|
+
|
|
27
|
+
const { width, height } = pixels;
|
|
28
|
+
const stride = pixels.channels ?? 4;
|
|
29
|
+
const data = new Float32Array( width * height * channels );
|
|
30
|
+
const topDown = pixels.topDown !== false;
|
|
31
|
+
|
|
32
|
+
for ( let y = 0; y < height; y ++ ) {
|
|
33
|
+
|
|
34
|
+
const row = topDown ? y : height - 1 - y;
|
|
35
|
+
for ( let x = 0; x < width; x ++ ) {
|
|
36
|
+
|
|
37
|
+
const src = ( row * width + x ) * stride, dst = ( y * width + x ) * channels;
|
|
38
|
+
for ( let c = 0; c < channels; c ++ ) {
|
|
39
|
+
|
|
40
|
+
const raw = pixels.data[ src + Math.min( c, stride - 1 ) ];
|
|
41
|
+
const v = scale * ( decode ? decode( raw / 255 ) : raw );
|
|
42
|
+
data[ dst + c ] = invert ? Math.max( 0, 1 - v ) : v;
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { width, height, channels, data, su: 1, sv: 1, du: 0, dv: 0, ...mapping };
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The layer's mean value (a float layer's only channel; a spectrum layer's average of three). */
|
|
55
|
+
export function layerMean( layer ) {
|
|
56
|
+
|
|
57
|
+
let sum = 0;
|
|
58
|
+
for ( let i = 0; i < layer.data.length; i ++ ) sum += layer.data[ i ];
|
|
59
|
+
return layer.data.length ? sum / layer.data.length : 0;
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Bilinear, repeating, at pbrt's (s, t).
|
|
64
|
+
function sample( layer, s, t, out ) {
|
|
65
|
+
|
|
66
|
+
const { width: w, height: h, channels: n, data } = layer;
|
|
67
|
+
const x = s * w - 0.5, y = ( 1 - t ) * h - 0.5;
|
|
68
|
+
const x0 = Math.floor( x ), y0 = Math.floor( y );
|
|
69
|
+
const fx = x - x0, fy = y - y0;
|
|
70
|
+
const wrap = ( i, size ) => ( ( i % size ) + size ) % size;
|
|
71
|
+
const xa = wrap( x0, w ), xb = wrap( x0 + 1, w ), ya = wrap( y0, h ), yb = wrap( y0 + 1, h );
|
|
72
|
+
|
|
73
|
+
for ( let c = 0; c < 3; c ++ ) {
|
|
74
|
+
|
|
75
|
+
const k = Math.min( c, n - 1 );
|
|
76
|
+
const top = data[ ( ya * w + xa ) * n + k ] * ( 1 - fx ) + data[ ( ya * w + xb ) * n + k ] * fx;
|
|
77
|
+
const bottom = data[ ( yb * w + xa ) * n + k ] * ( 1 - fx ) + data[ ( yb * w + xb ) * n + k ] * fx;
|
|
78
|
+
out[ c ] = top * ( 1 - fy ) + bottom * fy;
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return out;
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Each term is `{ rgb }` (a constant) or `{ layer, tint? }` (an image, times `tint`); `clamp` clamps it to [0, 1]
|
|
88
|
+
* first, as pbrt clamps a colour texture read as an albedo, before anything mixes it. Evaluates
|
|
89
|
+
* `combine( values )` over the texel grid of the largest image among the terms, in that image's uv mapping;
|
|
90
|
+
* a term with another mapping is sampled through it. The result is sRGB bytes laid out for a DataTexture
|
|
91
|
+
* (bottom row first), clamped to [0, 1] as pbrt clamps an albedo. Null when every term is a constant.
|
|
92
|
+
* @param {Array<{ rgb?: number[], layer?: object, tint?: number[] }>} terms
|
|
93
|
+
* @param {(values: number[][]) => number[]} combine - linear RGB from each term's linear RGB
|
|
94
|
+
* @returns {{ data: Uint8Array, width: number, height: number, mapping: { su: number, sv: number, du: number, dv: number } } | null}
|
|
95
|
+
*/
|
|
96
|
+
export function bake( terms, combine ) {
|
|
97
|
+
|
|
98
|
+
let driver = null;
|
|
99
|
+
for ( const term of terms ) if ( term.layer && ( ! driver || term.layer.width * term.layer.height > driver.width * driver.height ) ) driver = term.layer;
|
|
100
|
+
if ( ! driver ) return null;
|
|
101
|
+
|
|
102
|
+
const width = Math.min( driver.width, MAX_BAKE_SIZE ), height = Math.min( driver.height, MAX_BAKE_SIZE );
|
|
103
|
+
const out = new Uint8Array( width * height * 4 );
|
|
104
|
+
const values = terms.map( () => [ 0, 0, 0 ] );
|
|
105
|
+
|
|
106
|
+
for ( let j = 0; j < height; j ++ ) {
|
|
107
|
+
|
|
108
|
+
const t = ( j + 0.5 ) / height;
|
|
109
|
+
const v = ( t - driver.dv ) / driver.sv;
|
|
110
|
+
|
|
111
|
+
for ( let i = 0; i < width; i ++ ) {
|
|
112
|
+
|
|
113
|
+
const s = ( i + 0.5 ) / width;
|
|
114
|
+
const u = ( s - driver.du ) / driver.su;
|
|
115
|
+
|
|
116
|
+
for ( let k = 0; k < terms.length; k ++ ) {
|
|
117
|
+
|
|
118
|
+
const term = terms[ k ], value = values[ k ];
|
|
119
|
+
if ( term.layer ) {
|
|
120
|
+
|
|
121
|
+
const L = term.layer;
|
|
122
|
+
sample( L, L === driver ? s : L.su * u + L.du, L === driver ? t : L.sv * v + L.dv, value );
|
|
123
|
+
if ( term.tint ) for ( let c = 0; c < 3; c ++ ) value[ c ] *= term.tint[ c ];
|
|
124
|
+
|
|
125
|
+
} else {
|
|
126
|
+
|
|
127
|
+
value[ 0 ] = term.rgb[ 0 ]; value[ 1 ] = term.rgb[ 1 ]; value[ 2 ] = term.rgb[ 2 ];
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if ( term.clamp ) for ( let c = 0; c < 3; c ++ ) value[ c ] = Math.min( 1, Math.max( 0, value[ c ] ) );
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rgb = combine( values );
|
|
136
|
+
const o = ( j * width + i ) * 4;
|
|
137
|
+
for ( let c = 0; c < 3; c ++ ) out[ o + c ] = Math.round( 255 * linearToSRGB( Math.min( 1, Math.max( 0, rgb[ c ] ) ) ) );
|
|
138
|
+
out[ o + 3 ] = 255;
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { data: out, width, height, mapping: { su: driver.su, sv: driver.sv, du: driver.du, dv: driver.dv } };
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** pbrt's mix: ( 1 − amount ) · a + amount · b, amount read from its first channel. */
|
|
149
|
+
export const mixOf = ( [ a, b, amount ] ) => {
|
|
150
|
+
|
|
151
|
+
const t = amount[ 0 ];
|
|
152
|
+
return [ a[ 0 ] + ( b[ 0 ] - a[ 0 ] ) * t, a[ 1 ] + ( b[ 1 ] - a[ 1 ] ) * t, a[ 2 ] + ( b[ 2 ] - a[ 2 ] ) * t ];
|
|
153
|
+
|
|
154
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DataTexture, FloatType, LinearFilter, RGBAFormat } from 'three';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Portable float map: "PF" (RGB) or "Pf" (grey), width and height, then a scale whose sign is the byte
|
|
5
|
+
* order, then rows bottom first — the order three's EXR and HDR loaders fill a DataTexture in.
|
|
6
|
+
* A scale other than ±1 multiplies the pixels, as pbrt reads it.
|
|
7
|
+
* @param {Uint8Array} bytes
|
|
8
|
+
* @returns {{ data: Float32Array, width: number, height: number }} RGBA, row 0 the bottom
|
|
9
|
+
*/
|
|
10
|
+
export function decodePFM( bytes ) {
|
|
11
|
+
|
|
12
|
+
let pos = 0;
|
|
13
|
+
const token = () => {
|
|
14
|
+
|
|
15
|
+
while ( pos < bytes.length && bytes[ pos ] <= 32 ) pos ++;
|
|
16
|
+
const start = pos;
|
|
17
|
+
while ( pos < bytes.length && bytes[ pos ] > 32 ) pos ++;
|
|
18
|
+
return String.fromCharCode( ...bytes.subarray( start, pos ) );
|
|
19
|
+
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const magic = token();
|
|
23
|
+
if ( magic !== 'PF' && magic !== 'Pf' ) throw new Error( 'not a PFM image' );
|
|
24
|
+
const width = parseInt( token(), 10 );
|
|
25
|
+
const height = parseInt( token(), 10 );
|
|
26
|
+
const scale = parseFloat( token() );
|
|
27
|
+
pos ++;
|
|
28
|
+
|
|
29
|
+
const channels = magic === 'PF' ? 3 : 1;
|
|
30
|
+
const count = width * height;
|
|
31
|
+
if ( ! ( width > 0 && height > 0 ) || ! Number.isFinite( scale ) || scale === 0 ) throw new Error( 'bad PFM header' );
|
|
32
|
+
if ( bytes.length - pos < count * channels * 4 ) throw new Error( 'PFM image is truncated' );
|
|
33
|
+
|
|
34
|
+
const view = new DataView( bytes.buffer, bytes.byteOffset + pos, count * channels * 4 );
|
|
35
|
+
const little = scale < 0;
|
|
36
|
+
const gain = Math.abs( scale );
|
|
37
|
+
const data = new Float32Array( count * 4 );
|
|
38
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
39
|
+
|
|
40
|
+
for ( let c = 0; c < 3; c ++ ) data[ i * 4 + c ] = view.getFloat32( ( i * channels + ( channels === 3 ? c : 0 ) ) * 4, little ) * gain;
|
|
41
|
+
data[ i * 4 + 3 ] = 1;
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { data, width, height };
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @param {Uint8Array} bytes */
|
|
50
|
+
export function pfmTexture( bytes ) {
|
|
51
|
+
|
|
52
|
+
const { data, width, height } = decodePFM( bytes );
|
|
53
|
+
const texture = new DataTexture( data, width, height, RGBAFormat, FloatType );
|
|
54
|
+
texture.minFilter = LinearFilter;
|
|
55
|
+
texture.magFilter = LinearFilter;
|
|
56
|
+
texture.needsUpdate = true;
|
|
57
|
+
return texture;
|
|
58
|
+
|
|
59
|
+
}
|
|
@@ -44,11 +44,19 @@ export const HIT = {
|
|
|
44
44
|
NORMAL_MAT: 1, // uvec4(octNormal, matIndex, instanceLeaf + 1, 0)
|
|
45
45
|
// The path's RNG state, here rather than in a buffer of its own: Shade was at the device's
|
|
46
46
|
// 10 storage buffers, and the second triangle buffer needed the slot. Extend never writes it.
|
|
47
|
-
|
|
47
|
+
// Bidirectional mode keeps the path's MIS partial sums in .y/.z.
|
|
48
|
+
RNG: 2, // uvec4(rngState, bits(dVCM), bits(dVC), 0)
|
|
49
|
+
// Bidirectional only: a camera vertex awaiting its connection, or a light path's emission.
|
|
50
|
+
VERTEX: 3, // 4 slots, a vertex record (see writeVertexRecord)
|
|
48
51
|
};
|
|
49
52
|
|
|
53
|
+
// Bidirectional: the light vertex cache follows the path regions — Shade has no binding left.
|
|
54
|
+
export const HIT_STRIDE_BIDIRECTIONAL = HIT_STRIDE + 4;
|
|
55
|
+
export const LIGHT_VERTEX_STRIDE = 4;
|
|
56
|
+
|
|
50
57
|
// SoA region stride, baked into the shader graph at build time; single instance, rebuilt on resize.
|
|
51
58
|
let _cap = 0;
|
|
59
|
+
let _lightVertexBase = 0;
|
|
52
60
|
|
|
53
61
|
const soa = ( id, slot ) => ( slot === 0 ? id : id.add( slot * _cap ) );
|
|
54
62
|
|
|
@@ -68,9 +76,10 @@ export class PackedRayBuffer {
|
|
|
68
76
|
// image resolution and fixed for the device — the image is streamed through the pool in row bands
|
|
69
77
|
// (see docs/internal/specs/wavefront-chunked-pool.md), so the SoA stride _cap never changes with
|
|
70
78
|
// resolution and the wavefront kernels are built once (no resize-triggered WGSL regen).
|
|
71
|
-
constructor( capacity = 0, renderer = null ) {
|
|
79
|
+
constructor( capacity = 0, renderer = null, lightVertices = 0 ) {
|
|
72
80
|
|
|
73
81
|
this.capacity = 0;
|
|
82
|
+
this.lightVertices = 0;
|
|
74
83
|
this._renderer = renderer;
|
|
75
84
|
this._attrs = {};
|
|
76
85
|
|
|
@@ -78,16 +87,23 @@ export class PackedRayBuffer {
|
|
|
78
87
|
this.rayBuffer = null;
|
|
79
88
|
this.hitBuffer = null;
|
|
80
89
|
|
|
81
|
-
if ( capacity > 0 ) this.allocate( capacity );
|
|
90
|
+
if ( capacity > 0 ) this.allocate( capacity, lightVertices );
|
|
82
91
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
94
|
+
/**
|
|
95
|
+
* @param {number} capacity - paths in flight
|
|
96
|
+
* @param {number} [lightVertices] - light vertex cache slots; non-zero selects the bidirectional layout
|
|
97
|
+
*/
|
|
98
|
+
allocate( capacity, lightVertices = 0 ) {
|
|
86
99
|
|
|
87
100
|
this.dispose();
|
|
88
101
|
|
|
89
102
|
this.capacity = capacity;
|
|
103
|
+
this.lightVertices = lightVertices;
|
|
90
104
|
_cap = capacity;
|
|
105
|
+
const hitSlots = lightVertices > 0 ? HIT_STRIDE_BIDIRECTIONAL : HIT_STRIDE;
|
|
106
|
+
_lightVertexBase = capacity * hitSlots;
|
|
91
107
|
|
|
92
108
|
// count=0 so StorageBufferNode.getHash() shares the buffer → RW and RO nodes bind the same GPU data.
|
|
93
109
|
const rayCount = capacity * RAY_STRIDE;
|
|
@@ -98,7 +114,7 @@ export class PackedRayBuffer {
|
|
|
98
114
|
ro: storage( rayAttr, 'vec4' ).toReadOnly(),
|
|
99
115
|
};
|
|
100
116
|
|
|
101
|
-
const hitCount = capacity *
|
|
117
|
+
const hitCount = capacity * hitSlots + lightVertices * LIGHT_VERTEX_STRIDE;
|
|
102
118
|
const hitAttr = gpuOnlyStorageAttribute( hitCount, 4, Uint32Array );
|
|
103
119
|
this._attrs.hit = hitAttr;
|
|
104
120
|
this.hitBuffer = {
|
|
@@ -115,11 +131,11 @@ export class PackedRayBuffer {
|
|
|
115
131
|
|
|
116
132
|
}
|
|
117
133
|
|
|
118
|
-
// Reallocates
|
|
119
|
-
resize( capacity ) {
|
|
134
|
+
// Reallocates if a larger budget or a different light vertex cache is requested; returns true if it did.
|
|
135
|
+
resize( capacity, lightVertices = 0 ) {
|
|
120
136
|
|
|
121
|
-
if ( capacity <= this.capacity && this.capacity > 0 ) return false;
|
|
122
|
-
this.allocate( capacity );
|
|
137
|
+
if ( capacity <= this.capacity && this.capacity > 0 && lightVertices === this.lightVertices ) return false;
|
|
138
|
+
this.allocate( capacity, lightVertices );
|
|
123
139
|
return true;
|
|
124
140
|
|
|
125
141
|
}
|
|
@@ -132,6 +148,7 @@ export class PackedRayBuffer {
|
|
|
132
148
|
this.rayBuffer = null;
|
|
133
149
|
this.hitBuffer = null;
|
|
134
150
|
this.capacity = 0;
|
|
151
|
+
this.lightVertices = 0;
|
|
135
152
|
|
|
136
153
|
}
|
|
137
154
|
|
|
@@ -282,6 +299,96 @@ export const readRngState = ( buf, id ) => buf.element( soa( id, HIT.RNG ) ).x;
|
|
|
282
299
|
|
|
283
300
|
export const writeRngState = ( buf, id, state ) => buf.element( soa( id, HIT.RNG ) ).assign( uvec4( state, 0, 0, 0 ) );
|
|
284
301
|
|
|
302
|
+
/** The subpath's MIS partial sums (Georgiev 2012, "Implementing Vertex Connection and Merging"). */
|
|
303
|
+
export const readMisState = ( buf, id ) => {
|
|
304
|
+
|
|
305
|
+
const v = buf.element( soa( id, HIT.RNG ) );
|
|
306
|
+
return { dVCM: uintBitsToFloat( v.y ), dVC: uintBitsToFloat( v.z ) };
|
|
307
|
+
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const writeRngMis = ( buf, id, state, dVCM, dVC ) =>
|
|
311
|
+
buf.element( soa( id, HIT.RNG ) ).assign( uvec4( state, floatBitsToUint( dVCM ), floatBitsToUint( dVC ), uint( 0 ) ) );
|
|
312
|
+
|
|
313
|
+
// A vertex record is four uvec4s:
|
|
314
|
+
// 0 bits(position.xyz), tag
|
|
315
|
+
// 1 bits(throughput.xyz), oct(V) — V points back along the subpath it was reached by
|
|
316
|
+
// 2 oct(N), oct(facet normal), material index | extra << 24, bits(uv.x) — both normals on V's side
|
|
317
|
+
// 3 bits(uv.y), bits(dVCM), bits(dVC), next — the merge grid's list (MergeKernel), in the cache only
|
|
318
|
+
// `at( k )` addresses quad k, in a path's VERTEX slots or in the cache.
|
|
319
|
+
export const pendingVertex = ( id ) => ( k ) => soa( id, HIT.VERTEX + k );
|
|
320
|
+
export const cachedVertex = ( slot ) => ( k ) => uint( slot ).mul( LIGHT_VERTEX_STRIDE ).add( _lightVertexBase + k );
|
|
321
|
+
|
|
322
|
+
export const writeVertexRecord = ( buf, at, v ) => {
|
|
323
|
+
|
|
324
|
+
buf.element( at( 0 ) ).assign( uvec4( floatBitsToUint( v.position ), v.tag ) );
|
|
325
|
+
buf.element( at( 1 ) ).assign( uvec4( floatBitsToUint( v.throughput ), packNormalOct( v.V ) ) );
|
|
326
|
+
buf.element( at( 2 ) ).assign( uvec4( packNormalOct( v.N ), packNormalOct( v.facetN ),
|
|
327
|
+
uint( v.materialIndex ).bitOr( uint( v.extra ).bitAnd( uint( 0xFF ) ).shiftLeft( uint( 24 ) ) ), floatBitsToUint( v.uv.x ) ) );
|
|
328
|
+
buf.element( at( 3 ) ).assign( uvec4( floatBitsToUint( v.uv.y ), floatBitsToUint( v.dVCM ), floatBitsToUint( v.dVC ), uint( 0 ) ) );
|
|
329
|
+
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export const readVertexTag = ( buf, at ) => buf.element( at( 0 ) ).w;
|
|
333
|
+
export const readVertexPosition = ( buf, at ) => uintBitsToFloat( buf.element( at( 0 ) ).xyz );
|
|
334
|
+
export const readVertexNext = ( buf, at ) => buf.element( at( 3 ) ).w;
|
|
335
|
+
export const writeVertexNext = ( buf, at, next ) => buf.element( at( 3 ) ).w.assign( next );
|
|
336
|
+
|
|
337
|
+
export const readVertexRecord = ( buf, at ) => {
|
|
338
|
+
|
|
339
|
+
const q0 = buf.element( at( 0 ) ).toVar();
|
|
340
|
+
const q1 = buf.element( at( 1 ) ).toVar();
|
|
341
|
+
const q2 = buf.element( at( 2 ) ).toVar();
|
|
342
|
+
const q3 = buf.element( at( 3 ) ).toVar();
|
|
343
|
+
return {
|
|
344
|
+
position: uintBitsToFloat( q0.xyz ),
|
|
345
|
+
tag: q0.w,
|
|
346
|
+
throughput: uintBitsToFloat( q1.xyz ),
|
|
347
|
+
V: unpackTriangleNormal( q1.w ),
|
|
348
|
+
N: unpackTriangleNormal( q2.x ),
|
|
349
|
+
facetN: unpackTriangleNormal( q2.y ),
|
|
350
|
+
materialIndex: q2.z.bitAnd( uint( 0xFFFFFF ) ),
|
|
351
|
+
uv: vec2( uintBitsToFloat( q2.w ), uintBitsToFloat( q3.x ) ),
|
|
352
|
+
dVCM: uintBitsToFloat( q3.y ),
|
|
353
|
+
dVC: uintBitsToFloat( q3.z ),
|
|
354
|
+
extra: q2.z.shiftRight( uint( 24 ) ),
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// A light path's vertex count, in its first slot's tag lane, stamped with the frame's tag.
|
|
360
|
+
export const readLightPathLength = ( buf, at, frameTag ) => {
|
|
361
|
+
|
|
362
|
+
const word = buf.element( at( 0 ) ).w;
|
|
363
|
+
return select( word.shiftRight( uint( 8 ) ).equal( frameTag ), word.bitAnd( uint( 0xFF ) ), uint( 0 ) );
|
|
364
|
+
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export const writeLightPathLength = ( buf, at, frameTag, length ) => {
|
|
368
|
+
|
|
369
|
+
const keep = buf.element( at( 0 ) ).xyz.toVar();
|
|
370
|
+
buf.element( at( 0 ) ).assign( uvec4( keep, frameTag.shiftLeft( uint( 8 ) ).bitOr( uint( length ) ) ) );
|
|
371
|
+
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/** A light path's point of emission, held in its VERTEX slot until the first hit settles its MIS. */
|
|
375
|
+
export const writeLightOrigin = ( buf, id, triangle, instanceLeaf, cosLight, scale ) =>
|
|
376
|
+
buf.element( soa( id, HIT.VERTEX ) ).assign( uvec4(
|
|
377
|
+
uint( triangle ), uint( int( instanceLeaf ).add( int( 1 ) ) ), floatBitsToUint( cosLight ), floatBitsToUint( scale ),
|
|
378
|
+
) );
|
|
379
|
+
|
|
380
|
+
export const readLightOrigin = ( buf, id ) => {
|
|
381
|
+
|
|
382
|
+
const v = buf.element( soa( id, HIT.VERTEX ) ).toVar();
|
|
383
|
+
return {
|
|
384
|
+
triangle: int( v.x ),
|
|
385
|
+
instanceLeaf: int( v.y ).sub( int( 1 ) ),
|
|
386
|
+
cosLight: uintBitsToFloat( v.z ),
|
|
387
|
+
scale: uintBitsToFloat( v.w ),
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
};
|
|
391
|
+
|
|
285
392
|
export const writeHitPacked = ( buf, id, distance, triIndex, baryU, baryV, normal, matIndex, instanceLeaf, facet = uint( 0 ) ) => {
|
|
286
393
|
|
|
287
394
|
buf.element( soa( id, HIT.DIST_TRI_BARY ) )
|
|
@@ -31,7 +31,7 @@ const MAX_PARALLEL_WORKERS = 8;
|
|
|
31
31
|
* @param {Uint32Array} triangles - Triangle records (20 lanes each)
|
|
32
32
|
* @param {number} depth - Maximum BVH depth
|
|
33
33
|
* @param {Function|null} progressCallback - Optional progress callback (0-100)
|
|
34
|
-
* @param {Object} config - Builder config (maxLeafSize, numBins,
|
|
34
|
+
* @param {Object} config - Builder config (maxLeafSize, numBins, reinsertion settings, etc.)
|
|
35
35
|
* @returns {Promise<{bvhData: Float32Array, bvhRoot: true, order?: Uint32Array, triangles?: Uint32Array, reorderedTriangles?: Uint32Array, splitStats: Object}>}
|
|
36
36
|
* the parallel build returns the input `triangles` and the BVH `order` of them (record i of the
|
|
37
37
|
* result is record order[i] of the input); its single-worker fallback returns `reorderedTriangles`.
|
|
@@ -192,7 +192,6 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
|
|
|
192
192
|
depth,
|
|
193
193
|
parallelDepth,
|
|
194
194
|
reportProgress: !! progressCallback,
|
|
195
|
-
treeletOptimization: config.treeletOptimization
|
|
196
195
|
} );
|
|
197
196
|
|
|
198
197
|
} )().catch( ( error ) => {
|
|
@@ -413,7 +412,6 @@ async function handlePhase2(
|
|
|
413
412
|
numBins: config.numBins,
|
|
414
413
|
maxBins: config.maxBins,
|
|
415
414
|
minBins: config.minBins,
|
|
416
|
-
treeletConfig: config.treeletOptimization,
|
|
417
415
|
reinsertionConfig: config.reinsertionOptimization,
|
|
418
416
|
reportProgress: !! progressCallback
|
|
419
417
|
} );
|
|
@@ -442,7 +440,7 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
|
|
|
442
440
|
|
|
443
441
|
worker.onmessage = ( e ) => {
|
|
444
442
|
|
|
445
|
-
const { bvhData, triangles: transferredTriangles, originalToBvh, error, progress,
|
|
443
|
+
const { bvhData, triangles: transferredTriangles, originalToBvh, error, progress, splitStats } = e.data;
|
|
446
444
|
|
|
447
445
|
if ( error ) {
|
|
448
446
|
|
|
@@ -464,7 +462,7 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
|
|
|
464
462
|
? new Uint32Array( sharedReorderBuffer )
|
|
465
463
|
: transferredTriangles;
|
|
466
464
|
|
|
467
|
-
resolve( { bvhData, bvhRoot: true, reorderedTriangles, originalToBvh: originalToBvh || null, splitStats:
|
|
465
|
+
resolve( { bvhData, bvhRoot: true, reorderedTriangles, originalToBvh: originalToBvh || null, splitStats: splitStats || {} } );
|
|
468
466
|
|
|
469
467
|
};
|
|
470
468
|
|
|
@@ -484,7 +482,6 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
|
|
|
484
482
|
depth,
|
|
485
483
|
reportProgress: !! progressCallback,
|
|
486
484
|
sharedReorderBuffer,
|
|
487
|
-
treeletOptimization: config.treeletOptimization,
|
|
488
485
|
reinsertionOptimization: config.reinsertionOptimization,
|
|
489
486
|
foldLeaves: config.foldLeaves
|
|
490
487
|
}, [ transferBuffer ] );
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
RGBAFormat,
|
|
2
|
+
RGBAFormat, FloatType, LinearFilter, RepeatWrapping, ClampToEdgeWrapping,
|
|
3
3
|
EquirectangularReflectionMapping, LinearSRGBColorSpace, DataTexture, Vector3, Vector4,
|
|
4
4
|
} from 'three';
|
|
5
5
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
@@ -50,7 +50,8 @@ export class PhysicalSky {
|
|
|
50
50
|
this._texture.wrapS = RepeatWrapping;
|
|
51
51
|
this._texture.wrapT = ClampToEdgeWrapping;
|
|
52
52
|
this._texture.generateMipmaps = false;
|
|
53
|
-
|
|
53
|
+
// packExactTable's layout (EnvironmentExactTable.js): running sums and guides, RGBA.
|
|
54
|
+
this._cdfTexture = gpuTexture( width + 1, height, RGBAFormat );
|
|
54
55
|
this._rec709Weights = spectrumToRec709( SKY_RADIANCE_SCALE );
|
|
55
56
|
|
|
56
57
|
this._attrs = null;
|
|
@@ -209,7 +210,7 @@ export class PhysicalSky {
|
|
|
209
210
|
* @param {number} p.altitude - metres
|
|
210
211
|
* @param {number} p.sunAngularDiameter - radians
|
|
211
212
|
* @param {number} [p.sunStrength=1]
|
|
212
|
-
* @returns {{ texture: DataTexture, cdfTexture: DataTexture, sun: Object, stats: Promise<{ totalSum: number,
|
|
213
|
+
* @returns {{ texture: DataTexture, cdfTexture: DataTexture, sun: Object, stats: Promise<{ totalSum: number, radianceIntegral: number }> }}
|
|
213
214
|
*/
|
|
214
215
|
bake( renderer, p ) {
|
|
215
216
|
|
|
@@ -258,7 +259,7 @@ export class PhysicalSky {
|
|
|
258
259
|
const stats = renderer.getArrayBufferAsync( this._attrs.cdfStats ).then( buffer => {
|
|
259
260
|
|
|
260
261
|
const s = new Float32Array( buffer );
|
|
261
|
-
return { totalSum: s[ 4 ],
|
|
262
|
+
return { totalSum: s[ 4 ], radianceIntegral: 2 * Math.PI * Math.PI * s[ 0 ] / ( this.width * this.height ) };
|
|
262
263
|
|
|
263
264
|
} );
|
|
264
265
|
|
|
@@ -59,6 +59,32 @@ async function fetchBytes( url ) {
|
|
|
59
59
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function imageBytes( parser, def ) {
|
|
63
|
+
|
|
64
|
+
return def.bufferView !== undefined
|
|
65
|
+
? parser.getDependency( 'bufferView', def.bufferView ).then( ( buffer ) => new Uint8Array( buffer ) )
|
|
66
|
+
: fetchBytes( parser.options.manager.resolveURL( LoaderUtils.resolveURL( def.uri, parser.options.path ) ) );
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// GLTFLoader hands an image its extension decodes (KHR_texture_basisu → KTX2Loader) a loader of its own; three's way to
|
|
71
|
+
// it is an object URL through `self.URL`, so here the loader parses the bytes.
|
|
72
|
+
const ownsLoader = ( parser, loader ) => !! loader && loader !== parser.textureLoader && typeof loader.parse === 'function';
|
|
73
|
+
|
|
74
|
+
function loadWithOwnLoader( parser, sourceIndex, loader ) {
|
|
75
|
+
|
|
76
|
+
if ( parser.sourceCache[ sourceIndex ] !== undefined ) return parser.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() );
|
|
77
|
+
|
|
78
|
+
const promise = imageBytes( parser, parser.json.images[ sourceIndex ] ).then( ( bytes ) => new Promise( ( resolve, reject ) => {
|
|
79
|
+
|
|
80
|
+
loader.parse( bytes.buffer.slice( bytes.byteOffset, bytes.byteOffset + bytes.byteLength ), resolve, reject );
|
|
81
|
+
|
|
82
|
+
} ) );
|
|
83
|
+
parser.sourceCache[ sourceIndex ] = promise;
|
|
84
|
+
return promise;
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
/**
|
|
63
89
|
* An image file as a texture, decoded by the platform: the counterpart of TextureLoader for hosts
|
|
64
90
|
* with no DOM. Rows stay top first with `flipY` on, exactly as TextureLoader leaves an image.
|
|
@@ -71,6 +97,27 @@ export async function loadPlatformImage( url, mimeType = mimeTypeOf( { uri: url
|
|
|
71
97
|
|
|
72
98
|
}
|
|
73
99
|
|
|
100
|
+
/**
|
|
101
|
+
* A GLTFLoader plugin for a runtime with no way to decode an image (Node without `decodeImage`): each image fails with
|
|
102
|
+
* what to configure, where three.js's own path threw `self is not defined` and took the whole load down.
|
|
103
|
+
*/
|
|
104
|
+
export function missingImageDecoderPlugin( parser, onFailure = null ) {
|
|
105
|
+
|
|
106
|
+
parser.loadImageSource = function ( sourceIndex, loader ) {
|
|
107
|
+
|
|
108
|
+
if ( ownsLoader( this, loader ) ) return loadWithOwnLoader( this, sourceIndex, loader );
|
|
109
|
+
|
|
110
|
+
const def = this.json.images[ sourceIndex ];
|
|
111
|
+
const error = new Error( 'no image decoder in this runtime: configurePlatform( nodePlatform( { decodeImage } ) ) — see "Running in Node"' );
|
|
112
|
+
onFailure?.( def.uri ?? `bufferView ${def.bufferView}`, error );
|
|
113
|
+
return Promise.reject( error );
|
|
114
|
+
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return { name: 'RAYZEE_missing_image_decoder' };
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
74
121
|
/**
|
|
75
122
|
* A GLTFLoader plugin for hosts with the platform's `decodeImage`: every image of the model, embedded
|
|
76
123
|
* or not, is decoded by the host and becomes a DataTexture of its pixels, which the texture packer
|
|
@@ -82,19 +129,16 @@ export async function loadPlatformImage( url, mimeType = mimeTypeOf( { uri: url
|
|
|
82
129
|
*/
|
|
83
130
|
export function platformImagesPlugin( parser, onFailure = null ) {
|
|
84
131
|
|
|
85
|
-
parser.loadImageSource = function ( sourceIndex ) {
|
|
132
|
+
parser.loadImageSource = function ( sourceIndex, loader ) {
|
|
86
133
|
|
|
134
|
+
if ( ownsLoader( this, loader ) ) return loadWithOwnLoader( this, sourceIndex, loader );
|
|
87
135
|
if ( this.sourceCache[ sourceIndex ] !== undefined ) return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() );
|
|
88
136
|
|
|
89
137
|
const def = this.json.images[ sourceIndex ];
|
|
90
138
|
const mimeType = mimeTypeOf( def );
|
|
91
139
|
const where = def.uri ?? `bufferView ${def.bufferView}`;
|
|
92
140
|
|
|
93
|
-
const
|
|
94
|
-
? this.getDependency( 'bufferView', def.bufferView ).then( ( buffer ) => new Uint8Array( buffer ) )
|
|
95
|
-
: fetchBytes( this.options.manager.resolveURL( LoaderUtils.resolveURL( def.uri, this.options.path ) ) );
|
|
96
|
-
|
|
97
|
-
const promise = bytes.then( ( data ) => decodeBytes( data, mimeType ) ).then( ( pixels ) => {
|
|
141
|
+
const promise = imageBytes( this, def ).then( ( data ) => decodeBytes( data, mimeType ) ).then( ( pixels ) => {
|
|
98
142
|
|
|
99
143
|
const texture = pixelTexture( pixels, { shared: true } );
|
|
100
144
|
if ( def.extras && typeof def.extras === 'object' ) Object.assign( texture.userData, def.extras );
|
|
@@ -31,9 +31,14 @@ export const COUNTER = {
|
|
|
31
31
|
// Survivors' summed throughput (max channel, fixed-point ENERGY_SCALE). The early exit gates on this,
|
|
32
32
|
// not the count: past Russian roulette a few survivors carry the weight of many.
|
|
33
33
|
ACTIVE_ENERGY: 7,
|
|
34
|
+
// Words read back; the light guide's escape counts (TSL/LightGuide.js) follow, never read back.
|
|
34
35
|
COUNT: 8,
|
|
36
|
+
GUIDE: 8,
|
|
35
37
|
};
|
|
36
38
|
|
|
39
|
+
/** The light guide's counts: 16 direction bins × 64 × 64 disc cells (TSL/LightGuide.js). */
|
|
40
|
+
export const GUIDE_COUNTER_WORDS = 16 * 64 * 64;
|
|
41
|
+
|
|
37
42
|
export const ENERGY_SCALE = 64;
|
|
38
43
|
export const ENERGY_RAY_CLAMP = 65535;
|
|
39
44
|
|
|
@@ -52,6 +57,9 @@ export const RAY_FLAG = {
|
|
|
52
57
|
REDIRECTED: 1 << 18, // bit 18: ray has been redirected (refraction/reflection/SSS/opaque scatter) since the camera, so env it reaches is transported light (sharp), NOT the direct backdrop. NOT set by pure alpha/transparent passthrough (direction unchanged) → env through cutout holes is still the backdrop (blur/intensity/show/color/ground-projection). Set via bitOr only (positive mask) so it never disturbs ACTIVE/bounce bits.
|
|
53
58
|
SUN_NEE: 1 << 19, // bit 19: the last opaque scatter's sun NEE could have drawn this direction (MIS partner for a sun hit on miss)
|
|
54
59
|
UNDER_SURFACE: 1 << 20, // bit 20: the last opaque scatter left below its own facet (shading normal allowed it), so the ray culls back faces to leave that shell
|
|
60
|
+
LIGHT_PATH: 1 << 21, // bit 21: bidirectional light subpath (traced from an emitter, gathers nothing)
|
|
61
|
+
LIGHT_EMITTED: 1 << 22, // bit 22: light subpath not yet past its first interaction (its MIS start is still open)
|
|
62
|
+
EMISSION_ONLY: 1 << 23, // bit 23: camera ray past its last bounce, traced only for the light it hits
|
|
55
63
|
};
|
|
56
64
|
|
|
57
65
|
export class QueueManager {
|
|
@@ -87,13 +95,14 @@ export class QueueManager {
|
|
|
87
95
|
this.capacity = capacity;
|
|
88
96
|
|
|
89
97
|
// explicit attribute (not attributeArray) so it can be referenced for async readback
|
|
90
|
-
this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( COUNTER.
|
|
98
|
+
this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( COUNTER.GUIDE + GUIDE_COUNTER_WORDS ), 1 );
|
|
91
99
|
this.counters = storage( this._countersAttr, 'uint' ).toAtomic();
|
|
92
100
|
|
|
93
|
-
// per-bounce snapshots for the async readback: [0, MAX) ACTIVE_RAY_COUNT, [MAX, 2·MAX) ACTIVE_ENERGY
|
|
101
|
+
// per-bounce snapshots for the async readback: [0, MAX) ACTIVE_RAY_COUNT, [MAX, 2·MAX) ACTIVE_ENERGY,
|
|
102
|
+
// then the same pair for the bidirectional light pass
|
|
94
103
|
this.MAX_BOUNCE_SNAPSHOTS = 32;
|
|
95
104
|
this._bounceCountsAttr = new StorageInstancedBufferAttribute(
|
|
96
|
-
new Uint32Array(
|
|
105
|
+
new Uint32Array( 4 * this.MAX_BOUNCE_SNAPSHOTS ), 1,
|
|
97
106
|
);
|
|
98
107
|
this.bounceCounts = storage( this._bounceCountsAttr, 'uint' );
|
|
99
108
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 3. Sort reinsertions by area improvement, apply non-conflicting ones greedily.
|
|
12
12
|
* 4. Repeat for a configurable number of iterations.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* On this binned-SAH build it buys ~0.04 % tree SAH (3.5M triangles, 2026-08) for ~430 ms of worker time.
|
|
15
15
|
*/
|
|
16
16
|
export class ReinsertionOptimizer {
|
|
17
17
|
|
|
@@ -52,7 +52,7 @@ export class ReinsertionOptimizer {
|
|
|
52
52
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// --- Surface area (half SA
|
|
55
|
+
// --- Surface area (half SA) ---
|
|
56
56
|
|
|
57
57
|
surfaceArea( node ) {
|
|
58
58
|
|