rayzee 7.31.1 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -0
- package/dist/rayzee.es.js +6366 -4361
- package/dist/rayzee.umd.js +122 -73
- package/package.json +1 -1
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +120 -19
- 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 +577 -92
- 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/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/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +128 -3
- 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
package/src/TSL/patches.js
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
* loop) and `initTimestampQuery` (enlarges the stats-gl timestamp query pool so
|
|
8
8
|
* the wavefront tracer's high per-frame compute-pass count doesn't overflow it).
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* a proxy factory that supports GLSL-style dot-notation field access
|
|
10
|
+
* Exports: `struct()` — drop-in replacement for TSL's `struct()` returning
|
|
11
|
+
* a proxy factory that supports GLSL-style dot-notation field access — and
|
|
12
|
+
* `gpuOnlyStorageAttribute()`, a storage attribute with no CPU backing array.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
|
-
import { WebGPUBackend } from 'three/webgpu';
|
|
15
|
+
import { StorageInstancedBufferAttribute, WebGPUBackend } from 'three/webgpu';
|
|
15
16
|
import { struct as _struct } from 'three/tsl';
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -69,6 +70,130 @@ WebGPUBackend.prototype.createNodeBuilder = function ( object, renderer ) {
|
|
|
69
70
|
|
|
70
71
|
};
|
|
71
72
|
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// 1b. GPU-only storage attributes
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// `StorageBufferAttribute( count, ... )` always allocates a CPU TypedArray, even
|
|
77
|
+
// for a buffer only compute shaders ever touch — three.js has no GPU-only path
|
|
78
|
+
// (upstream request open). The wavefront ray/hit/rng state and the per-pixel
|
|
79
|
+
// G-buffer are exactly that: every lane is written by a kernel before anything
|
|
80
|
+
// reads it, nothing is ever read back, and WebGPU zero-initialises a new buffer,
|
|
81
|
+
// so the CPU copy is pure overhead — 704 MB at a 4M path budget.
|
|
82
|
+
//
|
|
83
|
+
// `gpuOnlyStorageAttribute()` builds the attribute over a zero-length array and
|
|
84
|
+
// records the size the GPU buffer needs; `createStorageAttribute` below allocates
|
|
85
|
+
// that size unmapped, and `updateAttribute` becomes a no-op since there is
|
|
86
|
+
// nothing to upload. Everything else (binding, dispose, readback plumbing) is
|
|
87
|
+
// untouched — a storage binding takes the whole buffer with no size argument.
|
|
88
|
+
|
|
89
|
+
export function gpuOnlyStorageAttribute( count, itemSize, typeClass = Float32Array ) {
|
|
90
|
+
|
|
91
|
+
const attr = new StorageInstancedBufferAttribute( new typeClass( 0 ), itemSize );
|
|
92
|
+
attr.count = count;
|
|
93
|
+
attr.gpuByteLength = count * itemSize * typeClass.BYTES_PER_ELEMENT;
|
|
94
|
+
attr.isGPUOnly = true;
|
|
95
|
+
return attr;
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const _origCreateStorageAttribute = WebGPUBackend.prototype.createStorageAttribute;
|
|
100
|
+
|
|
101
|
+
WebGPUBackend.prototype.createStorageAttribute = function ( attribute ) {
|
|
102
|
+
|
|
103
|
+
const bufferAttribute = this.attributeUtils._getBufferAttribute( attribute );
|
|
104
|
+
if ( ! bufferAttribute.isGPUOnly ) return _origCreateStorageAttribute.call( this, attribute );
|
|
105
|
+
|
|
106
|
+
const data = this.get( bufferAttribute );
|
|
107
|
+
if ( data.buffer !== undefined ) return;
|
|
108
|
+
|
|
109
|
+
data.buffer = this.device.createBuffer( {
|
|
110
|
+
label: bufferAttribute.name,
|
|
111
|
+
size: bufferAttribute.gpuByteLength,
|
|
112
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
113
|
+
} );
|
|
114
|
+
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Upload a storage attribute's contents from several CPU chunks.
|
|
119
|
+
*
|
|
120
|
+
* A GPU buffer may be up to `maxBufferSize` (4 GB here) while V8 caps a single ArrayBuffer at
|
|
121
|
+
* ~2 GB, so anything larger has to be staged in pieces. The attribute must be GPU-only: it owns
|
|
122
|
+
* no CPU array, and these writes are the only thing that fills it.
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} renderer - WebGPURenderer
|
|
125
|
+
* @param {Object} attr - attribute from gpuOnlyStorageAttribute()
|
|
126
|
+
* @param {Array<TypedArray>} chunks - in order; total byte length must match the attribute
|
|
127
|
+
*/
|
|
128
|
+
export function uploadStorageChunks( renderer, attr, chunks ) {
|
|
129
|
+
|
|
130
|
+
const backend = renderer.backend;
|
|
131
|
+
backend.createStorageAttribute( attr );
|
|
132
|
+
|
|
133
|
+
const buffer = backend.get( attr ).buffer;
|
|
134
|
+
let byteOffset = 0;
|
|
135
|
+
|
|
136
|
+
for ( const chunk of chunks ) {
|
|
137
|
+
|
|
138
|
+
backend.device.queue.writeBuffer( buffer, byteOffset, chunk, 0, chunk.length );
|
|
139
|
+
byteOffset += chunk.byteLength;
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if ( byteOffset !== attr.gpuByteLength ) {
|
|
144
|
+
|
|
145
|
+
throw new RangeError( `uploadStorageChunks wrote ${byteOffset} bytes into a ${attr.gpuByteLength}-byte buffer` );
|
|
146
|
+
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Re-upload one lane range of a chunked storage attribute, spanning chunks as needed.
|
|
153
|
+
*
|
|
154
|
+
* The partial-update path three.js offers (`addUpdateRange` + a version bump) cannot work here:
|
|
155
|
+
* a GPU-only attribute owns no CPU array and its `updateAttribute` is a no-op, so a dirty range
|
|
156
|
+
* would silently never reach the GPU.
|
|
157
|
+
*
|
|
158
|
+
* @param {Object} renderer - WebGPURenderer
|
|
159
|
+
* @param {Object} attr - attribute from gpuOnlyStorageAttribute()
|
|
160
|
+
* @param {Object} records - the ChunkedRecords backing it
|
|
161
|
+
* @param {number} startLane - first lane to write
|
|
162
|
+
* @param {number} laneCount - how many lanes
|
|
163
|
+
*/
|
|
164
|
+
export function uploadStorageChunkRange( renderer, attr, records, startLane, laneCount ) {
|
|
165
|
+
|
|
166
|
+
const backend = renderer.backend;
|
|
167
|
+
backend.createStorageAttribute( attr );
|
|
168
|
+
|
|
169
|
+
const buffer = backend.get( attr ).buffer;
|
|
170
|
+
const lanesPerChunk = records.recordsPerChunk * records.lanesPerRecord;
|
|
171
|
+
const bytesPerLane = records.chunks[ 0 ].BYTES_PER_ELEMENT;
|
|
172
|
+
const end = startLane + laneCount;
|
|
173
|
+
|
|
174
|
+
let lane = startLane;
|
|
175
|
+
while ( lane < end ) {
|
|
176
|
+
|
|
177
|
+
const ci = Math.floor( lane / lanesPerChunk );
|
|
178
|
+
const chunk = records.chunks[ ci ];
|
|
179
|
+
const local = lane - ci * lanesPerChunk;
|
|
180
|
+
const n = Math.min( chunk.length - local, end - lane );
|
|
181
|
+
backend.device.queue.writeBuffer( buffer, lane * bytesPerLane, chunk, local, n );
|
|
182
|
+
lane += n;
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const _origUpdateAttribute = WebGPUBackend.prototype.updateAttribute;
|
|
189
|
+
|
|
190
|
+
WebGPUBackend.prototype.updateAttribute = function ( attribute ) {
|
|
191
|
+
|
|
192
|
+
if ( this.attributeUtils._getBufferAttribute( attribute ).isGPUOnly ) return;
|
|
193
|
+
return _origUpdateAttribute.call( this, attribute );
|
|
194
|
+
|
|
195
|
+
};
|
|
196
|
+
|
|
72
197
|
// ---------------------------------------------------------------------------
|
|
73
198
|
// 2. Larger timestamp query pool (stats-gl GPU/compute timing)
|
|
74
199
|
// ---------------------------------------------------------------------------
|
package/src/index.js
CHANGED
|
@@ -72,6 +72,9 @@ export { OverlayManager } from './managers/OverlayManager.js';
|
|
|
72
72
|
|
|
73
73
|
// VRAM accounting
|
|
74
74
|
export { VRAMTracker, bufferBytes, textureBytes } from './Processor/VRAMTracker.js';
|
|
75
|
+
export {
|
|
76
|
+
MemoryLedger, estimateSceneBytes, probeAddressSpace, SAFE_SCENE_BYTES, MAX_SCENE_BYTES,
|
|
77
|
+
} from './Processor/HostMemory.js';
|
|
75
78
|
|
|
76
79
|
// Pipeline infrastructure (for advanced consumers building custom stages)
|
|
77
80
|
export { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Owns the Three.js AnimationMixer, advances clips each frame,
|
|
5
5
|
* extracts deformed vertex positions via CPU skinning, and returns
|
|
6
|
-
*
|
|
6
|
+
* a per-mesh reader in the shape PathTracerApp.refitBVH() accepts.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce } from 'three';
|
|
@@ -24,7 +24,7 @@ export class AnimationManager extends EventDispatcher {
|
|
|
24
24
|
this._mixerRoot = null; // mixer target (GLTF model root for track resolution)
|
|
25
25
|
this._meshes = null;
|
|
26
26
|
this._meshTriRanges = null; // { start, count, uniqueVerts, indices }[]
|
|
27
|
-
this.
|
|
27
|
+
this._meshPositions = null; // per-mesh Float32Array(triCount * 9), allocated on first use
|
|
28
28
|
this._tempVec = new Vector3();
|
|
29
29
|
this._skinnedCache = null; // per-mesh Float32Array for skinned vertex positions
|
|
30
30
|
this._clipsCache = null;
|
|
@@ -111,14 +111,14 @@ export class AnimationManager extends EventDispatcher {
|
|
|
111
111
|
const uniqueVerts = positions.count;
|
|
112
112
|
|
|
113
113
|
this._meshTriRanges.push( { start: offset, count, uniqueVerts, indices } );
|
|
114
|
-
// Pre-allocate per-mesh skinned position cache (3 floats per unique vertex)
|
|
115
|
-
this._skinnedCache.push( new Float32Array( uniqueVerts * 3 ) );
|
|
116
114
|
offset += count;
|
|
117
115
|
|
|
118
116
|
}
|
|
119
117
|
|
|
120
|
-
//
|
|
121
|
-
|
|
118
|
+
// Scratch is per mesh and allocated on first use. One scene-wide output buffer would be
|
|
119
|
+
// 9 floats × every triangle — 1,030 MB at 30M, past what a renderer can allocate.
|
|
120
|
+
this._skinnedCache = [];
|
|
121
|
+
this._meshPositions = [];
|
|
122
122
|
|
|
123
123
|
const skinnedCount = meshes.filter( m => m.isSkinnedMesh ).length;
|
|
124
124
|
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles` );
|
|
@@ -224,12 +224,12 @@ export class AnimationManager extends EventDispatcher {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
|
-
* Seek to an absolute time and
|
|
227
|
+
* Seek to an absolute time and prepare deformed vertex positions.
|
|
228
228
|
* Does not require playback — works from any state (stopped, paused, playing).
|
|
229
229
|
*
|
|
230
230
|
* @param {number} time - Absolute time in seconds
|
|
231
231
|
* @param {number} [clipIndex=0] - Clip to evaluate, or -1 for all active
|
|
232
|
-
* @returns {Float32Array|null}
|
|
232
|
+
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if no mixer
|
|
233
233
|
*/
|
|
234
234
|
seekTo( time, clipIndex = 0 ) {
|
|
235
235
|
|
|
@@ -261,8 +261,7 @@ export class AnimationManager extends EventDispatcher {
|
|
|
261
261
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
this.
|
|
265
|
-
return this._posBuffer;
|
|
264
|
+
return this._poseReader();
|
|
266
265
|
|
|
267
266
|
}
|
|
268
267
|
|
|
@@ -277,10 +276,10 @@ export class AnimationManager extends EventDispatcher {
|
|
|
277
276
|
}
|
|
278
277
|
|
|
279
278
|
/**
|
|
280
|
-
* Advance animation and
|
|
279
|
+
* Advance animation and prepare deformed positions.
|
|
281
280
|
* Call once per frame from the animate loop.
|
|
282
281
|
*
|
|
283
|
-
* @returns {Float32Array|null}
|
|
282
|
+
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if not playing
|
|
284
283
|
*/
|
|
285
284
|
update() {
|
|
286
285
|
|
|
@@ -290,100 +289,111 @@ export class AnimationManager extends EventDispatcher {
|
|
|
290
289
|
const delta = this.timer.getDelta();
|
|
291
290
|
this.mixer.update( delta );
|
|
292
291
|
|
|
293
|
-
this.
|
|
292
|
+
return this._poseReader();
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** A reusable per-mesh buffer, grown only when that mesh is first skinned. @private */
|
|
297
|
+
_scratch( cache, index, length ) {
|
|
294
298
|
|
|
295
|
-
|
|
299
|
+
const have = cache[ index ];
|
|
300
|
+
return have && have.length === length ? have : ( cache[ index ] = new Float32Array( length ) );
|
|
296
301
|
|
|
297
302
|
}
|
|
298
303
|
|
|
299
304
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* Phase 1: Skin all unique vertices per mesh
|
|
303
|
-
* Phase 2: Assemble triangles from index buffer
|
|
305
|
+
* Settle the pose, then hand back a reader that skins one mesh at a time. The refit pulls each
|
|
306
|
+
* mesh as it needs it, so only one mesh's worth of deformed positions exists at any moment.
|
|
304
307
|
* @private
|
|
305
308
|
*/
|
|
306
|
-
|
|
309
|
+
_poseReader() {
|
|
307
310
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Update mixer root subtree — bones live outside mesh subtrees so
|
|
312
|
-
// per-mesh updateMatrixWorld() misses them. Using mixerRoot (not full
|
|
313
|
-
// scene) avoids recomputing matrices for unrelated static objects.
|
|
311
|
+
// Bones live outside mesh subtrees so per-mesh updateMatrixWorld() misses them. Doing it
|
|
312
|
+
// once here, on mixerRoot rather than the scene, keeps unrelated static objects out of it.
|
|
314
313
|
this._mixerRoot.updateMatrixWorld( true );
|
|
315
314
|
|
|
316
|
-
|
|
315
|
+
return m => this._computeMeshPositions( m );
|
|
316
|
+
|
|
317
|
+
}
|
|
317
318
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Skin one mesh into its own buffer. Two phases for indexed geometry: skin the unique
|
|
321
|
+
* vertices, then assemble triangles from the index buffer.
|
|
322
|
+
* @private
|
|
323
|
+
*/
|
|
324
|
+
_computeMeshPositions( m ) {
|
|
321
325
|
|
|
322
|
-
|
|
326
|
+
const tempVec = this._tempVec;
|
|
327
|
+
const mesh = this._meshes[ m ];
|
|
328
|
+
const { count, uniqueVerts, indices } = this._meshTriRanges[ m ];
|
|
329
|
+
const skinned = this._scratch( this._skinnedCache, m, uniqueVerts * 3 );
|
|
330
|
+
const output = this._scratch( this._meshPositions, m, count * 9 );
|
|
323
331
|
|
|
324
|
-
|
|
325
|
-
for ( let v = 0; v < uniqueVerts; v ++ ) {
|
|
332
|
+
const worldMatrix = mesh.matrixWorld;
|
|
326
333
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// Transform to world space (matches GeometryExtractor behavior)
|
|
330
|
-
tempVec.applyMatrix4( worldMatrix );
|
|
334
|
+
// Phase 1: Compute world-space positions for all unique vertices
|
|
335
|
+
for ( let v = 0; v < uniqueVerts; v ++ ) {
|
|
331
336
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
337
|
+
// getVertexPosition handles morph targets + bone transforms (local space)
|
|
338
|
+
mesh.getVertexPosition( v, tempVec );
|
|
339
|
+
// Transform to world space (matches GeometryExtractor behavior)
|
|
340
|
+
tempVec.applyMatrix4( worldMatrix );
|
|
335
341
|
|
|
336
|
-
|
|
342
|
+
skinned[ v * 3 ] = tempVec.x;
|
|
343
|
+
skinned[ v * 3 + 1 ] = tempVec.y;
|
|
344
|
+
skinned[ v * 3 + 2 ] = tempVec.z;
|
|
337
345
|
|
|
338
|
-
|
|
339
|
-
if ( indices ) {
|
|
346
|
+
}
|
|
340
347
|
|
|
341
|
-
|
|
348
|
+
// Phase 2: Assemble triangles
|
|
349
|
+
if ( indices ) {
|
|
342
350
|
|
|
343
|
-
|
|
344
|
-
const i0 = indices[ t3 ] * 3;
|
|
345
|
-
const i1 = indices[ t3 + 1 ] * 3;
|
|
346
|
-
const i2 = indices[ t3 + 2 ] * 3;
|
|
347
|
-
const o = ( start + t ) * 9;
|
|
351
|
+
for ( let t = 0; t < count; t ++ ) {
|
|
348
352
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
output[ o + 5 ] = skinned[ i1 + 2 ];
|
|
355
|
-
output[ o + 6 ] = skinned[ i2 ];
|
|
356
|
-
output[ o + 7 ] = skinned[ i2 + 1 ];
|
|
357
|
-
output[ o + 8 ] = skinned[ i2 + 2 ];
|
|
353
|
+
const t3 = t * 3;
|
|
354
|
+
const i0 = indices[ t3 ] * 3;
|
|
355
|
+
const i1 = indices[ t3 + 1 ] * 3;
|
|
356
|
+
const i2 = indices[ t3 + 2 ] * 3;
|
|
357
|
+
const o = t * 9;
|
|
358
358
|
|
|
359
|
-
|
|
359
|
+
output[ o ] = skinned[ i0 ];
|
|
360
|
+
output[ o + 1 ] = skinned[ i0 + 1 ];
|
|
361
|
+
output[ o + 2 ] = skinned[ i0 + 2 ];
|
|
362
|
+
output[ o + 3 ] = skinned[ i1 ];
|
|
363
|
+
output[ o + 4 ] = skinned[ i1 + 1 ];
|
|
364
|
+
output[ o + 5 ] = skinned[ i1 + 2 ];
|
|
365
|
+
output[ o + 6 ] = skinned[ i2 ];
|
|
366
|
+
output[ o + 7 ] = skinned[ i2 + 1 ];
|
|
367
|
+
output[ o + 8 ] = skinned[ i2 + 2 ];
|
|
360
368
|
|
|
361
|
-
}
|
|
369
|
+
}
|
|
362
370
|
|
|
363
|
-
|
|
364
|
-
for ( let t = 0; t < count; t ++ ) {
|
|
371
|
+
} else {
|
|
365
372
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const v2 = ( t * 3 + 2 ) * 3;
|
|
369
|
-
const o = ( start + t ) * 9;
|
|
373
|
+
// Non-indexed: vertices are sequential triplets
|
|
374
|
+
for ( let t = 0; t < count; t ++ ) {
|
|
370
375
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
output[ o + 4 ] = skinned[ v1 + 1 ];
|
|
376
|
-
output[ o + 5 ] = skinned[ v1 + 2 ];
|
|
377
|
-
output[ o + 6 ] = skinned[ v2 ];
|
|
378
|
-
output[ o + 7 ] = skinned[ v2 + 1 ];
|
|
379
|
-
output[ o + 8 ] = skinned[ v2 + 2 ];
|
|
376
|
+
const v0 = ( t * 3 ) * 3;
|
|
377
|
+
const v1 = ( t * 3 + 1 ) * 3;
|
|
378
|
+
const v2 = ( t * 3 + 2 ) * 3;
|
|
379
|
+
const o = t * 9;
|
|
380
380
|
|
|
381
|
-
|
|
381
|
+
output[ o ] = skinned[ v0 ];
|
|
382
|
+
output[ o + 1 ] = skinned[ v0 + 1 ];
|
|
383
|
+
output[ o + 2 ] = skinned[ v0 + 2 ];
|
|
384
|
+
output[ o + 3 ] = skinned[ v1 ];
|
|
385
|
+
output[ o + 4 ] = skinned[ v1 + 1 ];
|
|
386
|
+
output[ o + 5 ] = skinned[ v1 + 2 ];
|
|
387
|
+
output[ o + 6 ] = skinned[ v2 ];
|
|
388
|
+
output[ o + 7 ] = skinned[ v2 + 1 ];
|
|
389
|
+
output[ o + 8 ] = skinned[ v2 + 2 ];
|
|
382
390
|
|
|
383
391
|
}
|
|
384
392
|
|
|
385
393
|
}
|
|
386
394
|
|
|
395
|
+
return output;
|
|
396
|
+
|
|
387
397
|
}
|
|
388
398
|
|
|
389
399
|
/**
|
|
@@ -433,7 +443,7 @@ export class AnimationManager extends EventDispatcher {
|
|
|
433
443
|
this._mixerRoot = null;
|
|
434
444
|
this._meshes = null;
|
|
435
445
|
this._meshTriRanges = null;
|
|
436
|
-
this.
|
|
446
|
+
this._meshPositions = null;
|
|
437
447
|
this._skinnedCache = null;
|
|
438
448
|
this._clipsCache = null;
|
|
439
449
|
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
|
|
10
10
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
11
11
|
import { storage } from 'three/tsl';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
MATERIAL_DATA_LAYOUT as M, TRIANGLE_DATA_LAYOUT as T, normalizeAttenuationDistance,
|
|
14
|
+
TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TRI_BLOCKER_SHIFT, shadowBlockerBits
|
|
15
|
+
} from '../EngineDefaults.js';
|
|
13
16
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
14
17
|
|
|
15
18
|
const log = createLogger( 'material' );
|
|
16
19
|
|
|
17
20
|
const PIXELS_PER_MATERIAL = M.SLOTS_PER_MATERIAL;
|
|
18
|
-
//
|
|
19
|
-
const
|
|
20
|
-
const TRI_SIDE_OFFSET = T.NORMAL_C_OFFSET + 3; // normalCData.w in shader
|
|
21
|
-
const TRI_BLOCKER_OFFSET = T.NORMAL_A_OFFSET + 3; // nA.w in shader (opaque-blocker fast path)
|
|
21
|
+
// Side and the opaque-blocker bit share the material lane, so a patch is a masked write.
|
|
22
|
+
const TRI_FLAGS_OFFSET = T.MATERIAL_FLAGS_OFFSET;
|
|
22
23
|
|
|
23
24
|
// Material properties that affect the shadow-ray opaque-blocker flag.
|
|
24
25
|
const BLOCKER_PROPS = new Set( [ 'transmission', 'transparent', 'opacity', 'alphaMode' ] );
|
|
@@ -722,9 +723,8 @@ export class MaterialDataManager {
|
|
|
722
723
|
* @private
|
|
723
724
|
*/
|
|
724
725
|
/**
|
|
725
|
-
* Re-derive the shadow-
|
|
726
|
-
*
|
|
727
|
-
* Kept in sync with the blocker definition in GeometryExtractor.
|
|
726
|
+
* Re-derive the two shadow-blocker bits for a material from its current buffer
|
|
727
|
+
* values and patch them on every matching triangle.
|
|
728
728
|
* @private
|
|
729
729
|
*/
|
|
730
730
|
_recomputeOpaqueBlockerForMaterial( materialIndex ) {
|
|
@@ -733,36 +733,42 @@ export class MaterialDataManager {
|
|
|
733
733
|
if ( ! matBuf ) return;
|
|
734
734
|
|
|
735
735
|
const matStride = materialIndex * M.FLOATS_PER_MATERIAL;
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
736
|
+
const bits = shadowBlockerBits( {
|
|
737
|
+
alphaMode: matBuf[ matStride + M.ALPHA_MODE ],
|
|
738
|
+
transparent: matBuf[ matStride + M.TRANSPARENT ],
|
|
739
|
+
transmission: matBuf[ matStride + M.TRANSMISSION ],
|
|
740
|
+
opacity: matBuf[ matStride + M.OPACITY ],
|
|
741
|
+
} );
|
|
741
742
|
|
|
742
|
-
this._patchTriangleFlagForMaterial( materialIndex,
|
|
743
|
+
this._patchTriangleFlagForMaterial( materialIndex, TRI_BLOCKER_SHIFT, 2, bits );
|
|
743
744
|
|
|
744
745
|
}
|
|
745
746
|
|
|
746
747
|
/**
|
|
747
|
-
* Generic helper:
|
|
748
|
-
*
|
|
748
|
+
* Generic helper: rewrite `width` bits at `shift` in the flags lane of every triangle
|
|
749
|
+
* whose materialIndex matches, then fire onTriangleDataChanged.
|
|
749
750
|
* @private
|
|
750
751
|
*/
|
|
751
|
-
_patchTriangleFlagForMaterial( materialIndex,
|
|
752
|
+
_patchTriangleFlagForMaterial( materialIndex, shift, width, value ) {
|
|
752
753
|
|
|
753
754
|
const triInfo = this.callbacks.getTriangleData?.();
|
|
754
|
-
|
|
755
|
+
// Chunked past the ~2 GB array cap; a flat array is the single-chunk case.
|
|
756
|
+
const records = triInfo?.records;
|
|
757
|
+
const flat = records ? null : triInfo?.array;
|
|
755
758
|
const triCount = triInfo?.count | 0;
|
|
756
|
-
if ( !
|
|
759
|
+
if ( ( ! flat && ! records ) || triCount === 0 ) return;
|
|
757
760
|
|
|
758
761
|
const stride = T.FLOATS_PER_TRIANGLE;
|
|
762
|
+
const mask = ( ( ( 1 << width ) - 1 ) << shift ) >>> 0;
|
|
763
|
+
const bits = ( ( value << shift ) & mask ) >>> 0;
|
|
759
764
|
let patched = 0;
|
|
760
765
|
for ( let i = 0; i < triCount; i ++ ) {
|
|
761
766
|
|
|
762
|
-
const
|
|
763
|
-
|
|
767
|
+
const triData = records ? records.chunkFor( i ) : flat;
|
|
768
|
+
const base = records ? records.baseOf( i ) : i * stride;
|
|
769
|
+
if ( ( triData[ base + TRI_FLAGS_OFFSET ] & TRI_MATERIAL_MASK ) === materialIndex ) {
|
|
764
770
|
|
|
765
|
-
triData[ base +
|
|
771
|
+
triData[ base + TRI_FLAGS_OFFSET ] = ( ( triData[ base + TRI_FLAGS_OFFSET ] & ~ mask ) | bits ) >>> 0;
|
|
766
772
|
patched ++;
|
|
767
773
|
|
|
768
774
|
}
|
|
@@ -779,7 +785,7 @@ export class MaterialDataManager {
|
|
|
779
785
|
|
|
780
786
|
_patchTriangleSideForMaterial( materialIndex, sideValue ) {
|
|
781
787
|
|
|
782
|
-
this._patchTriangleFlagForMaterial( materialIndex,
|
|
788
|
+
this._patchTriangleFlagForMaterial( materialIndex, TRI_SIDE_SHIFT, 2, sideValue );
|
|
783
789
|
|
|
784
790
|
}
|
|
785
791
|
|