@driftengine/core 4.8.4 → 4.8.6
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/CHANGELOG.json +4332 -4250
- package/dist/build/shaderComments.js +0 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +11 -1
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/areaLights.d.ts +19 -0
- package/dist/render/areaLights.js +20 -0
- package/dist/render/backend/api.d.ts +13 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +74 -1
- package/dist/render/backend/webgl2/renderer.js +229 -37
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +80 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +58 -7
- package/dist/render/backend/webgpu/renderer.js +329 -78
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +2 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +18 -4
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +55 -1
- package/dist/render/clusteredLights.js +182 -8
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/instanceCull.js +7 -0
- package/dist/render/instances.d.ts +32 -10
- package/dist/render/instances.js +17 -8
- package/dist/render/lightBudget.d.ts +26 -1
- package/dist/render/lightBudget.js +26 -3
- package/dist/render/lightmap.d.ts +102 -0
- package/dist/render/lightmap.js +118 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +11 -0
- package/dist/render/particlePool.js +2 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +96 -0
- package/dist/render/shaders/flat/index.js +30 -4
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +107 -105
- package/dist/render/shaders/flat/models.d.ts +16 -4
- package/dist/render/shaders/flat/models.js +64 -4
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +74 -37
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1631 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1669 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- package/dist/render/shaders/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/surfaceModel.d.ts +21 -1
- package/dist/render/surfaceModel.js +22 -0
- package/dist/render/surfaceTexture.d.ts +14 -1
- package/dist/render/surfaceTexture.js +15 -2
- package/package.json +6 -6
- package/src/build/shaderComments.ts +0 -0
- package/src/index.ts +20 -1
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/areaLights.ts +39 -0
- package/src/render/backend/api.ts +14 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +292 -35
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +118 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +396 -95
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +26 -5
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +221 -7
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/instanceCull.ts +5 -0
- package/src/render/instances.ts +40 -11
- package/src/render/lightBudget.ts +48 -2
- package/src/render/lightmap.ts +202 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +13 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/flat/areaLight.ts +96 -0
- package/src/render/shaders/flat/index.ts +30 -4
- package/src/render/shaders/flat/main.ts +107 -105
- package/src/render/shaders/flat/models.ts +73 -8
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +74 -37
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/flat.wgsl.ts +1669 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/surfaceModel.ts +36 -1
- package/src/render/surfaceTexture.ts +41 -3
|
@@ -59,12 +59,23 @@ import type {
|
|
|
59
59
|
} from '../webgl2/renderer.ts';
|
|
60
60
|
import type { IncrementalMeshHandle, MeshOptions } from '../api.ts';
|
|
61
61
|
import { resolveAtmosphere, type ResolvedAtmosphere } from '../../atmosphere.ts';
|
|
62
|
+
import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.ts';
|
|
63
|
+
import {
|
|
64
|
+
lightmapTexels,
|
|
65
|
+
warnRegionsWithoutPage,
|
|
66
|
+
warnUnpagedLightmap,
|
|
67
|
+
withLightmapUvs,
|
|
68
|
+
} from '../../lightmap.ts';
|
|
69
|
+
import type { LightmapPage } from '../../lightmap.ts';
|
|
62
70
|
import {
|
|
63
71
|
BLOOM_FILTER_RADIUS_UV,
|
|
64
72
|
BLOOM_LEVELS,
|
|
65
73
|
bloomLevelSizes,
|
|
66
74
|
bloomThresholdOf,
|
|
67
75
|
bloomProfileWarning,
|
|
76
|
+
defaultBloomResponse,
|
|
77
|
+
resolveBloomResponse,
|
|
78
|
+
type BloomResponse,
|
|
68
79
|
} from '../../bloomChain.ts';
|
|
69
80
|
import {
|
|
70
81
|
createResolvedWater,
|
|
@@ -221,7 +232,6 @@ import {
|
|
|
221
232
|
AO_FRAG_WGSL,
|
|
222
233
|
AO_TEXTURES,
|
|
223
234
|
AO_UNIFORMS,
|
|
224
|
-
ADDITIVE_BLEND,
|
|
225
235
|
BLOOM_DOWNSAMPLE_FIELDS,
|
|
226
236
|
BLOOM_DOWNSAMPLE_FRAG_WGSL,
|
|
227
237
|
BLOOM_PREFILTER_FIELDS,
|
|
@@ -230,6 +240,7 @@ import {
|
|
|
230
240
|
BLOOM_TEXTURES,
|
|
231
241
|
BLOOM_UNIFORMS,
|
|
232
242
|
BLOOM_UPSAMPLE_FIELDS,
|
|
243
|
+
TINTED_ADD_BLEND,
|
|
233
244
|
BLOOM_UPSAMPLE_FRAG_WGSL,
|
|
234
245
|
RESOLVED_DEPTH_FORMAT,
|
|
235
246
|
RUSH_FRAG_FIELDS,
|
|
@@ -325,7 +336,7 @@ import type { BoltSegments } from '../../boltPool.ts';
|
|
|
325
336
|
import type { LineSegments } from '../../linePoints.ts';
|
|
326
337
|
import { buildPlumeGeometry } from '../../plumeGeometry.ts';
|
|
327
338
|
import type { ParticleMaterial } from '../../particleMaterial.ts';
|
|
328
|
-
import type
|
|
339
|
+
import { facingCode, type ParticleBatchOptions } from '../../particleBatch.ts';
|
|
329
340
|
import type { ParticleInstances } from '../../particlePool.ts';
|
|
330
341
|
import { PARTICLE_INDICES } from '../../shaders/particle.ts';
|
|
331
342
|
import type { PlumeOptions, PlumePlacement } from '../../plumeRenderer.ts';
|
|
@@ -439,14 +450,17 @@ import {
|
|
|
439
450
|
type GpuPlumes,
|
|
440
451
|
} from './plumePass.ts';
|
|
441
452
|
import {
|
|
453
|
+
PARTICLE_INSTANCE_FLOATS,
|
|
442
454
|
PARTICLE_VERT_FIELDS,
|
|
443
455
|
createGpuParticles,
|
|
444
456
|
createParticleBindGroupLayout,
|
|
457
|
+
createSpriteBindGroup,
|
|
445
458
|
packInstances,
|
|
446
459
|
particleFragBindings,
|
|
447
460
|
particlePipeline,
|
|
448
461
|
type GpuParticles,
|
|
449
462
|
} from './particlePass.ts';
|
|
463
|
+
import { sortBackToFront } from '../../particleSort.ts';
|
|
450
464
|
import {
|
|
451
465
|
WIND_STREAK_FRAG_FIELDS,
|
|
452
466
|
WIND_STREAK_FRAG_SIZE,
|
|
@@ -528,6 +542,9 @@ const INDIRECT_PROBE_SPACING = 2;
|
|
|
528
542
|
|
|
529
543
|
/** What the trace is given when a frame never said what was lighting it. */
|
|
530
544
|
const NO_SUN: readonly number[] = [0, 0, 0];
|
|
545
|
+
|
|
546
|
+
/** The halves of a skin draw the spread keeps, in the order they are drawn. See `keepSkinDiffuse`. */
|
|
547
|
+
const SKIN_KEPT_HALVES = ['diffuse', 'albedo'] as const;
|
|
531
548
|
import type { ComposedField } from './fieldCompose.ts';
|
|
532
549
|
import type { FieldSource } from '../../gi/globalField.ts';
|
|
533
550
|
import {
|
|
@@ -569,7 +586,9 @@ import { ClusterBinner } from './clusterBinner.ts';
|
|
|
569
586
|
import {
|
|
570
587
|
clusteredMode,
|
|
571
588
|
createClusterLightSet,
|
|
589
|
+
clusteredLightTotal,
|
|
572
590
|
fillClusterLightSet,
|
|
591
|
+
lightChannelsOf,
|
|
573
592
|
} from '../../clusteredLights.ts';
|
|
574
593
|
import {
|
|
575
594
|
computeAt,
|
|
@@ -1224,12 +1243,17 @@ const DECAL_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
|
1224
1243
|
*/
|
|
1225
1244
|
const SSR_SLOT = Math.ceil(SSR_FRAG_SIZE / DYNAMIC_UNIFORM_ALIGNMENT) * DYNAMIC_UNIFORM_ALIGNMENT;
|
|
1226
1245
|
const BLOOM_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
1227
|
-
const BLOOM_SLOTS = BLOOM_LEVELS +
|
|
1246
|
+
const BLOOM_SLOTS = BLOOM_LEVELS + 2;
|
|
1228
1247
|
/** One slot per blur axis, aligned like every other dynamic-offset block here. */
|
|
1229
1248
|
const AO_BLUR_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
1230
1249
|
|
|
1231
|
-
/**
|
|
1250
|
+
/**
|
|
1251
|
+
* The tent radius every upsample draw binds, with white as the level it reads is multiplied by;
|
|
1252
|
+
* and the same for the first draw up, which multiplies the deepest level by that level's tint.
|
|
1253
|
+
* Every other level's tint is the blend constant of the draw adding onto it. See `BloomResponse`.
|
|
1254
|
+
*/
|
|
1232
1255
|
const BLOOM_UPSAMPLE_SLOT = BLOOM_LEVELS;
|
|
1256
|
+
const BLOOM_UPSAMPLE_DEEPEST_SLOT = BLOOM_LEVELS + 1;
|
|
1233
1257
|
|
|
1234
1258
|
/**
|
|
1235
1259
|
* What one particle material's fragment stage needs a bind group *shaped* like, built once and
|
|
@@ -1448,7 +1472,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1448
1472
|
* Held rather than passed, like every other per-draw state here: `setSkinPalette` chooses and
|
|
1449
1473
|
* the following draws use it, which is the shape `setMaterial` already has.
|
|
1450
1474
|
*/
|
|
1451
|
-
private readonly skinPalettes
|
|
1475
|
+
private readonly skinPalettes: SkinPaletteRing;
|
|
1452
1476
|
private readonly skinnedBindGroupLayout: GPUBindGroupLayout;
|
|
1453
1477
|
private readonly morphedBindGroupLayout: GPUBindGroupLayout;
|
|
1454
1478
|
private readonly bothBindGroupLayout: GPUBindGroupLayout;
|
|
@@ -1900,6 +1924,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1900
1924
|
/** Whether this frame has spread its skin: once, at its first blended draw or before its end. */
|
|
1901
1925
|
private skinScattered = false;
|
|
1902
1926
|
private readonly skinInverseProjection = mat4.create();
|
|
1927
|
+
/** Scratch for a sprite's depth conversion, so a particle draw allocates nothing. */
|
|
1928
|
+
private readonly spriteInverseProjection = mat4.create();
|
|
1929
|
+
/** Said once: a sprite asked for a soft edge where no depth copy could be had. */
|
|
1930
|
+
private warnedSpriteDepth = false;
|
|
1931
|
+
/** Said once: more rectangles than the fixed arm shades, and no froxel table for the rest. */
|
|
1932
|
+
private warnedAreaOverflow = false;
|
|
1933
|
+
/** What a sprite's depth binding holds where no copy was taken. See `spriteDepthStandIn`. */
|
|
1934
|
+
private spriteDepthEmpty: GPUTextureView | null = null;
|
|
1935
|
+
/** The samplers every sprite batch binds: its image filtered, the depth copy texel for texel. */
|
|
1936
|
+
private spriteLinearSampler: GPUSampler | null = null;
|
|
1937
|
+
private spriteNearestSampler: GPUSampler | null = null;
|
|
1903
1938
|
private sceneColorView: GPUTextureView | null = null;
|
|
1904
1939
|
/** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
|
|
1905
1940
|
private sceneColorMsaa: GPUTexture | null = null;
|
|
@@ -2905,6 +2940,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2905
2940
|
);
|
|
2906
2941
|
}
|
|
2907
2942
|
|
|
2943
|
+
/** A baked lightmap page, as a `lightmapModel` material's `modelMap`. See `RendererApi`. */
|
|
2944
|
+
createLightmap(page: LightmapPage): GpuSurfaceTexture {
|
|
2945
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, lightmapTexels(page), {
|
|
2946
|
+
wrap: 'clamp',
|
|
2947
|
+
mipmap: false,
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2908
2951
|
/**
|
|
2909
2952
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2910
2953
|
*
|
|
@@ -3160,6 +3203,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3160
3203
|
this.materialModel = material?.model ?? null;
|
|
3161
3204
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3162
3205
|
const model = material?.model;
|
|
3206
|
+
warnUnpagedLightmap(model, modelMap);
|
|
3163
3207
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
3164
3208
|
this.skinScatter.setProfile(
|
|
3165
3209
|
model.profile,
|
|
@@ -3186,6 +3230,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3186
3230
|
f[cut] = cutoff;
|
|
3187
3231
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
3188
3232
|
f[cut + 2] = this.cutoutFrame;
|
|
3233
|
+
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
3234
|
+
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
3189
3235
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
3190
3236
|
/* 0 is no map, and gates the whole block in the shader. See `uNormalStrength`. */
|
|
3191
3237
|
f[this.materialField('uNormalStrength')] =
|
|
@@ -3340,17 +3386,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3340
3386
|
);
|
|
3341
3387
|
}
|
|
3342
3388
|
|
|
3389
|
+
/**
|
|
3390
|
+
* The ambient the following draws take, nine spherical-harmonic coefficients or null for the
|
|
3391
|
+
* frame's. See `Renderer.setAmbientSH`: material state, so the next draw takes a slot of its own.
|
|
3392
|
+
*/
|
|
3393
|
+
setAmbientSH(coefficients: ArrayLike<number> | null): void {
|
|
3394
|
+
if (this.surface.lost) return;
|
|
3395
|
+
packAmbientSH(coefficients, this.ambientSH);
|
|
3396
|
+
this.material((f) => f.set(this.ambientSH, this.materialField('uAmbientSH')));
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3343
3399
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
3344
3400
|
setSurfaceReflectivity(amount: number): void {
|
|
3345
3401
|
this.material((f) => {
|
|
3346
|
-
f[this.materialField('
|
|
3402
|
+
f[this.materialField('uEnvironmentDials') + 1] = Math.min(1, Math.max(0, amount));
|
|
3347
3403
|
});
|
|
3348
3404
|
}
|
|
3349
3405
|
|
|
3350
3406
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
3351
3407
|
setEnvironmentGain(gain: number): void {
|
|
3352
3408
|
this.material((f) => {
|
|
3353
|
-
f[this.materialField('
|
|
3409
|
+
f[this.materialField('uEnvironmentDials') + 2] = Math.max(0, gain);
|
|
3354
3410
|
});
|
|
3355
3411
|
}
|
|
3356
3412
|
|
|
@@ -3390,7 +3446,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3390
3446
|
setSurfaceRelief(amount: number, cyclesPerMetre = 60): void {
|
|
3391
3447
|
this.material((f) => {
|
|
3392
3448
|
f[this.materialField('uRelief')] = Math.min(1, Math.max(0, amount));
|
|
3393
|
-
f[this.materialField('
|
|
3449
|
+
f[this.materialField('uRelief') + 1] = Math.max(0.01, cyclesPerMetre);
|
|
3394
3450
|
});
|
|
3395
3451
|
}
|
|
3396
3452
|
|
|
@@ -3410,7 +3466,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3410
3466
|
/** Scale the emissive of the following draws. See `Renderer.setEmissiveGain` for why. */
|
|
3411
3467
|
setEmissiveGain(gain: number): void {
|
|
3412
3468
|
this.material((f) => {
|
|
3413
|
-
f[this.materialField('
|
|
3469
|
+
f[this.materialField('uEmission')] = gain;
|
|
3414
3470
|
});
|
|
3415
3471
|
}
|
|
3416
3472
|
|
|
@@ -4035,6 +4091,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4035
4091
|
*/
|
|
4036
4092
|
createParticles(capacity: number, options: ParticleBatchOptions): GpuParticles {
|
|
4037
4093
|
const layout = this.particleLayoutFor(options.material);
|
|
4094
|
+
const texture = (options.texture ?? null) as unknown as GpuSurfaceTexture | null;
|
|
4095
|
+
if (options.material === 'sprite' && texture === null) {
|
|
4096
|
+
throw new Error("createParticles: a 'sprite' draws an image, and was given no `texture`");
|
|
4097
|
+
}
|
|
4038
4098
|
return createGpuParticles(
|
|
4039
4099
|
this.surface.device,
|
|
4040
4100
|
capacity,
|
|
@@ -4048,6 +4108,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4048
4108
|
layout.layout,
|
|
4049
4109
|
layout.fields,
|
|
4050
4110
|
layout.fragSize,
|
|
4111
|
+
{
|
|
4112
|
+
texture: texture?.view ?? null,
|
|
4113
|
+
cells: options.cells ?? [1, 1],
|
|
4114
|
+
blendCells: options.blendCells === true,
|
|
4115
|
+
softDepth: Math.max(0, options.softDepth ?? 0),
|
|
4116
|
+
cameraFade: Math.max(0, options.cameraFade ?? 0),
|
|
4117
|
+
cameraOffset: options.cameraOffset ?? 0,
|
|
4118
|
+
sort: options.sort === true,
|
|
4119
|
+
},
|
|
4051
4120
|
);
|
|
4052
4121
|
}
|
|
4053
4122
|
|
|
@@ -4099,11 +4168,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4099
4168
|
return;
|
|
4100
4169
|
}
|
|
4101
4170
|
batch.drawnInEncoder = this.encoderEpoch;
|
|
4102
|
-
|
|
4171
|
+
/* Farthest first where the batch asked; see `particleSort.ts`. */
|
|
4172
|
+
const drawn = batch.order === null ? data : sortBackToFront(data, camera.position, batch.order);
|
|
4173
|
+
const live = packInstances(batch, drawn);
|
|
4103
4174
|
if (live === 0) return;
|
|
4104
4175
|
|
|
4105
4176
|
const device = this.surface.device;
|
|
4106
|
-
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live *
|
|
4177
|
+
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live * PARTICLE_INSTANCE_FLOATS);
|
|
4107
4178
|
|
|
4108
4179
|
const v = batch.vertFloats;
|
|
4109
4180
|
const atV = (name: string): number => (PARTICLE_VERT_FIELDS[name]?.offset ?? -4) / 4;
|
|
@@ -4111,7 +4182,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4111
4182
|
v.set(this.correctedViewProj, atV('uViewProj'));
|
|
4112
4183
|
v.set(camera.position, atV('uCameraPos'));
|
|
4113
4184
|
v[atV('uStretchSec')] = batch.stretchSec;
|
|
4114
|
-
v[atV('uCameraFacing')] = batch.facing
|
|
4185
|
+
v[atV('uCameraFacing')] = facingCode(batch.facing);
|
|
4186
|
+
v[atV('uCameraOffset')] = batch.cameraOffset;
|
|
4115
4187
|
|
|
4116
4188
|
const f = batch.fragFloats;
|
|
4117
4189
|
const i = batch.fragInts;
|
|
@@ -4163,9 +4235,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4163
4235
|
i[atF('uOutputTransform')] = this.gradeCode();
|
|
4164
4236
|
f[atF('uOutputExposure')] = this.gradeExposure();
|
|
4165
4237
|
|
|
4238
|
+
const spriteGroup = batch.material === 'sprite' ? this.spriteFades(batch, camera, f) : null;
|
|
4239
|
+
|
|
4166
4240
|
device.queue.writeBuffer(batch.vertUniforms, 0, batch.vertStaging);
|
|
4167
4241
|
device.queue.writeBuffer(batch.fragUniforms, 0, batch.fragStaging);
|
|
4168
4242
|
|
|
4243
|
+
const group = spriteGroup ?? batch.bindGroup;
|
|
4244
|
+
if (group === null) return;
|
|
4169
4245
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
4170
4246
|
const particlePipe = particlePipeline(
|
|
4171
4247
|
this.blendedPipelines(),
|
|
@@ -4177,7 +4253,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4177
4253
|
const particleCommand = this.recordBlended(0);
|
|
4178
4254
|
if (particleCommand !== null) {
|
|
4179
4255
|
particleCommand.pipeline = particlePipe;
|
|
4180
|
-
particleCommand.bindGroup =
|
|
4256
|
+
particleCommand.bindGroup = group;
|
|
4181
4257
|
particleCommand.vertexBuffers[0] = batch.corners;
|
|
4182
4258
|
particleCommand.vertexBuffers[1] = batch.instances;
|
|
4183
4259
|
particleCommand.vertexCount = 2;
|
|
@@ -4189,7 +4265,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4189
4265
|
const pass = this.openPass();
|
|
4190
4266
|
if (pass === null) return;
|
|
4191
4267
|
pass.setPipeline(particlePipe);
|
|
4192
|
-
pass.setBindGroup(0,
|
|
4268
|
+
pass.setBindGroup(0, group);
|
|
4193
4269
|
pass.setVertexBuffer(0, batch.corners);
|
|
4194
4270
|
pass.setVertexBuffer(1, batch.instances);
|
|
4195
4271
|
pass.setIndexBuffer(batch.indexBuffer, 'uint32');
|
|
@@ -4197,6 +4273,103 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4197
4273
|
}
|
|
4198
4274
|
}
|
|
4199
4275
|
|
|
4276
|
+
/**
|
|
4277
|
+
* A sprite's flipbook and fades into its fragment block `f`, and the bind group holding the
|
|
4278
|
+
* frame's depth copy its soft edge reads — built again only when that copy is replaced.
|
|
4279
|
+
*
|
|
4280
|
+
* **The copy is the volume's**: `takeVolumeDepth` ends the frame's pass, resolves the opaque
|
|
4281
|
+
* depth and reopens, once a frame whoever asks. Only in the frame's own picture: in a mirror or
|
|
4282
|
+
* a probe's face it would be another view's depth, and the edge stays hard there, as on WebGL2.
|
|
4283
|
+
* Without a composite there is no copy, and the renderer says so once.
|
|
4284
|
+
*/
|
|
4285
|
+
private spriteFades(batch: GpuParticles, camera: Camera, f: Float32Array): GPUBindGroup | null {
|
|
4286
|
+
const fields = batch.fields;
|
|
4287
|
+
const atF = (name: string): number => (fields[name]?.offset ?? -4) / 4;
|
|
4288
|
+
let depth: GPUTextureView | null = null;
|
|
4289
|
+
if (batch.softDepth > 0 && !this.reflectionPassActive && !this.probePassActive) {
|
|
4290
|
+
if (this.openPass() !== null && this.takeVolumeDepth()) depth = this.resolvedDepthView;
|
|
4291
|
+
if (depth === null && !this.warnedSpriteDepth) {
|
|
4292
|
+
this.warnedSpriteDepth = true;
|
|
4293
|
+
console.warn(
|
|
4294
|
+
"WebGPU: a 'sprite' asked for a soft edge, which reads a copy of the frame's depth, and " +
|
|
4295
|
+
'this profile keeps none — screenEffects is off. The edge is hard.',
|
|
4296
|
+
);
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
f[atF('uCells')] = batch.cells[0];
|
|
4300
|
+
f[atF('uCells') + 1] = batch.cells[1];
|
|
4301
|
+
f[atF('uCells') + 2] = batch.blendCells ? 1 : 0;
|
|
4302
|
+
f[atF('uFade')] = batch.softDepth;
|
|
4303
|
+
f[atF('uFade') + 1] = batch.cameraFade;
|
|
4304
|
+
f[atF('uFade') + 2] = depth === null ? 0 : 1;
|
|
4305
|
+
mat4.invert(this.spriteInverseProjection, camera.projection);
|
|
4306
|
+
const inverse = this.spriteInverseProjection;
|
|
4307
|
+
f[atF('uDepthToViewZ')] = inverse[10] ?? 0;
|
|
4308
|
+
f[atF('uDepthToViewZ') + 1] = inverse[14] ?? 0;
|
|
4309
|
+
f[atF('uDepthToViewZ') + 2] = inverse[11] ?? 0;
|
|
4310
|
+
f[atF('uDepthToViewZ') + 3] = inverse[15] ?? 1;
|
|
4311
|
+
/*
|
|
4312
|
+
* The pixels a fragment's own coordinate counts in: the output's where blended draws land
|
|
4313
|
+
* after reconstruction, the scene's otherwise. The copy is sampled by the fraction, so its own
|
|
4314
|
+
* size does not matter. Read off the canvas rather than `sceneSize`, which builds an object.
|
|
4315
|
+
*/
|
|
4316
|
+
const { canvas } = this.surface;
|
|
4317
|
+
const ratio = this.drawsLate() || !this.reconstructionWanted ? 1 : this.quality.reconstruction;
|
|
4318
|
+
/* `reconRenderSize`'s own rounding — up, from the floored output — without its object. */
|
|
4319
|
+
const width = Math.max(1, Math.floor(canvas.width));
|
|
4320
|
+
const height = Math.max(1, Math.floor(canvas.height));
|
|
4321
|
+
f[atF('uViewport')] = ratio > 1 ? Math.max(1, Math.ceil(width / ratio)) : width;
|
|
4322
|
+
f[atF('uViewport') + 1] = ratio > 1 ? Math.max(1, Math.ceil(height / ratio)) : height;
|
|
4323
|
+
|
|
4324
|
+
/* Something single-sampled and unfilterable stands in where there is no copy to read. */
|
|
4325
|
+
const bound = depth ?? this.spriteDepthStandIn();
|
|
4326
|
+
if (batch.spriteGroup === null || batch.spriteGroupDepth !== bound) {
|
|
4327
|
+
batch.spriteGroup = createSpriteBindGroup(
|
|
4328
|
+
this.surface.device,
|
|
4329
|
+
this.particleLayoutFor('sprite').layout,
|
|
4330
|
+
batch,
|
|
4331
|
+
this.particleLayoutFor('sprite').fragSize,
|
|
4332
|
+
bound,
|
|
4333
|
+
this.spriteSampler(true),
|
|
4334
|
+
this.spriteSampler(false),
|
|
4335
|
+
);
|
|
4336
|
+
batch.spriteGroupDepth = bound;
|
|
4337
|
+
}
|
|
4338
|
+
return batch.spriteGroup;
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
/** A sprite's image sampler, repeating nothing and filtered, or its depth's, nearest. */
|
|
4342
|
+
private spriteSampler(linear: boolean): GPUSampler {
|
|
4343
|
+
const device = this.surface.device;
|
|
4344
|
+
if (linear) {
|
|
4345
|
+
this.spriteLinearSampler ??= device.createSampler({
|
|
4346
|
+
label: 'particle.sprite',
|
|
4347
|
+
magFilter: 'linear',
|
|
4348
|
+
minFilter: 'linear',
|
|
4349
|
+
mipmapFilter: 'linear',
|
|
4350
|
+
addressModeU: 'clamp-to-edge',
|
|
4351
|
+
addressModeV: 'clamp-to-edge',
|
|
4352
|
+
});
|
|
4353
|
+
return this.spriteLinearSampler;
|
|
4354
|
+
}
|
|
4355
|
+
this.spriteNearestSampler ??= device.createSampler({ label: 'particle.depth' });
|
|
4356
|
+
return this.spriteNearestSampler;
|
|
4357
|
+
}
|
|
4358
|
+
|
|
4359
|
+
/** A one-texel `r32float` for a sprite's depth binding where no copy was taken. */
|
|
4360
|
+
private spriteDepthStandIn(): GPUTextureView {
|
|
4361
|
+
if (this.spriteDepthEmpty === null) {
|
|
4362
|
+
const texture = this.surface.device.createTexture({
|
|
4363
|
+
label: 'particle.depthStandIn',
|
|
4364
|
+
size: [1, 1],
|
|
4365
|
+
format: 'r32float',
|
|
4366
|
+
usage: 0x04 | 0x02,
|
|
4367
|
+
});
|
|
4368
|
+
this.spriteDepthEmpty = texture.createView();
|
|
4369
|
+
}
|
|
4370
|
+
return this.spriteDepthEmpty;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4200
4373
|
/**
|
|
4201
4374
|
* The bind group layout for one particle material, built on first use.
|
|
4202
4375
|
*
|
|
@@ -4213,7 +4386,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4213
4386
|
const device = this.surface.device;
|
|
4214
4387
|
const frag = particleFragBindings(material);
|
|
4215
4388
|
const built: ParticleLayout = {
|
|
4216
|
-
layout: createParticleBindGroupLayout(device, frag.size),
|
|
4389
|
+
layout: createParticleBindGroupLayout(device, frag.size, material === 'sprite'),
|
|
4217
4390
|
fields: frag.fields,
|
|
4218
4391
|
fragSize: frag.size,
|
|
4219
4392
|
};
|
|
@@ -5443,6 +5616,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5443
5616
|
iesAxes: new Float32Array(0),
|
|
5444
5617
|
cookies: new Float32Array(0),
|
|
5445
5618
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
5619
|
+
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
5446
5620
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
5447
5621
|
};
|
|
5448
5622
|
/** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
|
|
@@ -6236,6 +6410,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6236
6410
|
this.materialBudget.limit(quality.materialChangesPerFrame);
|
|
6237
6411
|
this.drawBudget.limit(quality.drawsPerFrame);
|
|
6238
6412
|
this.shadowDrawBudget.limit(quality.drawsPerFrame);
|
|
6413
|
+
/* A palette per draw at most, which is the most a frame can bind. See `SkinPaletteRing`. */
|
|
6414
|
+
this.skinPalettes = new SkinPaletteRing(quality.drawsPerFrame);
|
|
6239
6415
|
this.maxDrawingBufferPixels = quality.maxDrawingBufferPixels;
|
|
6240
6416
|
this.exposure = quality.outputExposure;
|
|
6241
6417
|
this.flushSchedule =
|
|
@@ -7935,7 +8111,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7935
8111
|
return keyed;
|
|
7936
8112
|
}
|
|
7937
8113
|
|
|
7938
|
-
createMesh(
|
|
8114
|
+
createMesh(given: MeshData, options: MeshOptions = {}): GpuMesh {
|
|
8115
|
+
/* The streams it is drawn with, before anything records which it has. See `withLightmapUvs`. */
|
|
8116
|
+
const data = withLightmapUvs(given);
|
|
7939
8117
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
7940
8118
|
return this.keyed(createGpuMesh(this.surface.device, data, options.dynamic === true), fullKey);
|
|
7941
8119
|
}
|
|
@@ -7949,7 +8127,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7949
8127
|
* straddle one, and going on writing to a dead device would raise validation errors a consumer
|
|
7950
8128
|
* can do nothing about. Done with `complete` still false is the signal that it was abandoned.
|
|
7951
8129
|
*/
|
|
7952
|
-
createMeshIncremental(
|
|
8130
|
+
createMeshIncremental(given: MeshData, options: MeshOptions = {}): IncrementalMeshHandle {
|
|
8131
|
+
const data = withLightmapUvs(given);
|
|
7953
8132
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
7954
8133
|
const { mesh, upload } = createGpuMeshIncremental(
|
|
7955
8134
|
this.surface.device,
|
|
@@ -8017,6 +8196,19 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8017
8196
|
bindMeshPass(camera: Camera, env: Environment): void {
|
|
8018
8197
|
if (this.surface.lost) return;
|
|
8019
8198
|
|
|
8199
|
+
/* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
|
|
8200
|
+
if (
|
|
8201
|
+
!this.warnedAreaOverflow &&
|
|
8202
|
+
!this.quality.clusteredLights &&
|
|
8203
|
+
(env.areaLights?.count ?? 0) > this.shadedAreaLights
|
|
8204
|
+
) {
|
|
8205
|
+
this.warnedAreaOverflow = true;
|
|
8206
|
+
console.warn(
|
|
8207
|
+
`WebGPU: ${env.areaLights?.count ?? 0} rectangular lights and ${this.shadedAreaLights} ` +
|
|
8208
|
+
'shaded without clusteredLights: the rest are not drawn. Turn clusteredLights on, and the ' +
|
|
8209
|
+
'froxel table shades every one past the fixed ones, unshadowed.',
|
|
8210
|
+
);
|
|
8211
|
+
}
|
|
8020
8212
|
/* Corrected once a frame, not once a draw: every draw copies the same matrix. */
|
|
8021
8213
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
8022
8214
|
/*
|
|
@@ -8189,16 +8381,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8189
8381
|
* switched grain off. That is the same failure the missing properties had — silence that
|
|
8190
8382
|
* reads as a setting rather than as an omission.
|
|
8191
8383
|
*
|
|
8192
|
-
* `
|
|
8384
|
+
* `uRelief.y` is 60 because `renderer.ts` says 60. Nothing in either file explains the
|
|
8193
8385
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
8194
8386
|
*/
|
|
8195
8387
|
f[at('uGrain')] = 1;
|
|
8196
|
-
f[at('
|
|
8388
|
+
f[at('uEnvironmentDials') + 1] = 0;
|
|
8197
8389
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
8198
8390
|
f[at('uWriteMode')] = 0;
|
|
8199
8391
|
f[at('uWriteMode') + 1] = 0;
|
|
8200
8392
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
8201
|
-
f[at('
|
|
8393
|
+
f[at('uEnvironmentDials') + 2] = 1;
|
|
8394
|
+
/* The frame's own ambient until a draw asks for its own. See `setAmbientSH`. */
|
|
8395
|
+
f.fill(0, at('uAmbientSH'), at('uAmbientSH') + AMBIENT_SH_FLOATS);
|
|
8202
8396
|
/*
|
|
8203
8397
|
* **Only where the variant declares it.** The irradiance block lives inside the probe's own
|
|
8204
8398
|
* `#if`, because it exists to be filled from a cube only that permutation has, so `at` throws
|
|
@@ -8252,7 +8446,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8252
8446
|
f[base + 2] = sets[2] ?? 0;
|
|
8253
8447
|
}
|
|
8254
8448
|
/* Which chain is bound, because the level and the weight both depend on it. */
|
|
8255
|
-
f[at('
|
|
8449
|
+
f[at('uEnvironmentDials')] = this.quality.environmentPrefilter ? 1 : 0;
|
|
8256
8450
|
}
|
|
8257
8451
|
/* DriftLight, or zeros that switch it off: see `driftLight/uniforms.ts`. */
|
|
8258
8452
|
this.uploadLightField();
|
|
@@ -8283,10 +8477,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8283
8477
|
f[emissiveScale + 1] = 1;
|
|
8284
8478
|
f[emissiveScale + 2] = 1;
|
|
8285
8479
|
f[at('uRelief')] = 0;
|
|
8286
|
-
f[at('
|
|
8480
|
+
f[at('uRelief') + 1] = 60;
|
|
8287
8481
|
f[at('uTextureRelief')] = 0;
|
|
8288
|
-
f[at('
|
|
8289
|
-
f[at('
|
|
8482
|
+
f[at('uEmission')] = env.emissiveGain;
|
|
8483
|
+
f[at('uEmission') + 1] = env.nightFactor;
|
|
8290
8484
|
/*
|
|
8291
8485
|
* **Only where the variant declares it**, because `at` throws on a name the permutation does
|
|
8292
8486
|
* not hold and this term is compiled in by `nightEmissive` alone.
|
|
@@ -8303,7 +8497,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8303
8497
|
if (this.quality.nightEmissive) f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
8304
8498
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
8305
8499
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
8306
|
-
|
|
8500
|
+
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
8501
|
+
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
8307
8502
|
/*
|
|
8308
8503
|
* No grading here. `renderer.ts` grades in this pass only when there is no composite to do
|
|
8309
8504
|
* it in; this backend has no composite yet, and adding one is its own row. Exposure is 1
|
|
@@ -8400,11 +8595,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8400
8595
|
scatterInto(f, this.fragment.fields['uLightDir'], lights.directions, 3);
|
|
8401
8596
|
scatterInto(f, this.fragment.fields['uLightCone'], lights.coneCos, 2);
|
|
8402
8597
|
scatterInto(f, this.fragment.fields['uLightIesProfile'], lights.iesProfiles, 1);
|
|
8403
|
-
|
|
8598
|
+
/* The azimuth and, in w, the light's channels. See `axesAndChannels`. */
|
|
8599
|
+
scatterInto(f, this.fragment.fields['uLightIesAxis'], lights.axesAndChannels, 4);
|
|
8404
8600
|
scatterInto(f, this.fragment.fields['uLightCookie'], lights.cookies, 1);
|
|
8405
|
-
|
|
8406
|
-
f[at('
|
|
8407
|
-
f[at('
|
|
8601
|
+
/* The fixture atlas's shape, three numbers in one row: profile rows, planes, cookie tiles. */
|
|
8602
|
+
f[at('uFixtureShape')] = this.iesRows;
|
|
8603
|
+
f[at('uFixtureShape') + 1] = this.iesPlanes;
|
|
8604
|
+
f[at('uFixtureShape') + 2] = this.cookieTiles;
|
|
8408
8605
|
|
|
8409
8606
|
/*
|
|
8410
8607
|
* The rectangular emitters. Bound on both backends in the same change, per the 2026-08-13
|
|
@@ -8568,8 +8765,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8568
8765
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
8569
8766
|
*/
|
|
8570
8767
|
this.materials.dirty();
|
|
8571
|
-
/*
|
|
8768
|
+
/*
|
|
8769
|
+
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
8770
|
+
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
8771
|
+
* held, so a pass that kept the last pass's normal, ORM, emissive and model maps here while
|
|
8772
|
+
* binding the blank group made the next `setMaterial` naming those same maps a no-op: the draw
|
|
8773
|
+
* kept the blank group and read the stand-ins while its flags said the maps were bound. A scene
|
|
8774
|
+
* whose first material in a pass was the last of the pass before — any scene of one material —
|
|
8775
|
+
* lost its maps from the second frame on, on this backend alone. Until 4.8.6.
|
|
8776
|
+
*/
|
|
8572
8777
|
this.albedo = null;
|
|
8778
|
+
this.normalMap = null;
|
|
8779
|
+
this.ormMap = null;
|
|
8780
|
+
this.emissiveMap = null;
|
|
8781
|
+
this.modelMap = null;
|
|
8573
8782
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
8574
8783
|
this.uvScale[0] = 1;
|
|
8575
8784
|
this.uvScale[1] = 1;
|
|
@@ -9305,6 +9514,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9305
9514
|
options: TranslucentMeshOptions,
|
|
9306
9515
|
): void {
|
|
9307
9516
|
if (!this.canDraw() || this.viewProj === null) return;
|
|
9517
|
+
warnRegionsWithoutPage(this.materialModel, data.lightmapRegions);
|
|
9308
9518
|
const mesh = batch.mesh;
|
|
9309
9519
|
/* Nor a batch of a mesh with nothing in it: see `GpuMesh.indexCount`. */
|
|
9310
9520
|
if (!mesh.complete || mesh.indexCount === 0) return;
|
|
@@ -12595,7 +12805,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12595
12805
|
*
|
|
12596
12806
|
* `load` rather than `clear` wherever there is a blend, and that is the other half of what
|
|
12597
12807
|
* makes the sum of the octaves a sum: a cleared attachment discards the level the downsample
|
|
12598
|
-
* wrote and adds the octave to nothing. See `
|
|
12808
|
+
* wrote and adds the octave to nothing. See `TINTED_ADD_BLEND`.
|
|
12599
12809
|
*/
|
|
12600
12810
|
const stage = (
|
|
12601
12811
|
label: string,
|
|
@@ -12605,6 +12815,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12605
12815
|
source: number,
|
|
12606
12816
|
slot: number,
|
|
12607
12817
|
blend?: GPUBlendState,
|
|
12818
|
+
constant?: number[],
|
|
12608
12819
|
): void => {
|
|
12609
12820
|
const group = this.bloomGroups[source];
|
|
12610
12821
|
if (group === undefined) return;
|
|
@@ -12632,6 +12843,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12632
12843
|
),
|
|
12633
12844
|
);
|
|
12634
12845
|
pass.setBindGroup(0, group, [slot * BLOOM_SLOT]);
|
|
12846
|
+
if (constant !== undefined) pass.setBlendConstant(constant);
|
|
12635
12847
|
pass.draw(3);
|
|
12636
12848
|
pass.end();
|
|
12637
12849
|
};
|
|
@@ -12654,19 +12866,29 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12654
12866
|
);
|
|
12655
12867
|
}
|
|
12656
12868
|
|
|
12657
|
-
/*
|
|
12658
|
-
|
|
12869
|
+
/*
|
|
12870
|
+
* And back up. Every draw reads the same tent radius, so they share one slot rather than
|
|
12871
|
+
* holding five copies of one constant — but for the first, which multiplies the deepest level
|
|
12872
|
+
* by its tint. Each level being added onto takes its own tint from the blend constant.
|
|
12873
|
+
*/
|
|
12874
|
+
const response = this.bloomResponse;
|
|
12659
12875
|
for (let index = levels.length - 1; index > 0; index--) {
|
|
12660
12876
|
const target = levels[index - 1];
|
|
12661
|
-
|
|
12877
|
+
const constant = this.bloomConstants[index - 1];
|
|
12878
|
+
if (target === undefined || constant === undefined) break;
|
|
12879
|
+
const into = 1 + (index - 1) * 3;
|
|
12880
|
+
constant[0] = response[into] ?? 1;
|
|
12881
|
+
constant[1] = response[into + 1] ?? 1;
|
|
12882
|
+
constant[2] = response[into + 2] ?? 1;
|
|
12662
12883
|
stage(
|
|
12663
12884
|
'post.bloomUp',
|
|
12664
12885
|
'post.bloomUpsample',
|
|
12665
12886
|
BLOOM_UPSAMPLE_FRAG_WGSL,
|
|
12666
12887
|
target,
|
|
12667
12888
|
index + 1,
|
|
12668
|
-
BLOOM_UPSAMPLE_SLOT,
|
|
12669
|
-
|
|
12889
|
+
index === levels.length - 1 ? BLOOM_UPSAMPLE_DEEPEST_SLOT : BLOOM_UPSAMPLE_SLOT,
|
|
12890
|
+
TINTED_ADD_BLEND,
|
|
12891
|
+
constant,
|
|
12670
12892
|
);
|
|
12671
12893
|
}
|
|
12672
12894
|
}
|
|
@@ -12829,9 +13051,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12829
13051
|
}
|
|
12830
13052
|
|
|
12831
13053
|
/**
|
|
12832
|
-
* A skin draw's diffuse
|
|
12833
|
-
* buffers and bind group, through the
|
|
12834
|
-
*
|
|
13054
|
+
* A skin draw's diffuse and colour halves, kept for the spread: the frame's draw again, twice,
|
|
13055
|
+
* with the same slots, buffers and bind group, through the pipelines that write the diffuse's
|
|
13056
|
+
* light alone and its colour alone into the blur's targets and test for the depth its frame half
|
|
13057
|
+
* wrote.
|
|
12835
13058
|
*/
|
|
12836
13059
|
private keepSkinDiffuse(
|
|
12837
13060
|
mesh: GpuMesh,
|
|
@@ -12850,45 +13073,49 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12850
13073
|
const pipelines = this.skinPipelines;
|
|
12851
13074
|
const present = this.meshPresent.get(base);
|
|
12852
13075
|
if (skin === null || pipelines === null || present === undefined) return;
|
|
12853
|
-
const
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
command.
|
|
13076
|
+
const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
13077
|
+
/* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
|
|
13078
|
+
for (const half of SKIN_KEPT_HALVES) {
|
|
13079
|
+
const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
|
|
13080
|
+
const pipeline =
|
|
13081
|
+
pipelines.peek(key) ??
|
|
13082
|
+
flatPipeline(
|
|
13083
|
+
pipelines,
|
|
13084
|
+
this.surface.device,
|
|
13085
|
+
this.flatLayoutFor(skinned, morphed),
|
|
13086
|
+
this.variant,
|
|
13087
|
+
key,
|
|
13088
|
+
present,
|
|
13089
|
+
false,
|
|
13090
|
+
skinned,
|
|
13091
|
+
morphed,
|
|
13092
|
+
true,
|
|
13093
|
+
layer,
|
|
13094
|
+
false,
|
|
13095
|
+
'none',
|
|
13096
|
+
this.materialDoubleSided,
|
|
13097
|
+
false,
|
|
13098
|
+
eight,
|
|
13099
|
+
clothed,
|
|
13100
|
+
'skin',
|
|
13101
|
+
half,
|
|
13102
|
+
);
|
|
13103
|
+
const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
|
|
13104
|
+
command.pipeline = pipeline;
|
|
13105
|
+
command.bindGroup = bindGroup;
|
|
13106
|
+
command.offsetA = slot;
|
|
13107
|
+
command.offsetB = material;
|
|
13108
|
+
command.offsetCount = 2;
|
|
13109
|
+
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
13110
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
13111
|
+
}
|
|
13112
|
+
command.vertexCount = mesh.vertexBuffers.length;
|
|
13113
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
13114
|
+
command.indexed = true;
|
|
13115
|
+
command.count = mesh.indexCount;
|
|
13116
|
+
command.instances = 1;
|
|
13117
|
+
command.indirect = null;
|
|
12885
13118
|
}
|
|
12886
|
-
command.vertexCount = mesh.vertexBuffers.length;
|
|
12887
|
-
command.indexBuffer = mesh.indexBuffer;
|
|
12888
|
-
command.indexed = true;
|
|
12889
|
-
command.count = mesh.indexCount;
|
|
12890
|
-
command.instances = 1;
|
|
12891
|
-
command.indirect = null;
|
|
12892
13119
|
}
|
|
12893
13120
|
|
|
12894
13121
|
/**
|
|
@@ -14373,10 +14600,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14373
14600
|
f[frustumAt + 3] = aspect;
|
|
14374
14601
|
f.set(camera.view as Float32Array, at('uView'));
|
|
14375
14602
|
|
|
14376
|
-
|
|
14603
|
+
/* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
|
|
14604
|
+
const set = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
|
|
14377
14605
|
/* A slot is read only under `POINT_SHADOWS`; see `lightHasFixture`. */
|
|
14378
14606
|
const shadowSlots = this.quality.pointShadows ? MAX_POINT_LIGHTS : 0;
|
|
14379
|
-
i[at('uClustered')] = clusteredMode(set, set
|
|
14607
|
+
i[at('uClustered')] = clusteredMode(set, clusteredLightTotal(set), shadowSlots);
|
|
14380
14608
|
binner.setFrame({
|
|
14381
14609
|
lights: set,
|
|
14382
14610
|
view: camera.view as Float32Array,
|
|
@@ -14715,6 +14943,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14715
14943
|
*/
|
|
14716
14944
|
|
|
14717
14945
|
private bloomScale = 1;
|
|
14946
|
+
/** The draw's own ambient, as `packAmbientSH` writes it; zeros are the frame's. */
|
|
14947
|
+
private readonly ambientSH = new Float32Array(AMBIENT_SH_FLOATS);
|
|
14948
|
+
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
14949
|
+
private readonly bloomResponse = defaultBloomResponse();
|
|
14950
|
+
/** The response before the last `setBloom`, to tell whether the blocks need writing again. */
|
|
14951
|
+
private readonly bloomResponseNext = defaultBloomResponse();
|
|
14952
|
+
/** Each level's tint as a blend constant, rewritten in place: `setBlendConstant` takes a colour. */
|
|
14953
|
+
private readonly bloomConstants: number[][] = Array.from({ length: BLOOM_LEVELS }, () => [
|
|
14954
|
+
1, 1, 1, 1,
|
|
14955
|
+
]);
|
|
14718
14956
|
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
14719
14957
|
private bloomThresholdSet: number | null = null;
|
|
14720
14958
|
private rushStrength = 0;
|
|
@@ -14858,14 +15096,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14858
15096
|
this.gradeSize = lut.size;
|
|
14859
15097
|
}
|
|
14860
15098
|
|
|
14861
|
-
setBloom(scale: number, threshold?: number): void {
|
|
15099
|
+
setBloom(scale: number, threshold?: number, response?: BloomResponse | null): void {
|
|
14862
15100
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
15101
|
+
let moved = false;
|
|
15102
|
+
if (threshold !== undefined) {
|
|
15103
|
+
const next = bloomThresholdOf(threshold);
|
|
15104
|
+
moved = next !== this.bloomThresholdSet;
|
|
15105
|
+
this.bloomThresholdSet = next;
|
|
15106
|
+
}
|
|
15107
|
+
if (response !== undefined) {
|
|
15108
|
+
this.bloomResponseNext.set(this.bloomResponse);
|
|
15109
|
+
resolveBloomResponse(response, this.bloomResponse);
|
|
15110
|
+
for (let i = 0; i < this.bloomResponse.length && !moved; i++) {
|
|
15111
|
+
moved = this.bloomResponse[i] !== this.bloomResponseNext[i];
|
|
15112
|
+
}
|
|
15113
|
+
}
|
|
15114
|
+
/* The blocks are written once per size, so a moved threshold or response rewrites them now. */
|
|
15115
|
+
if (moved && this.bloomLevels.length > 0) this.writeBloomBlocks();
|
|
14869
15116
|
}
|
|
14870
15117
|
|
|
14871
15118
|
/**
|
|
@@ -15678,6 +15925,60 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15678
15925
|
if (!this.setProbeGrid({ origin: WORLD_ORIGIN, spacing: UNIT_STEP, counts: SINGLE_PROBE })) {
|
|
15679
15926
|
return false;
|
|
15680
15927
|
}
|
|
15928
|
+
this.uploadProbeFaces(probe, image);
|
|
15929
|
+
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
15930
|
+
this.buildProbeChain(encoder, probe, 0);
|
|
15931
|
+
device.queue.submit([encoder.finish()]);
|
|
15932
|
+
|
|
15933
|
+
this.markProbeFilled(0);
|
|
15934
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
15935
|
+
this.rebuildFlatBindGroup();
|
|
15936
|
+
return true;
|
|
15937
|
+
}
|
|
15938
|
+
|
|
15939
|
+
/** One layer of the declared grid from an image. See `Renderer.setProbeLayerImage`. */
|
|
15940
|
+
setProbeLayerImage(
|
|
15941
|
+
layer: number,
|
|
15942
|
+
image: {
|
|
15943
|
+
readonly width: number;
|
|
15944
|
+
readonly height: number;
|
|
15945
|
+
readonly data: Float32Array;
|
|
15946
|
+
},
|
|
15947
|
+
options?: ProbeBakeOptions,
|
|
15948
|
+
): boolean {
|
|
15949
|
+
if (this.surface.lost) return false;
|
|
15950
|
+
const probe = this.probe;
|
|
15951
|
+
const grid = this.probes;
|
|
15952
|
+
if (probe === null || grid === null || this.probeArray === null) {
|
|
15953
|
+
console.warn(
|
|
15954
|
+
'WebGpuRenderer: setProbeLayerImage needs a reflection probe (`reflectionProbeSize`) and ' +
|
|
15955
|
+
'a grid declared with setProbeGrid; the image was ignored.',
|
|
15956
|
+
);
|
|
15957
|
+
return false;
|
|
15958
|
+
}
|
|
15959
|
+
if (!Number.isInteger(layer) || layer < 0 || layer >= grid.layers) {
|
|
15960
|
+
console.warn(
|
|
15961
|
+
`WebGpuRenderer: setProbeLayerImage was given layer ${layer}, and the grid has ${grid.layers}.`,
|
|
15962
|
+
);
|
|
15963
|
+
return false;
|
|
15964
|
+
}
|
|
15965
|
+
this.uploadProbeFaces(probe, image);
|
|
15966
|
+
const { device } = this.surface;
|
|
15967
|
+
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
15968
|
+
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
15969
|
+
device.queue.submit([encoder.finish()]);
|
|
15970
|
+
this.markProbeFilled(layer);
|
|
15971
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
15972
|
+
this.rebuildFlatBindGroup();
|
|
15973
|
+
return true;
|
|
15974
|
+
}
|
|
15975
|
+
|
|
15976
|
+
/** An equirectangular image onto the capture cube's six faces, in the cube's own format. */
|
|
15977
|
+
private uploadProbeFaces(
|
|
15978
|
+
probe: GPUTexture,
|
|
15979
|
+
image: { readonly width: number; readonly height: number; readonly data: Float32Array },
|
|
15980
|
+
): void {
|
|
15981
|
+
const { device } = this.surface;
|
|
15681
15982
|
const faces = equirectToCubeFaces(image, this.probeSize);
|
|
15682
15983
|
const format = this.pipelines.format;
|
|
15683
15984
|
for (let face = 0; face < faces.length; face++) {
|
|
@@ -15708,15 +16009,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15708
16009
|
{ width: this.probeSize, height: this.probeSize, depthOrArrayLayers: 1 },
|
|
15709
16010
|
);
|
|
15710
16011
|
}
|
|
15711
|
-
|
|
15712
|
-
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
15713
|
-
this.buildProbeChain(encoder, probe, 0);
|
|
15714
|
-
device.queue.submit([encoder.finish()]);
|
|
15715
|
-
|
|
15716
|
-
this.markProbeFilled(0);
|
|
15717
|
-
this.probeAmbient = options?.irradiance ?? true;
|
|
15718
|
-
this.rebuildFlatBindGroup();
|
|
15719
|
-
return true;
|
|
15720
16012
|
}
|
|
15721
16013
|
|
|
15722
16014
|
/**
|
|
@@ -16660,6 +16952,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16660
16952
|
|
|
16661
16953
|
const up = (name: string): number => this.postField(BLOOM_UPSAMPLE_FIELDS, name);
|
|
16662
16954
|
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
16955
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
16956
|
+
/* White on every draw up but the first, which reads the deepest level and carries its tint. */
|
|
16957
|
+
const response = this.bloomResponse;
|
|
16958
|
+
const deepest = 1 + (levels.length - 1) * 3;
|
|
16959
|
+
for (let c = 0; c < 3; c++) {
|
|
16960
|
+
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uScale') + c] = 1;
|
|
16961
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uScale') + c] = response[deepest + c] ?? 1;
|
|
16962
|
+
}
|
|
16963
|
+
f[slot(0) + prefilter('uRamp')] = response[0] ?? 0;
|
|
16663
16964
|
|
|
16664
16965
|
this.surface.device.queue.writeBuffer(this.bloomUniforms, 0, this.bloomStaging);
|
|
16665
16966
|
}
|