@driftengine/core 4.8.5 → 4.8.7
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 +4392 -4250
- package/README.md +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -2
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +54 -0
- package/dist/render/areaLights.js +55 -0
- package/dist/render/backend/api.d.ts +43 -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 +120 -11
- package/dist/render/backend/webgl2/renderer.js +356 -53
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- 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 +81 -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 +93 -8
- package/dist/render/backend/webgpu/renderer.js +483 -93
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- 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 +5 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- 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 +78 -6
- package/dist/render/clusteredLights.js +214 -13
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +12 -0
- package/dist/render/instances.d.ts +47 -11
- package/dist/render/instances.js +24 -9
- package/dist/render/lightBudget.d.ts +38 -2
- package/dist/render/lightBudget.js +48 -5
- package/dist/render/lightmap.d.ts +116 -0
- package/dist/render/lightmap.js +140 -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 +32 -0
- package/dist/render/particlePool.js +9 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/renderQuality.d.ts +8 -7
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +142 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +3 -1
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +46 -6
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +158 -121
- package/dist/render/shaders/flat/models.d.ts +21 -6
- package/dist/render/shaders/flat/models.js +161 -17
- 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 +84 -40
- 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/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1685 -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/generated/ssrTrace.wgsl.d.ts +11 -6
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/shaders/ssrTrace.js +20 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.js +1 -0
- package/dist/render/surfaceModel.d.ts +49 -8
- package/dist/render/surfaceModel.js +54 -8
- package/dist/render/surfaceTexture.d.ts +38 -1
- package/dist/render/surfaceTexture.js +37 -2
- package/package.json +6 -6
- package/src/index.ts +26 -3
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +97 -0
- package/src/render/backend/api.ts +61 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +470 -59
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- 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 +119 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +589 -113
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- 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 +57 -6
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +259 -12
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +9 -0
- package/src/render/instances.ts +61 -12
- package/src/render/lightBudget.ts +88 -4
- package/src/render/lightmap.ts +243 -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 +36 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/renderQuality.ts +8 -7
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +142 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +51 -6
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +158 -121
- package/src/render/shaders/flat/models.ts +170 -21
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +84 -40
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -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/generated/ssrTrace.wgsl.ts +11 -6
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +82 -10
- package/src/render/surfaceTexture.ts +101 -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,
|
|
@@ -118,7 +129,7 @@ import {
|
|
|
118
129
|
FACE_COUNT,
|
|
119
130
|
createResolvedPointShadows,
|
|
120
131
|
} from '../../pointShadowImage.ts';
|
|
121
|
-
import { MAX_AREA_LIGHTS, type AreaLightSource } from '../../areaLights.ts';
|
|
132
|
+
import { MAX_AREA_LIGHTS, packAreaSizesAndDoors, type AreaLightSource } from '../../areaLights.ts';
|
|
122
133
|
import {
|
|
123
134
|
areaShadowLayerCount,
|
|
124
135
|
castingRange,
|
|
@@ -164,11 +175,18 @@ import {
|
|
|
164
175
|
import type { TextStyle } from '../../textLayout.ts';
|
|
165
176
|
import { deviceSnappedCellSize, deviceSnappedOrigin } from '../../textLayout.ts';
|
|
166
177
|
import { GpuSurfaceTexture } from './surfaceTexturePass.ts';
|
|
178
|
+
import { GpuSceneCaptureTarget } from './sceneCaptureTarget.ts';
|
|
179
|
+
import { sceneCaptureTexels } from '../../sceneCapture.ts';
|
|
167
180
|
import { gpuCompressedFormats } from './compressedUpload.ts';
|
|
168
181
|
import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.ts';
|
|
169
182
|
import type { ClothViews } from './clothTextures.ts';
|
|
170
183
|
import type { ComputeHost } from './computeHost.ts';
|
|
171
|
-
import {
|
|
184
|
+
import {
|
|
185
|
+
MODEL_PARAM_FLOATS,
|
|
186
|
+
eyeAxisInWorld,
|
|
187
|
+
packModel,
|
|
188
|
+
skinProfileDistance,
|
|
189
|
+
} from '../../surfaceModel.ts';
|
|
172
190
|
import type { SurfaceModel } from '../../surfaceModel.ts';
|
|
173
191
|
import { validateClothBinding } from '../../clothBindingData.ts';
|
|
174
192
|
import type { ClothBindingData } from '../../clothBindingData.ts';
|
|
@@ -221,7 +239,6 @@ import {
|
|
|
221
239
|
AO_FRAG_WGSL,
|
|
222
240
|
AO_TEXTURES,
|
|
223
241
|
AO_UNIFORMS,
|
|
224
|
-
ADDITIVE_BLEND,
|
|
225
242
|
BLOOM_DOWNSAMPLE_FIELDS,
|
|
226
243
|
BLOOM_DOWNSAMPLE_FRAG_WGSL,
|
|
227
244
|
BLOOM_PREFILTER_FIELDS,
|
|
@@ -230,6 +247,7 @@ import {
|
|
|
230
247
|
BLOOM_TEXTURES,
|
|
231
248
|
BLOOM_UNIFORMS,
|
|
232
249
|
BLOOM_UPSAMPLE_FIELDS,
|
|
250
|
+
TINTED_ADD_BLEND,
|
|
233
251
|
BLOOM_UPSAMPLE_FRAG_WGSL,
|
|
234
252
|
RESOLVED_DEPTH_FORMAT,
|
|
235
253
|
RUSH_FRAG_FIELDS,
|
|
@@ -325,8 +343,8 @@ import type { BoltSegments } from '../../boltPool.ts';
|
|
|
325
343
|
import type { LineSegments } from '../../linePoints.ts';
|
|
326
344
|
import { buildPlumeGeometry } from '../../plumeGeometry.ts';
|
|
327
345
|
import type { ParticleMaterial } from '../../particleMaterial.ts';
|
|
328
|
-
import type
|
|
329
|
-
import type { ParticleInstances } from '../../particlePool.ts';
|
|
346
|
+
import { facingCode, type ParticleBatchOptions } from '../../particleBatch.ts';
|
|
347
|
+
import type { DeviceParticles, ParticleInstances } from '../../particlePool.ts';
|
|
330
348
|
import { PARTICLE_INDICES } from '../../shaders/particle.ts';
|
|
331
349
|
import type { PlumeOptions, PlumePlacement } from '../../plumeRenderer.ts';
|
|
332
350
|
import type { InstanceData } from '../../instancedMesh.ts';
|
|
@@ -439,14 +457,17 @@ import {
|
|
|
439
457
|
type GpuPlumes,
|
|
440
458
|
} from './plumePass.ts';
|
|
441
459
|
import {
|
|
460
|
+
PARTICLE_INSTANCE_FLOATS,
|
|
442
461
|
PARTICLE_VERT_FIELDS,
|
|
443
462
|
createGpuParticles,
|
|
444
463
|
createParticleBindGroupLayout,
|
|
464
|
+
createSpriteBindGroup,
|
|
445
465
|
packInstances,
|
|
446
466
|
particleFragBindings,
|
|
447
467
|
particlePipeline,
|
|
448
468
|
type GpuParticles,
|
|
449
469
|
} from './particlePass.ts';
|
|
470
|
+
import { sortBackToFront } from '../../particleSort.ts';
|
|
450
471
|
import {
|
|
451
472
|
WIND_STREAK_FRAG_FIELDS,
|
|
452
473
|
WIND_STREAK_FRAG_SIZE,
|
|
@@ -528,6 +549,9 @@ const INDIRECT_PROBE_SPACING = 2;
|
|
|
528
549
|
|
|
529
550
|
/** What the trace is given when a frame never said what was lighting it. */
|
|
530
551
|
const NO_SUN: readonly number[] = [0, 0, 0];
|
|
552
|
+
|
|
553
|
+
/** The halves of a skin draw the spread keeps, in the order they are drawn. See `keepSkinDiffuse`. */
|
|
554
|
+
const SKIN_KEPT_HALVES = ['diffuse', 'albedo'] as const;
|
|
531
555
|
import type { ComposedField } from './fieldCompose.ts';
|
|
532
556
|
import type { FieldSource } from '../../gi/globalField.ts';
|
|
533
557
|
import {
|
|
@@ -569,7 +593,9 @@ import { ClusterBinner } from './clusterBinner.ts';
|
|
|
569
593
|
import {
|
|
570
594
|
clusteredMode,
|
|
571
595
|
createClusterLightSet,
|
|
596
|
+
clusteredLightTotal,
|
|
572
597
|
fillClusterLightSet,
|
|
598
|
+
lightChannelsOf,
|
|
573
599
|
} from '../../clusteredLights.ts';
|
|
574
600
|
import {
|
|
575
601
|
computeAt,
|
|
@@ -1224,12 +1250,17 @@ const DECAL_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
|
1224
1250
|
*/
|
|
1225
1251
|
const SSR_SLOT = Math.ceil(SSR_FRAG_SIZE / DYNAMIC_UNIFORM_ALIGNMENT) * DYNAMIC_UNIFORM_ALIGNMENT;
|
|
1226
1252
|
const BLOOM_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
1227
|
-
const BLOOM_SLOTS = BLOOM_LEVELS +
|
|
1253
|
+
const BLOOM_SLOTS = BLOOM_LEVELS + 2;
|
|
1228
1254
|
/** One slot per blur axis, aligned like every other dynamic-offset block here. */
|
|
1229
1255
|
const AO_BLUR_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
1230
1256
|
|
|
1231
|
-
/**
|
|
1257
|
+
/**
|
|
1258
|
+
* The tent radius every upsample draw binds, with white as the level it reads is multiplied by;
|
|
1259
|
+
* and the same for the first draw up, which multiplies the deepest level by that level's tint.
|
|
1260
|
+
* Every other level's tint is the blend constant of the draw adding onto it. See `BloomResponse`.
|
|
1261
|
+
*/
|
|
1232
1262
|
const BLOOM_UPSAMPLE_SLOT = BLOOM_LEVELS;
|
|
1263
|
+
const BLOOM_UPSAMPLE_DEEPEST_SLOT = BLOOM_LEVELS + 1;
|
|
1233
1264
|
|
|
1234
1265
|
/**
|
|
1235
1266
|
* What one particle material's fragment stage needs a bind group *shaped* like, built once and
|
|
@@ -1448,7 +1479,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1448
1479
|
* Held rather than passed, like every other per-draw state here: `setSkinPalette` chooses and
|
|
1449
1480
|
* the following draws use it, which is the shape `setMaterial` already has.
|
|
1450
1481
|
*/
|
|
1451
|
-
private readonly skinPalettes
|
|
1482
|
+
private readonly skinPalettes: SkinPaletteRing;
|
|
1452
1483
|
private readonly skinnedBindGroupLayout: GPUBindGroupLayout;
|
|
1453
1484
|
private readonly morphedBindGroupLayout: GPUBindGroupLayout;
|
|
1454
1485
|
private readonly bothBindGroupLayout: GPUBindGroupLayout;
|
|
@@ -1900,6 +1931,17 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1900
1931
|
/** Whether this frame has spread its skin: once, at its first blended draw or before its end. */
|
|
1901
1932
|
private skinScattered = false;
|
|
1902
1933
|
private readonly skinInverseProjection = mat4.create();
|
|
1934
|
+
/** Scratch for a sprite's depth conversion, so a particle draw allocates nothing. */
|
|
1935
|
+
private readonly spriteInverseProjection = mat4.create();
|
|
1936
|
+
/** Said once: a sprite asked for a soft edge where no depth copy could be had. */
|
|
1937
|
+
private warnedSpriteDepth = false;
|
|
1938
|
+
/** Said once: more rectangles than the fixed arm shades, and no froxel table for the rest. */
|
|
1939
|
+
private warnedAreaOverflow = false;
|
|
1940
|
+
/** What a sprite's depth binding holds where no copy was taken. See `spriteDepthStandIn`. */
|
|
1941
|
+
private spriteDepthEmpty: GPUTextureView | null = null;
|
|
1942
|
+
/** The samplers every sprite batch binds: its image filtered, the depth copy texel for texel. */
|
|
1943
|
+
private spriteLinearSampler: GPUSampler | null = null;
|
|
1944
|
+
private spriteNearestSampler: GPUSampler | null = null;
|
|
1903
1945
|
private sceneColorView: GPUTextureView | null = null;
|
|
1904
1946
|
/** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
|
|
1905
1947
|
private sceneColorMsaa: GPUTexture | null = null;
|
|
@@ -2905,6 +2947,118 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2905
2947
|
);
|
|
2906
2948
|
}
|
|
2907
2949
|
|
|
2950
|
+
/** A baked lightmap page, as a `lightmapModel` material's `modelMap`. See `RendererApi`. */
|
|
2951
|
+
createLightmap(page: LightmapPage): GpuSurfaceTexture {
|
|
2952
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, lightmapTexels(page), {
|
|
2953
|
+
wrap: 'clamp',
|
|
2954
|
+
mipmap: false,
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
/**
|
|
2959
|
+
* A texture the scene can be drawn into, for a material to show. See `RendererApi` and
|
|
2960
|
+
* `sceneCapture.ts`. At the world's own format, which under `hdrScene` is half floats.
|
|
2961
|
+
*/
|
|
2962
|
+
createSceneCapture(width: number, height: number): GpuSurfaceTexture {
|
|
2963
|
+
const texture = new GpuSurfaceTexture(
|
|
2964
|
+
this.surface.device,
|
|
2965
|
+
this.pipelines,
|
|
2966
|
+
sceneCaptureTexels(width, height, this.quality.hdrScene),
|
|
2967
|
+
{ wrap: 'clamp', mipmap: false },
|
|
2968
|
+
);
|
|
2969
|
+
this.captureTargets.set(texture, new GpuSceneCaptureTarget(this.surface.device, width, height));
|
|
2970
|
+
return texture;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
/** Each capture's attachments, beside the texture a material binds. */
|
|
2974
|
+
private readonly captureTargets = new WeakMap<GpuSurfaceTexture, GpuSceneCaptureTarget>();
|
|
2975
|
+
/**
|
|
2976
|
+
* Whether a scene capture is being drawn: an off-screen pass as a probe bake is, so the frame's
|
|
2977
|
+
* camera, jitter, motion, refraction copy, skin's spread and late routing are left alone, but
|
|
2978
|
+
* lit by the probes and DriftLight as the frame is. See `captureScene`.
|
|
2979
|
+
*/
|
|
2980
|
+
private capturePassActive = false;
|
|
2981
|
+
|
|
2982
|
+
/**
|
|
2983
|
+
* Draw the scene into a capture from `camera`. See `RendererApi.captureScene`.
|
|
2984
|
+
*
|
|
2985
|
+
* **Its own encoder, as a probe bake's**: the frame's pass is set aside and restored, the rings
|
|
2986
|
+
* are marked and rewound around the submit, and the frame graph is flushed at both boundaries so
|
|
2987
|
+
* no recorded draw crosses one. **Radiance**: the output transform and exposure are held off.
|
|
2988
|
+
*/
|
|
2989
|
+
captureScene(
|
|
2990
|
+
capture: GpuSurfaceTexture,
|
|
2991
|
+
camera: Camera,
|
|
2992
|
+
clearColor: Vec3,
|
|
2993
|
+
draw: (camera: Camera) => void,
|
|
2994
|
+
): boolean {
|
|
2995
|
+
if (this.surface.lost) return false;
|
|
2996
|
+
const target = this.captureTargets.get(capture);
|
|
2997
|
+
const resolve = capture.layerView();
|
|
2998
|
+
if (target === undefined || resolve === null) return false;
|
|
2999
|
+
camera.updateMatrices(target.width / target.height);
|
|
3000
|
+
const { device } = this.surface;
|
|
3001
|
+
const { depth, colorMsaa } = target.attachments(this.samples, this.pipelines.format);
|
|
3002
|
+
|
|
3003
|
+
const savedPass = this.pass;
|
|
3004
|
+
if (this.quality.frameGraph) this.flushGraph();
|
|
3005
|
+
const savedEncoder = this.encoder;
|
|
3006
|
+
this.markRings();
|
|
3007
|
+
const skinMark = this.skinPalettes.mark();
|
|
3008
|
+
const frameEpoch = this.encoderEpoch;
|
|
3009
|
+
this.encoderEpoch = ++this.encoderEpochs;
|
|
3010
|
+
const encoder = device.createCommandEncoder({ label: 'capture' });
|
|
3011
|
+
this.encoder = encoder;
|
|
3012
|
+
this.capturePassActive = true;
|
|
3013
|
+
const heldGrade = this.perFrameInts[this.materialField('uOutputTransform')] ?? 0;
|
|
3014
|
+
const heldExposure = this.perFrameFloats[this.materialField('uOutputExposure')] ?? 1;
|
|
3015
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = 0;
|
|
3016
|
+
this.perFrameFloats[this.materialField('uOutputExposure')] = 1;
|
|
3017
|
+
this.materials.dirty();
|
|
3018
|
+
const pass = encoder.beginRenderPass({
|
|
3019
|
+
label: 'capture',
|
|
3020
|
+
colorAttachments: [
|
|
3021
|
+
{
|
|
3022
|
+
view: colorMsaa ?? resolve,
|
|
3023
|
+
resolveTarget: colorMsaa === null ? undefined : resolve,
|
|
3024
|
+
loadOp: 'clear',
|
|
3025
|
+
storeOp: resolvedStoreOp(
|
|
3026
|
+
colorMsaa !== null,
|
|
3027
|
+
true,
|
|
3028
|
+
this.quality.discardResolvedAttachments,
|
|
3029
|
+
),
|
|
3030
|
+
clearValue: { r: clearColor[0], g: clearColor[1], b: clearColor[2], a: 1 },
|
|
3031
|
+
},
|
|
3032
|
+
],
|
|
3033
|
+
depthStencilAttachment: {
|
|
3034
|
+
view: depth,
|
|
3035
|
+
depthClearValue: DEPTH_CLEAR,
|
|
3036
|
+
depthLoadOp: 'clear',
|
|
3037
|
+
depthStoreOp: 'discard',
|
|
3038
|
+
},
|
|
3039
|
+
});
|
|
3040
|
+
this.pass = pass;
|
|
3041
|
+
try {
|
|
3042
|
+
draw(camera);
|
|
3043
|
+
/* The callback's draws replayed into this pass before it ends: see `bakeProbe`. */
|
|
3044
|
+
if (this.quality.frameGraph) this.flushGraph();
|
|
3045
|
+
} finally {
|
|
3046
|
+
pass.end();
|
|
3047
|
+
this.pass = null;
|
|
3048
|
+
this.flushRings();
|
|
3049
|
+
device.queue.submit([encoder.finish()]);
|
|
3050
|
+
this.rewindRings(skinMark);
|
|
3051
|
+
this.encoderEpoch = frameEpoch;
|
|
3052
|
+
this.encoder = savedEncoder;
|
|
3053
|
+
this.pass = savedPass;
|
|
3054
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
3055
|
+
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
3056
|
+
this.materials.dirty();
|
|
3057
|
+
this.capturePassActive = false;
|
|
3058
|
+
}
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
2908
3062
|
/**
|
|
2909
3063
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2910
3064
|
*
|
|
@@ -2953,6 +3107,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2953
3107
|
if (this.modelMap === texture) this.modelMap = null;
|
|
2954
3108
|
this.forgetBindingsOf(texture);
|
|
2955
3109
|
texture.dispose();
|
|
3110
|
+
this.captureTargets.get(texture)?.dispose();
|
|
3111
|
+
this.captureTargets.delete(texture);
|
|
2956
3112
|
}
|
|
2957
3113
|
|
|
2958
3114
|
/**
|
|
@@ -3160,12 +3316,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3160
3316
|
this.materialModel = material?.model ?? null;
|
|
3161
3317
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
3162
3318
|
const model = material?.model;
|
|
3319
|
+
warnUnpagedLightmap(model, modelMap);
|
|
3163
3320
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
3164
3321
|
this.skinScatter.setProfile(
|
|
3165
3322
|
model.profile,
|
|
3166
|
-
model
|
|
3167
|
-
model
|
|
3168
|
-
model
|
|
3323
|
+
skinProfileDistance(model, 0),
|
|
3324
|
+
skinProfileDistance(model, 1),
|
|
3325
|
+
skinProfileDistance(model, 2),
|
|
3169
3326
|
);
|
|
3170
3327
|
}
|
|
3171
3328
|
this.material((f, i) => {
|
|
@@ -3186,6 +3343,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3186
3343
|
f[cut] = cutoff;
|
|
3187
3344
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
3188
3345
|
f[cut + 2] = this.cutoutFrame;
|
|
3346
|
+
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
3347
|
+
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
3189
3348
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
3190
3349
|
/* 0 is no map, and gates the whole block in the shader. See `uNormalStrength`. */
|
|
3191
3350
|
f[this.materialField('uNormalStrength')] =
|
|
@@ -3213,7 +3372,14 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3213
3372
|
f[emissive + 1] = asked?.[1] ?? 1;
|
|
3214
3373
|
f[emissive + 2] = asked?.[2] ?? 1;
|
|
3215
3374
|
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
3216
|
-
packModel(
|
|
3375
|
+
packModel(
|
|
3376
|
+
material?.model ?? null,
|
|
3377
|
+
modelMap !== null,
|
|
3378
|
+
this.modelParams,
|
|
3379
|
+
material?.physicalSpecular === true,
|
|
3380
|
+
);
|
|
3381
|
+
/* The first physical highlight asked for is when the lit pipelines start carrying one. */
|
|
3382
|
+
if (material?.physicalSpecular === true) this.enableLit('PHYSICAL_SPECULAR');
|
|
3217
3383
|
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
3218
3384
|
});
|
|
3219
3385
|
/*
|
|
@@ -3340,17 +3506,27 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3340
3506
|
);
|
|
3341
3507
|
}
|
|
3342
3508
|
|
|
3509
|
+
/**
|
|
3510
|
+
* The ambient the following draws take, nine spherical-harmonic coefficients or null for the
|
|
3511
|
+
* frame's. See `Renderer.setAmbientSH`: material state, so the next draw takes a slot of its own.
|
|
3512
|
+
*/
|
|
3513
|
+
setAmbientSH(coefficients: ArrayLike<number> | null): void {
|
|
3514
|
+
if (this.surface.lost) return;
|
|
3515
|
+
packAmbientSH(coefficients, this.ambientSH);
|
|
3516
|
+
this.material((f) => f.set(this.ambientSH, this.materialField('uAmbientSH')));
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3343
3519
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
3344
3520
|
setSurfaceReflectivity(amount: number): void {
|
|
3345
3521
|
this.material((f) => {
|
|
3346
|
-
f[this.materialField('
|
|
3522
|
+
f[this.materialField('uEnvironmentDials') + 1] = Math.min(1, Math.max(0, amount));
|
|
3347
3523
|
});
|
|
3348
3524
|
}
|
|
3349
3525
|
|
|
3350
3526
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
3351
3527
|
setEnvironmentGain(gain: number): void {
|
|
3352
3528
|
this.material((f) => {
|
|
3353
|
-
f[this.materialField('
|
|
3529
|
+
f[this.materialField('uEnvironmentDials') + 2] = Math.max(0, gain);
|
|
3354
3530
|
});
|
|
3355
3531
|
}
|
|
3356
3532
|
|
|
@@ -3390,7 +3566,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3390
3566
|
setSurfaceRelief(amount: number, cyclesPerMetre = 60): void {
|
|
3391
3567
|
this.material((f) => {
|
|
3392
3568
|
f[this.materialField('uRelief')] = Math.min(1, Math.max(0, amount));
|
|
3393
|
-
f[this.materialField('
|
|
3569
|
+
f[this.materialField('uRelief') + 1] = Math.max(0.01, cyclesPerMetre);
|
|
3394
3570
|
});
|
|
3395
3571
|
}
|
|
3396
3572
|
|
|
@@ -3410,7 +3586,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3410
3586
|
/** Scale the emissive of the following draws. See `Renderer.setEmissiveGain` for why. */
|
|
3411
3587
|
setEmissiveGain(gain: number): void {
|
|
3412
3588
|
this.material((f) => {
|
|
3413
|
-
f[this.materialField('
|
|
3589
|
+
f[this.materialField('uEmission')] = gain;
|
|
3414
3590
|
});
|
|
3415
3591
|
}
|
|
3416
3592
|
|
|
@@ -4035,6 +4211,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4035
4211
|
*/
|
|
4036
4212
|
createParticles(capacity: number, options: ParticleBatchOptions): GpuParticles {
|
|
4037
4213
|
const layout = this.particleLayoutFor(options.material);
|
|
4214
|
+
const texture = (options.texture ?? null) as unknown as GpuSurfaceTexture | null;
|
|
4215
|
+
if (options.material === 'sprite' && texture === null) {
|
|
4216
|
+
throw new Error("createParticles: a 'sprite' draws an image, and was given no `texture`");
|
|
4217
|
+
}
|
|
4038
4218
|
return createGpuParticles(
|
|
4039
4219
|
this.surface.device,
|
|
4040
4220
|
capacity,
|
|
@@ -4048,6 +4228,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4048
4228
|
layout.layout,
|
|
4049
4229
|
layout.fields,
|
|
4050
4230
|
layout.fragSize,
|
|
4231
|
+
{
|
|
4232
|
+
texture: texture?.view ?? null,
|
|
4233
|
+
cells: options.cells ?? [1, 1],
|
|
4234
|
+
blendCells: options.blendCells === true,
|
|
4235
|
+
softDepth: Math.max(0, options.softDepth ?? 0),
|
|
4236
|
+
cameraFade: Math.max(0, options.cameraFade ?? 0),
|
|
4237
|
+
cameraOffset: options.cameraOffset ?? 0,
|
|
4238
|
+
sort: options.sort === true,
|
|
4239
|
+
},
|
|
4051
4240
|
);
|
|
4052
4241
|
}
|
|
4053
4242
|
|
|
@@ -4099,19 +4288,58 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4099
4288
|
return;
|
|
4100
4289
|
}
|
|
4101
4290
|
batch.drawnInEncoder = this.encoderEpoch;
|
|
4102
|
-
|
|
4291
|
+
/* Farthest first where the batch asked; see `particleSort.ts`. */
|
|
4292
|
+
const drawn = batch.order === null ? data : sortBackToFront(data, camera.position, batch.order);
|
|
4293
|
+
const live = packInstances(batch, drawn);
|
|
4103
4294
|
if (live === 0) return;
|
|
4104
4295
|
|
|
4105
4296
|
const device = this.surface.device;
|
|
4106
|
-
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live *
|
|
4297
|
+
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live * PARTICLE_INSTANCE_FLOATS);
|
|
4298
|
+
this.drawParticleInstances(batch, batch.instances, live, camera, env, timeSeconds);
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
/**
|
|
4302
|
+
* Draw particles a caller's own compute wrote, where they lie. See `DeviceParticles`.
|
|
4303
|
+
*
|
|
4304
|
+
* **The pool's draw with its instance stream swapped**: the same material, blend, flipbook, soft
|
|
4305
|
+
* edge and fog, reading the caller's buffer in place of the one the pool uploads into — so nothing
|
|
4306
|
+
* crosses back to the CPU. Not sorted, and a count past what the buffer holds is cut to it rather
|
|
4307
|
+
* than handed to a draw the device would refuse with the frame.
|
|
4308
|
+
*/
|
|
4309
|
+
drawDeviceParticles(
|
|
4310
|
+
batch: GpuParticles,
|
|
4311
|
+
particles: DeviceParticles,
|
|
4312
|
+
camera: Camera,
|
|
4313
|
+
env: Environment,
|
|
4314
|
+
timeSeconds: number,
|
|
4315
|
+
): void {
|
|
4316
|
+
const fits = Math.floor(particles.buffer.size / (PARTICLE_INSTANCE_FLOATS * 4));
|
|
4317
|
+
const live = Math.min(Math.max(0, Math.floor(particles.count)), fits);
|
|
4318
|
+
/* Not above zero covers a NaN as well: a count or a size that is no number draws nothing. */
|
|
4319
|
+
if (!this.canDraw() || !(live > 0)) return;
|
|
4320
|
+
if (batch.drawnInEncoder === this.encoderEpoch) return;
|
|
4321
|
+
batch.drawnInEncoder = this.encoderEpoch;
|
|
4322
|
+
this.drawParticleInstances(batch, particles.buffer, live, camera, env, timeSeconds);
|
|
4323
|
+
}
|
|
4107
4324
|
|
|
4325
|
+
/** A pool's uniforms for this view, and its draw from `instances`: both entry points' tail. */
|
|
4326
|
+
private drawParticleInstances(
|
|
4327
|
+
batch: GpuParticles,
|
|
4328
|
+
instances: GPUBuffer,
|
|
4329
|
+
live: number,
|
|
4330
|
+
camera: Camera,
|
|
4331
|
+
env: Environment,
|
|
4332
|
+
timeSeconds: number,
|
|
4333
|
+
): void {
|
|
4334
|
+
const device = this.surface.device;
|
|
4108
4335
|
const v = batch.vertFloats;
|
|
4109
4336
|
const atV = (name: string): number => (PARTICLE_VERT_FIELDS[name]?.offset ?? -4) / 4;
|
|
4110
4337
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
4111
4338
|
v.set(this.correctedViewProj, atV('uViewProj'));
|
|
4112
4339
|
v.set(camera.position, atV('uCameraPos'));
|
|
4113
4340
|
v[atV('uStretchSec')] = batch.stretchSec;
|
|
4114
|
-
v[atV('uCameraFacing')] = batch.facing
|
|
4341
|
+
v[atV('uCameraFacing')] = facingCode(batch.facing);
|
|
4342
|
+
v[atV('uCameraOffset')] = batch.cameraOffset;
|
|
4115
4343
|
|
|
4116
4344
|
const f = batch.fragFloats;
|
|
4117
4345
|
const i = batch.fragInts;
|
|
@@ -4163,9 +4391,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4163
4391
|
i[atF('uOutputTransform')] = this.gradeCode();
|
|
4164
4392
|
f[atF('uOutputExposure')] = this.gradeExposure();
|
|
4165
4393
|
|
|
4394
|
+
const spriteGroup = batch.material === 'sprite' ? this.spriteFades(batch, camera, f) : null;
|
|
4395
|
+
|
|
4166
4396
|
device.queue.writeBuffer(batch.vertUniforms, 0, batch.vertStaging);
|
|
4167
4397
|
device.queue.writeBuffer(batch.fragUniforms, 0, batch.fragStaging);
|
|
4168
4398
|
|
|
4399
|
+
const group = spriteGroup ?? batch.bindGroup;
|
|
4400
|
+
if (group === null) return;
|
|
4169
4401
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
4170
4402
|
const particlePipe = particlePipeline(
|
|
4171
4403
|
this.blendedPipelines(),
|
|
@@ -4177,9 +4409,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4177
4409
|
const particleCommand = this.recordBlended(0);
|
|
4178
4410
|
if (particleCommand !== null) {
|
|
4179
4411
|
particleCommand.pipeline = particlePipe;
|
|
4180
|
-
particleCommand.bindGroup =
|
|
4412
|
+
particleCommand.bindGroup = group;
|
|
4181
4413
|
particleCommand.vertexBuffers[0] = batch.corners;
|
|
4182
|
-
particleCommand.vertexBuffers[1] =
|
|
4414
|
+
particleCommand.vertexBuffers[1] = instances;
|
|
4183
4415
|
particleCommand.vertexCount = 2;
|
|
4184
4416
|
particleCommand.indexBuffer = batch.indexBuffer;
|
|
4185
4417
|
particleCommand.indexed = true;
|
|
@@ -4189,14 +4421,116 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4189
4421
|
const pass = this.openPass();
|
|
4190
4422
|
if (pass === null) return;
|
|
4191
4423
|
pass.setPipeline(particlePipe);
|
|
4192
|
-
pass.setBindGroup(0,
|
|
4424
|
+
pass.setBindGroup(0, group);
|
|
4193
4425
|
pass.setVertexBuffer(0, batch.corners);
|
|
4194
|
-
pass.setVertexBuffer(1,
|
|
4426
|
+
pass.setVertexBuffer(1, instances);
|
|
4195
4427
|
pass.setIndexBuffer(batch.indexBuffer, 'uint32');
|
|
4196
4428
|
pass.drawIndexed(PARTICLE_INDICES, live);
|
|
4197
4429
|
}
|
|
4198
4430
|
}
|
|
4199
4431
|
|
|
4432
|
+
/**
|
|
4433
|
+
* A sprite's flipbook and fades into its fragment block `f`, and the bind group holding the
|
|
4434
|
+
* frame's depth copy its soft edge reads — built again only when that copy is replaced.
|
|
4435
|
+
*
|
|
4436
|
+
* **The copy is the volume's**: `takeVolumeDepth` ends the frame's pass, resolves the opaque
|
|
4437
|
+
* depth and reopens, once a frame whoever asks. Only in the frame's own picture: in a mirror or
|
|
4438
|
+
* a probe's face it would be another view's depth, and the edge stays hard there, as on WebGL2.
|
|
4439
|
+
* Without a composite there is no copy, and the renderer says so once.
|
|
4440
|
+
*/
|
|
4441
|
+
private spriteFades(batch: GpuParticles, camera: Camera, f: Float32Array): GPUBindGroup | null {
|
|
4442
|
+
const fields = batch.fields;
|
|
4443
|
+
const atF = (name: string): number => (fields[name]?.offset ?? -4) / 4;
|
|
4444
|
+
let depth: GPUTextureView | null = null;
|
|
4445
|
+
if (
|
|
4446
|
+
batch.softDepth > 0 &&
|
|
4447
|
+
!this.reflectionPassActive &&
|
|
4448
|
+
!this.probePassActive &&
|
|
4449
|
+
!this.capturePassActive
|
|
4450
|
+
) {
|
|
4451
|
+
if (this.openPass() !== null && this.takeVolumeDepth()) depth = this.resolvedDepthView;
|
|
4452
|
+
if (depth === null && !this.warnedSpriteDepth) {
|
|
4453
|
+
this.warnedSpriteDepth = true;
|
|
4454
|
+
console.warn(
|
|
4455
|
+
"WebGPU: a 'sprite' asked for a soft edge, which reads a copy of the frame's depth, and " +
|
|
4456
|
+
'this profile keeps none — screenEffects is off. The edge is hard.',
|
|
4457
|
+
);
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
f[atF('uCells')] = batch.cells[0];
|
|
4461
|
+
f[atF('uCells') + 1] = batch.cells[1];
|
|
4462
|
+
f[atF('uCells') + 2] = batch.blendCells ? 1 : 0;
|
|
4463
|
+
f[atF('uFade')] = batch.softDepth;
|
|
4464
|
+
f[atF('uFade') + 1] = batch.cameraFade;
|
|
4465
|
+
f[atF('uFade') + 2] = depth === null ? 0 : 1;
|
|
4466
|
+
mat4.invert(this.spriteInverseProjection, camera.projection);
|
|
4467
|
+
const inverse = this.spriteInverseProjection;
|
|
4468
|
+
f[atF('uDepthToViewZ')] = inverse[10] ?? 0;
|
|
4469
|
+
f[atF('uDepthToViewZ') + 1] = inverse[14] ?? 0;
|
|
4470
|
+
f[atF('uDepthToViewZ') + 2] = inverse[11] ?? 0;
|
|
4471
|
+
f[atF('uDepthToViewZ') + 3] = inverse[15] ?? 1;
|
|
4472
|
+
/*
|
|
4473
|
+
* The pixels a fragment's own coordinate counts in: the output's where blended draws land
|
|
4474
|
+
* after reconstruction, the scene's otherwise. The copy is sampled by the fraction, so its own
|
|
4475
|
+
* size does not matter. Read off the canvas rather than `sceneSize`, which builds an object.
|
|
4476
|
+
*/
|
|
4477
|
+
const { canvas } = this.surface;
|
|
4478
|
+
const ratio = this.drawsLate() || !this.reconstructionWanted ? 1 : this.quality.reconstruction;
|
|
4479
|
+
/* `reconRenderSize`'s own rounding — up, from the floored output — without its object. */
|
|
4480
|
+
const width = Math.max(1, Math.floor(canvas.width));
|
|
4481
|
+
const height = Math.max(1, Math.floor(canvas.height));
|
|
4482
|
+
f[atF('uViewport')] = ratio > 1 ? Math.max(1, Math.ceil(width / ratio)) : width;
|
|
4483
|
+
f[atF('uViewport') + 1] = ratio > 1 ? Math.max(1, Math.ceil(height / ratio)) : height;
|
|
4484
|
+
|
|
4485
|
+
/* Something single-sampled and unfilterable stands in where there is no copy to read. */
|
|
4486
|
+
const bound = depth ?? this.spriteDepthStandIn();
|
|
4487
|
+
if (batch.spriteGroup === null || batch.spriteGroupDepth !== bound) {
|
|
4488
|
+
batch.spriteGroup = createSpriteBindGroup(
|
|
4489
|
+
this.surface.device,
|
|
4490
|
+
this.particleLayoutFor('sprite').layout,
|
|
4491
|
+
batch,
|
|
4492
|
+
this.particleLayoutFor('sprite').fragSize,
|
|
4493
|
+
bound,
|
|
4494
|
+
this.spriteSampler(true),
|
|
4495
|
+
this.spriteSampler(false),
|
|
4496
|
+
);
|
|
4497
|
+
batch.spriteGroupDepth = bound;
|
|
4498
|
+
}
|
|
4499
|
+
return batch.spriteGroup;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
/** A sprite's image sampler, repeating nothing and filtered, or its depth's, nearest. */
|
|
4503
|
+
private spriteSampler(linear: boolean): GPUSampler {
|
|
4504
|
+
const device = this.surface.device;
|
|
4505
|
+
if (linear) {
|
|
4506
|
+
this.spriteLinearSampler ??= device.createSampler({
|
|
4507
|
+
label: 'particle.sprite',
|
|
4508
|
+
magFilter: 'linear',
|
|
4509
|
+
minFilter: 'linear',
|
|
4510
|
+
mipmapFilter: 'linear',
|
|
4511
|
+
addressModeU: 'clamp-to-edge',
|
|
4512
|
+
addressModeV: 'clamp-to-edge',
|
|
4513
|
+
});
|
|
4514
|
+
return this.spriteLinearSampler;
|
|
4515
|
+
}
|
|
4516
|
+
this.spriteNearestSampler ??= device.createSampler({ label: 'particle.depth' });
|
|
4517
|
+
return this.spriteNearestSampler;
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
/** A one-texel `r32float` for a sprite's depth binding where no copy was taken. */
|
|
4521
|
+
private spriteDepthStandIn(): GPUTextureView {
|
|
4522
|
+
if (this.spriteDepthEmpty === null) {
|
|
4523
|
+
const texture = this.surface.device.createTexture({
|
|
4524
|
+
label: 'particle.depthStandIn',
|
|
4525
|
+
size: [1, 1],
|
|
4526
|
+
format: 'r32float',
|
|
4527
|
+
usage: 0x04 | 0x02,
|
|
4528
|
+
});
|
|
4529
|
+
this.spriteDepthEmpty = texture.createView();
|
|
4530
|
+
}
|
|
4531
|
+
return this.spriteDepthEmpty;
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4200
4534
|
/**
|
|
4201
4535
|
* The bind group layout for one particle material, built on first use.
|
|
4202
4536
|
*
|
|
@@ -4213,7 +4547,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4213
4547
|
const device = this.surface.device;
|
|
4214
4548
|
const frag = particleFragBindings(material);
|
|
4215
4549
|
const built: ParticleLayout = {
|
|
4216
|
-
layout: createParticleBindGroupLayout(device, frag.size),
|
|
4550
|
+
layout: createParticleBindGroupLayout(device, frag.size, material === 'sprite'),
|
|
4217
4551
|
fields: frag.fields,
|
|
4218
4552
|
fragSize: frag.size,
|
|
4219
4553
|
};
|
|
@@ -4487,6 +4821,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4487
4821
|
scatterInto(f, WATER_FRAG_FIELDS['uLightRadius'], lights.radii, 1);
|
|
4488
4822
|
scatterInto(f, WATER_FRAG_FIELDS['uLightSourceRadius'], lights.sourceRadii, 1);
|
|
4489
4823
|
scatterInto(f, WATER_FRAG_FIELDS['uLightWeight'], lights.weights, 1);
|
|
4824
|
+
scatterInto(f, WATER_FRAG_FIELDS['uLightCone'], lights.conesAndFalloffs, 4);
|
|
4490
4825
|
|
|
4491
4826
|
const medium = resolveAtmosphere(
|
|
4492
4827
|
env,
|
|
@@ -5443,8 +5778,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5443
5778
|
iesAxes: new Float32Array(0),
|
|
5444
5779
|
cookies: new Float32Array(0),
|
|
5445
5780
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
5781
|
+
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
5782
|
+
conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
5446
5783
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
5447
5784
|
};
|
|
5785
|
+
/** The fixed arm's rectangles as `uAreaLightSize` holds them. See `packAreaSizesAndDoors`. */
|
|
5786
|
+
private readonly areaSizesAndDoors = new Float32Array(MAX_AREA_LIGHTS * 4);
|
|
5448
5787
|
/** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
|
|
5449
5788
|
private readonly medium: ResolvedAtmosphere = {
|
|
5450
5789
|
fogColor: new Float32Array(3),
|
|
@@ -6236,6 +6575,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
6236
6575
|
this.materialBudget.limit(quality.materialChangesPerFrame);
|
|
6237
6576
|
this.drawBudget.limit(quality.drawsPerFrame);
|
|
6238
6577
|
this.shadowDrawBudget.limit(quality.drawsPerFrame);
|
|
6578
|
+
/* A palette per draw at most, which is the most a frame can bind. See `SkinPaletteRing`. */
|
|
6579
|
+
this.skinPalettes = new SkinPaletteRing(quality.drawsPerFrame);
|
|
6239
6580
|
this.maxDrawingBufferPixels = quality.maxDrawingBufferPixels;
|
|
6240
6581
|
this.exposure = quality.outputExposure;
|
|
6241
6582
|
this.flushSchedule =
|
|
@@ -7935,7 +8276,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7935
8276
|
return keyed;
|
|
7936
8277
|
}
|
|
7937
8278
|
|
|
7938
|
-
createMesh(
|
|
8279
|
+
createMesh(given: MeshData, options: MeshOptions = {}): GpuMesh {
|
|
8280
|
+
/* The streams it is drawn with, before anything records which it has. See `withLightmapUvs`. */
|
|
8281
|
+
const data = withLightmapUvs(given);
|
|
7939
8282
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
7940
8283
|
return this.keyed(createGpuMesh(this.surface.device, data, options.dynamic === true), fullKey);
|
|
7941
8284
|
}
|
|
@@ -7949,7 +8292,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
7949
8292
|
* straddle one, and going on writing to a dead device would raise validation errors a consumer
|
|
7950
8293
|
* can do nothing about. Done with `complete` still false is the signal that it was abandoned.
|
|
7951
8294
|
*/
|
|
7952
|
-
createMeshIncremental(
|
|
8295
|
+
createMeshIncremental(given: MeshData, options: MeshOptions = {}): IncrementalMeshHandle {
|
|
8296
|
+
const data = withLightmapUvs(given);
|
|
7953
8297
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
7954
8298
|
const { mesh, upload } = createGpuMeshIncremental(
|
|
7955
8299
|
this.surface.device,
|
|
@@ -8017,6 +8361,19 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8017
8361
|
bindMeshPass(camera: Camera, env: Environment): void {
|
|
8018
8362
|
if (this.surface.lost) return;
|
|
8019
8363
|
|
|
8364
|
+
/* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
|
|
8365
|
+
if (
|
|
8366
|
+
!this.warnedAreaOverflow &&
|
|
8367
|
+
!this.quality.clusteredLights &&
|
|
8368
|
+
(env.areaLights?.count ?? 0) > this.shadedAreaLights
|
|
8369
|
+
) {
|
|
8370
|
+
this.warnedAreaOverflow = true;
|
|
8371
|
+
console.warn(
|
|
8372
|
+
`WebGPU: ${env.areaLights?.count ?? 0} rectangular lights and ${this.shadedAreaLights} ` +
|
|
8373
|
+
'shaded without clusteredLights: the rest are not drawn. Turn clusteredLights on, and the ' +
|
|
8374
|
+
'froxel table shades every one past the fixed ones, unshadowed.',
|
|
8375
|
+
);
|
|
8376
|
+
}
|
|
8020
8377
|
/* Corrected once a frame, not once a draw: every draw copies the same matrix. */
|
|
8021
8378
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
8022
8379
|
/*
|
|
@@ -8025,7 +8382,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8025
8382
|
* the viewer sees, and bounding it through a mirrored camera puts it in the wrong half of the
|
|
8026
8383
|
* screen.
|
|
8027
8384
|
*/
|
|
8028
|
-
if (!this.reflectionPassActive) {
|
|
8385
|
+
if (!this.reflectionPassActive && !this.capturePassActive) {
|
|
8029
8386
|
mat4.copy(this.frameRawViewProj, camera.viewProjection);
|
|
8030
8387
|
/* The eye in world space, which the resolve's normals are turned to face. */
|
|
8031
8388
|
this.reconEye.set(camera.position);
|
|
@@ -8059,7 +8416,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8059
8416
|
* second temporal frame stepped the sequence twice a frame, so the world saw every other
|
|
8060
8417
|
* position and the anti-flicker's period never started.
|
|
8061
8418
|
*/
|
|
8062
|
-
|
|
8419
|
+
/* A capture has no resolve to undo a jitter and is no frame of the sequence. */
|
|
8420
|
+
if (this.framePresented || this.capturePassActive) this.viewProj = this.correctedViewProj;
|
|
8063
8421
|
else this.jitterFrame();
|
|
8064
8422
|
/*
|
|
8065
8423
|
* The frustum, once a frame, and from the **uncorrected** matrix.
|
|
@@ -8189,16 +8547,18 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8189
8547
|
* switched grain off. That is the same failure the missing properties had — silence that
|
|
8190
8548
|
* reads as a setting rather than as an omission.
|
|
8191
8549
|
*
|
|
8192
|
-
* `
|
|
8550
|
+
* `uRelief.y` is 60 because `renderer.ts` says 60. Nothing in either file explains the
|
|
8193
8551
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
8194
8552
|
*/
|
|
8195
8553
|
f[at('uGrain')] = 1;
|
|
8196
|
-
f[at('
|
|
8554
|
+
f[at('uEnvironmentDials') + 1] = 0;
|
|
8197
8555
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
8198
8556
|
f[at('uWriteMode')] = 0;
|
|
8199
8557
|
f[at('uWriteMode') + 1] = 0;
|
|
8200
8558
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
8201
|
-
f[at('
|
|
8559
|
+
f[at('uEnvironmentDials') + 2] = 1;
|
|
8560
|
+
/* The frame's own ambient until a draw asks for its own. See `setAmbientSH`. */
|
|
8561
|
+
f.fill(0, at('uAmbientSH'), at('uAmbientSH') + AMBIENT_SH_FLOATS);
|
|
8202
8562
|
/*
|
|
8203
8563
|
* **Only where the variant declares it.** The irradiance block lives inside the probe's own
|
|
8204
8564
|
* `#if`, because it exists to be filled from a cube only that permutation has, so `at` throws
|
|
@@ -8252,7 +8612,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8252
8612
|
f[base + 2] = sets[2] ?? 0;
|
|
8253
8613
|
}
|
|
8254
8614
|
/* Which chain is bound, because the level and the weight both depend on it. */
|
|
8255
|
-
f[at('
|
|
8615
|
+
f[at('uEnvironmentDials')] = this.quality.environmentPrefilter ? 1 : 0;
|
|
8256
8616
|
}
|
|
8257
8617
|
/* DriftLight, or zeros that switch it off: see `driftLight/uniforms.ts`. */
|
|
8258
8618
|
this.uploadLightField();
|
|
@@ -8283,10 +8643,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8283
8643
|
f[emissiveScale + 1] = 1;
|
|
8284
8644
|
f[emissiveScale + 2] = 1;
|
|
8285
8645
|
f[at('uRelief')] = 0;
|
|
8286
|
-
f[at('
|
|
8646
|
+
f[at('uRelief') + 1] = 60;
|
|
8287
8647
|
f[at('uTextureRelief')] = 0;
|
|
8288
|
-
f[at('
|
|
8289
|
-
f[at('
|
|
8648
|
+
f[at('uEmission')] = env.emissiveGain;
|
|
8649
|
+
f[at('uEmission') + 1] = env.nightFactor;
|
|
8290
8650
|
/*
|
|
8291
8651
|
* **Only where the variant declares it**, because `at` throws on a name the permutation does
|
|
8292
8652
|
* not hold and this term is compiled in by `nightEmissive` alone.
|
|
@@ -8303,7 +8663,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8303
8663
|
if (this.quality.nightEmissive) f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
8304
8664
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
8305
8665
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
8306
|
-
|
|
8666
|
+
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
8667
|
+
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
8307
8668
|
/*
|
|
8308
8669
|
* No grading here. `renderer.ts` grades in this pass only when there is no composite to do
|
|
8309
8670
|
* it in; this backend has no composite yet, and adding one is its own row. Exposure is 1
|
|
@@ -8398,13 +8759,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8398
8759
|
* uniform added on one backend is bound on both in the same change.
|
|
8399
8760
|
*/
|
|
8400
8761
|
scatterInto(f, this.fragment.fields['uLightDir'], lights.directions, 3);
|
|
8401
|
-
|
|
8762
|
+
/* The cone and, in z, the light's own falloff exponent. See `conesAndFalloffs`. */
|
|
8763
|
+
scatterInto(f, this.fragment.fields['uLightCone'], lights.conesAndFalloffs, 4);
|
|
8402
8764
|
scatterInto(f, this.fragment.fields['uLightIesProfile'], lights.iesProfiles, 1);
|
|
8403
|
-
|
|
8765
|
+
/* The azimuth and, in w, the light's channels. See `axesAndChannels`. */
|
|
8766
|
+
scatterInto(f, this.fragment.fields['uLightIesAxis'], lights.axesAndChannels, 4);
|
|
8404
8767
|
scatterInto(f, this.fragment.fields['uLightCookie'], lights.cookies, 1);
|
|
8405
|
-
|
|
8406
|
-
f[at('
|
|
8407
|
-
f[at('
|
|
8768
|
+
/* The fixture atlas's shape, three numbers in one row: profile rows, planes, cookie tiles. */
|
|
8769
|
+
f[at('uFixtureShape')] = this.iesRows;
|
|
8770
|
+
f[at('uFixtureShape') + 1] = this.iesPlanes;
|
|
8771
|
+
f[at('uFixtureShape') + 2] = this.cookieTiles;
|
|
8408
8772
|
|
|
8409
8773
|
/*
|
|
8410
8774
|
* The rectangular emitters. Bound on both backends in the same change, per the 2026-08-13
|
|
@@ -8418,7 +8782,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8418
8782
|
scatterInto(f, this.fragment.fields['uAreaLightColor'], area.colors, 3);
|
|
8419
8783
|
scatterInto(f, this.fragment.fields['uAreaLightRight'], area.right, 3);
|
|
8420
8784
|
scatterInto(f, this.fragment.fields['uAreaLightUp'], area.up, 3);
|
|
8421
|
-
|
|
8785
|
+
/* The half extents and, in zw, the barn doors. See `packAreaSizesAndDoors`. */
|
|
8786
|
+
packAreaSizesAndDoors(area, this.areaSizesAndDoors);
|
|
8787
|
+
scatterInto(f, this.fragment.fields['uAreaLightSize'], this.areaSizesAndDoors, 4);
|
|
8422
8788
|
scatterInto(f, this.fragment.fields['uAreaLightTwoSided'], area.twoSided, 1);
|
|
8423
8789
|
}
|
|
8424
8790
|
|
|
@@ -8568,8 +8934,20 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8568
8934
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
8569
8935
|
*/
|
|
8570
8936
|
this.materials.dirty();
|
|
8571
|
-
/*
|
|
8937
|
+
/*
|
|
8938
|
+
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
8939
|
+
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
8940
|
+
* held, so a pass that kept the last pass's normal, ORM, emissive and model maps here while
|
|
8941
|
+
* binding the blank group made the next `setMaterial` naming those same maps a no-op: the draw
|
|
8942
|
+
* kept the blank group and read the stand-ins while its flags said the maps were bound. A scene
|
|
8943
|
+
* whose first material in a pass was the last of the pass before — any scene of one material —
|
|
8944
|
+
* lost its maps from the second frame on, on this backend alone. Until 4.8.6.
|
|
8945
|
+
*/
|
|
8572
8946
|
this.albedo = null;
|
|
8947
|
+
this.normalMap = null;
|
|
8948
|
+
this.ormMap = null;
|
|
8949
|
+
this.emissiveMap = null;
|
|
8950
|
+
this.modelMap = null;
|
|
8573
8951
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
8574
8952
|
this.uvScale[0] = 1;
|
|
8575
8953
|
this.uvScale[1] = 1;
|
|
@@ -9305,6 +9683,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9305
9683
|
options: TranslucentMeshOptions,
|
|
9306
9684
|
): void {
|
|
9307
9685
|
if (!this.canDraw() || this.viewProj === null) return;
|
|
9686
|
+
warnRegionsWithoutPage(this.materialModel, data.lightmapRegions);
|
|
9308
9687
|
const mesh = batch.mesh;
|
|
9309
9688
|
/* Nor a batch of a mesh with nothing in it: see `GpuMesh.indexCount`. */
|
|
9310
9689
|
if (!mesh.complete || mesh.indexCount === 0) return;
|
|
@@ -11513,7 +11892,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11513
11892
|
routesLate(
|
|
11514
11893
|
this.reconstructing,
|
|
11515
11894
|
this.reflectionPassActive,
|
|
11516
|
-
this.probePassActive,
|
|
11895
|
+
this.probePassActive || this.capturePassActive,
|
|
11517
11896
|
this.framePresented,
|
|
11518
11897
|
)
|
|
11519
11898
|
);
|
|
@@ -11593,7 +11972,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
11593
11972
|
return tracksMotion(
|
|
11594
11973
|
this.reconstructing,
|
|
11595
11974
|
this.reflectionPassActive,
|
|
11596
|
-
this.probePassActive,
|
|
11975
|
+
this.probePassActive || this.capturePassActive,
|
|
11597
11976
|
this.framePresented,
|
|
11598
11977
|
);
|
|
11599
11978
|
}
|
|
@@ -12050,6 +12429,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12050
12429
|
f.set(surface.axis, base + at('uSsrAxis'));
|
|
12051
12430
|
f.set(surface.tint, base + at('uSsrTint'));
|
|
12052
12431
|
f[base + at('uSsrStrength')] = surface.strength;
|
|
12432
|
+
f[base + at('uSsrFresnel')] = surface.fresnel ? 1 : 0;
|
|
12433
|
+
f[base + at('uSsrFresnel') + 1] = surface.roughness;
|
|
12053
12434
|
f[base + at('uSsrFacingCos')] = surface.facingCos;
|
|
12054
12435
|
f[base + at('uSsrReach')] = surface.reachM;
|
|
12055
12436
|
f[base + at('uSsrThickness')] = surface.thicknessM;
|
|
@@ -12595,7 +12976,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12595
12976
|
*
|
|
12596
12977
|
* `load` rather than `clear` wherever there is a blend, and that is the other half of what
|
|
12597
12978
|
* 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 `
|
|
12979
|
+
* wrote and adds the octave to nothing. See `TINTED_ADD_BLEND`.
|
|
12599
12980
|
*/
|
|
12600
12981
|
const stage = (
|
|
12601
12982
|
label: string,
|
|
@@ -12605,6 +12986,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12605
12986
|
source: number,
|
|
12606
12987
|
slot: number,
|
|
12607
12988
|
blend?: GPUBlendState,
|
|
12989
|
+
constant?: number[],
|
|
12608
12990
|
): void => {
|
|
12609
12991
|
const group = this.bloomGroups[source];
|
|
12610
12992
|
if (group === undefined) return;
|
|
@@ -12632,6 +13014,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12632
13014
|
),
|
|
12633
13015
|
);
|
|
12634
13016
|
pass.setBindGroup(0, group, [slot * BLOOM_SLOT]);
|
|
13017
|
+
if (constant !== undefined) pass.setBlendConstant(constant);
|
|
12635
13018
|
pass.draw(3);
|
|
12636
13019
|
pass.end();
|
|
12637
13020
|
};
|
|
@@ -12654,19 +13037,29 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12654
13037
|
);
|
|
12655
13038
|
}
|
|
12656
13039
|
|
|
12657
|
-
/*
|
|
12658
|
-
|
|
13040
|
+
/*
|
|
13041
|
+
* And back up. Every draw reads the same tent radius, so they share one slot rather than
|
|
13042
|
+
* holding five copies of one constant — but for the first, which multiplies the deepest level
|
|
13043
|
+
* by its tint. Each level being added onto takes its own tint from the blend constant.
|
|
13044
|
+
*/
|
|
13045
|
+
const response = this.bloomResponse;
|
|
12659
13046
|
for (let index = levels.length - 1; index > 0; index--) {
|
|
12660
13047
|
const target = levels[index - 1];
|
|
12661
|
-
|
|
13048
|
+
const constant = this.bloomConstants[index - 1];
|
|
13049
|
+
if (target === undefined || constant === undefined) break;
|
|
13050
|
+
const into = 1 + (index - 1) * 3;
|
|
13051
|
+
constant[0] = response[into] ?? 1;
|
|
13052
|
+
constant[1] = response[into + 1] ?? 1;
|
|
13053
|
+
constant[2] = response[into + 2] ?? 1;
|
|
12662
13054
|
stage(
|
|
12663
13055
|
'post.bloomUp',
|
|
12664
13056
|
'post.bloomUpsample',
|
|
12665
13057
|
BLOOM_UPSAMPLE_FRAG_WGSL,
|
|
12666
13058
|
target,
|
|
12667
13059
|
index + 1,
|
|
12668
|
-
BLOOM_UPSAMPLE_SLOT,
|
|
12669
|
-
|
|
13060
|
+
index === levels.length - 1 ? BLOOM_UPSAMPLE_DEEPEST_SLOT : BLOOM_UPSAMPLE_SLOT,
|
|
13061
|
+
TINTED_ADD_BLEND,
|
|
13062
|
+
constant,
|
|
12670
13063
|
);
|
|
12671
13064
|
}
|
|
12672
13065
|
}
|
|
@@ -12741,7 +13134,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12741
13134
|
* glass, and a frame that refracts nothing breaks no pass at all.
|
|
12742
13135
|
*/
|
|
12743
13136
|
private takeRefractSnapshot(): boolean {
|
|
12744
|
-
if (this.reflectionPassActive || this.probePassActive) return false;
|
|
13137
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive) return false;
|
|
12745
13138
|
if (this.refractSnapshotTaken) return true;
|
|
12746
13139
|
/* A late draw refracts the reconstructed picture, which does not exist until the resolve has
|
|
12747
13140
|
run; `runLate` takes the copy then, once, before any late draw is replayed. */
|
|
@@ -12822,6 +13215,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12822
13215
|
!this.skinScattered &&
|
|
12823
13216
|
!this.reflectionPassActive &&
|
|
12824
13217
|
!this.probePassActive &&
|
|
13218
|
+
!this.capturePassActive &&
|
|
12825
13219
|
!this.framePresented &&
|
|
12826
13220
|
!this.overlayActive &&
|
|
12827
13221
|
this.oitMode === 'none'
|
|
@@ -12829,9 +13223,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12829
13223
|
}
|
|
12830
13224
|
|
|
12831
13225
|
/**
|
|
12832
|
-
* A skin draw's diffuse
|
|
12833
|
-
* buffers and bind group, through the
|
|
12834
|
-
*
|
|
13226
|
+
* A skin draw's diffuse and colour halves, kept for the spread: the frame's draw again, twice,
|
|
13227
|
+
* with the same slots, buffers and bind group, through the pipelines that write the diffuse's
|
|
13228
|
+
* light alone and its colour alone into the blur's targets and test for the depth its frame half
|
|
13229
|
+
* wrote.
|
|
12835
13230
|
*/
|
|
12836
13231
|
private keepSkinDiffuse(
|
|
12837
13232
|
mesh: GpuMesh,
|
|
@@ -12850,45 +13245,49 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12850
13245
|
const pipelines = this.skinPipelines;
|
|
12851
13246
|
const present = this.meshPresent.get(base);
|
|
12852
13247
|
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.
|
|
13248
|
+
const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
13249
|
+
/* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
|
|
13250
|
+
for (const half of SKIN_KEPT_HALVES) {
|
|
13251
|
+
const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
|
|
13252
|
+
const pipeline =
|
|
13253
|
+
pipelines.peek(key) ??
|
|
13254
|
+
flatPipeline(
|
|
13255
|
+
pipelines,
|
|
13256
|
+
this.surface.device,
|
|
13257
|
+
this.flatLayoutFor(skinned, morphed),
|
|
13258
|
+
this.variant,
|
|
13259
|
+
key,
|
|
13260
|
+
present,
|
|
13261
|
+
false,
|
|
13262
|
+
skinned,
|
|
13263
|
+
morphed,
|
|
13264
|
+
true,
|
|
13265
|
+
layer,
|
|
13266
|
+
false,
|
|
13267
|
+
'none',
|
|
13268
|
+
this.materialDoubleSided,
|
|
13269
|
+
false,
|
|
13270
|
+
eight,
|
|
13271
|
+
clothed,
|
|
13272
|
+
'skin',
|
|
13273
|
+
half,
|
|
13274
|
+
);
|
|
13275
|
+
const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
|
|
13276
|
+
command.pipeline = pipeline;
|
|
13277
|
+
command.bindGroup = bindGroup;
|
|
13278
|
+
command.offsetA = slot;
|
|
13279
|
+
command.offsetB = material;
|
|
13280
|
+
command.offsetCount = 2;
|
|
13281
|
+
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
13282
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index] as GPUBuffer;
|
|
13283
|
+
}
|
|
13284
|
+
command.vertexCount = mesh.vertexBuffers.length;
|
|
13285
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
13286
|
+
command.indexed = true;
|
|
13287
|
+
command.count = mesh.indexCount;
|
|
13288
|
+
command.instances = 1;
|
|
13289
|
+
command.indirect = null;
|
|
12885
13290
|
}
|
|
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
13291
|
}
|
|
12893
13292
|
|
|
12894
13293
|
/**
|
|
@@ -12906,6 +13305,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12906
13305
|
this.skinScattered = true;
|
|
12907
13306
|
if (!skin.pending) return;
|
|
12908
13307
|
if (this.reflectionPassActive || this.probePassActive || this.oitReplaying) return;
|
|
13308
|
+
if (this.capturePassActive) return;
|
|
12909
13309
|
const encoder = this.encoder;
|
|
12910
13310
|
const depth = this.depthView;
|
|
12911
13311
|
const resolved = this.resolvedDepthView;
|
|
@@ -12974,7 +13374,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12974
13374
|
* own terms: `resolvedDepthView` holds the *frame's* depth, and clamping a mirrored beam
|
|
12975
13375
|
* against it would stop it at geometry that is not in front of it.
|
|
12976
13376
|
*/
|
|
12977
|
-
if (this.reflectionPassActive) return false;
|
|
13377
|
+
if (this.reflectionPassActive || this.capturePassActive) return false;
|
|
12978
13378
|
if (this.resolvedDepthView === null) return false;
|
|
12979
13379
|
if (this.volumeDepthTaken) return true;
|
|
12980
13380
|
const encoder = this.encoder;
|
|
@@ -13534,7 +13934,9 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13534
13934
|
* any future branch added to one belongs in the other.
|
|
13535
13935
|
*/
|
|
13536
13936
|
private targetPipelines(): PipelineCache {
|
|
13537
|
-
if (this.probePassActive || this.reflectionPassActive
|
|
13937
|
+
if (this.probePassActive || this.reflectionPassActive || this.capturePassActive) {
|
|
13938
|
+
return this.pipelines;
|
|
13939
|
+
}
|
|
13538
13940
|
return this.overlayActive || this.framePresented ? this.overlayPipelines : this.pipelines;
|
|
13539
13941
|
}
|
|
13540
13942
|
|
|
@@ -14373,10 +14775,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14373
14775
|
f[frustumAt + 3] = aspect;
|
|
14374
14776
|
f.set(camera.view as Float32Array, at('uView'));
|
|
14375
14777
|
|
|
14376
|
-
|
|
14778
|
+
/* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
|
|
14779
|
+
const set = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
|
|
14377
14780
|
/* A slot is read only under `POINT_SHADOWS`; see `lightHasFixture`. */
|
|
14378
14781
|
const shadowSlots = this.quality.pointShadows ? MAX_POINT_LIGHTS : 0;
|
|
14379
|
-
i[at('uClustered')] = clusteredMode(set, set
|
|
14782
|
+
i[at('uClustered')] = clusteredMode(set, clusteredLightTotal(set), shadowSlots);
|
|
14380
14783
|
binner.setFrame({
|
|
14381
14784
|
lights: set,
|
|
14382
14785
|
view: camera.view as Float32Array,
|
|
@@ -14715,6 +15118,16 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14715
15118
|
*/
|
|
14716
15119
|
|
|
14717
15120
|
private bloomScale = 1;
|
|
15121
|
+
/** The draw's own ambient, as `packAmbientSH` writes it; zeros are the frame's. */
|
|
15122
|
+
private readonly ambientSH = new Float32Array(AMBIENT_SH_FLOATS);
|
|
15123
|
+
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
15124
|
+
private readonly bloomResponse = defaultBloomResponse();
|
|
15125
|
+
/** The response before the last `setBloom`, to tell whether the blocks need writing again. */
|
|
15126
|
+
private readonly bloomResponseNext = defaultBloomResponse();
|
|
15127
|
+
/** Each level's tint as a blend constant, rewritten in place: `setBlendConstant` takes a colour. */
|
|
15128
|
+
private readonly bloomConstants: number[][] = Array.from({ length: BLOOM_LEVELS }, () => [
|
|
15129
|
+
1, 1, 1, 1,
|
|
15130
|
+
]);
|
|
14718
15131
|
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
14719
15132
|
private bloomThresholdSet: number | null = null;
|
|
14720
15133
|
private rushStrength = 0;
|
|
@@ -14858,14 +15271,23 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14858
15271
|
this.gradeSize = lut.size;
|
|
14859
15272
|
}
|
|
14860
15273
|
|
|
14861
|
-
setBloom(scale: number, threshold?: number): void {
|
|
15274
|
+
setBloom(scale: number, threshold?: number, response?: BloomResponse | null): void {
|
|
14862
15275
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
15276
|
+
let moved = false;
|
|
15277
|
+
if (threshold !== undefined) {
|
|
15278
|
+
const next = bloomThresholdOf(threshold);
|
|
15279
|
+
moved = next !== this.bloomThresholdSet;
|
|
15280
|
+
this.bloomThresholdSet = next;
|
|
15281
|
+
}
|
|
15282
|
+
if (response !== undefined) {
|
|
15283
|
+
this.bloomResponseNext.set(this.bloomResponse);
|
|
15284
|
+
resolveBloomResponse(response, this.bloomResponse);
|
|
15285
|
+
for (let i = 0; i < this.bloomResponse.length && !moved; i++) {
|
|
15286
|
+
moved = this.bloomResponse[i] !== this.bloomResponseNext[i];
|
|
15287
|
+
}
|
|
15288
|
+
}
|
|
15289
|
+
/* The blocks are written once per size, so a moved threshold or response rewrites them now. */
|
|
15290
|
+
if (moved && this.bloomLevels.length > 0) this.writeBloomBlocks();
|
|
14869
15291
|
}
|
|
14870
15292
|
|
|
14871
15293
|
/**
|
|
@@ -15678,6 +16100,60 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15678
16100
|
if (!this.setProbeGrid({ origin: WORLD_ORIGIN, spacing: UNIT_STEP, counts: SINGLE_PROBE })) {
|
|
15679
16101
|
return false;
|
|
15680
16102
|
}
|
|
16103
|
+
this.uploadProbeFaces(probe, image);
|
|
16104
|
+
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
16105
|
+
this.buildProbeChain(encoder, probe, 0);
|
|
16106
|
+
device.queue.submit([encoder.finish()]);
|
|
16107
|
+
|
|
16108
|
+
this.markProbeFilled(0);
|
|
16109
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
16110
|
+
this.rebuildFlatBindGroup();
|
|
16111
|
+
return true;
|
|
16112
|
+
}
|
|
16113
|
+
|
|
16114
|
+
/** One layer of the declared grid from an image. See `Renderer.setProbeLayerImage`. */
|
|
16115
|
+
setProbeLayerImage(
|
|
16116
|
+
layer: number,
|
|
16117
|
+
image: {
|
|
16118
|
+
readonly width: number;
|
|
16119
|
+
readonly height: number;
|
|
16120
|
+
readonly data: Float32Array;
|
|
16121
|
+
},
|
|
16122
|
+
options?: ProbeBakeOptions,
|
|
16123
|
+
): boolean {
|
|
16124
|
+
if (this.surface.lost) return false;
|
|
16125
|
+
const probe = this.probe;
|
|
16126
|
+
const grid = this.probes;
|
|
16127
|
+
if (probe === null || grid === null || this.probeArray === null) {
|
|
16128
|
+
console.warn(
|
|
16129
|
+
'WebGpuRenderer: setProbeLayerImage needs a reflection probe (`reflectionProbeSize`) and ' +
|
|
16130
|
+
'a grid declared with setProbeGrid; the image was ignored.',
|
|
16131
|
+
);
|
|
16132
|
+
return false;
|
|
16133
|
+
}
|
|
16134
|
+
if (!Number.isInteger(layer) || layer < 0 || layer >= grid.layers) {
|
|
16135
|
+
console.warn(
|
|
16136
|
+
`WebGpuRenderer: setProbeLayerImage was given layer ${layer}, and the grid has ${grid.layers}.`,
|
|
16137
|
+
);
|
|
16138
|
+
return false;
|
|
16139
|
+
}
|
|
16140
|
+
this.uploadProbeFaces(probe, image);
|
|
16141
|
+
const { device } = this.surface;
|
|
16142
|
+
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
16143
|
+
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
16144
|
+
device.queue.submit([encoder.finish()]);
|
|
16145
|
+
this.markProbeFilled(layer);
|
|
16146
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
16147
|
+
this.rebuildFlatBindGroup();
|
|
16148
|
+
return true;
|
|
16149
|
+
}
|
|
16150
|
+
|
|
16151
|
+
/** An equirectangular image onto the capture cube's six faces, in the cube's own format. */
|
|
16152
|
+
private uploadProbeFaces(
|
|
16153
|
+
probe: GPUTexture,
|
|
16154
|
+
image: { readonly width: number; readonly height: number; readonly data: Float32Array },
|
|
16155
|
+
): void {
|
|
16156
|
+
const { device } = this.surface;
|
|
15681
16157
|
const faces = equirectToCubeFaces(image, this.probeSize);
|
|
15682
16158
|
const format = this.pipelines.format;
|
|
15683
16159
|
for (let face = 0; face < faces.length; face++) {
|
|
@@ -15708,15 +16184,6 @@ export class WebGPURenderer implements RendererApi {
|
|
|
15708
16184
|
{ width: this.probeSize, height: this.probeSize, depthOrArrayLayers: 1 },
|
|
15709
16185
|
);
|
|
15710
16186
|
}
|
|
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
16187
|
}
|
|
15721
16188
|
|
|
15722
16189
|
/**
|
|
@@ -16660,6 +17127,15 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16660
17127
|
|
|
16661
17128
|
const up = (name: string): number => this.postField(BLOOM_UPSAMPLE_FIELDS, name);
|
|
16662
17129
|
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
17130
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
17131
|
+
/* White on every draw up but the first, which reads the deepest level and carries its tint. */
|
|
17132
|
+
const response = this.bloomResponse;
|
|
17133
|
+
const deepest = 1 + (levels.length - 1) * 3;
|
|
17134
|
+
for (let c = 0; c < 3; c++) {
|
|
17135
|
+
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uScale') + c] = 1;
|
|
17136
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uScale') + c] = response[deepest + c] ?? 1;
|
|
17137
|
+
}
|
|
17138
|
+
f[slot(0) + prefilter('uRamp')] = response[0] ?? 0;
|
|
16663
17139
|
|
|
16664
17140
|
this.surface.device.queue.writeBuffer(this.bloomUniforms, 0, this.bloomStaging);
|
|
16665
17141
|
}
|