@driftengine/core 4.8.5 → 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 +4321 -4250
- 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/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
|
@@ -23,7 +23,9 @@ import { PickableSet } from '../../pickable.js';
|
|
|
23
23
|
import { GpuTimestamps } from './gpuTimestamps.js';
|
|
24
24
|
import { isWeakGpuFamily } from '../../gpuCapability.js';
|
|
25
25
|
import { resolveAtmosphere } from '../../atmosphere.js';
|
|
26
|
-
import {
|
|
26
|
+
import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.js';
|
|
27
|
+
import { lightmapTexels, warnRegionsWithoutPage, warnUnpagedLightmap, withLightmapUvs, } from '../../lightmap.js';
|
|
28
|
+
import { BLOOM_FILTER_RADIUS_UV, BLOOM_LEVELS, bloomLevelSizes, bloomThresholdOf, bloomProfileWarning, defaultBloomResponse, resolveBloomResponse, } from '../../bloomChain.js';
|
|
27
29
|
import { createResolvedWater, resolveWater, waterAppearance, } from '../../waterDraw.js';
|
|
28
30
|
import { createResolvedLightVolume, DEPTH_01_TO_CLIP_Y_DOWN, resolveLightVolume, volumeMediumGain, } from '../../lightVolumeDraw.js';
|
|
29
31
|
import { WIND_STREAK_DEFAULTS, createResolvedWindStreaks, resolveWindStreaks, } from '../../windStreakDraw.js';
|
|
@@ -65,7 +67,7 @@ import { ExposurePass } from './exposurePass.js';
|
|
|
65
67
|
import { AO_STORE } from '../../shaders/ambientOcclusion.js';
|
|
66
68
|
import { clampAutoExposure } from '../../shaders/exposure.js';
|
|
67
69
|
import { clampLocalExposure } from '../../shaders/localExposure.js';
|
|
68
|
-
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS,
|
|
70
|
+
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, TINTED_ADD_BLEND, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, REFLECTION_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
69
71
|
import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
|
|
70
72
|
import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
|
|
71
73
|
import { createProbeMipBindGroupLayout, createProbePrefilterBindGroupLayout, PREFILTER_UNIFORM_OFFSETS, probeLevels, probeMipPipeline, probePrefilterPipeline, PROBE_FACES, PROBE_FACE_BASIS, PROBE_FAR_M, PROBE_FOV_DEG, PROBE_MIP_UNIFORM_SIZE, PROBE_NEAR_M, } from './probePass.js';
|
|
@@ -75,6 +77,7 @@ import { seaStateForWind } from '../../seaState.js';
|
|
|
75
77
|
import { causticsPipeline, createCausticsBindGroup, createCausticsBindGroupLayout, createGpuCaustics, CAUSTICS_FRAG_FIELDS, CAUSTICS_FRAG_SIZE, CAUSTICS_VERT_FIELDS, CAUSTICS_VERT_SIZE, } from './causticsPass.js';
|
|
76
78
|
import { createInsetBindGroup, createInsetBindGroupLayout, insetPipeline, INSET_UNIFORM_SIZE, } from './insetPass.js';
|
|
77
79
|
import { buildPlumeGeometry } from '../../plumeGeometry.js';
|
|
80
|
+
import { facingCode } from '../../particleBatch.js';
|
|
78
81
|
import { PARTICLE_INDICES } from '../../shaders/particle.js';
|
|
79
82
|
import { createScatterDeform, resolveScatterDeform, } from '../../scatterDeform.js';
|
|
80
83
|
import { resolvePointLights } from '../../lightBudget.js';
|
|
@@ -96,7 +99,8 @@ import { SKY_FIELDS, SKY_UNIFORM_SIZE, createSkyBindGroup, createSkyBindGroupLay
|
|
|
96
99
|
import { SCATTER_DEPTH_FIELDS, SCATTER_DEPTH_SIZE, SCATTER_FRAG_FIELDS, SCATTER_FRAG_SIZE, SCATTER_VERT_FIELDS, SCATTER_VERT_SIZE, createGpuScatter, createScatterBindGroup, createScatterBindGroupLayout, createScatterDepthBindGroup, createScatterDepthBindGroupLayout, scatterDepthPipeline, scatterPipeline, } from './scatterPass.js';
|
|
97
100
|
import { WATER_FRAG_FIELDS, WATER_FRAG_SIZE, WATER_VERT_FIELDS, WATER_VERT_SIZE, createGpuWater, createWaterBindGroup, createWaterBindGroupLayout, waterPipeline, } from './waterPass.js';
|
|
98
101
|
import { PLUME_FRAG_FIELDS, PLUME_FRAG_SIZE, PLUME_VERT_FIELDS, PLUME_VERT_SIZE, createGpuPlumes, createPlumeBindGroup, createPlumeBindGroupLayout, plumePipeline, } from './plumePass.js';
|
|
99
|
-
import { PARTICLE_VERT_FIELDS, createGpuParticles, createParticleBindGroupLayout, packInstances, particleFragBindings, particlePipeline, } from './particlePass.js';
|
|
102
|
+
import { PARTICLE_INSTANCE_FLOATS, PARTICLE_VERT_FIELDS, createGpuParticles, createParticleBindGroupLayout, createSpriteBindGroup, packInstances, particleFragBindings, particlePipeline, } from './particlePass.js';
|
|
103
|
+
import { sortBackToFront } from '../../particleSort.js';
|
|
100
104
|
import { WIND_STREAK_FRAG_FIELDS, WIND_STREAK_FRAG_SIZE, WIND_STREAK_VERT_FIELDS, WIND_STREAK_VERT_SIZE, createGpuWindStreaks, createWindStreakBindGroup, createWindStreakBindGroupLayout, windStreakPipeline, } from './windStreakPass.js';
|
|
101
105
|
import { FLOCK_FRAG_FIELDS, FLOCK_FRAG_SIZE, FLOCK_VERT_FIELDS, FLOCK_VERT_SIZE, createFlockBindGroup, createFlockBindGroupLayout, createGpuFlock, flockPipeline, } from './flockPass.js';
|
|
102
106
|
import { BOLT_FRAG_FIELDS, BOLT_FRAG_SIZE, BOLT_VERT_FIELDS, BOLT_VERT_SIZE, boltPipeline, createBoltBindGroup, createBoltBindGroupLayout, createGpuBolts, } from './boltPass.js';
|
|
@@ -127,6 +131,8 @@ import { fitProbeGrid } from '../../gi/probeGridFit.js';
|
|
|
127
131
|
const INDIRECT_PROBE_SPACING = 2;
|
|
128
132
|
/** What the trace is given when a frame never said what was lighting it. */
|
|
129
133
|
const NO_SUN = [0, 0, 0];
|
|
134
|
+
/** The halves of a skin draw the spread keeps, in the order they are drawn. See `keepSkinDiffuse`. */
|
|
135
|
+
const SKIN_KEPT_HALVES = ['diffuse', 'albedo'];
|
|
130
136
|
import { CLIP_Y_FLIP, MAX_REFLECTIVE_SURFACES, REFLECTION_EDGE_FADE, ReflectionQueue, } from '../../screenSpaceReflection.js';
|
|
131
137
|
import { decalScissor } from '../../decalProjector.js';
|
|
132
138
|
import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
|
|
@@ -138,7 +144,7 @@ import { OcclusionBuffer } from '../../occlusion.js';
|
|
|
138
144
|
import { boundsVisible } from '../../visibility.js';
|
|
139
145
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.js';
|
|
140
146
|
import { ClusterBinner } from './clusterBinner.js';
|
|
141
|
-
import { clusteredMode, createClusterLightSet, fillClusterLightSet, } from '../../clusteredLights.js';
|
|
147
|
+
import { clusteredMode, createClusterLightSet, clusteredLightTotal, fillClusterLightSet, lightChannelsOf, } from '../../clusteredLights.js';
|
|
142
148
|
import { computeAt, createComputeRegistry, } from '../../compute.js';
|
|
143
149
|
import { GpuInstancedBatch } from './instanced.js';
|
|
144
150
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
@@ -688,11 +694,16 @@ const DECAL_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
|
688
694
|
*/
|
|
689
695
|
const SSR_SLOT = Math.ceil(SSR_FRAG_SIZE / DYNAMIC_UNIFORM_ALIGNMENT) * DYNAMIC_UNIFORM_ALIGNMENT;
|
|
690
696
|
const BLOOM_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
691
|
-
const BLOOM_SLOTS = BLOOM_LEVELS +
|
|
697
|
+
const BLOOM_SLOTS = BLOOM_LEVELS + 2;
|
|
692
698
|
/** One slot per blur axis, aligned like every other dynamic-offset block here. */
|
|
693
699
|
const AO_BLUR_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
694
|
-
/**
|
|
700
|
+
/**
|
|
701
|
+
* The tent radius every upsample draw binds, with white as the level it reads is multiplied by;
|
|
702
|
+
* and the same for the first draw up, which multiplies the deepest level by that level's tint.
|
|
703
|
+
* Every other level's tint is the blend constant of the draw adding onto it. See `BloomResponse`.
|
|
704
|
+
*/
|
|
695
705
|
const BLOOM_UPSAMPLE_SLOT = BLOOM_LEVELS;
|
|
706
|
+
const BLOOM_UPSAMPLE_DEEPEST_SLOT = BLOOM_LEVELS + 1;
|
|
696
707
|
/** The tint a draw gets when it asks for none, matching the WebGL2 path's reset-to-white. */
|
|
697
708
|
const WHITE = new Float32Array([1, 1, 1]);
|
|
698
709
|
/** A dust field standing still, for a light volume that asked for dust and not for wind. */
|
|
@@ -849,7 +860,7 @@ export class WebGPURenderer {
|
|
|
849
860
|
* Held rather than passed, like every other per-draw state here: `setSkinPalette` chooses and
|
|
850
861
|
* the following draws use it, which is the shape `setMaterial` already has.
|
|
851
862
|
*/
|
|
852
|
-
skinPalettes
|
|
863
|
+
skinPalettes;
|
|
853
864
|
skinnedBindGroupLayout;
|
|
854
865
|
morphedBindGroupLayout;
|
|
855
866
|
bothBindGroupLayout;
|
|
@@ -1279,6 +1290,17 @@ export class WebGPURenderer {
|
|
|
1279
1290
|
/** Whether this frame has spread its skin: once, at its first blended draw or before its end. */
|
|
1280
1291
|
skinScattered = false;
|
|
1281
1292
|
skinInverseProjection = mat4.create();
|
|
1293
|
+
/** Scratch for a sprite's depth conversion, so a particle draw allocates nothing. */
|
|
1294
|
+
spriteInverseProjection = mat4.create();
|
|
1295
|
+
/** Said once: a sprite asked for a soft edge where no depth copy could be had. */
|
|
1296
|
+
warnedSpriteDepth = false;
|
|
1297
|
+
/** Said once: more rectangles than the fixed arm shades, and no froxel table for the rest. */
|
|
1298
|
+
warnedAreaOverflow = false;
|
|
1299
|
+
/** What a sprite's depth binding holds where no copy was taken. See `spriteDepthStandIn`. */
|
|
1300
|
+
spriteDepthEmpty = null;
|
|
1301
|
+
/** The samplers every sprite batch binds: its image filtered, the depth copy texel for texel. */
|
|
1302
|
+
spriteLinearSampler = null;
|
|
1303
|
+
spriteNearestSampler = null;
|
|
1282
1304
|
sceneColorView = null;
|
|
1283
1305
|
/** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
|
|
1284
1306
|
sceneColorMsaa = null;
|
|
@@ -2198,6 +2220,13 @@ export class WebGPURenderer {
|
|
|
2198
2220
|
createSurfaceTextureArray(sources, options = {}) {
|
|
2199
2221
|
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options, this.compressedFormats);
|
|
2200
2222
|
}
|
|
2223
|
+
/** A baked lightmap page, as a `lightmapModel` material's `modelMap`. See `RendererApi`. */
|
|
2224
|
+
createLightmap(page) {
|
|
2225
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, lightmapTexels(page), {
|
|
2226
|
+
wrap: 'clamp',
|
|
2227
|
+
mipmap: false,
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2201
2230
|
/**
|
|
2202
2231
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2203
2232
|
*
|
|
@@ -2427,6 +2456,7 @@ export class WebGPURenderer {
|
|
|
2427
2456
|
this.materialModel = material?.model ?? null;
|
|
2428
2457
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2429
2458
|
const model = material?.model;
|
|
2459
|
+
warnUnpagedLightmap(model, modelMap);
|
|
2430
2460
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
2431
2461
|
this.skinScatter.setProfile(model.profile, model.scatterColor[0] * model.radius, model.scatterColor[1] * model.radius, model.scatterColor[2] * model.radius);
|
|
2432
2462
|
}
|
|
@@ -2444,6 +2474,8 @@ export class WebGPURenderer {
|
|
|
2444
2474
|
f[cut] = cutoff;
|
|
2445
2475
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
2446
2476
|
f[cut + 2] = this.cutoutFrame;
|
|
2477
|
+
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
2478
|
+
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
2447
2479
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
2448
2480
|
/* 0 is no map, and gates the whole block in the shader. See `uNormalStrength`. */
|
|
2449
2481
|
f[this.materialField('uNormalStrength')] =
|
|
@@ -2583,16 +2615,26 @@ export class WebGPURenderer {
|
|
|
2583
2615
|
console.warn('WebGPU: setEnvironmentAmbient no longer does anything. A baked probe now lights a ' +
|
|
2584
2616
|
'surface through projected irradiance, and there is nothing left to tune.');
|
|
2585
2617
|
}
|
|
2618
|
+
/**
|
|
2619
|
+
* The ambient the following draws take, nine spherical-harmonic coefficients or null for the
|
|
2620
|
+
* frame's. See `Renderer.setAmbientSH`: material state, so the next draw takes a slot of its own.
|
|
2621
|
+
*/
|
|
2622
|
+
setAmbientSH(coefficients) {
|
|
2623
|
+
if (this.surface.lost)
|
|
2624
|
+
return;
|
|
2625
|
+
packAmbientSH(coefficients, this.ambientSH);
|
|
2626
|
+
this.material((f) => f.set(this.ambientSH, this.materialField('uAmbientSH')));
|
|
2627
|
+
}
|
|
2586
2628
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
2587
2629
|
setSurfaceReflectivity(amount) {
|
|
2588
2630
|
this.material((f) => {
|
|
2589
|
-
f[this.materialField('
|
|
2631
|
+
f[this.materialField('uEnvironmentDials') + 1] = Math.min(1, Math.max(0, amount));
|
|
2590
2632
|
});
|
|
2591
2633
|
}
|
|
2592
2634
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
2593
2635
|
setEnvironmentGain(gain) {
|
|
2594
2636
|
this.material((f) => {
|
|
2595
|
-
f[this.materialField('
|
|
2637
|
+
f[this.materialField('uEnvironmentDials') + 2] = Math.max(0, gain);
|
|
2596
2638
|
});
|
|
2597
2639
|
}
|
|
2598
2640
|
setSurfaceGrain(amount) {
|
|
@@ -2629,7 +2671,7 @@ export class WebGPURenderer {
|
|
|
2629
2671
|
setSurfaceRelief(amount, cyclesPerMetre = 60) {
|
|
2630
2672
|
this.material((f) => {
|
|
2631
2673
|
f[this.materialField('uRelief')] = Math.min(1, Math.max(0, amount));
|
|
2632
|
-
f[this.materialField('
|
|
2674
|
+
f[this.materialField('uRelief') + 1] = Math.max(0.01, cyclesPerMetre);
|
|
2633
2675
|
});
|
|
2634
2676
|
}
|
|
2635
2677
|
/**
|
|
@@ -2647,7 +2689,7 @@ export class WebGPURenderer {
|
|
|
2647
2689
|
/** Scale the emissive of the following draws. See `Renderer.setEmissiveGain` for why. */
|
|
2648
2690
|
setEmissiveGain(gain) {
|
|
2649
2691
|
this.material((f) => {
|
|
2650
|
-
f[this.materialField('
|
|
2692
|
+
f[this.materialField('uEmission')] = gain;
|
|
2651
2693
|
});
|
|
2652
2694
|
}
|
|
2653
2695
|
/* -- Overlays ------------------------------------------------------------------------ */
|
|
@@ -3220,7 +3262,19 @@ export class WebGPURenderer {
|
|
|
3220
3262
|
*/
|
|
3221
3263
|
createParticles(capacity, options) {
|
|
3222
3264
|
const layout = this.particleLayoutFor(options.material);
|
|
3223
|
-
|
|
3265
|
+
const texture = (options.texture ?? null);
|
|
3266
|
+
if (options.material === 'sprite' && texture === null) {
|
|
3267
|
+
throw new Error("createParticles: a 'sprite' draws an image, and was given no `texture`");
|
|
3268
|
+
}
|
|
3269
|
+
return createGpuParticles(this.surface.device, capacity, options.material, options.blend, options.stretchSec ?? 0, options.facing ?? 'camera', options.erosion ?? 0, options.coreGain ?? 1, options.fog ?? false, layout.layout, layout.fields, layout.fragSize, {
|
|
3270
|
+
texture: texture?.view ?? null,
|
|
3271
|
+
cells: options.cells ?? [1, 1],
|
|
3272
|
+
blendCells: options.blendCells === true,
|
|
3273
|
+
softDepth: Math.max(0, options.softDepth ?? 0),
|
|
3274
|
+
cameraFade: Math.max(0, options.cameraFade ?? 0),
|
|
3275
|
+
cameraOffset: options.cameraOffset ?? 0,
|
|
3276
|
+
sort: options.sort === true,
|
|
3277
|
+
});
|
|
3224
3278
|
}
|
|
3225
3279
|
disposeParticles(batch) {
|
|
3226
3280
|
batch.dispose();
|
|
@@ -3260,18 +3314,21 @@ export class WebGPURenderer {
|
|
|
3260
3314
|
return;
|
|
3261
3315
|
}
|
|
3262
3316
|
batch.drawnInEncoder = this.encoderEpoch;
|
|
3263
|
-
|
|
3317
|
+
/* Farthest first where the batch asked; see `particleSort.ts`. */
|
|
3318
|
+
const drawn = batch.order === null ? data : sortBackToFront(data, camera.position, batch.order);
|
|
3319
|
+
const live = packInstances(batch, drawn);
|
|
3264
3320
|
if (live === 0)
|
|
3265
3321
|
return;
|
|
3266
3322
|
const device = this.surface.device;
|
|
3267
|
-
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live *
|
|
3323
|
+
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live * PARTICLE_INSTANCE_FLOATS);
|
|
3268
3324
|
const v = batch.vertFloats;
|
|
3269
3325
|
const atV = (name) => (PARTICLE_VERT_FIELDS[name]?.offset ?? -4) / 4;
|
|
3270
3326
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
3271
3327
|
v.set(this.correctedViewProj, atV('uViewProj'));
|
|
3272
3328
|
v.set(camera.position, atV('uCameraPos'));
|
|
3273
3329
|
v[atV('uStretchSec')] = batch.stretchSec;
|
|
3274
|
-
v[atV('uCameraFacing')] = batch.facing
|
|
3330
|
+
v[atV('uCameraFacing')] = facingCode(batch.facing);
|
|
3331
|
+
v[atV('uCameraOffset')] = batch.cameraOffset;
|
|
3275
3332
|
const f = batch.fragFloats;
|
|
3276
3333
|
const i = batch.fragInts;
|
|
3277
3334
|
const fields = batch.fields;
|
|
@@ -3322,14 +3379,18 @@ export class WebGPURenderer {
|
|
|
3322
3379
|
`uCameraPos` rather than guarded like the material-specific fields above. */
|
|
3323
3380
|
i[atF('uOutputTransform')] = this.gradeCode();
|
|
3324
3381
|
f[atF('uOutputExposure')] = this.gradeExposure();
|
|
3382
|
+
const spriteGroup = batch.material === 'sprite' ? this.spriteFades(batch, camera, f) : null;
|
|
3325
3383
|
device.queue.writeBuffer(batch.vertUniforms, 0, batch.vertStaging);
|
|
3326
3384
|
device.queue.writeBuffer(batch.fragUniforms, 0, batch.fragStaging);
|
|
3385
|
+
const group = spriteGroup ?? batch.bindGroup;
|
|
3386
|
+
if (group === null)
|
|
3387
|
+
return;
|
|
3327
3388
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3328
3389
|
const particlePipe = particlePipeline(this.blendedPipelines(), device, layout, batch.material, batch.blend);
|
|
3329
3390
|
const particleCommand = this.recordBlended(0);
|
|
3330
3391
|
if (particleCommand !== null) {
|
|
3331
3392
|
particleCommand.pipeline = particlePipe;
|
|
3332
|
-
particleCommand.bindGroup =
|
|
3393
|
+
particleCommand.bindGroup = group;
|
|
3333
3394
|
particleCommand.vertexBuffers[0] = batch.corners;
|
|
3334
3395
|
particleCommand.vertexBuffers[1] = batch.instances;
|
|
3335
3396
|
particleCommand.vertexCount = 2;
|
|
@@ -3343,13 +3404,97 @@ export class WebGPURenderer {
|
|
|
3343
3404
|
if (pass === null)
|
|
3344
3405
|
return;
|
|
3345
3406
|
pass.setPipeline(particlePipe);
|
|
3346
|
-
pass.setBindGroup(0,
|
|
3407
|
+
pass.setBindGroup(0, group);
|
|
3347
3408
|
pass.setVertexBuffer(0, batch.corners);
|
|
3348
3409
|
pass.setVertexBuffer(1, batch.instances);
|
|
3349
3410
|
pass.setIndexBuffer(batch.indexBuffer, 'uint32');
|
|
3350
3411
|
pass.drawIndexed(PARTICLE_INDICES, live);
|
|
3351
3412
|
}
|
|
3352
3413
|
}
|
|
3414
|
+
/**
|
|
3415
|
+
* A sprite's flipbook and fades into its fragment block `f`, and the bind group holding the
|
|
3416
|
+
* frame's depth copy its soft edge reads — built again only when that copy is replaced.
|
|
3417
|
+
*
|
|
3418
|
+
* **The copy is the volume's**: `takeVolumeDepth` ends the frame's pass, resolves the opaque
|
|
3419
|
+
* depth and reopens, once a frame whoever asks. Only in the frame's own picture: in a mirror or
|
|
3420
|
+
* a probe's face it would be another view's depth, and the edge stays hard there, as on WebGL2.
|
|
3421
|
+
* Without a composite there is no copy, and the renderer says so once.
|
|
3422
|
+
*/
|
|
3423
|
+
spriteFades(batch, camera, f) {
|
|
3424
|
+
const fields = batch.fields;
|
|
3425
|
+
const atF = (name) => (fields[name]?.offset ?? -4) / 4;
|
|
3426
|
+
let depth = null;
|
|
3427
|
+
if (batch.softDepth > 0 && !this.reflectionPassActive && !this.probePassActive) {
|
|
3428
|
+
if (this.openPass() !== null && this.takeVolumeDepth())
|
|
3429
|
+
depth = this.resolvedDepthView;
|
|
3430
|
+
if (depth === null && !this.warnedSpriteDepth) {
|
|
3431
|
+
this.warnedSpriteDepth = true;
|
|
3432
|
+
console.warn("WebGPU: a 'sprite' asked for a soft edge, which reads a copy of the frame's depth, and " +
|
|
3433
|
+
'this profile keeps none — screenEffects is off. The edge is hard.');
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
f[atF('uCells')] = batch.cells[0];
|
|
3437
|
+
f[atF('uCells') + 1] = batch.cells[1];
|
|
3438
|
+
f[atF('uCells') + 2] = batch.blendCells ? 1 : 0;
|
|
3439
|
+
f[atF('uFade')] = batch.softDepth;
|
|
3440
|
+
f[atF('uFade') + 1] = batch.cameraFade;
|
|
3441
|
+
f[atF('uFade') + 2] = depth === null ? 0 : 1;
|
|
3442
|
+
mat4.invert(this.spriteInverseProjection, camera.projection);
|
|
3443
|
+
const inverse = this.spriteInverseProjection;
|
|
3444
|
+
f[atF('uDepthToViewZ')] = inverse[10] ?? 0;
|
|
3445
|
+
f[atF('uDepthToViewZ') + 1] = inverse[14] ?? 0;
|
|
3446
|
+
f[atF('uDepthToViewZ') + 2] = inverse[11] ?? 0;
|
|
3447
|
+
f[atF('uDepthToViewZ') + 3] = inverse[15] ?? 1;
|
|
3448
|
+
/*
|
|
3449
|
+
* The pixels a fragment's own coordinate counts in: the output's where blended draws land
|
|
3450
|
+
* after reconstruction, the scene's otherwise. The copy is sampled by the fraction, so its own
|
|
3451
|
+
* size does not matter. Read off the canvas rather than `sceneSize`, which builds an object.
|
|
3452
|
+
*/
|
|
3453
|
+
const { canvas } = this.surface;
|
|
3454
|
+
const ratio = this.drawsLate() || !this.reconstructionWanted ? 1 : this.quality.reconstruction;
|
|
3455
|
+
/* `reconRenderSize`'s own rounding — up, from the floored output — without its object. */
|
|
3456
|
+
const width = Math.max(1, Math.floor(canvas.width));
|
|
3457
|
+
const height = Math.max(1, Math.floor(canvas.height));
|
|
3458
|
+
f[atF('uViewport')] = ratio > 1 ? Math.max(1, Math.ceil(width / ratio)) : width;
|
|
3459
|
+
f[atF('uViewport') + 1] = ratio > 1 ? Math.max(1, Math.ceil(height / ratio)) : height;
|
|
3460
|
+
/* Something single-sampled and unfilterable stands in where there is no copy to read. */
|
|
3461
|
+
const bound = depth ?? this.spriteDepthStandIn();
|
|
3462
|
+
if (batch.spriteGroup === null || batch.spriteGroupDepth !== bound) {
|
|
3463
|
+
batch.spriteGroup = createSpriteBindGroup(this.surface.device, this.particleLayoutFor('sprite').layout, batch, this.particleLayoutFor('sprite').fragSize, bound, this.spriteSampler(true), this.spriteSampler(false));
|
|
3464
|
+
batch.spriteGroupDepth = bound;
|
|
3465
|
+
}
|
|
3466
|
+
return batch.spriteGroup;
|
|
3467
|
+
}
|
|
3468
|
+
/** A sprite's image sampler, repeating nothing and filtered, or its depth's, nearest. */
|
|
3469
|
+
spriteSampler(linear) {
|
|
3470
|
+
const device = this.surface.device;
|
|
3471
|
+
if (linear) {
|
|
3472
|
+
this.spriteLinearSampler ??= device.createSampler({
|
|
3473
|
+
label: 'particle.sprite',
|
|
3474
|
+
magFilter: 'linear',
|
|
3475
|
+
minFilter: 'linear',
|
|
3476
|
+
mipmapFilter: 'linear',
|
|
3477
|
+
addressModeU: 'clamp-to-edge',
|
|
3478
|
+
addressModeV: 'clamp-to-edge',
|
|
3479
|
+
});
|
|
3480
|
+
return this.spriteLinearSampler;
|
|
3481
|
+
}
|
|
3482
|
+
this.spriteNearestSampler ??= device.createSampler({ label: 'particle.depth' });
|
|
3483
|
+
return this.spriteNearestSampler;
|
|
3484
|
+
}
|
|
3485
|
+
/** A one-texel `r32float` for a sprite's depth binding where no copy was taken. */
|
|
3486
|
+
spriteDepthStandIn() {
|
|
3487
|
+
if (this.spriteDepthEmpty === null) {
|
|
3488
|
+
const texture = this.surface.device.createTexture({
|
|
3489
|
+
label: 'particle.depthStandIn',
|
|
3490
|
+
size: [1, 1],
|
|
3491
|
+
format: 'r32float',
|
|
3492
|
+
usage: 0x04 | 0x02,
|
|
3493
|
+
});
|
|
3494
|
+
this.spriteDepthEmpty = texture.createView();
|
|
3495
|
+
}
|
|
3496
|
+
return this.spriteDepthEmpty;
|
|
3497
|
+
}
|
|
3353
3498
|
/**
|
|
3354
3499
|
* The bind group layout for one particle material, built on first use.
|
|
3355
3500
|
*
|
|
@@ -3367,7 +3512,7 @@ export class WebGPURenderer {
|
|
|
3367
3512
|
const device = this.surface.device;
|
|
3368
3513
|
const frag = particleFragBindings(material);
|
|
3369
3514
|
const built = {
|
|
3370
|
-
layout: createParticleBindGroupLayout(device, frag.size),
|
|
3515
|
+
layout: createParticleBindGroupLayout(device, frag.size, material === 'sprite'),
|
|
3371
3516
|
fields: frag.fields,
|
|
3372
3517
|
fragSize: frag.size,
|
|
3373
3518
|
};
|
|
@@ -4363,6 +4508,7 @@ export class WebGPURenderer {
|
|
|
4363
4508
|
iesAxes: new Float32Array(0),
|
|
4364
4509
|
cookies: new Float32Array(0),
|
|
4365
4510
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
4511
|
+
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
4366
4512
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
4367
4513
|
};
|
|
4368
4514
|
/** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
|
|
@@ -5034,6 +5180,8 @@ export class WebGPURenderer {
|
|
|
5034
5180
|
this.materialBudget.limit(quality.materialChangesPerFrame);
|
|
5035
5181
|
this.drawBudget.limit(quality.drawsPerFrame);
|
|
5036
5182
|
this.shadowDrawBudget.limit(quality.drawsPerFrame);
|
|
5183
|
+
/* A palette per draw at most, which is the most a frame can bind. See `SkinPaletteRing`. */
|
|
5184
|
+
this.skinPalettes = new SkinPaletteRing(quality.drawsPerFrame);
|
|
5037
5185
|
this.maxDrawingBufferPixels = quality.maxDrawingBufferPixels;
|
|
5038
5186
|
this.exposure = quality.outputExposure;
|
|
5039
5187
|
this.flushSchedule =
|
|
@@ -6324,7 +6472,9 @@ export class WebGPURenderer {
|
|
|
6324
6472
|
keyed.key = fullKey;
|
|
6325
6473
|
return keyed;
|
|
6326
6474
|
}
|
|
6327
|
-
createMesh(
|
|
6475
|
+
createMesh(given, options = {}) {
|
|
6476
|
+
/* The streams it is drawn with, before anything records which it has. See `withLightmapUvs`. */
|
|
6477
|
+
const data = withLightmapUvs(given);
|
|
6328
6478
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
6329
6479
|
return this.keyed(createGpuMesh(this.surface.device, data, options.dynamic === true), fullKey);
|
|
6330
6480
|
}
|
|
@@ -6337,7 +6487,8 @@ export class WebGPURenderer {
|
|
|
6337
6487
|
* straddle one, and going on writing to a dead device would raise validation errors a consumer
|
|
6338
6488
|
* can do nothing about. Done with `complete` still false is the signal that it was abandoned.
|
|
6339
6489
|
*/
|
|
6340
|
-
createMeshIncremental(
|
|
6490
|
+
createMeshIncremental(given, options = {}) {
|
|
6491
|
+
const data = withLightmapUvs(given);
|
|
6341
6492
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
6342
6493
|
const { mesh, upload } = createGpuMeshIncremental(this.surface.device, data, options.dynamic === true);
|
|
6343
6494
|
const surface = this.surface;
|
|
@@ -6398,6 +6549,15 @@ export class WebGPURenderer {
|
|
|
6398
6549
|
bindMeshPass(camera, env) {
|
|
6399
6550
|
if (this.surface.lost)
|
|
6400
6551
|
return;
|
|
6552
|
+
/* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
|
|
6553
|
+
if (!this.warnedAreaOverflow &&
|
|
6554
|
+
!this.quality.clusteredLights &&
|
|
6555
|
+
(env.areaLights?.count ?? 0) > this.shadedAreaLights) {
|
|
6556
|
+
this.warnedAreaOverflow = true;
|
|
6557
|
+
console.warn(`WebGPU: ${env.areaLights?.count ?? 0} rectangular lights and ${this.shadedAreaLights} ` +
|
|
6558
|
+
'shaded without clusteredLights: the rest are not drawn. Turn clusteredLights on, and the ' +
|
|
6559
|
+
'froxel table shades every one past the fixed ones, unshadowed.');
|
|
6560
|
+
}
|
|
6401
6561
|
/* Corrected once a frame, not once a draw: every draw copies the same matrix. */
|
|
6402
6562
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
6403
6563
|
/*
|
|
@@ -6561,16 +6721,18 @@ export class WebGPURenderer {
|
|
|
6561
6721
|
* switched grain off. That is the same failure the missing properties had — silence that
|
|
6562
6722
|
* reads as a setting rather than as an omission.
|
|
6563
6723
|
*
|
|
6564
|
-
* `
|
|
6724
|
+
* `uRelief.y` is 60 because `renderer.ts` says 60. Nothing in either file explains the
|
|
6565
6725
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
6566
6726
|
*/
|
|
6567
6727
|
f[at('uGrain')] = 1;
|
|
6568
|
-
f[at('
|
|
6728
|
+
f[at('uEnvironmentDials') + 1] = 0;
|
|
6569
6729
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6570
6730
|
f[at('uWriteMode')] = 0;
|
|
6571
6731
|
f[at('uWriteMode') + 1] = 0;
|
|
6572
6732
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
6573
|
-
f[at('
|
|
6733
|
+
f[at('uEnvironmentDials') + 2] = 1;
|
|
6734
|
+
/* The frame's own ambient until a draw asks for its own. See `setAmbientSH`. */
|
|
6735
|
+
f.fill(0, at('uAmbientSH'), at('uAmbientSH') + AMBIENT_SH_FLOATS);
|
|
6574
6736
|
/*
|
|
6575
6737
|
* **Only where the variant declares it.** The irradiance block lives inside the probe's own
|
|
6576
6738
|
* `#if`, because it exists to be filled from a cube only that permutation has, so `at` throws
|
|
@@ -6624,7 +6786,7 @@ export class WebGPURenderer {
|
|
|
6624
6786
|
f[base + 2] = sets[2] ?? 0;
|
|
6625
6787
|
}
|
|
6626
6788
|
/* Which chain is bound, because the level and the weight both depend on it. */
|
|
6627
|
-
f[at('
|
|
6789
|
+
f[at('uEnvironmentDials')] = this.quality.environmentPrefilter ? 1 : 0;
|
|
6628
6790
|
}
|
|
6629
6791
|
/* DriftLight, or zeros that switch it off: see `driftLight/uniforms.ts`. */
|
|
6630
6792
|
this.uploadLightField();
|
|
@@ -6650,10 +6812,10 @@ export class WebGPURenderer {
|
|
|
6650
6812
|
f[emissiveScale + 1] = 1;
|
|
6651
6813
|
f[emissiveScale + 2] = 1;
|
|
6652
6814
|
f[at('uRelief')] = 0;
|
|
6653
|
-
f[at('
|
|
6815
|
+
f[at('uRelief') + 1] = 60;
|
|
6654
6816
|
f[at('uTextureRelief')] = 0;
|
|
6655
|
-
f[at('
|
|
6656
|
-
f[at('
|
|
6817
|
+
f[at('uEmission')] = env.emissiveGain;
|
|
6818
|
+
f[at('uEmission') + 1] = env.nightFactor;
|
|
6657
6819
|
/*
|
|
6658
6820
|
* **Only where the variant declares it**, because `at` throws on a name the permutation does
|
|
6659
6821
|
* not hold and this term is compiled in by `nightEmissive` alone.
|
|
@@ -6671,7 +6833,8 @@ export class WebGPURenderer {
|
|
|
6671
6833
|
f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
6672
6834
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
6673
6835
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
6674
|
-
|
|
6836
|
+
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
6837
|
+
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
6675
6838
|
/*
|
|
6676
6839
|
* No grading here. `renderer.ts` grades in this pass only when there is no composite to do
|
|
6677
6840
|
* it in; this backend has no composite yet, and adding one is its own row. Exposure is 1
|
|
@@ -6767,11 +6930,13 @@ export class WebGPURenderer {
|
|
|
6767
6930
|
scatterInto(f, this.fragment.fields['uLightDir'], lights.directions, 3);
|
|
6768
6931
|
scatterInto(f, this.fragment.fields['uLightCone'], lights.coneCos, 2);
|
|
6769
6932
|
scatterInto(f, this.fragment.fields['uLightIesProfile'], lights.iesProfiles, 1);
|
|
6770
|
-
|
|
6933
|
+
/* The azimuth and, in w, the light's channels. See `axesAndChannels`. */
|
|
6934
|
+
scatterInto(f, this.fragment.fields['uLightIesAxis'], lights.axesAndChannels, 4);
|
|
6771
6935
|
scatterInto(f, this.fragment.fields['uLightCookie'], lights.cookies, 1);
|
|
6772
|
-
|
|
6773
|
-
f[at('
|
|
6774
|
-
f[at('
|
|
6936
|
+
/* The fixture atlas's shape, three numbers in one row: profile rows, planes, cookie tiles. */
|
|
6937
|
+
f[at('uFixtureShape')] = this.iesRows;
|
|
6938
|
+
f[at('uFixtureShape') + 1] = this.iesPlanes;
|
|
6939
|
+
f[at('uFixtureShape') + 2] = this.cookieTiles;
|
|
6775
6940
|
/*
|
|
6776
6941
|
* The rectangular emitters. Bound on both backends in the same change, per the 2026-08-13
|
|
6777
6942
|
* rule's fourth clause — and the count is what makes the whole thing free to a scene with
|
|
@@ -6916,8 +7081,20 @@ export class WebGPURenderer {
|
|
|
6916
7081
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
6917
7082
|
*/
|
|
6918
7083
|
this.materials.dirty();
|
|
6919
|
-
/*
|
|
7084
|
+
/*
|
|
7085
|
+
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
7086
|
+
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
7087
|
+
* held, so a pass that kept the last pass's normal, ORM, emissive and model maps here while
|
|
7088
|
+
* binding the blank group made the next `setMaterial` naming those same maps a no-op: the draw
|
|
7089
|
+
* kept the blank group and read the stand-ins while its flags said the maps were bound. A scene
|
|
7090
|
+
* whose first material in a pass was the last of the pass before — any scene of one material —
|
|
7091
|
+
* lost its maps from the second frame on, on this backend alone. Until 4.8.6.
|
|
7092
|
+
*/
|
|
6920
7093
|
this.albedo = null;
|
|
7094
|
+
this.normalMap = null;
|
|
7095
|
+
this.ormMap = null;
|
|
7096
|
+
this.emissiveMap = null;
|
|
7097
|
+
this.modelMap = null;
|
|
6921
7098
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
6922
7099
|
this.uvScale[0] = 1;
|
|
6923
7100
|
this.uvScale[1] = 1;
|
|
@@ -7548,6 +7725,7 @@ export class WebGPURenderer {
|
|
|
7548
7725
|
submitInstanced(batch, data, opacity, blend, options) {
|
|
7549
7726
|
if (!this.canDraw() || this.viewProj === null)
|
|
7550
7727
|
return;
|
|
7728
|
+
warnRegionsWithoutPage(this.materialModel, data.lightmapRegions);
|
|
7551
7729
|
const mesh = batch.mesh;
|
|
7552
7730
|
/* Nor a batch of a mesh with nothing in it: see `GpuMesh.indexCount`. */
|
|
7553
7731
|
if (!mesh.complete || mesh.indexCount === 0)
|
|
@@ -10362,9 +10540,9 @@ export class WebGPURenderer {
|
|
|
10362
10540
|
*
|
|
10363
10541
|
* `load` rather than `clear` wherever there is a blend, and that is the other half of what
|
|
10364
10542
|
* makes the sum of the octaves a sum: a cleared attachment discards the level the downsample
|
|
10365
|
-
* wrote and adds the octave to nothing. See `
|
|
10543
|
+
* wrote and adds the octave to nothing. See `TINTED_ADD_BLEND`.
|
|
10366
10544
|
*/
|
|
10367
|
-
const stage = (label, key, fragment, target, source, slot, blend) => {
|
|
10545
|
+
const stage = (label, key, fragment, target, source, slot, blend, constant) => {
|
|
10368
10546
|
const group = this.bloomGroups[source];
|
|
10369
10547
|
if (group === undefined)
|
|
10370
10548
|
return;
|
|
@@ -10382,6 +10560,8 @@ export class WebGPURenderer {
|
|
|
10382
10560
|
});
|
|
10383
10561
|
pass.setPipeline(postPipeline(this.pipelines, this.surface.device, this.bloomLayout, key, fragment, format, blend));
|
|
10384
10562
|
pass.setBindGroup(0, group, [slot * BLOOM_SLOT]);
|
|
10563
|
+
if (constant !== undefined)
|
|
10564
|
+
pass.setBlendConstant(constant);
|
|
10385
10565
|
pass.draw(3);
|
|
10386
10566
|
pass.end();
|
|
10387
10567
|
};
|
|
@@ -10395,13 +10575,22 @@ export class WebGPURenderer {
|
|
|
10395
10575
|
break;
|
|
10396
10576
|
stage('post.bloomDown', 'post.bloomDownsample', BLOOM_DOWNSAMPLE_FRAG_WGSL, target, index, index);
|
|
10397
10577
|
}
|
|
10398
|
-
/*
|
|
10399
|
-
|
|
10578
|
+
/*
|
|
10579
|
+
* And back up. Every draw reads the same tent radius, so they share one slot rather than
|
|
10580
|
+
* holding five copies of one constant — but for the first, which multiplies the deepest level
|
|
10581
|
+
* by its tint. Each level being added onto takes its own tint from the blend constant.
|
|
10582
|
+
*/
|
|
10583
|
+
const response = this.bloomResponse;
|
|
10400
10584
|
for (let index = levels.length - 1; index > 0; index--) {
|
|
10401
10585
|
const target = levels[index - 1];
|
|
10402
|
-
|
|
10586
|
+
const constant = this.bloomConstants[index - 1];
|
|
10587
|
+
if (target === undefined || constant === undefined)
|
|
10403
10588
|
break;
|
|
10404
|
-
|
|
10589
|
+
const into = 1 + (index - 1) * 3;
|
|
10590
|
+
constant[0] = response[into] ?? 1;
|
|
10591
|
+
constant[1] = response[into + 1] ?? 1;
|
|
10592
|
+
constant[2] = response[into + 2] ?? 1;
|
|
10593
|
+
stage('post.bloomUp', 'post.bloomUpsample', BLOOM_UPSAMPLE_FRAG_WGSL, target, index + 1, index === levels.length - 1 ? BLOOM_UPSAMPLE_DEEPEST_SLOT : BLOOM_UPSAMPLE_SLOT, TINTED_ADD_BLEND, constant);
|
|
10405
10594
|
}
|
|
10406
10595
|
}
|
|
10407
10596
|
/**
|
|
@@ -10553,9 +10742,10 @@ export class WebGPURenderer {
|
|
|
10553
10742
|
this.oitMode === 'none');
|
|
10554
10743
|
}
|
|
10555
10744
|
/**
|
|
10556
|
-
* A skin draw's diffuse
|
|
10557
|
-
* buffers and bind group, through the
|
|
10558
|
-
*
|
|
10745
|
+
* A skin draw's diffuse and colour halves, kept for the spread: the frame's draw again, twice,
|
|
10746
|
+
* with the same slots, buffers and bind group, through the pipelines that write the diffuse's
|
|
10747
|
+
* light alone and its colour alone into the blur's targets and test for the depth its frame half
|
|
10748
|
+
* wrote.
|
|
10559
10749
|
*/
|
|
10560
10750
|
keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
|
|
10561
10751
|
const skin = this.skinScatter;
|
|
@@ -10563,24 +10753,28 @@ export class WebGPURenderer {
|
|
|
10563
10753
|
const present = this.meshPresent.get(base);
|
|
10564
10754
|
if (skin === null || pipelines === null || present === undefined)
|
|
10565
10755
|
return;
|
|
10566
|
-
const
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
command.
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10756
|
+
const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
10757
|
+
/* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
|
|
10758
|
+
for (const half of SKIN_KEPT_HALVES) {
|
|
10759
|
+
const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
|
|
10760
|
+
const pipeline = pipelines.peek(key) ??
|
|
10761
|
+
flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half);
|
|
10762
|
+
const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
|
|
10763
|
+
command.pipeline = pipeline;
|
|
10764
|
+
command.bindGroup = bindGroup;
|
|
10765
|
+
command.offsetA = slot;
|
|
10766
|
+
command.offsetB = material;
|
|
10767
|
+
command.offsetCount = 2;
|
|
10768
|
+
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
10769
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
10770
|
+
}
|
|
10771
|
+
command.vertexCount = mesh.vertexBuffers.length;
|
|
10772
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
10773
|
+
command.indexed = true;
|
|
10774
|
+
command.count = mesh.indexCount;
|
|
10775
|
+
command.instances = 1;
|
|
10776
|
+
command.indirect = null;
|
|
10777
|
+
}
|
|
10584
10778
|
}
|
|
10585
10779
|
/**
|
|
10586
10780
|
* Spread the frame's skin and add it back: once, at the first blended draw or before the frame's
|
|
@@ -11963,10 +12157,11 @@ export class WebGPURenderer {
|
|
|
11963
12157
|
f[frustumAt + 2] = tanHalfFovY;
|
|
11964
12158
|
f[frustumAt + 3] = aspect;
|
|
11965
12159
|
f.set(camera.view, at('uView'));
|
|
11966
|
-
|
|
12160
|
+
/* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
|
|
12161
|
+
const set = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
|
|
11967
12162
|
/* A slot is read only under `POINT_SHADOWS`; see `lightHasFixture`. */
|
|
11968
12163
|
const shadowSlots = this.quality.pointShadows ? MAX_POINT_LIGHTS : 0;
|
|
11969
|
-
i[at('uClustered')] = clusteredMode(set, set
|
|
12164
|
+
i[at('uClustered')] = clusteredMode(set, clusteredLightTotal(set), shadowSlots);
|
|
11970
12165
|
binner.setFrame({
|
|
11971
12166
|
lights: set,
|
|
11972
12167
|
view: camera.view,
|
|
@@ -12281,6 +12476,16 @@ export class WebGPURenderer {
|
|
|
12281
12476
|
* -----------------------------------------------------------------------------------------
|
|
12282
12477
|
*/
|
|
12283
12478
|
bloomScale = 1;
|
|
12479
|
+
/** The draw's own ambient, as `packAmbientSH` writes it; zeros are the frame's. */
|
|
12480
|
+
ambientSH = new Float32Array(AMBIENT_SH_FLOATS);
|
|
12481
|
+
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
12482
|
+
bloomResponse = defaultBloomResponse();
|
|
12483
|
+
/** The response before the last `setBloom`, to tell whether the blocks need writing again. */
|
|
12484
|
+
bloomResponseNext = defaultBloomResponse();
|
|
12485
|
+
/** Each level's tint as a blend constant, rewritten in place: `setBlendConstant` takes a colour. */
|
|
12486
|
+
bloomConstants = Array.from({ length: BLOOM_LEVELS }, () => [
|
|
12487
|
+
1, 1, 1, 1,
|
|
12488
|
+
]);
|
|
12284
12489
|
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
12285
12490
|
bloomThresholdSet = null;
|
|
12286
12491
|
rushStrength = 0;
|
|
@@ -12411,16 +12616,23 @@ export class WebGPURenderer {
|
|
|
12411
12616
|
this.gradeView = this.gradeTexture.createView({ dimension: '3d' });
|
|
12412
12617
|
this.gradeSize = lut.size;
|
|
12413
12618
|
}
|
|
12414
|
-
setBloom(scale, threshold) {
|
|
12619
|
+
setBloom(scale, threshold, response) {
|
|
12415
12620
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12621
|
+
let moved = false;
|
|
12622
|
+
if (threshold !== undefined) {
|
|
12623
|
+
const next = bloomThresholdOf(threshold);
|
|
12624
|
+
moved = next !== this.bloomThresholdSet;
|
|
12625
|
+
this.bloomThresholdSet = next;
|
|
12626
|
+
}
|
|
12627
|
+
if (response !== undefined) {
|
|
12628
|
+
this.bloomResponseNext.set(this.bloomResponse);
|
|
12629
|
+
resolveBloomResponse(response, this.bloomResponse);
|
|
12630
|
+
for (let i = 0; i < this.bloomResponse.length && !moved; i++) {
|
|
12631
|
+
moved = this.bloomResponse[i] !== this.bloomResponseNext[i];
|
|
12632
|
+
}
|
|
12633
|
+
}
|
|
12634
|
+
/* The blocks are written once per size, so a moved threshold or response rewrites them now. */
|
|
12635
|
+
if (moved && this.bloomLevels.length > 0)
|
|
12424
12636
|
this.writeBloomBlocks();
|
|
12425
12637
|
}
|
|
12426
12638
|
/**
|
|
@@ -13152,6 +13364,43 @@ export class WebGPURenderer {
|
|
|
13152
13364
|
if (!this.setProbeGrid({ origin: WORLD_ORIGIN, spacing: UNIT_STEP, counts: SINGLE_PROBE })) {
|
|
13153
13365
|
return false;
|
|
13154
13366
|
}
|
|
13367
|
+
this.uploadProbeFaces(probe, image);
|
|
13368
|
+
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
13369
|
+
this.buildProbeChain(encoder, probe, 0);
|
|
13370
|
+
device.queue.submit([encoder.finish()]);
|
|
13371
|
+
this.markProbeFilled(0);
|
|
13372
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
13373
|
+
this.rebuildFlatBindGroup();
|
|
13374
|
+
return true;
|
|
13375
|
+
}
|
|
13376
|
+
/** One layer of the declared grid from an image. See `Renderer.setProbeLayerImage`. */
|
|
13377
|
+
setProbeLayerImage(layer, image, options) {
|
|
13378
|
+
if (this.surface.lost)
|
|
13379
|
+
return false;
|
|
13380
|
+
const probe = this.probe;
|
|
13381
|
+
const grid = this.probes;
|
|
13382
|
+
if (probe === null || grid === null || this.probeArray === null) {
|
|
13383
|
+
console.warn('WebGpuRenderer: setProbeLayerImage needs a reflection probe (`reflectionProbeSize`) and ' +
|
|
13384
|
+
'a grid declared with setProbeGrid; the image was ignored.');
|
|
13385
|
+
return false;
|
|
13386
|
+
}
|
|
13387
|
+
if (!Number.isInteger(layer) || layer < 0 || layer >= grid.layers) {
|
|
13388
|
+
console.warn(`WebGpuRenderer: setProbeLayerImage was given layer ${layer}, and the grid has ${grid.layers}.`);
|
|
13389
|
+
return false;
|
|
13390
|
+
}
|
|
13391
|
+
this.uploadProbeFaces(probe, image);
|
|
13392
|
+
const { device } = this.surface;
|
|
13393
|
+
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
13394
|
+
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
13395
|
+
device.queue.submit([encoder.finish()]);
|
|
13396
|
+
this.markProbeFilled(layer);
|
|
13397
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
13398
|
+
this.rebuildFlatBindGroup();
|
|
13399
|
+
return true;
|
|
13400
|
+
}
|
|
13401
|
+
/** An equirectangular image onto the capture cube's six faces, in the cube's own format. */
|
|
13402
|
+
uploadProbeFaces(probe, image) {
|
|
13403
|
+
const { device } = this.surface;
|
|
13155
13404
|
const faces = equirectToCubeFaces(image, this.probeSize);
|
|
13156
13405
|
const format = this.pipelines.format;
|
|
13157
13406
|
for (let face = 0; face < faces.length; face++) {
|
|
@@ -13178,13 +13427,6 @@ export class WebGPURenderer {
|
|
|
13178
13427
|
const converted = environmentTexels(pixels, format);
|
|
13179
13428
|
device.queue.writeTexture({ texture: probe, mipLevel: 0, origin: { x: 0, y: 0, z: face } }, converted.data, { bytesPerRow: this.probeSize * converted.bytesPerTexel, rowsPerImage: this.probeSize }, { width: this.probeSize, height: this.probeSize, depthOrArrayLayers: 1 });
|
|
13180
13429
|
}
|
|
13181
|
-
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
13182
|
-
this.buildProbeChain(encoder, probe, 0);
|
|
13183
|
-
device.queue.submit([encoder.finish()]);
|
|
13184
|
-
this.markProbeFilled(0);
|
|
13185
|
-
this.probeAmbient = options?.irradiance ?? true;
|
|
13186
|
-
this.rebuildFlatBindGroup();
|
|
13187
|
-
return true;
|
|
13188
13430
|
}
|
|
13189
13431
|
/**
|
|
13190
13432
|
* Upload the photometric profiles a consumer loaded, and rebuild the bind group that holds them.
|
|
@@ -14029,6 +14271,15 @@ export class WebGPURenderer {
|
|
|
14029
14271
|
}
|
|
14030
14272
|
const up = (name) => this.postField(BLOOM_UPSAMPLE_FIELDS, name);
|
|
14031
14273
|
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
14274
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
14275
|
+
/* White on every draw up but the first, which reads the deepest level and carries its tint. */
|
|
14276
|
+
const response = this.bloomResponse;
|
|
14277
|
+
const deepest = 1 + (levels.length - 1) * 3;
|
|
14278
|
+
for (let c = 0; c < 3; c++) {
|
|
14279
|
+
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uScale') + c] = 1;
|
|
14280
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uScale') + c] = response[deepest + c] ?? 1;
|
|
14281
|
+
}
|
|
14282
|
+
f[slot(0) + prefilter('uRamp')] = response[0] ?? 0;
|
|
14032
14283
|
this.surface.device.queue.writeBuffer(this.bloomUniforms, 0, this.bloomStaging);
|
|
14033
14284
|
}
|
|
14034
14285
|
ensureReflection(width, height) {
|