@gnsx/three 0.184.8 → 0.184.10
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/build/three.tsl.js +5 -1
- package/build/three.tsl.min.js +1 -1
- package/build/three.webgpu.js +480 -8
- package/build/three.webgpu.min.js +1 -1
- package/build/three.webgpu.nodes.js +480 -8
- package/build/three.webgpu.nodes.min.js +1 -1
- package/examples/jsm/helpers/LightProbeGridCubeMapHelper.js +222 -0
- package/examples/jsm/helpers/LightProbeGridHelper.js +223 -99
- package/examples/jsm/inspector/tabs/Settings.js +1 -1
- package/examples/jsm/lighting/LightProbeGrid.js +628 -84
- package/examples/jsm/tsl/lighting/DynamicLightsNode.js +19 -1
- package/package.json +1 -1
- package/src/Three.TSL.js +4 -0
- package/src/nodes/Nodes.js +3 -0
- package/src/nodes/TSL.js +4 -0
- package/src/nodes/accessors/CubeTextureNode.js +7 -1
- package/src/nodes/accessors/TextureNode.js +21 -1
- package/src/nodes/functions/material/getLightProbeGridIrradiance.js +68 -0
- package/src/nodes/lighting/LightProbeGridNode.js +137 -0
- package/src/nodes/lighting/LightsNode.js +98 -1
- package/src/renderers/common/Background.js +33 -0
- package/src/renderers/common/RenderList.js +28 -0
- package/src/renderers/common/RenderObjects.js +47 -0
- package/src/renderers/common/Renderer.js +32 -2
- package/src/renderers/common/nodes/NodeManager.js +24 -0
|
@@ -18,6 +18,26 @@ import {
|
|
|
18
18
|
WebGLCubeRenderTarget,
|
|
19
19
|
WebGLRenderTarget
|
|
20
20
|
} from 'three';
|
|
21
|
+
// WITH_GENESYS
|
|
22
|
+
import { CubeRenderTarget, Storage3DTexture } from 'three/webgpu';
|
|
23
|
+
import {
|
|
24
|
+
Fn,
|
|
25
|
+
If,
|
|
26
|
+
Loop,
|
|
27
|
+
PI,
|
|
28
|
+
convert,
|
|
29
|
+
cubeTexture,
|
|
30
|
+
float,
|
|
31
|
+
int,
|
|
32
|
+
select,
|
|
33
|
+
storageTexture,
|
|
34
|
+
textureStore,
|
|
35
|
+
uniform,
|
|
36
|
+
uvec3,
|
|
37
|
+
vec3,
|
|
38
|
+
vec4
|
|
39
|
+
} from 'three/tsl';
|
|
40
|
+
// !WITH_GENESYS
|
|
21
41
|
|
|
22
42
|
// Shared fullscreen-quad scene / camera
|
|
23
43
|
let _scene = null;
|
|
@@ -38,6 +58,21 @@ let _cachedCubemapSize = 0;
|
|
|
38
58
|
let _cachedNear = 0;
|
|
39
59
|
let _cachedFar = 0;
|
|
40
60
|
|
|
61
|
+
// WITH_GENESYS
|
|
62
|
+
// Cached WebGPU bake resources. The WebGPU path projects cubemaps on the GPU
|
|
63
|
+
// via TSL compute and writes directly into a Storage3DTexture atlas.
|
|
64
|
+
let _webgpuCubeRenderTarget = null;
|
|
65
|
+
let _webgpuCubeCamera = null;
|
|
66
|
+
let _webgpuCachedCubemapSize = 0;
|
|
67
|
+
let _webgpuCachedNear = 0;
|
|
68
|
+
let _webgpuCachedFar = 0;
|
|
69
|
+
let _webgpuComputeNode = null;
|
|
70
|
+
let _webgpuComputeCubemapSize = 0;
|
|
71
|
+
let _webgpuComputeAtlas = null;
|
|
72
|
+
let _webgpuComputeEnvMap = null;
|
|
73
|
+
let _webgpuProbeUniforms = null;
|
|
74
|
+
// !WITH_GENESYS
|
|
75
|
+
|
|
41
76
|
// Cached batch render target
|
|
42
77
|
let _batchTarget = null;
|
|
43
78
|
let _batchTargetProbes = 0;
|
|
@@ -161,6 +196,33 @@ class LightProbeGrid extends Object3D {
|
|
|
161
196
|
*/
|
|
162
197
|
this._renderTarget = null;
|
|
163
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Array of cube textures captured during baking, only populated
|
|
201
|
+
* when `retainCubemaps` option is true in {@link LightProbeGrid#bake}.
|
|
202
|
+
*
|
|
203
|
+
* @type {?CubeTexture[]}
|
|
204
|
+
* @default null
|
|
205
|
+
*/
|
|
206
|
+
this.cubeTextures = null;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Render targets backing retained cube textures.
|
|
210
|
+
*
|
|
211
|
+
* @private
|
|
212
|
+
* @type {?Array<WebGLCubeRenderTarget|CubeRenderTarget>}
|
|
213
|
+
* @default null
|
|
214
|
+
*/
|
|
215
|
+
this._cubeRenderTargets = null;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Whether retained cube render targets are WebGPU render targets.
|
|
219
|
+
*
|
|
220
|
+
* @private
|
|
221
|
+
* @type {?boolean}
|
|
222
|
+
* @default null
|
|
223
|
+
*/
|
|
224
|
+
this._cubeRenderTargetsWebGPU = null;
|
|
225
|
+
|
|
164
226
|
this.updateBoundingBox();
|
|
165
227
|
|
|
166
228
|
}
|
|
@@ -210,134 +272,395 @@ class LightProbeGrid extends Object3D {
|
|
|
210
272
|
* @param {number} [options.cubemapSize=8] - Resolution of each cubemap face.
|
|
211
273
|
* @param {number} [options.near=0.1] - Near plane for the cube camera.
|
|
212
274
|
* @param {number} [options.far=100] - Far plane for the cube camera.
|
|
275
|
+
* @param {boolean} [options.retainCubemaps=false] - Whether to retain the captured cube maps for visualization.
|
|
276
|
+
* @return {Promise<void>} Resolves when baking has completed.
|
|
213
277
|
*/
|
|
214
|
-
bake( renderer, scene, options = {} ) {
|
|
278
|
+
async bake( renderer, scene, options = {} ) {
|
|
215
279
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
this._ensureTextures();
|
|
219
|
-
this.updateBoundingBox();
|
|
280
|
+
const wasVisible = this.visible; // WITH_GENESYS
|
|
281
|
+
const retainCubemaps = options.retainCubemaps === true;
|
|
220
282
|
|
|
221
283
|
// Prevent feedback: temporarily hide the volume during baking
|
|
222
284
|
this.visible = false;
|
|
223
285
|
|
|
224
|
-
|
|
225
|
-
const totalProbes = res.x * res.y * res.z;
|
|
286
|
+
if ( ! retainCubemaps ) this._disposeRetainedCubemaps();
|
|
226
287
|
|
|
227
|
-
//
|
|
228
|
-
|
|
288
|
+
// WITH_GENESYS
|
|
289
|
+
if ( renderer.isWebGPURenderer === true ) {
|
|
229
290
|
|
|
230
|
-
|
|
231
|
-
const savedRenderTarget = renderer.getRenderTarget();
|
|
232
|
-
renderer.getViewport( _savedViewport );
|
|
233
|
-
renderer.getScissor( _savedScissor );
|
|
234
|
-
const savedScissorTest = renderer.getScissorTest();
|
|
291
|
+
await this._bakeWebGPU( renderer, scene, options );
|
|
235
292
|
|
|
236
|
-
|
|
237
|
-
batchTarget.scissorTest = false;
|
|
238
|
-
batchTarget.viewport.set( 0, 0, 9, totalProbes );
|
|
239
|
-
renderer.setRenderTarget( batchTarget );
|
|
240
|
-
renderer.clear();
|
|
293
|
+
} else { // !WITH_GENESYS
|
|
241
294
|
|
|
242
|
-
|
|
295
|
+
const { cubeRenderTarget, cubeCamera } = _ensureBakeResources( options );
|
|
243
296
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
batchTarget.scissorTest = true;
|
|
297
|
+
this._ensureTextures();
|
|
298
|
+
this.updateBoundingBox();
|
|
247
299
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const savedShadowAutoUpdate = renderer.shadowMap.autoUpdate;
|
|
251
|
-
renderer.shadowMap.autoUpdate = false;
|
|
252
|
-
renderer.shadowMap.needsUpdate = true;
|
|
300
|
+
const res = this.resolution;
|
|
301
|
+
const totalProbes = res.x * res.y * res.z;
|
|
253
302
|
|
|
254
|
-
|
|
303
|
+
// Batch render target for SH coefficients: 9 pixels wide, one row per probe
|
|
304
|
+
const batchTarget = _ensureBatchTarget( totalProbes );
|
|
255
305
|
|
|
256
|
-
|
|
306
|
+
// Save renderer state
|
|
307
|
+
const savedRenderTarget = renderer.getRenderTarget();
|
|
308
|
+
renderer.getViewport( _savedViewport );
|
|
309
|
+
renderer.getScissor( _savedScissor );
|
|
310
|
+
const savedScissorTest = renderer.getScissorTest();
|
|
257
311
|
|
|
258
|
-
|
|
312
|
+
// Clear pooled batch target so skipped probes read as zero
|
|
313
|
+
batchTarget.scissorTest = false;
|
|
314
|
+
batchTarget.viewport.set( 0, 0, 9, totalProbes );
|
|
315
|
+
renderer.setRenderTarget( batchTarget );
|
|
316
|
+
renderer.clear();
|
|
259
317
|
|
|
260
|
-
|
|
318
|
+
// const t0 = performance.now();
|
|
261
319
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
320
|
+
// Phase 1: Render cubemaps and project to SH into batch target
|
|
321
|
+
// Note: set viewport/scissor on the render target directly to avoid pixel ratio scaling
|
|
322
|
+
batchTarget.scissorTest = true;
|
|
265
323
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
324
|
+
// Disable shadow map auto-update during bake — lights don't move between probes.
|
|
325
|
+
// Force one shadow update on the first render so maps are initialized.
|
|
326
|
+
const savedShadowAutoUpdate = renderer.shadowMap.autoUpdate;
|
|
327
|
+
renderer.shadowMap.autoUpdate = false;
|
|
328
|
+
renderer.shadowMap.needsUpdate = true;
|
|
329
|
+
|
|
330
|
+
// Initialize cube texture/render target arrays if retaining
|
|
331
|
+
if ( retainCubemaps ) {
|
|
332
|
+
|
|
333
|
+
if ( this.cubeTextures === null || this.cubeTextures.length !== totalProbes ) {
|
|
334
|
+
|
|
335
|
+
this.cubeTextures = new Array( totalProbes );
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if ( this._cubeRenderTargets === null || this._cubeRenderTargets.length !== totalProbes ) {
|
|
340
|
+
|
|
341
|
+
this._disposeRetainedCubemaps();
|
|
342
|
+
this.cubeTextures = new Array( totalProbes );
|
|
343
|
+
|
|
344
|
+
this._cubeRenderTargets = new Array( totalProbes );
|
|
273
345
|
|
|
274
346
|
}
|
|
275
347
|
|
|
276
348
|
}
|
|
277
349
|
|
|
278
|
-
|
|
350
|
+
for ( let iz = 0; iz < res.z; iz ++ ) {
|
|
279
351
|
|
|
280
|
-
|
|
352
|
+
for ( let iy = 0; iy < res.y; iy ++ ) {
|
|
281
353
|
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
// For each of the 7 packed sub-volumes (texture index t) we write:
|
|
285
|
-
// - A leading padding slice (copy of data slice iz = 0)
|
|
286
|
-
// - All nz data slices (iz = 0 … nz-1)
|
|
287
|
-
// - A trailing padding slice (copy of data slice iz = nz-1)
|
|
288
|
-
//
|
|
289
|
-
// In the atlas the slices for sub-volume t occupy the range:
|
|
290
|
-
// [ t * paddedSlices, t * paddedSlices + paddedSlices - 1 ]
|
|
291
|
-
// where paddedSlices = nz + 2 * ATLAS_PADDING.
|
|
354
|
+
for ( let ix = 0; ix < res.x; ix ++ ) {
|
|
292
355
|
|
|
293
|
-
|
|
356
|
+
const probeIndex = ix + iy * res.x + iz * res.x * res.y;
|
|
294
357
|
|
|
295
|
-
|
|
296
|
-
const rt = this._renderTarget;
|
|
297
|
-
rt.scissorTest = false;
|
|
298
|
-
rt.viewport.set( 0, 0, res.x, res.y );
|
|
358
|
+
this.getProbePosition( ix, iy, iz, _position );
|
|
299
359
|
|
|
300
|
-
|
|
360
|
+
let envMapTexture = cubeRenderTarget.texture;
|
|
301
361
|
|
|
302
|
-
|
|
303
|
-
_repackMaterials[ t ].uniforms.resolution.value.copy( res );
|
|
362
|
+
if ( retainCubemaps ) {
|
|
304
363
|
|
|
305
|
-
|
|
306
|
-
|
|
364
|
+
const retainedTarget = this._ensureRetainedCubeRenderTarget( probeIndex, options );
|
|
365
|
+
const retainedCamera = new CubeCamera(
|
|
366
|
+
options.near !== undefined ? options.near : 0.1,
|
|
367
|
+
options.far !== undefined ? options.far : 100,
|
|
368
|
+
retainedTarget
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
retainedCamera.position.copy( _position );
|
|
372
|
+
retainedCamera.update( renderer, scene );
|
|
373
|
+
|
|
374
|
+
envMapTexture = retainedTarget.texture;
|
|
375
|
+
this.cubeTextures[ probeIndex ] = retainedTarget.texture;
|
|
376
|
+
|
|
377
|
+
} else {
|
|
378
|
+
|
|
379
|
+
cubeCamera.position.copy( _position );
|
|
380
|
+
cubeCamera.update( renderer, scene );
|
|
381
|
+
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// SH projection
|
|
385
|
+
_shMaterial.uniforms.envMap.value = envMapTexture;
|
|
386
|
+
_mesh.material = _shMaterial;
|
|
387
|
+
batchTarget.viewport.set( 0, probeIndex, 9, 1 );
|
|
388
|
+
batchTarget.scissor.set( 0, probeIndex, 9, 1 );
|
|
389
|
+
renderer.setRenderTarget( batchTarget );
|
|
390
|
+
renderer.render( _scene, _camera );
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
renderer.shadowMap.autoUpdate = savedShadowAutoUpdate;
|
|
399
|
+
|
|
400
|
+
// Phase 2: Repack SH data from batch target into the atlas 3D texture (GPU-to-GPU).
|
|
401
|
+
//
|
|
402
|
+
// For each of the 7 packed sub-volumes (texture index t) we write:
|
|
403
|
+
// - A leading padding slice (copy of data slice iz = 0)
|
|
404
|
+
// - All nz data slices (iz = 0 … nz-1)
|
|
405
|
+
// - A trailing padding slice (copy of data slice iz = nz-1)
|
|
406
|
+
//
|
|
407
|
+
// In the atlas the slices for sub-volume t occupy the range:
|
|
408
|
+
// [ t * paddedSlices, t * paddedSlices + paddedSlices - 1 ]
|
|
409
|
+
// where paddedSlices = nz + 2 * ATLAS_PADDING.
|
|
410
|
+
|
|
411
|
+
_ensureRepackResources();
|
|
412
|
+
|
|
413
|
+
const paddedSlices = res.z + 2 * ATLAS_PADDING;
|
|
414
|
+
const rt = this._renderTarget;
|
|
415
|
+
rt.scissorTest = false;
|
|
416
|
+
rt.viewport.set( 0, 0, res.x, res.y );
|
|
417
|
+
|
|
418
|
+
for ( let t = 0; t < 7; t ++ ) {
|
|
419
|
+
|
|
420
|
+
_repackMaterials[ t ].uniforms.batchTexture.value = batchTarget.texture;
|
|
421
|
+
_repackMaterials[ t ].uniforms.resolution.value.copy( res );
|
|
422
|
+
|
|
423
|
+
// Write data slices
|
|
424
|
+
for ( let iz = 0; iz < res.z; iz ++ ) {
|
|
425
|
+
|
|
426
|
+
_repackMaterials[ t ].uniforms.sliceZ.value = iz;
|
|
427
|
+
_mesh.material = _repackMaterials[ t ];
|
|
428
|
+
renderer.setRenderTarget( rt, t * paddedSlices + ATLAS_PADDING + iz );
|
|
429
|
+
renderer.render( _scene, _camera );
|
|
430
|
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Leading padding: copy of data slice iz = 0
|
|
434
|
+
_repackMaterials[ t ].uniforms.sliceZ.value = 0;
|
|
435
|
+
_mesh.material = _repackMaterials[ t ];
|
|
436
|
+
renderer.setRenderTarget( rt, t * paddedSlices );
|
|
437
|
+
renderer.render( _scene, _camera );
|
|
307
438
|
|
|
308
|
-
|
|
439
|
+
// Trailing padding: copy of data slice iz = nz - 1
|
|
440
|
+
_repackMaterials[ t ].uniforms.sliceZ.value = res.z - 1;
|
|
309
441
|
_mesh.material = _repackMaterials[ t ];
|
|
310
|
-
renderer.setRenderTarget( rt, t * paddedSlices + ATLAS_PADDING +
|
|
442
|
+
renderer.setRenderTarget( rt, t * paddedSlices + ATLAS_PADDING + res.z );
|
|
311
443
|
renderer.render( _scene, _camera );
|
|
312
444
|
|
|
313
445
|
}
|
|
314
446
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
renderer.
|
|
319
|
-
renderer.
|
|
447
|
+
// Restore renderer state
|
|
448
|
+
renderer.setRenderTarget( savedRenderTarget );
|
|
449
|
+
renderer.setViewport( _savedViewport );
|
|
450
|
+
renderer.setScissor( _savedScissor );
|
|
451
|
+
renderer.setScissorTest( savedScissorTest );
|
|
320
452
|
|
|
321
|
-
//
|
|
322
|
-
_repackMaterials[ t ].uniforms.sliceZ.value = res.z - 1;
|
|
323
|
-
_mesh.material = _repackMaterials[ t ];
|
|
324
|
-
renderer.setRenderTarget( rt, t * paddedSlices + ATLAS_PADDING + res.z );
|
|
325
|
-
renderer.render( _scene, _camera );
|
|
453
|
+
// console.log( `LightProbeGrid: bake complete ${ ( performance.now() - t0 ).toFixed( 1 ) }ms` );
|
|
326
454
|
|
|
327
455
|
}
|
|
328
456
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
renderer.setViewport( _savedViewport );
|
|
332
|
-
renderer.setScissor( _savedScissor );
|
|
333
|
-
renderer.setScissorTest( savedScissorTest );
|
|
457
|
+
this.visible = wasVisible; // WITH_GENESYS
|
|
458
|
+
// this.visible = true;
|
|
334
459
|
|
|
335
|
-
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// WITH_GENESYS
|
|
463
|
+
/**
|
|
464
|
+
* Ensures a retained cube render target exists for a probe.
|
|
465
|
+
*
|
|
466
|
+
* @private
|
|
467
|
+
* @param {number} probeIndex - The probe index.
|
|
468
|
+
* @param {Object} options - Bake options.
|
|
469
|
+
* @param {boolean} [webgpu=false] - Whether to create a WebGPU cube render target.
|
|
470
|
+
* @return {WebGLCubeRenderTarget|CubeRenderTarget} The retained cube render target.
|
|
471
|
+
*/
|
|
472
|
+
_ensureRetainedCubeRenderTarget( probeIndex, options, webgpu = false ) {
|
|
473
|
+
|
|
474
|
+
const cubemapSize = options.cubemapSize !== undefined ? options.cubemapSize : 8;
|
|
475
|
+
|
|
476
|
+
if ( this._cubeRenderTargetsWebGPU !== null && this._cubeRenderTargetsWebGPU !== webgpu ) {
|
|
477
|
+
|
|
478
|
+
this._disposeRetainedCubemaps();
|
|
479
|
+
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if ( this._cubeRenderTargets === null ) {
|
|
483
|
+
|
|
484
|
+
this._cubeRenderTargets = [];
|
|
485
|
+
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
this._cubeRenderTargetsWebGPU = webgpu;
|
|
489
|
+
|
|
490
|
+
let target = this._cubeRenderTargets[ probeIndex ];
|
|
336
491
|
|
|
337
|
-
|
|
492
|
+
if ( target === undefined || target.width !== cubemapSize || target.height !== cubemapSize ) {
|
|
493
|
+
|
|
494
|
+
if ( target !== undefined ) target.dispose();
|
|
495
|
+
|
|
496
|
+
target = webgpu === true
|
|
497
|
+
? new CubeRenderTarget( cubemapSize, { type: HalfFloatType } )
|
|
498
|
+
: new WebGLCubeRenderTarget( cubemapSize, { type: HalfFloatType } );
|
|
499
|
+
this._cubeRenderTargets[ probeIndex ] = target;
|
|
500
|
+
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return target;
|
|
338
504
|
|
|
339
505
|
}
|
|
340
506
|
|
|
507
|
+
/**
|
|
508
|
+
* Frees retained cube map render targets.
|
|
509
|
+
*
|
|
510
|
+
* @private
|
|
511
|
+
*/
|
|
512
|
+
_disposeRetainedCubemaps() {
|
|
513
|
+
|
|
514
|
+
if ( this._cubeRenderTargets !== null ) {
|
|
515
|
+
|
|
516
|
+
for ( const target of this._cubeRenderTargets ) {
|
|
517
|
+
|
|
518
|
+
if ( target !== undefined ) target.dispose();
|
|
519
|
+
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
this._cubeRenderTargets = null;
|
|
523
|
+
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
this._cubeRenderTargetsWebGPU = null;
|
|
527
|
+
this.cubeTextures = null;
|
|
528
|
+
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Frees retained cube maps used for cube map visualization.
|
|
533
|
+
*/
|
|
534
|
+
disposeRetainedCubemaps() {
|
|
535
|
+
|
|
536
|
+
this._disposeRetainedCubemaps();
|
|
537
|
+
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Bakes all probes with a WebGPU renderer.
|
|
542
|
+
*
|
|
543
|
+
* @private
|
|
544
|
+
* @async
|
|
545
|
+
* @param {WebGPURenderer} renderer - The renderer.
|
|
546
|
+
* @param {Scene} scene - The scene to render.
|
|
547
|
+
* @param {Object} [options] - Bake options.
|
|
548
|
+
* @return {Promise<void>} Resolves when baking has completed.
|
|
549
|
+
*/
|
|
550
|
+
async _bakeWebGPU( renderer, scene, options = {} ) {
|
|
551
|
+
|
|
552
|
+
const cubemapSize = options.cubemapSize !== undefined ? options.cubemapSize : 8;
|
|
553
|
+
const retainCubemaps = options.retainCubemaps === true;
|
|
554
|
+
const { cubeRenderTarget, cubeCamera } = _ensureWebGPUBakeResources( options );
|
|
555
|
+
|
|
556
|
+
this._ensureWebGPUTexture();
|
|
557
|
+
this.updateBoundingBox();
|
|
558
|
+
|
|
559
|
+
const computeNode = _ensureWebGPUComputeResources( cubemapSize, cubeRenderTarget.texture, this.texture );
|
|
560
|
+
const uniforms = _webgpuProbeUniforms;
|
|
561
|
+
|
|
562
|
+
const res = this.resolution;
|
|
563
|
+
const nx = res.x, ny = res.y, nz = res.z;
|
|
564
|
+
const totalProbes = nx * ny * nz;
|
|
565
|
+
const paddedSlices = nz + 2 * ATLAS_PADDING;
|
|
566
|
+
|
|
567
|
+
uniforms.paddedSlices.value = paddedSlices;
|
|
568
|
+
|
|
569
|
+
const savedShadowAutoUpdate = renderer.shadowMap.autoUpdate;
|
|
570
|
+
renderer.shadowMap.autoUpdate = false;
|
|
571
|
+
renderer.shadowMap.needsUpdate = true;
|
|
572
|
+
|
|
573
|
+
// Initialize cube textures array if retaining
|
|
574
|
+
if ( retainCubemaps ) {
|
|
575
|
+
|
|
576
|
+
if ( this.cubeTextures === null || this.cubeTextures.length !== totalProbes ) {
|
|
577
|
+
|
|
578
|
+
this.cubeTextures = new Array( totalProbes );
|
|
579
|
+
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if ( this._cubeRenderTargets === null || this._cubeRenderTargets.length !== totalProbes ) {
|
|
583
|
+
|
|
584
|
+
this._disposeRetainedCubemaps();
|
|
585
|
+
this.cubeTextures = new Array( totalProbes );
|
|
586
|
+
this._cubeRenderTargets = new Array( totalProbes );
|
|
587
|
+
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
for ( let iz = 0; iz < nz; iz ++ ) {
|
|
593
|
+
|
|
594
|
+
for ( let iy = 0; iy < ny; iy ++ ) {
|
|
595
|
+
|
|
596
|
+
for ( let ix = 0; ix < nx; ix ++ ) {
|
|
597
|
+
|
|
598
|
+
const probeIndex = ix + iy * nx + iz * nx * ny;
|
|
599
|
+
|
|
600
|
+
this.getProbePosition( ix, iy, iz, _position );
|
|
601
|
+
cubeCamera.position.copy( _position );
|
|
602
|
+
cubeCamera.update( renderer, scene );
|
|
603
|
+
|
|
604
|
+
if ( retainCubemaps ) {
|
|
605
|
+
|
|
606
|
+
const retainedTarget = this._ensureRetainedCubeRenderTarget( probeIndex, options, true );
|
|
607
|
+
const retainedCamera = new CubeCamera(
|
|
608
|
+
options.near !== undefined ? options.near : 0.1,
|
|
609
|
+
options.far !== undefined ? options.far : 100,
|
|
610
|
+
retainedTarget
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
retainedCamera.position.copy( _position );
|
|
614
|
+
retainedCamera.update( renderer, scene );
|
|
615
|
+
|
|
616
|
+
this.cubeTextures[ probeIndex ] = retainedTarget.texture;
|
|
617
|
+
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
uniforms.probeIx.value = ix;
|
|
621
|
+
uniforms.probeIy.value = iy;
|
|
622
|
+
uniforms.probeIz.value = iz;
|
|
623
|
+
|
|
624
|
+
renderer.compute( computeNode );
|
|
625
|
+
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
renderer.shadowMap.autoUpdate = savedShadowAutoUpdate;
|
|
633
|
+
|
|
634
|
+
// Padding slices (GPU-to-GPU copy of nearest edge data slices).
|
|
635
|
+
const atlas = this.texture;
|
|
636
|
+
|
|
637
|
+
for ( let textureIndex = 0; textureIndex < 7; textureIndex ++ ) {
|
|
638
|
+
|
|
639
|
+
const baseSlice = textureIndex * paddedSlices;
|
|
640
|
+
const dataSlice = baseSlice + ATLAS_PADDING;
|
|
641
|
+
const trailingSlice = dataSlice + nz;
|
|
642
|
+
|
|
643
|
+
renderer.copyTextureToTexture(
|
|
644
|
+
atlas,
|
|
645
|
+
atlas,
|
|
646
|
+
new Box3( new Vector3( 0, 0, dataSlice ), new Vector3( nx, ny, dataSlice + 1 ) ),
|
|
647
|
+
new Vector3( 0, 0, baseSlice )
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
renderer.copyTextureToTexture(
|
|
651
|
+
atlas,
|
|
652
|
+
atlas,
|
|
653
|
+
new Box3( new Vector3( 0, 0, dataSlice + nz - 1 ), new Vector3( nx, ny, dataSlice + nz ) ),
|
|
654
|
+
new Vector3( 0, 0, trailingSlice )
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
await renderer.backend.device.queue.onSubmittedWorkDone();
|
|
660
|
+
|
|
661
|
+
}
|
|
662
|
+
// !WITH_GENESYS
|
|
663
|
+
|
|
341
664
|
/**
|
|
342
665
|
* Ensures the atlas 3D render target exists with the correct dimensions.
|
|
343
666
|
*
|
|
@@ -347,6 +670,15 @@ class LightProbeGrid extends Object3D {
|
|
|
347
670
|
|
|
348
671
|
if ( this._renderTarget !== null ) return;
|
|
349
672
|
|
|
673
|
+
// WITH_GENESYS
|
|
674
|
+
if ( this.texture !== null ) {
|
|
675
|
+
|
|
676
|
+
this.texture.dispose();
|
|
677
|
+
this.texture = null;
|
|
678
|
+
|
|
679
|
+
}
|
|
680
|
+
// !WITH_GENESYS
|
|
681
|
+
|
|
350
682
|
const res = this.resolution;
|
|
351
683
|
const nx = res.x, ny = res.y, nz = res.z;
|
|
352
684
|
|
|
@@ -367,6 +699,54 @@ class LightProbeGrid extends Object3D {
|
|
|
367
699
|
|
|
368
700
|
}
|
|
369
701
|
|
|
702
|
+
// WITH_GENESYS
|
|
703
|
+
/**
|
|
704
|
+
* Ensures the atlas Storage3DTexture exists with the correct dimensions for WebGPU.
|
|
705
|
+
*
|
|
706
|
+
* @private
|
|
707
|
+
*/
|
|
708
|
+
_ensureWebGPUTexture() {
|
|
709
|
+
|
|
710
|
+
if ( this._renderTarget !== null ) {
|
|
711
|
+
|
|
712
|
+
this._renderTarget.dispose();
|
|
713
|
+
this._renderTarget = null;
|
|
714
|
+
this.texture = null;
|
|
715
|
+
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const res = this.resolution;
|
|
719
|
+
const nx = res.x, ny = res.y, nz = res.z;
|
|
720
|
+
const atlasDepth = 7 * ( nz + 2 * ATLAS_PADDING );
|
|
721
|
+
|
|
722
|
+
if ( this.texture !== null ) {
|
|
723
|
+
|
|
724
|
+
const image = this.texture.image;
|
|
725
|
+
|
|
726
|
+
if ( image.width === nx && image.height === ny && image.depth === atlasDepth ) {
|
|
727
|
+
|
|
728
|
+
return;
|
|
729
|
+
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
this.texture.dispose();
|
|
733
|
+
this.texture = null;
|
|
734
|
+
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const texture = new Storage3DTexture( nx, ny, atlasDepth );
|
|
738
|
+
|
|
739
|
+
texture.format = RGBAFormat;
|
|
740
|
+
texture.type = FloatType;
|
|
741
|
+
texture.minFilter = LinearFilter;
|
|
742
|
+
texture.magFilter = LinearFilter;
|
|
743
|
+
texture.generateMipmaps = false;
|
|
744
|
+
|
|
745
|
+
this.texture = texture;
|
|
746
|
+
|
|
747
|
+
}
|
|
748
|
+
// !WITH_GENESYS
|
|
749
|
+
|
|
370
750
|
/**
|
|
371
751
|
* Frees GPU resources.
|
|
372
752
|
*/
|
|
@@ -378,7 +758,14 @@ class LightProbeGrid extends Object3D {
|
|
|
378
758
|
this._renderTarget = null;
|
|
379
759
|
this.texture = null;
|
|
380
760
|
|
|
381
|
-
}
|
|
761
|
+
} else if ( this.texture !== null ) { // WITH_GENESYS
|
|
762
|
+
|
|
763
|
+
this.texture.dispose();
|
|
764
|
+
this.texture = null;
|
|
765
|
+
|
|
766
|
+
} // !WITH_GENESYS
|
|
767
|
+
|
|
768
|
+
this._disposeRetainedCubemaps();
|
|
382
769
|
|
|
383
770
|
}
|
|
384
771
|
|
|
@@ -627,6 +1014,163 @@ function _ensureBakeResources( options ) {
|
|
|
627
1014
|
|
|
628
1015
|
}
|
|
629
1016
|
|
|
1017
|
+
// WITH_GENESYS
|
|
1018
|
+
function _ensureWebGPUBakeResources( options ) {
|
|
1019
|
+
|
|
1020
|
+
const {
|
|
1021
|
+
cubemapSize = 8,
|
|
1022
|
+
near = 0.1,
|
|
1023
|
+
far = 100
|
|
1024
|
+
} = options;
|
|
1025
|
+
|
|
1026
|
+
if ( _webgpuCubeRenderTarget === null || cubemapSize !== _webgpuCachedCubemapSize || near !== _webgpuCachedNear || far !== _webgpuCachedFar ) {
|
|
1027
|
+
|
|
1028
|
+
if ( _webgpuCubeRenderTarget !== null ) _webgpuCubeRenderTarget.dispose();
|
|
1029
|
+
|
|
1030
|
+
_webgpuCubeRenderTarget = new CubeRenderTarget( cubemapSize, { type: HalfFloatType } );
|
|
1031
|
+
_webgpuCubeCamera = new CubeCamera( near, far, _webgpuCubeRenderTarget );
|
|
1032
|
+
_webgpuCachedCubemapSize = cubemapSize;
|
|
1033
|
+
_webgpuCachedNear = near;
|
|
1034
|
+
_webgpuCachedFar = far;
|
|
1035
|
+
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
return { cubeRenderTarget: _webgpuCubeRenderTarget, cubeCamera: _webgpuCubeCamera };
|
|
1039
|
+
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function _ensureWebGPUComputeResources( cubemapSize, envMapTexture, atlasTexture ) {
|
|
1043
|
+
|
|
1044
|
+
if (
|
|
1045
|
+
_webgpuComputeNode !== null &&
|
|
1046
|
+
_webgpuComputeCubemapSize === cubemapSize &&
|
|
1047
|
+
_webgpuComputeAtlas === atlasTexture &&
|
|
1048
|
+
_webgpuComputeEnvMap === envMapTexture
|
|
1049
|
+
) {
|
|
1050
|
+
|
|
1051
|
+
return _webgpuComputeNode;
|
|
1052
|
+
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
_webgpuComputeCubemapSize = cubemapSize;
|
|
1056
|
+
_webgpuComputeAtlas = atlasTexture;
|
|
1057
|
+
_webgpuComputeEnvMap = envMapTexture;
|
|
1058
|
+
|
|
1059
|
+
const probeIx = uniform( 'uint' );
|
|
1060
|
+
const probeIy = uniform( 'uint' );
|
|
1061
|
+
const probeIz = uniform( 'uint' );
|
|
1062
|
+
const paddedSlices = uniform( 'uint' );
|
|
1063
|
+
|
|
1064
|
+
_webgpuProbeUniforms = { probeIx, probeIy, probeIz, paddedSlices };
|
|
1065
|
+
|
|
1066
|
+
const atlas = storageTexture( atlasTexture );
|
|
1067
|
+
|
|
1068
|
+
const projectProbe = Fn( () => {
|
|
1069
|
+
|
|
1070
|
+
const accum0 = vec3( 0 ).toVar();
|
|
1071
|
+
const accum1 = vec3( 0 ).toVar();
|
|
1072
|
+
const accum2 = vec3( 0 ).toVar();
|
|
1073
|
+
const accum3 = vec3( 0 ).toVar();
|
|
1074
|
+
const accum4 = vec3( 0 ).toVar();
|
|
1075
|
+
const accum5 = vec3( 0 ).toVar();
|
|
1076
|
+
const accum6 = vec3( 0 ).toVar();
|
|
1077
|
+
const accum7 = vec3( 0 ).toVar();
|
|
1078
|
+
const accum8 = vec3( 0 ).toVar();
|
|
1079
|
+
const totalWeight = float( 0 ).toVar();
|
|
1080
|
+
const pixelSize = float( 2 ).div( float( cubemapSize ) );
|
|
1081
|
+
|
|
1082
|
+
Loop(
|
|
1083
|
+
{ end: 6, name: 'face', type: 'int' },
|
|
1084
|
+
{ end: cubemapSize, name: 'iy', type: 'int' },
|
|
1085
|
+
{ end: cubemapSize, name: 'ix', type: 'int' },
|
|
1086
|
+
( { face, iy, ix } ) => {
|
|
1087
|
+
|
|
1088
|
+
const col = convert( ix, 'float' ).add( 0.5 ).mul( pixelSize ).sub( 1.0 );
|
|
1089
|
+
const row = float( 1 ).sub( convert( iy, 'float' ).add( 0.5 ).mul( pixelSize ) );
|
|
1090
|
+
|
|
1091
|
+
const dirX = select( face.equal( int( 0 ) ), float( 1 ), select( face.equal( int( 1 ) ), float( - 1 ), select( face.equal( int( 5 ) ), col.negate(), col ) ) );
|
|
1092
|
+
const dirY = select( face.equal( int( 2 ) ), float( 1 ), select( face.equal( int( 3 ) ), float( - 1 ), row ) );
|
|
1093
|
+
const dirZ = select( face.equal( int( 0 ) ), col.negate(), select( face.equal( int( 1 ) ), col, select( face.equal( int( 2 ) ), row.negate(), select( face.equal( int( 3 ) ), row, select( face.equal( int( 4 ) ), float( 1 ), float( - 1 ) ) ) ) ) );
|
|
1094
|
+
const coord = vec3( dirX, dirY, dirZ );
|
|
1095
|
+
|
|
1096
|
+
const lengthSq = coord.dot( coord );
|
|
1097
|
+
const weight = float( 4 ).div( lengthSq.sqrt().mul( lengthSq ) );
|
|
1098
|
+
totalWeight.addAssign( weight );
|
|
1099
|
+
|
|
1100
|
+
const dir = coord.normalize();
|
|
1101
|
+
// Pass explicit direction — omitting UV would default to reflectVector (needs camera).
|
|
1102
|
+
const cw = cubeTexture( envMapTexture, coord ).rgb.mul( weight );
|
|
1103
|
+
|
|
1104
|
+
accum0.addAssign( cw.mul( 0.282095 ) );
|
|
1105
|
+
accum1.addAssign( cw.mul( 0.488603 ).mul( dir.y ) );
|
|
1106
|
+
accum2.addAssign( cw.mul( 0.488603 ).mul( dir.z ) );
|
|
1107
|
+
accum3.addAssign( cw.mul( 0.488603 ).mul( dir.x ) );
|
|
1108
|
+
accum4.addAssign( cw.mul( 1.092548 ).mul( dir.x ).mul( dir.y ) );
|
|
1109
|
+
accum5.addAssign( cw.mul( 1.092548 ).mul( dir.y ).mul( dir.z ) );
|
|
1110
|
+
accum6.addAssign( cw.mul( 0.315392 ).mul( dir.z.mul( dir.z ).mul( 3 ).sub( 1 ) ) );
|
|
1111
|
+
accum7.addAssign( cw.mul( 1.092548 ).mul( dir.x ).mul( dir.z ) );
|
|
1112
|
+
accum8.addAssign( cw.mul( 0.546274 ).mul( dir.x.mul( dir.x ).sub( dir.y.mul( dir.y ) ) ) );
|
|
1113
|
+
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
);
|
|
1117
|
+
|
|
1118
|
+
const norm = float( 4 ).mul( PI ).div( totalWeight );
|
|
1119
|
+
const c0 = accum0.mul( norm );
|
|
1120
|
+
const c1 = accum1.mul( norm );
|
|
1121
|
+
const c2 = accum2.mul( norm );
|
|
1122
|
+
const c3 = accum3.mul( norm );
|
|
1123
|
+
const c4 = accum4.mul( norm );
|
|
1124
|
+
const c5 = accum5.mul( norm );
|
|
1125
|
+
const c6 = accum6.mul( norm );
|
|
1126
|
+
const c7 = accum7.mul( norm );
|
|
1127
|
+
const c8 = accum8.mul( norm );
|
|
1128
|
+
|
|
1129
|
+
Loop( { end: 7, name: 't', type: 'int' }, ( { t } ) => {
|
|
1130
|
+
|
|
1131
|
+
const atlasZ = paddedSlices.mul( t ).add( 1 ).add( probeIz );
|
|
1132
|
+
|
|
1133
|
+
If( t.equal( int( 0 ) ), () => {
|
|
1134
|
+
|
|
1135
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c0.x, c0.y, c0.z, c1.x ) ).toWriteOnly();
|
|
1136
|
+
|
|
1137
|
+
} ).ElseIf( t.equal( int( 1 ) ), () => {
|
|
1138
|
+
|
|
1139
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c1.y, c1.z, c2.x, c2.y ) ).toWriteOnly();
|
|
1140
|
+
|
|
1141
|
+
} ).ElseIf( t.equal( int( 2 ) ), () => {
|
|
1142
|
+
|
|
1143
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c2.z, c3.x, c3.y, c3.z ) ).toWriteOnly();
|
|
1144
|
+
|
|
1145
|
+
} ).ElseIf( t.equal( int( 3 ) ), () => {
|
|
1146
|
+
|
|
1147
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c4.x, c4.y, c4.z, c5.x ) ).toWriteOnly();
|
|
1148
|
+
|
|
1149
|
+
} ).ElseIf( t.equal( int( 4 ) ), () => {
|
|
1150
|
+
|
|
1151
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c5.y, c5.z, c6.x, c6.y ) ).toWriteOnly();
|
|
1152
|
+
|
|
1153
|
+
} ).ElseIf( t.equal( int( 5 ) ), () => {
|
|
1154
|
+
|
|
1155
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c6.z, c7.x, c7.y, c7.z ) ).toWriteOnly();
|
|
1156
|
+
|
|
1157
|
+
} ).Else( () => {
|
|
1158
|
+
|
|
1159
|
+
textureStore( atlas, uvec3( probeIx, probeIy, atlasZ ), vec4( c8.x, c8.y, c8.z, float( 0 ) ) ).toWriteOnly();
|
|
1160
|
+
|
|
1161
|
+
} );
|
|
1162
|
+
|
|
1163
|
+
} );
|
|
1164
|
+
|
|
1165
|
+
} );
|
|
1166
|
+
|
|
1167
|
+
_webgpuComputeNode = projectProbe().compute( 1 );
|
|
1168
|
+
|
|
1169
|
+
return _webgpuComputeNode;
|
|
1170
|
+
|
|
1171
|
+
}
|
|
1172
|
+
// !WITH_GENESYS
|
|
1173
|
+
|
|
630
1174
|
function _ensureBatchTarget( totalProbes ) {
|
|
631
1175
|
|
|
632
1176
|
if ( _batchTarget === null || _batchTargetProbes !== totalProbes ) {
|