@forgeax/engine-shader 0.1.21 → 0.1.23

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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +4 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.mjs +33019 -8
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/ltc/provenance.d.ts +17 -0
  7. package/dist/ltc/provenance.d.ts.map +1 -0
  8. package/dist/ltc/tables.d.ts +22 -0
  9. package/dist/ltc/tables.d.ts.map +1 -0
  10. package/dist/material/artifact-types.d.ts +52 -0
  11. package/dist/material/artifact-types.d.ts.map +1 -1
  12. package/dist/material-schemas.d.ts +5 -0
  13. package/dist/material-schemas.d.ts.map +1 -1
  14. package/dist/register-default-sprite-lit.d.ts +3 -6
  15. package/dist/register-default-sprite-lit.d.ts.map +1 -1
  16. package/dist/types.d.ts +2 -1
  17. package/dist/types.d.ts.map +1 -1
  18. package/package.json +4 -4
  19. package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +121 -58
  20. package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
  21. package/src/__tests__/ibl-irradiance.unit.test.ts +7 -4
  22. package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
  23. package/src/__tests__/lighting-point-spot-volume.unit.test.ts +8 -2
  24. package/src/__tests__/lighting-punctual.unit.test.ts +29 -2
  25. package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
  26. package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
  27. package/src/__tests__/manifest.fixture.json +33 -0
  28. package/src/__tests__/material-contract.unit.test.ts +61 -1
  29. package/src/__tests__/probe-lighting-composition.unit.test.ts +38 -0
  30. package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
  31. package/src/__tests__/reflection-probe-sampling.unit.test.ts +24 -2
  32. package/src/__tests__/shader.unit.test.ts +6 -2
  33. package/src/__tests__/spot-cookie-composition.unit.test.ts +57 -0
  34. package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
  35. package/src/__tests__/sprite-variants.unit.test.ts +4 -1
  36. package/src/brdf.wgsl +115 -1
  37. package/src/common.wgsl +53 -79
  38. package/src/default-standard-pbr-skin.wgsl +80 -23
  39. package/src/default-standard-pbr.wgsl +119 -50
  40. package/src/ibl-irradiance.wgsl +4 -1
  41. package/src/ibl-sampling.wgsl +2 -2
  42. package/src/index.ts +17 -0
  43. package/src/lighting-directional.wgsl +6 -116
  44. package/src/lighting-probe.wgsl +51 -0
  45. package/src/lighting-punctual.wgsl +21 -11
  46. package/src/lighting-rect-area.wgsl +162 -0
  47. package/src/lighting-spot-modifiers.wgsl +93 -0
  48. package/src/ltc/provenance.ts +19 -0
  49. package/src/ltc/tables.ts +2831 -0
  50. package/src/material/artifact-types.ts +137 -0
  51. package/src/material-schemas.ts +12 -0
  52. package/src/register-default-sprite-lit.ts +3 -6
  53. package/src/sprite-lit.wgsl +3 -3
  54. package/src/sprite.wgsl +1 -1
  55. package/src/standard-cluster.wgsl +94 -57
  56. package/src/types.ts +2 -1
  57. package/src/volume/volume-inject.wgsl +34 -36
  58. package/src/volume/volume-integrate.wgsl +48 -38
  59. package/dist/.tsbuildinfo +0 -1
package/src/common.wgsl CHANGED
@@ -142,13 +142,10 @@ fn ditherUnorm8(value : vec3<f32>, pixelPosition : vec2<f32>) -> vec3<f32> {
142
142
  // the per-spot perspective `spotLightViewProj` matrices (4 lanes, fragment-read)
143
143
  // fold into the View UBO tail instead of a standalone @group(0) binding 9
144
144
  // uniform buffer. The standalone binding pushed the fragment-stage uniform
145
- // buffer count to 12 (view + pointLights + spotLights + shadowParams +
146
- // shadowCasterCascade + spotLightViewProj on the WebGL2 fallback path),
147
- // exceeding GLES 3.0's `max_uniform_buffers_per_shader_stage = 11` so the
148
- // WebGL2 fallback (mobile / WebKit this feat's compat target) crashed
149
- // pipeline-layout creation. Folding into the View UBO costs zero new bindings
150
- // (the count drops back to 11). D-1 rejected the View UBO for the CASTER vertex
151
- // channel (binding 7) because directional + spot casters both need a light-view
145
+ // buffer count to 12 before the local-light arrays were removed. The current
146
+ // view declares no point/spot light arrays; the spot matrices remain folded
147
+ // into the View UBO with no standalone binding. D-1 rejected the View UBO for
148
+ // the CASTER vertex channel (binding 7) because directional + spot casters both need a light-view
152
149
  // matrix in the same frame (same-frame write contention); the FRAGMENT read
153
150
  // channel has no such contention — the host writes all <=4 spot matrices once
154
151
  // per frame before the forward pass, so the fold is safe. mat4 array align=16:
@@ -207,12 +204,22 @@ struct Mesh {
207
204
  #endif
208
205
  };
209
206
 
207
+ // Retained CPU ProbeBlendRecord ABI: 4 scalar lanes plus 9 vec4 lanes = 160 B.
208
+ // The last lane carries padding for the 27 f32 SH coefficients. Sky residual
209
+ // is derived in lighting-probe.wgsl, never stored as a second fraction.
210
+ struct ProbeBlendRecord {
211
+ objectKey : f32,
212
+ generation : f32,
213
+ localBlendFraction : f32,
214
+ flags : f32,
215
+ shPreblend : array<vec4<f32>, 9>,
216
+ };
217
+
210
218
  // feat-20260519-light-casters-point-spot-pbr M4 / w21 (D-S1 + D-S2 +
211
219
  // AC-04 b/c + AC-05 binding declaration). Punctual-light std430 storage
212
- // types byte-for-byte mirror packPointLight / packSpotLight in
213
- // `packages/runtime/src/light-buffer-layout.ts`:
220
+ // types use the shared five-row host ABI in `light-buffer-layout.ts`:
214
221
  //
215
- // PointLight (32 B / 8 floats):
222
+ // Point/Spot/Rect direct slot (80 B / 20 u32-or-float lanes):
216
223
  // [ 0..2 ] position vec3<f32>
217
224
  // [ 3 ] invRangeSquared f32 (Bevy color_inverse_square_range.w; 0
218
225
  // collapses range falloff to a pure 1/d^2 inverse-square law)
@@ -220,7 +227,7 @@ struct Mesh {
220
227
  // color * intensity so the shader avoids the per-fragment mul)
221
228
  // [ 7 ] pad f32 = 0
222
229
  //
223
- // SpotLight (80 B / 20 floats):
230
+ // The metadata row carries kind, shadow, IES, and Cookie identities.
224
231
  // [ 0..2 ] position vec3<f32>
225
232
  // [ 3 ] invRangeSquared f32
226
233
  // [ 4..6 ] colorTimesIntensity vec3<f32>
@@ -229,81 +236,31 @@ struct Mesh {
229
236
  // [ 8..10] direction vec3<f32> (raw outgoing vector; shader reads
230
237
  // via dot(L, -direction) for cone angle test)
231
238
  // [ 11 ] cosOuter f32
232
- // [12] depthBias f32, [13] normalBias f32, [14] pcfKernelSize f32
233
- // [ 15 ] shadowAtlasTile i32 (sentinel -1 = unassigned/clipped,
234
- // 0..3 = spot atlas tile index; feat-20260625 M2/M3 D-4)
235
- // [ 16 ] shadowIntensity f32 (fraction of sampled shadow to apply)
236
- // [17..19] alignment tail padding
239
+ // Spot row 3: [12] depthBias, [13] normalBias, [14] shadowIntensity,
240
+ // [15] rollDeg. The unified slot has no separate pcf lane; the current
241
+ // surface and volume receivers use the stable PCF3 profile.
242
+ // Rect row 1/2/3: halfWidth, axisY + halfHeight, axisX + zero pad. The
243
+ // final metadata row carries the kind,
244
+ // shadow, IES, and Cookie identities.
237
245
  //
238
246
  // WGSL std430 alignment audit: vec3<f32> alignof=16 / sizeof=12. The
239
247
  // f32 lane wedged immediately after each vec3 fills the 4 B remainder
240
248
  // (WGSL struct member rule: next.offset = roundUp(prev.offset +
241
249
  // prev.size, this.alignof) - for f32 alignof=4 the round-up is a
242
250
  // no-op, so position[3] / color[3] / direction[3] sit at byte
243
- // offsets 12 / 28 / 44 with zero internal padding). PointLight stays
244
- // 32 B; SpotLight grows from 64 B (feat-20260625 M2/M3 D-4) for the three
245
- // receiver controls plus `shadowAtlasTile: i32`, then to 80 B for the
246
- // parity-only `shadowIntensity` lane at byte 64. These struct sizes match the
247
- // host packers (packPointLight 32 B / packSpotLight 80 B) exactly.
251
+ // offsets 12 / 28 / 44 with zero internal padding). Point, Spot, and Rect all
252
+ // use the unified 80 B host packer; this shared slot is the sole local-light
253
+ // carrier.
248
254
  //
249
- // Header `count: u32` lives in a wrapper struct with the trailing
250
- // runtime-sized array. Array-of-vec4-aligned-struct alignof = 16, so
251
- // `slots` starts at byte 16 (count u32 occupies 0..4; bytes 4..16 are
252
- // implicit padding). This matches `packLightArrayHeader`'s 16 B
253
- // (count u32 + 12 B pad) layout in light-buffer-layout.ts.
254
-
255
- struct PointLight {
256
- position : vec3<f32>,
257
- invRangeSquared : f32,
258
- colorTimesIntensity : vec3<f32>,
259
- // feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 (plan-strategy §D-2):
260
- // Repurposes the prior `pointPadW` (f32 zero-pad) lane as `shadowAtlasLayer`
261
- // (i32 sentinel, -1 = no shadow, 0..3 = cube_array atlas layer index for
262
- // shadow-casting point lights). Cap = 4 enforced by the PointLightShadow
263
- // ECS component cardinality. The shader path samples the cube_array atlas
264
- // (binding 5) only when `shadowAtlasLayer >= 0`. Field name is unique to
265
- // this struct (no `pad0`/`pad1` collision with the pbr Material struct
266
- // under naga_oil writeback substitution).
267
- shadowAtlasLayer : i32,
268
- };
269
-
270
- struct SpotLight {
271
- position : vec3<f32>,
272
- invRangeSquared : f32,
273
- colorTimesIntensity : vec3<f32>,
274
- cosInner : f32,
275
- direction : vec3<f32>,
276
- cosOuter : f32,
277
- // feat-20260625-spot-light-shadow-mapping M3 / w13 (plan-strategy D-4):
278
- // The trailing vec4 transports receiver controls and the tile identity.
279
- // Host packSpotLight writes slots 12..14 from the same snapshot; slot 15 is
280
- // the i32 shadow tile sentinel and slot 16 carries shadowIntensity.
281
- depthBias : f32,
282
- normalBias : f32,
283
- pcfKernelSize : f32,
284
- shadowAtlasTile : i32,
285
- shadowIntensity : f32,
286
- };
287
-
288
- struct PointLightsArray {
289
- count : u32,
290
- slots : array<PointLight, 4>,
291
- };
292
-
293
- struct SpotLightsArray {
294
- count : u32,
295
- slots : array<SpotLight, 4>,
255
+ struct DirectLightSlot {
256
+ position : vec4<f32>, // row0: position + inverse range
257
+ colorTimesIntensity : vec4<f32>, // row1: color + first angular fact
258
+ direction : vec4<f32>, // row2: primary axis + second angular fact
259
+ auxiliary : vec4<f32>, // row3: auxiliary Rect axis
260
+ metadata : vec4<u32>,
296
261
  };
297
262
 
298
263
  @group(0) @binding(0) var<uniform> view : View;
299
-
300
- #if STORAGE_BUFFER_AVAILABLE == true
301
- @group(0) @binding(1) var<storage, read> pointLightsBuffer : PointLightsArray;
302
- @group(0) @binding(2) var<storage, read> spotLightsBuffer : SpotLightsArray;
303
- #else
304
- @group(0) @binding(1) var<uniform> pointLightsBuffer : PointLightsArray;
305
- @group(0) @binding(2) var<uniform> spotLightsBuffer : SpotLightsArray;
306
- #endif
307
264
  // feat-20260520-directional-light-shadow-mapping M3 / w16 (D-1 / plan-strategy §8.1):
308
265
  // shadowMap + shadowSampler consume @group(0) bindings 3/4 (smallest unused
309
266
  // slots adjacent to view UBO at 0). M3 uses 3x3 PCF with textureLoad on
@@ -314,7 +271,7 @@ struct SpotLightsArray {
314
271
  // layers=4, depth32float; one cube per shadow-casting point light, cap=4).
315
272
  // Binding 6 declares the per-light shadow params buffer (URP only — proj
316
273
  // constants for cube depth-ref reconstruction); HDRP rides the same constants
317
- // on LightSlot pad lanes (.kind_and_pad.wyz) so binding 6 stays unbound on
274
+ // on direct-light metadata so binding 6 stays unbound on
318
275
  // the HDRP path. Sample-time gating by PointLight.shadowAtlasLayer >= 0.
319
276
  @group(0) @binding(3) var shadowMap : texture_depth_2d;
320
277
  @group(0) @binding(4) var shadowSampler : sampler_comparison;
@@ -337,7 +294,7 @@ struct SpotLightsArray {
337
294
  // depth-ref reconstruction (research L0.5 + L1.13). Each lane stores the
338
295
  // shader-side reconstruction constants for one shadow-casting point light;
339
296
  // slot N matches `PointLight.shadowAtlasLayer = N`. HDRP rides the same
340
- // constants on `LightSlot.kind_and_pad.wyz` so binding 6 stays unbound on
297
+ // constants on direct-light metadata so binding 6 stays unbound on
341
298
  // the HDRP path.
342
299
  #ifdef POINT_SHADOW_AVAILABLE
343
300
  @group(0) @binding(5) var shadowAtlas : texture_depth_cube_array;
@@ -410,6 +367,23 @@ struct ShadowCasterCascade {
410
367
  // — folded there in feat-20260625 w25 (scope-amend) to keep the WebGL2 fallback
411
368
  // fragment uniform-buffer count <= 11; binding 8 is the last view-BG binding.
412
369
  @group(0) @binding(8) var spotShadowMap : texture_depth_2d;
370
+ // Extended-lighting resources are a real optional view topology. The
371
+ // renderer selects the matching material variant and BGL together; keeping
372
+ // these declarations behind the same axis prevents a no-extension variant
373
+ // from consuming texture budget or requiring absent bindings.
374
+ #ifdef EXTENDED_LIGHTING_AVAILABLE
375
+ @group(0) @binding(9) var spotModifierSampler : sampler;
376
+ @group(0) @binding(11) var iesProfileTexture : texture_2d_array<f32>;
377
+ @group(0) @binding(12) var cookieTexture : texture_2d_array<f32>;
378
+ @group(0) @binding(13) var ltcLambertTexture : texture_2d<f32>;
379
+ @group(0) @binding(14) var ltcGgxTexture : texture_2d<f32>;
380
+ // One aspect-correction matrix per Cookie array slice. The matrix is kept in
381
+ // the shared view group so the same projected Cookie semantics are available
382
+ // to both the ordinary and clustered Standard PBR consumers.
383
+ @group(0) @binding(15) var<uniform> cookieMatrices : array<mat4x4<f32>, 32>;
384
+ #endif
385
+ #ifdef PROJECTOR_AVAILABLE
386
+ #ifndef EXTENDED_LIGHTING_AVAILABLE
413
387
  // Optional authored SpotLight projector. Surface and volume bind the same
414
388
  // accepted TextureAsset view and linear-clamp sampler; an unprojected spot
415
389
  // receives the renderer-owned white fallback. The declaration is capability
@@ -417,10 +391,10 @@ struct ShadowCasterCascade {
417
391
  // minimum of 16 sampled textures without this optional cookie. Devices that
418
392
  // cannot expose the 17th sampled texture select the matching white-projector
419
393
  // shader variant and omit these resources from the view BGL.
420
- #ifdef PROJECTOR_AVAILABLE
421
394
  @group(0) @binding(11) var projectorTexture : texture_2d<f32>;
422
395
  @group(0) @binding(12) var projectorSampler : sampler;
423
396
  #endif
397
+ #endif
424
398
  #if STORAGE_BUFFER_AVAILABLE == true
425
399
  @group(2) @binding(0) var<storage, read> meshes : array<Mesh>;
426
400
  #else
@@ -1,8 +1,9 @@
1
- #import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture}
1
+ #import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, shadowMap, shadowSampler, sampleMaterialTexture}
2
2
  #import forgeax_scene_temporal::{sceneViewZ}
3
3
  #import forgeax_pbr::temporal::{projectPbrSceneTemporal}
4
4
  #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
5
5
  #import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular}
6
+ #import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
6
7
  #import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
7
8
  #import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
8
9
  #ifdef CLUSTER_FORWARD_AVAILABLE
@@ -13,6 +14,9 @@
13
14
  #pragma variant_axis STORAGE_BUFFER_AVAILABLE
14
15
  #pragma variant_axis CLUSTER_FORWARD_AVAILABLE
15
16
  #pragma variant_axis VERTEX_COLOR_AVAILABLE
17
+ #pragma variant_axis PROBE_BLEND_AVAILABLE
18
+ #pragma variant_axis EXTENDED_LIGHTING_AVAILABLE
19
+
16
20
  #pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
17
21
 
18
22
  // @forgeax/engine-shader - default-standard-pbr-skin.wgsl
@@ -171,14 +175,36 @@ struct SkylightUniforms {
171
175
  @group(1) @binding(22) var brdfLutSampler : sampler;
172
176
  @group(1) @binding(23) var<uniform> skylight : SkylightUniforms;
173
177
 
174
- #if STORAGE_BUFFER_AVAILABLE == true
175
- @group(2) @binding(1) var<storage, read> palette : array<mat4x4<f32>>;
176
- @group(2) @binding(2) var<storage, read> previousPalette : array<mat4x4<f32>>;
178
+ #ifdef PROBE_BLEND_AVAILABLE
179
+ // Optional consumer lane. The host must provide the retained 160B record
180
+ // buffer and matching BGL before enabling this capability.
181
+ @group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
182
+ #endif
183
+
184
+ #ifdef STORAGE_BUFFER_AVAILABLE
185
+ @group(2) @binding(1) var<storage, read> paletteStorage : array<mat4x4<f32>>;
186
+ @group(2) @binding(2) var<storage, read> previousPaletteStorage : array<mat4x4<f32>>;
177
187
  #else
178
- @group(2) @binding(1) var<uniform> palette : array<mat4x4<f32>, 255>;
179
- @group(2) @binding(2) var<uniform> previousPalette : array<mat4x4<f32>, 255>;
188
+ @group(2) @binding(1) var<uniform> paletteUniform : array<mat4x4<f32>, 255>;
189
+ @group(2) @binding(2) var<uniform> previousPaletteUniform : array<mat4x4<f32>, 255>;
180
190
  #endif
181
191
 
192
+ fn skinPalette(index : u32) -> mat4x4<f32> {
193
+ #ifdef STORAGE_BUFFER_AVAILABLE
194
+ return paletteStorage[index];
195
+ #else
196
+ return paletteUniform[index];
197
+ #endif
198
+ }
199
+
200
+ fn previousSkinPalette(index : u32) -> mat4x4<f32> {
201
+ #ifdef STORAGE_BUFFER_AVAILABLE
202
+ return previousPaletteStorage[index];
203
+ #else
204
+ return previousPaletteUniform[index];
205
+ #endif
206
+ }
207
+
182
208
  struct VsIn {
183
209
  @location(0) pos : vec3<f32>,
184
210
  @location(1) normal : vec3<f32>,
@@ -232,8 +258,7 @@ fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
232
258
  }
233
259
  }
234
260
 
235
- @vertex
236
- fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
261
+ fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
237
262
  // 4-bone weighted skinning (plan-strategy D-3 / D-3a).
238
263
  // The host pre-computes each joint matrix as worldFromJoint * inverseBindMatrix
239
264
  // (CPU-side pre-multiplication, per D-4) and writes them into the palette
@@ -245,10 +270,10 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
245
270
  // The host sets the BindGroup dynamic offset to (byteOffset / 64) so the
246
271
  // first palette entry visible to this draw is palette[0]. The shader
247
272
  // indexes into palette directly using the per-vertex skinIndex values.
248
- let skinMatrix = palette[in.skinIndex.x] * in.skinWeight.x +
249
- palette[in.skinIndex.y] * in.skinWeight.y +
250
- palette[in.skinIndex.z] * in.skinWeight.z +
251
- palette[in.skinIndex.w] * in.skinWeight.w;
273
+ let skinMatrix = skinPalette(in.skinIndex.x) * in.skinWeight.x +
274
+ skinPalette(in.skinIndex.y) * in.skinWeight.y +
275
+ skinPalette(in.skinIndex.z) * in.skinWeight.z +
276
+ skinPalette(in.skinIndex.w) * in.skinWeight.w;
252
277
 
253
278
  // glTF 2.0 sec.Skins Implementation Note: when a mesh node has a skin
254
279
  // property, the joint matrices already encode the global transform of each
@@ -260,7 +285,7 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
260
285
  // skinnedLocal IS the world position -- no additional left-multiply by
261
286
  // meshes[0].worldFromLocal or instanceLocal is needed.
262
287
  //
263
- // This removes the implicit contract "Skin entity Transform.world must be
288
+ // This removes the implicit contract "Skin entity GlobalTransform.world must be
264
289
  // identity" -- a skin entity can be parented under any Transform chain and
265
290
  // the skinned mesh will rigidly follow via joint propagation alone.
266
291
  let skinnedLocal = skinMatrix * vec4<f32>(in.pos, 1.0);
@@ -312,6 +337,16 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
312
337
  return out;
313
338
  }
314
339
 
340
+ @vertex
341
+ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
342
+ return vs_main_impl(in, idx);
343
+ }
344
+
345
+ @vertex
346
+ fn vs_scene_index(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
347
+ return vs_main_impl(in, idx);
348
+ }
349
+
315
350
  fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
316
351
  var source = in.uv;
317
352
  if (metadata.x >= 1.0) { source = in.uv1; }
@@ -383,16 +418,34 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
383
418
  prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
384
419
  );
385
420
  let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
421
+ let skyFactor = skyColor * skylight.intensity;
386
422
  var ambient = (kD * irradiance * albedo + specularIbl) * (vec3<f32>(1.0) - coatF);
423
+ #ifdef PROBE_BLEND_AVAILABLE
424
+ // group(3) is the per-object bind group; record zero is the object selected
425
+ // by this draw, independent of instance count.
426
+ let probeShPreblend = array<vec4<f32>, 9>(
427
+ probeBlendRecords[1], probeBlendRecords[2], probeBlendRecords[3],
428
+ probeBlendRecords[4], probeBlendRecords[5], probeBlendRecords[6],
429
+ probeBlendRecords[7], probeBlendRecords[8], probeBlendRecords[9],
430
+ );
431
+ let probeLocalBlendFraction = probeBlendRecords[0].z;
432
+ let probeDiffuseK = kD / max(vec3<f32>(1.0 - metallic), vec3<f32>(0.0001));
433
+ let probeDiffuse = evaluateProbeDiffuse(probeShPreblend, probeLocalBlendFraction, n, irradiance * skyFactor, probeDiffuseK, albedo, metallic);
434
+ ambient = (probeDiffuse + specularIbl * skyFactor) * (vec3<f32>(1.0) - coatF);
435
+ #endif
387
436
  if (material.clearcoat != 0.0) {
388
437
  let clearcoatIbl = sampleIblSpecular(
389
438
  n, v, coatRoughness, vec3<f32>(0.04),
390
439
  skylight.rotation,
391
440
  prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
392
441
  );
393
- ambient = ambient + clearcoatIbl * material.clearcoat;
442
+ ambient = ambient + clearcoatIbl * material.clearcoat * skyFactor;
394
443
  }
395
- ambient = ambient * skyColor * skylight.intensity;
444
+ #ifdef PROBE_BLEND_AVAILABLE
445
+ ambient = ambient;
446
+ #else
447
+ ambient = ambient * skyFactor;
448
+ #endif
396
449
  var color = ambient;
397
450
  let directionalShadow = evalDirectionalShadowFactor(n, in.worldPos, in.viewZ);
398
451
  let directionalBase = evalDirectionalNoShadow(n, v, albedo, metallic, a, f0);
@@ -437,14 +490,14 @@ struct TemporalVsOut {
437
490
 
438
491
  @vertex
439
492
  fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
440
- let currentSkin = palette[in.skinIndex.x] * in.skinWeight.x +
441
- palette[in.skinIndex.y] * in.skinWeight.y +
442
- palette[in.skinIndex.z] * in.skinWeight.z +
443
- palette[in.skinIndex.w] * in.skinWeight.w;
444
- let previousSkin = previousPalette[in.skinIndex.x] * in.skinWeight.x +
445
- previousPalette[in.skinIndex.y] * in.skinWeight.y +
446
- previousPalette[in.skinIndex.z] * in.skinWeight.z +
447
- previousPalette[in.skinIndex.w] * in.skinWeight.w;
493
+ let currentSkin = skinPalette(in.skinIndex.x) * in.skinWeight.x +
494
+ skinPalette(in.skinIndex.y) * in.skinWeight.y +
495
+ skinPalette(in.skinIndex.z) * in.skinWeight.z +
496
+ skinPalette(in.skinIndex.w) * in.skinWeight.w;
497
+ let previousSkin = previousSkinPalette(in.skinIndex.x) * in.skinWeight.x +
498
+ previousSkinPalette(in.skinIndex.y) * in.skinWeight.y +
499
+ previousSkinPalette(in.skinIndex.z) * in.skinWeight.z +
500
+ previousSkinPalette(in.skinIndex.w) * in.skinWeight.w;
448
501
  let currentWorld = currentSkin * vec4<f32>(in.pos, 1.0);
449
502
  let previousWorld = previousSkin * vec4<f32>(in.pos, 1.0);
450
503
  var out : TemporalVsOut;
@@ -481,6 +534,10 @@ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
481
534
 
482
535
  @fragment
483
536
  fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
537
+ var reactive = 0.0;
538
+ #ifdef STORAGE_BUFFER_AVAILABLE
539
+ reactive = meshes[0].temporal.x;
540
+ #endif
484
541
  return projectPbrSceneTemporal(
485
542
  material.baseColor.a * temporalVertexAlpha(in),
486
543
  material.alphaCutoff,