rayzee 7.31.1 → 8.1.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 +164 -0
- package/dist/rayzee.es.js +6728 -4735
- package/dist/rayzee.umd.js +121 -73
- package/package.json +2 -2
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +151 -25
- package/src/Processor/ArchiveReader.js +538 -0
- package/src/Processor/AssetLoader.js +265 -23
- package/src/Processor/BVHBuilder.js +48 -32
- package/src/Processor/BVHRefitter.js +244 -99
- package/src/Processor/ChunkedRecords.js +302 -0
- package/src/Processor/EmissiveTriangleBuilder.js +48 -21
- package/src/Processor/GeometryExtractor.js +580 -93
- package/src/Processor/HostMemory.js +222 -0
- package/src/Processor/InstanceTable.js +548 -97
- package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
- package/src/Processor/PBRT/PBRTCurves.js +249 -0
- package/src/Processor/PBRT/PBRTMath.js +19 -0
- package/src/Processor/PBRT/PBRTParser.js +187 -41
- package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
- package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
- package/src/Processor/PBRT/index.js +257 -50
- package/src/Processor/PackedRayBuffer.js +29 -18
- package/src/Processor/ParallelBVHBuilder.js +9 -8
- package/src/Processor/QueueManager.js +5 -3
- package/src/Processor/SceneProcessor.js +1046 -365
- package/src/Processor/TLASBuilder.js +546 -201
- package/src/Processor/TextureCreator.js +11 -9
- package/src/Processor/VRAMTracker.js +12 -3
- package/src/Processor/Workers/BVHRefitWorker.js +16 -11
- package/src/Processor/Workers/BVHWorker.js +12 -12
- package/src/Processor/Workers/TLASWorker.js +37 -0
- package/src/Processor/utils.js +8 -20
- package/src/Stages/NormalDepth.js +6 -2
- package/src/Stages/PathTracer.js +5 -5
- package/src/Stages/PathTracerStage.js +163 -41
- package/src/TSL/BVHTraversal.js +208 -90
- package/src/TSL/Common.js +135 -2
- package/src/TSL/Displacement.js +21 -11
- package/src/TSL/EmissiveSampling.js +49 -25
- package/src/TSL/ExtendKernel.js +3 -2
- package/src/TSL/LightBVHSampling.js +7 -4
- package/src/TSL/LightsDirect.js +30 -29
- package/src/TSL/ShadeKernel.js +8 -6
- package/src/TSL/SortGlobalKernels.js +3 -4
- package/src/TSL/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +149 -84
- package/src/index.js +3 -0
- package/src/managers/AnimationManager.js +86 -76
- package/src/managers/MaterialDataManager.js +29 -23
- package/src/managers/TransformManager.js +9 -192
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChunkedRecords — a logically contiguous array of fixed-size records, stored as several
|
|
3
|
+
* TypedArrays so it can exceed the ~2 GB cap V8 puts on a single ArrayBuffer.
|
|
4
|
+
*
|
|
5
|
+
* A GPU buffer may be 4 GB here while one JS array tops out near 2,040 MB, so the triangle and
|
|
6
|
+
* BVH buffers hit a CPU-side wall long before the GPU one. Splitting the CPU side lifts that
|
|
7
|
+
* without changing a single shader binding: the chunks are written into one GPU buffer in order.
|
|
8
|
+
*
|
|
9
|
+
* Chunk boundaries always fall on a record boundary, so a record never straddles two arrays.
|
|
10
|
+
* Callers resolve the chunk once per record and then index its lanes directly, which keeps the
|
|
11
|
+
* hot loops as fast as a flat array — one extra shift and mask per record, nothing per lane.
|
|
12
|
+
*
|
|
13
|
+
* One chunk is the overwhelmingly common case and is the fast path throughout: `single` returns
|
|
14
|
+
* the whole array, and callers that only work on flat data can keep using it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Below this a scene stays single-chunk. Far under the 2,040 MB V8 cap on purpose: a big
|
|
19
|
+
* ArrayBuffer needs that much *contiguous* address space, so chunk size sets the usable total.
|
|
20
|
+
* Placed before the allocator gave up, fresh renderer: 256 MB chunks 5,120 MB · 64 MB 7,040 MB ·
|
|
21
|
+
* 32 MB 7,744 MB.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_CHUNK_BYTES = 64 * 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
/** Whether a worker can be handed these arrays without copying them first. */
|
|
26
|
+
export const SHARED_MEMORY_AVAILABLE = typeof SharedArrayBuffer !== 'undefined';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Notified of every chunk allocated, so a build can attribute memory to the phase that spent it.
|
|
30
|
+
* At most one watcher, and it must not throw — this sits on the allocation path.
|
|
31
|
+
* @type {?function(number, boolean): void}
|
|
32
|
+
*/
|
|
33
|
+
let chunkObserver = null;
|
|
34
|
+
|
|
35
|
+
/** Watch chunk allocations. Pass null to stop. @param {?function(number, boolean): void} fn */
|
|
36
|
+
export function setChunkObserver( fn ) {
|
|
37
|
+
|
|
38
|
+
chunkObserver = fn;
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One chunk's storage, shared with workers when the page is cross-origin isolated. */
|
|
43
|
+
function allocChunk( LaneType, lanes, shared ) {
|
|
44
|
+
|
|
45
|
+
const chunk = shared
|
|
46
|
+
? new LaneType( new SharedArrayBuffer( lanes * LaneType.BYTES_PER_ELEMENT ) )
|
|
47
|
+
: new LaneType( lanes );
|
|
48
|
+
|
|
49
|
+
if ( chunkObserver && chunk.byteLength > 0 ) chunkObserver( chunk.byteLength, shared );
|
|
50
|
+
return chunk;
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class ChunkedRecords {
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {number} recordCount - total records
|
|
58
|
+
* @param {number} lanesPerRecord - elements per record (20 for a triangle, 16 for a BVH node)
|
|
59
|
+
* @param {Function} LaneType - typed array constructor for one lane
|
|
60
|
+
* @param {number} [maxBytesPerChunk]
|
|
61
|
+
* @param {boolean} [shared] - back the chunks with SharedArrayBuffer so a worker can read and
|
|
62
|
+
* write them in place. Without it a refit has to copy the whole store into shared memory,
|
|
63
|
+
* which is a second copy of the largest thing in the scene.
|
|
64
|
+
*/
|
|
65
|
+
constructor( recordCount, lanesPerRecord, LaneType, maxBytesPerChunk = DEFAULT_CHUNK_BYTES, shared = false ) {
|
|
66
|
+
|
|
67
|
+
const recordBytes = lanesPerRecord * LaneType.BYTES_PER_ELEMENT;
|
|
68
|
+
const perChunk = Math.max( 1, Math.floor( maxBytesPerChunk / recordBytes ) );
|
|
69
|
+
|
|
70
|
+
this.recordCount = recordCount;
|
|
71
|
+
this.lanesPerRecord = lanesPerRecord;
|
|
72
|
+
this.LaneType = LaneType;
|
|
73
|
+
this.recordsPerChunk = recordCount <= perChunk ? Math.max( recordCount, 1 ) : perChunk;
|
|
74
|
+
this.shared = shared;
|
|
75
|
+
|
|
76
|
+
this.chunks = [];
|
|
77
|
+
let remaining = recordCount;
|
|
78
|
+
while ( remaining > 0 ) {
|
|
79
|
+
|
|
80
|
+
const n = Math.min( remaining, this.recordsPerChunk );
|
|
81
|
+
this.chunks.push( allocChunk( LaneType, n * lanesPerRecord, shared ) );
|
|
82
|
+
remaining -= n;
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ( this.chunks.length === 0 ) this.chunks.push( allocChunk( LaneType, 0, shared ) );
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Adopt already-built chunks (e.g. handed over by the extractor) without copying. */
|
|
91
|
+
static adopt( chunks, recordCount, lanesPerRecord, recordsPerChunk ) {
|
|
92
|
+
|
|
93
|
+
const c = Object.create( ChunkedRecords.prototype );
|
|
94
|
+
c.recordCount = recordCount;
|
|
95
|
+
c.lanesPerRecord = lanesPerRecord;
|
|
96
|
+
c.LaneType = chunks[ 0 ]?.constructor ?? null;
|
|
97
|
+
c.recordsPerChunk = recordsPerChunk;
|
|
98
|
+
c.chunks = chunks;
|
|
99
|
+
c.shared = typeof SharedArrayBuffer !== 'undefined' && chunks[ 0 ]?.buffer instanceof SharedArrayBuffer;
|
|
100
|
+
return c;
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Like the constructor, but a chunk is allocated the first time it is touched, so a fill that
|
|
106
|
+
* releases each source as it writes it never holds the whole source and destination at once.
|
|
107
|
+
*/
|
|
108
|
+
static lazy( recordCount, lanesPerRecord, LaneType, maxBytesPerChunk = DEFAULT_CHUNK_BYTES, shared = false ) {
|
|
109
|
+
|
|
110
|
+
const perChunk = Math.max( 1, Math.floor( maxBytesPerChunk / ( lanesPerRecord * LaneType.BYTES_PER_ELEMENT ) ) );
|
|
111
|
+
const c = Object.create( ChunkedRecords.prototype );
|
|
112
|
+
c.recordCount = recordCount;
|
|
113
|
+
c.lanesPerRecord = lanesPerRecord;
|
|
114
|
+
c.LaneType = LaneType;
|
|
115
|
+
c.recordsPerChunk = recordCount <= perChunk ? Math.max( recordCount, 1 ) : perChunk;
|
|
116
|
+
c.shared = shared;
|
|
117
|
+
c.chunks = new Array( Math.max( 1, Math.ceil( recordCount / c.recordsPerChunk ) ) );
|
|
118
|
+
c._views = [];
|
|
119
|
+
return c;
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Allocate chunk `k`, plus the matching chunk of every view taken over this storage. @private */
|
|
124
|
+
_materialize( k ) {
|
|
125
|
+
|
|
126
|
+
if ( this._owner ) {
|
|
127
|
+
|
|
128
|
+
this._owner._materialize( k );
|
|
129
|
+
return this.chunks[ k ];
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const from = k * this.recordsPerChunk;
|
|
134
|
+
const lanes = Math.min( this.recordsPerChunk, this.recordCount - from ) * this.lanesPerRecord;
|
|
135
|
+
const chunk = this.chunks[ k ] = allocChunk( this.LaneType, lanes, this.shared );
|
|
136
|
+
|
|
137
|
+
for ( const v of this._views ) {
|
|
138
|
+
|
|
139
|
+
v.chunks[ k ] = new v.LaneType( chunk.buffer, chunk.byteOffset, chunk.byteLength / v.LaneType.BYTES_PER_ELEMENT );
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return chunk;
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Allocate whatever a lazy fill never touched, so the result behaves like an eager one. */
|
|
148
|
+
materializeAll() {
|
|
149
|
+
|
|
150
|
+
for ( let k = 0; k < this.chunks.length; k ++ ) if ( ! this.chunks[ k ] ) this._materialize( k );
|
|
151
|
+
return this;
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get chunkCount() {
|
|
156
|
+
|
|
157
|
+
return this.chunks.length;
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The whole thing as one array when it fits in one, else null. */
|
|
162
|
+
get single() {
|
|
163
|
+
|
|
164
|
+
return this.chunks.length === 1 ? this.chunks[ 0 ] : null;
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get byteLength() {
|
|
169
|
+
|
|
170
|
+
let n = 0;
|
|
171
|
+
for ( const c of this.chunks ) if ( c ) n += c.byteLength;
|
|
172
|
+
return n;
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The array holding `record`. */
|
|
177
|
+
chunkFor( record ) {
|
|
178
|
+
|
|
179
|
+
const k = ( record / this.recordsPerChunk ) | 0;
|
|
180
|
+
return this.chunks[ k ] ?? this._materialize( k );
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Lane offset of `record` within its own chunk. */
|
|
185
|
+
baseOf( record ) {
|
|
186
|
+
|
|
187
|
+
return ( record % this.recordsPerChunk ) * this.lanesPerRecord;
|
|
188
|
+
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** True when records [start, start+count) sit inside one chunk. */
|
|
192
|
+
isContiguous( start, count ) {
|
|
193
|
+
|
|
194
|
+
if ( count <= 0 ) return true;
|
|
195
|
+
return ( ( start / this.recordsPerChunk ) | 0 ) === ( ( ( start + count - 1 ) / this.recordsPerChunk ) | 0 );
|
|
196
|
+
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A view over records [start, start+count) when they share a chunk, otherwise a copy.
|
|
201
|
+
* Callers that only read may use it freely; callers that write must use {@link setRecords}.
|
|
202
|
+
*/
|
|
203
|
+
slice( start, count ) {
|
|
204
|
+
|
|
205
|
+
const base = this.baseOf( start );
|
|
206
|
+
const lanes = count * this.lanesPerRecord;
|
|
207
|
+
|
|
208
|
+
if ( this.isContiguous( start, count ) ) return this.chunkFor( start ).subarray( base, base + lanes );
|
|
209
|
+
|
|
210
|
+
const out = new this.LaneType( lanes );
|
|
211
|
+
this.readRecords( start, count, out );
|
|
212
|
+
return out;
|
|
213
|
+
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Copy records [start, start+count) into `out`. */
|
|
217
|
+
readRecords( start, count, out ) {
|
|
218
|
+
|
|
219
|
+
let done = 0;
|
|
220
|
+
while ( done < count ) {
|
|
221
|
+
|
|
222
|
+
const rec = start + done;
|
|
223
|
+
const chunk = this.chunkFor( rec );
|
|
224
|
+
const base = this.baseOf( rec );
|
|
225
|
+
const room = this.recordsPerChunk - ( rec % this.recordsPerChunk );
|
|
226
|
+
const n = Math.min( room, count - done );
|
|
227
|
+
out.set( chunk.subarray( base, base + n * this.lanesPerRecord ), done * this.lanesPerRecord );
|
|
228
|
+
done += n;
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return out;
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Write `src` (whole records) starting at record `start`, spanning chunks as needed. */
|
|
237
|
+
setRecords( start, src ) {
|
|
238
|
+
|
|
239
|
+
const count = src.length / this.lanesPerRecord;
|
|
240
|
+
let done = 0;
|
|
241
|
+
while ( done < count ) {
|
|
242
|
+
|
|
243
|
+
const rec = start + done;
|
|
244
|
+
const chunk = this.chunkFor( rec );
|
|
245
|
+
const base = this.baseOf( rec );
|
|
246
|
+
const room = this.recordsPerChunk - ( rec % this.recordsPerChunk );
|
|
247
|
+
const n = Math.min( room, count - done );
|
|
248
|
+
chunk.set(
|
|
249
|
+
src.subarray( done * this.lanesPerRecord, ( done + n ) * this.lanesPerRecord ),
|
|
250
|
+
base
|
|
251
|
+
);
|
|
252
|
+
done += n;
|
|
253
|
+
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** A fresh copy of records [start, start+count). Always a copy, so it is safe to transfer. */
|
|
259
|
+
copyOf( start, count ) {
|
|
260
|
+
|
|
261
|
+
return this.readRecords( start, count, new this.LaneType( count * this.lanesPerRecord ) );
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The same storage narrowed to the first `recordCount` records: whole unused chunks are
|
|
267
|
+
* dropped and the last one is trimmed, so the upload writes exactly what was filled.
|
|
268
|
+
*/
|
|
269
|
+
trimTo( recordCount ) {
|
|
270
|
+
|
|
271
|
+
const needed = Math.max( 1, Math.ceil( recordCount / this.recordsPerChunk ) );
|
|
272
|
+
const chunks = this.chunks.slice( 0, needed );
|
|
273
|
+
const lanes = ( recordCount - ( needed - 1 ) * this.recordsPerChunk ) * this.lanesPerRecord;
|
|
274
|
+
const last = chunks[ needed - 1 ];
|
|
275
|
+
if ( last.length > lanes ) chunks[ needed - 1 ] = last.subarray( 0, Math.max( 0, lanes ) );
|
|
276
|
+
|
|
277
|
+
return ChunkedRecords.adopt( chunks, recordCount, this.lanesPerRecord, this.recordsPerChunk );
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** A parallel set of views of another lane type over the same memory (u32 records read as f32). */
|
|
282
|
+
viewAs( LaneType ) {
|
|
283
|
+
|
|
284
|
+
const wrap = c => new LaneType( c.buffer, c.byteOffset, c.byteLength / LaneType.BYTES_PER_ELEMENT );
|
|
285
|
+
const v = ChunkedRecords.adopt(
|
|
286
|
+
this.chunks.map( c => ( c ? wrap( c ) : undefined ) ),
|
|
287
|
+
this.recordCount, this.lanesPerRecord, this.recordsPerChunk
|
|
288
|
+
);
|
|
289
|
+
v.LaneType = LaneType;
|
|
290
|
+
|
|
291
|
+
if ( this._views ) {
|
|
292
|
+
|
|
293
|
+
v._owner = this;
|
|
294
|
+
this._views.push( v );
|
|
295
|
+
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return v;
|
|
299
|
+
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { DataTexture, RGBAFormat, FloatType, NearestFilter } from 'three';
|
|
10
|
-
import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
10
|
+
import { TRIANGLE_DATA_LAYOUT, TRI_MATERIAL_MASK } from '../EngineDefaults.js';
|
|
11
11
|
import { LightBVHBuilder } from './LightBVHBuilder.js';
|
|
12
12
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
13
13
|
|
|
@@ -38,25 +38,37 @@ export class EmissiveTriangleBuilder {
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Extract emissive triangles from processed geometry
|
|
41
|
-
* @param {
|
|
41
|
+
* @param {Uint32Array} triangleData - Packed triangle records
|
|
42
42
|
* @param {Array} materials - Array of material objects
|
|
43
43
|
* @param {number} triangleCount - Total number of triangles
|
|
44
|
+
* @param {import('./InstanceTable.js').InstanceTable} [table] - per placement; triangles are
|
|
45
|
+
* stored in object space, so power, bounds and the emission cone all have to be
|
|
46
|
+
* measured after the instance transform. One record per triangle, so a template
|
|
47
|
+
* placed more than once is lit by its first placement — the extractor expands an
|
|
48
|
+
* emissive instanced mesh into per-instance triangles to avoid exactly that.
|
|
44
49
|
*/
|
|
45
|
-
extractEmissiveTriangles( triangleData, materials, triangleCount ) {
|
|
50
|
+
extractEmissiveTriangles( triangleData, materials, triangleCount, table = null ) {
|
|
46
51
|
|
|
47
52
|
this.emissiveTriangles = [];
|
|
48
53
|
this.totalEmissivePower = 0;
|
|
49
54
|
this._totalTriangleCount = triangleCount;
|
|
50
55
|
|
|
51
56
|
const FLOATS_PER_TRIANGLE = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
52
|
-
const
|
|
53
|
-
const MESH_INDEX_OFFSET = TRIANGLE_DATA_LAYOUT.
|
|
57
|
+
const MATERIAL_FLAGS_OFFSET = TRIANGLE_DATA_LAYOUT.MATERIAL_FLAGS_OFFSET;
|
|
58
|
+
const MESH_INDEX_OFFSET = TRIANGLE_DATA_LAYOUT.MESH_INDEX_OFFSET;
|
|
59
|
+
// Flat array or ChunkedRecords; the chunk is resolved once per triangle.
|
|
60
|
+
const chunked = triangleData && triangleData.chunks ? triangleData : null;
|
|
61
|
+
const chunkedF = chunked ? chunked.viewAs( Float32Array ) : null;
|
|
62
|
+
const flatU = chunked ? null : triangleData;
|
|
63
|
+
const flatF = chunked ? null : new Float32Array( triangleData.buffer, triangleData.byteOffset, triangleData.length );
|
|
54
64
|
|
|
55
65
|
for ( let i = 0; i < triangleCount; i ++ ) {
|
|
56
66
|
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
67
|
+
const triU = chunked ? chunked.chunkFor( i ) : flatU;
|
|
68
|
+
const triFloats = chunked ? chunkedF.chunkFor( i ) : flatF;
|
|
69
|
+
const baseOffset = chunked ? chunked.baseOf( i ) : i * FLOATS_PER_TRIANGLE;
|
|
70
|
+
const materialIndex = triU[ baseOffset + MATERIAL_FLAGS_OFFSET ] & TRI_MATERIAL_MASK;
|
|
71
|
+
const meshIndex = triU[ baseOffset + MESH_INDEX_OFFSET ];
|
|
60
72
|
|
|
61
73
|
// Get material
|
|
62
74
|
const material = materials[ materialIndex ];
|
|
@@ -73,16 +85,25 @@ export class EmissiveTriangleBuilder {
|
|
|
73
85
|
if ( isEmissive ) {
|
|
74
86
|
|
|
75
87
|
// Calculate triangle area for power weighting
|
|
76
|
-
// Positions are at offsets 0-11
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
88
|
+
// Positions are at offsets 0-11, in the instance's own space
|
|
89
|
+
// `meshIndex` names the template the triangle belongs to, never a row of the
|
|
90
|
+
// per-placement table: indexing the table with it put one object's matrix on
|
|
91
|
+
// another object's light as soon as any instanced mesh came earlier.
|
|
92
|
+
const placement = table?.placementRunOf?.( meshIndex )?.start ?? - 1;
|
|
93
|
+
const hasInstance = placement >= 0 && table.isSet[ placement ];
|
|
94
|
+
const m = hasInstance ? table.world : null;
|
|
95
|
+
const mo = hasInstance ? placement * 16 : 0;
|
|
96
|
+
const px = ( x, y, z ) => ( m ? m[ mo ] * x + m[ mo + 4 ] * y + m[ mo + 8 ] * z + m[ mo + 12 ] : x );
|
|
97
|
+
const py = ( x, y, z ) => ( m ? m[ mo + 1 ] * x + m[ mo + 5 ] * y + m[ mo + 9 ] * z + m[ mo + 13 ] : y );
|
|
98
|
+
const pz = ( x, y, z ) => ( m ? m[ mo + 2 ] * x + m[ mo + 6 ] * y + m[ mo + 10 ] * z + m[ mo + 14 ] : z );
|
|
99
|
+
|
|
100
|
+
const o0x = triFloats[ baseOffset + 0 ], o0y = triFloats[ baseOffset + 1 ], o0z = triFloats[ baseOffset + 2 ];
|
|
101
|
+
const o1x = triFloats[ baseOffset + 4 ], o1y = triFloats[ baseOffset + 5 ], o1z = triFloats[ baseOffset + 6 ];
|
|
102
|
+
const o2x = triFloats[ baseOffset + 8 ], o2y = triFloats[ baseOffset + 9 ], o2z = triFloats[ baseOffset + 10 ];
|
|
103
|
+
|
|
104
|
+
const v0x = px( o0x, o0y, o0z ), v0y = py( o0x, o0y, o0z ), v0z = pz( o0x, o0y, o0z );
|
|
105
|
+
const v1x = px( o1x, o1y, o1z ), v1y = py( o1x, o1y, o1z ), v1z = pz( o1x, o1y, o1z );
|
|
106
|
+
const v2x = px( o2x, o2y, o2z ), v2y = py( o2x, o2y, o2z ), v2z = pz( o2x, o2y, o2z );
|
|
86
107
|
|
|
87
108
|
const area = this._calculateTriangleArea( v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z );
|
|
88
109
|
|
|
@@ -119,6 +140,7 @@ export class EmissiveTriangleBuilder {
|
|
|
119
140
|
triangleIndex: i,
|
|
120
141
|
materialIndex: materialIndex,
|
|
121
142
|
meshIndex: meshIndex,
|
|
143
|
+
instanceLeaf: hasInstance ? table.tlasLeafIndex[ placement ] : - 1,
|
|
122
144
|
power: power,
|
|
123
145
|
area: area,
|
|
124
146
|
emissive: { r: emissive.r, g: emissive.g, b: emissive.b },
|
|
@@ -369,7 +391,10 @@ export class EmissiveTriangleBuilder {
|
|
|
369
391
|
data[ offset + 0 ] = tri.triangleIndex;
|
|
370
392
|
data[ offset + 1 ] = tri.power;
|
|
371
393
|
data[ offset + 2 ] = this.cdfArray[ i ];
|
|
372
|
-
|
|
394
|
+
// Slot 3 used to repeat power/total, which the shader recomputes anyway. It now
|
|
395
|
+
// names the TLAS leaf that owns this emitter, so sampling can put the triangle
|
|
396
|
+
// back into world space.
|
|
397
|
+
data[ offset + 3 ] = tri.instanceLeaf;
|
|
373
398
|
|
|
374
399
|
// vec4[1]: pre-multiplied emission (emissive * intensity), area
|
|
375
400
|
data[ offset + 4 ] = tri.emissive.r * tri.emissiveIntensity;
|
|
@@ -600,11 +625,13 @@ export class EmissiveTriangleBuilder {
|
|
|
600
625
|
const tri = tris[ origIdx ];
|
|
601
626
|
const offset = i * 8;
|
|
602
627
|
|
|
603
|
-
// vec4[0]: triangleIndex, power, cdf,
|
|
628
|
+
// vec4[0]: triangleIndex, power, cdf, instance. This is the authoritative writer —
|
|
629
|
+
// buildLightBVH re-sorts the entries and replaces emissiveTriangleData, so the
|
|
630
|
+
// instance has to be carried here too, not only in createEmissiveRawData.
|
|
604
631
|
data[ offset + 0 ] = tri.triangleIndex;
|
|
605
632
|
data[ offset + 1 ] = tri.power;
|
|
606
633
|
data[ offset + 2 ] = this.cdfArray[ i ];
|
|
607
|
-
data[ offset + 3 ] =
|
|
634
|
+
data[ offset + 3 ] = tri.instanceLeaf;
|
|
608
635
|
|
|
609
636
|
// vec4[1]: pre-multiplied emission (emissive * intensity), area
|
|
610
637
|
data[ offset + 4 ] = tri.emissive.r * tri.emissiveIntensity;
|