@forgeax/engine-shader 0.1.21 → 0.1.24

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 (83) hide show
  1. package/README.md +14 -5
  2. package/dist/ShaderRegistry.d.ts.map +1 -1
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.mjs +33042 -55
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/ltc/provenance.d.ts +17 -0
  8. package/dist/ltc/provenance.d.ts.map +1 -0
  9. package/dist/ltc/tables.d.ts +22 -0
  10. package/dist/ltc/tables.d.ts.map +1 -0
  11. package/dist/material/artifact-types.d.ts +52 -0
  12. package/dist/material/artifact-types.d.ts.map +1 -1
  13. package/dist/material-schemas.d.ts +36 -0
  14. package/dist/material-schemas.d.ts.map +1 -1
  15. package/dist/register-default-sprite-lit.d.ts +3 -6
  16. package/dist/register-default-sprite-lit.d.ts.map +1 -1
  17. package/dist/types.d.ts +2 -1
  18. package/dist/types.d.ts.map +1 -1
  19. package/package.json +4 -4
  20. package/src/ShaderRegistry.ts +0 -17
  21. package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +9 -3
  22. package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +67 -122
  23. package/src/__tests__/deferred-lighting-ssao.test.ts +3 -3
  24. package/src/__tests__/direct-light-layout.unit.test.ts +40 -0
  25. package/src/__tests__/ibl-irradiance.unit.test.ts +16 -4
  26. package/src/__tests__/ibl-sampling.unit.test.ts +3 -4
  27. package/src/__tests__/lighting-directional.unit.test.ts +36 -0
  28. package/src/__tests__/lighting-point-spot-volume.unit.test.ts +8 -2
  29. package/src/__tests__/lighting-punctual.unit.test.ts +40 -5
  30. package/src/__tests__/lighting-spot-volume-attenuation.unit.test.ts +1 -1
  31. package/src/__tests__/ltc-provenance.unit.test.ts +33 -0
  32. package/src/__tests__/manifest.fixture.json +33 -0
  33. package/src/__tests__/material-builtins.unit.test.ts +21 -3
  34. package/src/__tests__/material-contract.unit.test.ts +188 -14
  35. package/src/__tests__/physical-clearcoat.unit.test.ts +65 -0
  36. package/src/__tests__/probe-lighting-composition.unit.test.ts +39 -0
  37. package/src/__tests__/rect-area-brdf.unit.test.ts +36 -0
  38. package/src/__tests__/reflection-probe-sampling.unit.test.ts +24 -2
  39. package/src/__tests__/shader-manifest-transmission.unit.test.ts +8 -10
  40. package/src/__tests__/shader.unit.test.ts +54 -85
  41. package/src/__tests__/spot-cookie-composition.unit.test.ts +58 -0
  42. package/src/__tests__/spot-modifier-composition.unit.test.ts +19 -0
  43. package/src/__tests__/sprite-variants.unit.test.ts +4 -1
  44. package/src/__tests__/standard-webgl2-varying-budget.unit.test.ts +17 -0
  45. package/src/__tests__/surface-v1-negative.unit.test.ts +37 -0
  46. package/src/__tests__/surface-v1.unit.test.ts +83 -0
  47. package/src/__tests__/transmission-thickness-scale.unit.test.ts +2 -6
  48. package/src/__tests__/vertex-color-variant.unit.test.ts +16 -5
  49. package/src/brdf.wgsl +115 -1
  50. package/src/common.wgsl +53 -79
  51. package/src/default-standard-pbr-skin.wgsl +687 -144
  52. package/src/default-standard-pbr.wgsl +600 -247
  53. package/src/default_standard_surface.wgsl +88 -0
  54. package/src/hdrp-cluster-forward.wgsl +225 -0
  55. package/src/ibl-irradiance.wgsl +47 -9
  56. package/src/ibl-prefilter.wgsl +13 -3
  57. package/src/ibl-sampling.wgsl +2 -2
  58. package/src/ibl-shared.wgsl +9 -2
  59. package/src/index.ts +23 -0
  60. package/src/lighting-directional.wgsl +9 -117
  61. package/src/lighting-probe.wgsl +51 -0
  62. package/src/lighting-punctual.wgsl +21 -11
  63. package/src/lighting-rect-area.wgsl +162 -0
  64. package/src/lighting-spot-modifiers.wgsl +100 -0
  65. package/src/lighting-spot-projector.wgsl +31 -0
  66. package/src/ltc/provenance.ts +19 -0
  67. package/src/ltc/tables.ts +2831 -0
  68. package/src/material/artifact-types.ts +137 -0
  69. package/src/material/physical/anisotropy.wgsl +26 -0
  70. package/src/material/physical/clearcoat.wgsl +24 -0
  71. package/src/material/physical/iridescence.wgsl +23 -0
  72. package/src/material/physical/sheen.wgsl +17 -0
  73. package/src/material-schemas.ts +77 -30
  74. package/src/register-default-sprite-lit.ts +3 -6
  75. package/src/register-default-standard-pbr-skin.ts +2 -2
  76. package/src/sprite-lit.wgsl +3 -3
  77. package/src/sprite.wgsl +1 -1
  78. package/src/standard-cluster.wgsl +80 -78
  79. package/src/surface_v1.wgsl +26 -0
  80. package/src/types.ts +2 -1
  81. package/src/volume/volume-inject.wgsl +34 -36
  82. package/src/volume/volume-integrate.wgsl +48 -38
  83. package/dist/.tsbuildinfo +0 -1
@@ -1,10 +1,34 @@
1
1
  #define_import_path forgeax_material::standard
2
- #import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, sampleMaterialTexture}
2
+ #pragma material_slot surface
3
+ #import forgeax_material::slot::surface::{evaluate_surface}
4
+ #import forgeax_material::surface_v1::{SurfaceInput, SurfaceData}
5
+ #import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, shadowMap, shadowSampler, sampleMaterialTexture}
3
6
  #import forgeax_scene_temporal::{sceneViewZ}
7
+ #ifdef EXTENDED_LIGHTING_AVAILABLE
8
+ #import forgeax_view::common::{spotModifierSampler, iesProfileTexture, cookieTexture, cookieMatrices}
9
+ #endif
10
+ #ifdef PROJECTOR_AVAILABLE
11
+ #ifndef EXTENDED_LIGHTING_AVAILABLE
12
+ #import forgeax_view::common::{projectorTexture, projectorSampler}
13
+ #endif
14
+ #endif
4
15
  #import forgeax_pbr::temporal::{projectPbrSceneTemporal}
5
16
  #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
6
17
  #import forgeax_pbr::ibl_sampling::{box_project, sampleIblDiffuse, sampleIblSpecular, sampleReflectionProbeSpecular}
18
+ #import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
7
19
  #import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
20
+ #ifdef CLEARCOAT_AVAILABLE
21
+ #import forgeax_pbr::clearcoat::{evaluateClearcoatLayer}
22
+ #endif
23
+ #ifdef ANISOTROPY_AVAILABLE
24
+ #import forgeax_pbr::anisotropy::{evaluateAnisotropicNormal}
25
+ #endif
26
+ #ifdef SHEEN_AVAILABLE
27
+ #import forgeax_pbr::sheen::{evaluateSheenLayer}
28
+ #endif
29
+ #ifdef IRIDESCENCE_AVAILABLE
30
+ #import forgeax_pbr::iridescence::{evaluateIridescenceFresnel}
31
+ #endif
8
32
  #import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
9
33
  #ifdef CLUSTER_FORWARD_AVAILABLE
10
34
  #import forgeax_standard::cluster::{evaluateStandardClusterLights, get_ssao_intensity}
@@ -13,13 +37,20 @@
13
37
  #pragma variant_axis STORAGE_BUFFER_AVAILABLE
14
38
  #pragma variant_axis CLUSTER_FORWARD_AVAILABLE
15
39
  #pragma variant_axis VERTEX_COLOR_AVAILABLE
40
+ #pragma variant_axis PROBE_BLEND_AVAILABLE
41
+ #pragma variant_axis EXTENDED_LIGHTING_AVAILABLE
16
42
  #pragma variant_axis TRANSMISSION_AVAILABLE
17
43
  #pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
18
44
  #pragma variant_axis PROJECTOR_AVAILABLE
45
+ #pragma variant_axis REFLECTION_FALLBACK_AVAILABLE
19
46
 
20
- // Reflection probes remain a bounded scene-projected sampling lane. Their
21
- // optional texture resources are the same Skylight inputs; when no probe is
22
- // admitted, the explicit Skylight path below remains authoritative.
47
+ // reflection_probe is a scene-projected Standard material lane. Its optional
48
+ // texture arguments stay binding-free here; the host selects the bounded table
49
+ // row and falls back to the Skylight helpers when no candidate is admitted.
50
+ // Keep this compile-time witness in the probe variant only: authored physical
51
+ // aliases intentionally flatten renderer-owned probe axes, and naga_oil must
52
+ // not resolve a probe-only helper for those roots.
53
+ #ifdef PROBE_BLEND_AVAILABLE
23
54
  fn reflection_probe_sampling_contract(
24
55
  worldPosition: vec3<f32>,
25
56
  direction: vec3<f32>,
@@ -28,6 +59,7 @@ fn reflection_probe_sampling_contract(
28
59
  ) -> vec3<f32> {
29
60
  return box_project(worldPosition, direction, boxCenter, boxExtents);
30
61
  }
62
+ #endif
31
63
 
32
64
  // @forgeax/engine-shader - default-standard-pbr.wgsl
33
65
  // (feat-20260523-shader-template-instance-split M5 / T04).
@@ -50,14 +82,13 @@ fn reflection_probe_sampling_contract(
50
82
  // + metallic + roughness
51
83
  // + 4 channel selectors f32
52
84
  // + emissive vec3 + emissiveIntensity
53
- // + uv/alpha/clearcoat/specularTint = 112 B)
85
+ // + uv/alpha/specularColor = 104 B)
54
86
  // @group(1) @binding(1) baseColorSampler sampler
55
87
  // @group(1) @binding(2) baseColorTexture texture_2d<f32>
56
88
  // @group(1) @binding(3) metallicRoughnessSampler sampler
57
89
  // @group(1) @binding(4) metallicRoughnessTexture texture_2d<f32>
58
90
  // @group(1) @binding(5) normalSampler sampler
59
91
  // @group(1) @binding(6) normalTexture texture_2d<f32>
60
- // @group(1) @binding(7) specularTintSampler + @binding(8) specularTintTexture
61
92
  // @group(1) @binding(9..15) Skylight (irradiance / prefilter / brdfLut +
62
93
  // samplers) + skylight uniform (intensity)
63
94
  // @group(2) @binding(0) meshes storage (worldFromLocal mat4
@@ -97,63 +128,16 @@ fn reflection_probe_sampling_contract(
97
128
  // and tolerates RGB normal maps (b is dropped, z is recomputed --
98
129
  // equivalent for unit vectors).
99
130
 
100
- struct Material {
101
- baseColor : vec4<f32>,
102
- metallic : f32,
103
- roughness : f32,
104
- // Channel selectors (D-8): 4 independent f32 entries split out of the
105
- // legacy `channelMap : vec4<u32>`. Each value is a small integer encoded
106
- // as f32 in {0.0, 1.0, 2.0, 3.0} indexing into {r,g,b,a}. Default glTF 2.0
107
- // packing = (metallicChannel=2, roughnessChannel=1, aoChannel=0,
108
- // extraChannel=0). The fragment casts to u32 at the pick site -- f32 is
109
- // chosen so the schema entry type aligns with the 14-tuple
110
- // MaterialParamType numeric-run packing rule (4-byte stride, 16 B span).
111
- metallicChannel : f32,
112
- roughnessChannel : f32,
113
- aoChannel : f32,
114
- extraChannel : f32,
115
- // vec3 align=16 inserts 8 implicit padding bytes after extraChannel
116
- // (offsets 40..48) so emissive lands at offset 48.
117
- emissive : vec3<f32>,
118
- emissiveIntensity : f32,
119
- occlusionStrength : f32,
120
- alphaCutoff : f32,
121
- clearcoat : f32,
122
- clearcoatRoughness : f32,
123
- specularTint : vec3<f32>,
124
- normalScale : f32,
125
- transmission : f32,
126
- ior : f32,
127
- thickness : f32,
128
- attenuationColor : vec3<f32>,
129
- attenuationDistance : f32,
130
- baseColorTextureCoordinatesTransform : vec4<f32>,
131
- baseColorTextureCoordinatesMetadata : vec4<f32>,
132
- metallicRoughnessTextureCoordinatesTransform : vec4<f32>,
133
- metallicRoughnessTextureCoordinatesMetadata : vec4<f32>,
134
- normalTextureCoordinatesTransform : vec4<f32>,
135
- normalTextureCoordinatesMetadata : vec4<f32>,
136
- specularTintTextureCoordinatesTransform : vec4<f32>,
137
- specularTintTextureCoordinatesMetadata : vec4<f32>,
138
- emissiveTextureCoordinatesTransform : vec4<f32>,
139
- emissiveTextureCoordinatesMetadata : vec4<f32>,
140
- occlusionTextureCoordinatesTransform : vec4<f32>,
141
- occlusionTextureCoordinatesMetadata : vec4<f32>,
142
- transmissionTextureCoordinatesTransform : vec4<f32>,
143
- transmissionTextureCoordinatesMetadata : vec4<f32>,
144
- thicknessTextureCoordinatesTransform : vec4<f32>,
145
- thicknessTextureCoordinatesMetadata : vec4<f32>,
146
- };
147
-
148
- @group(1) @binding(0) var<uniform> material : Material;
131
+ // The MaterialParameters struct and its binding-0 declaration are generated
132
+ // from the root ParamSchema during composition. Keeping the ABI out of this
133
+ // template prevents a second handwritten interface from drifting from the
134
+ // runtime UBO writer.
149
135
  @group(1) @binding(1) var baseColorSampler : sampler;
150
136
  @group(1) @binding(2) var baseColorTexture : texture_2d<f32>;
151
137
  @group(1) @binding(3) var metallicRoughnessSampler : sampler;
152
138
  @group(1) @binding(4) var metallicRoughnessTexture : texture_2d<f32>;
153
139
  @group(1) @binding(5) var normalSampler : sampler;
154
140
  @group(1) @binding(6) var normalTexture : texture_2d<f32>;
155
- @group(1) @binding(7) var specularTintSampler : sampler;
156
- @group(1) @binding(8) var specularTintTexture : texture_2d<f32>;
157
141
  @group(1) @binding(9) var emissiveSampler : sampler;
158
142
  @group(1) @binding(10) var emissiveTexture : texture_2d<f32>;
159
143
  @group(1) @binding(11) var occlusionSampler : sampler;
@@ -166,6 +150,46 @@ struct Material {
166
150
  @group(1) @binding(24) var transmissionBackdropSampler : sampler;
167
151
  @group(1) @binding(25) var transmissionBackdropTexture : texture_2d<f32>;
168
152
  #endif
153
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
154
+ @group(1) @binding(26) var clearcoatSampler : sampler;
155
+ @group(1) @binding(27) var clearcoatTexture : texture_2d<f32>;
156
+ #endif
157
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
158
+ @group(1) @binding(28) var clearcoatRoughnessSampler : sampler;
159
+ @group(1) @binding(29) var clearcoatRoughnessTexture : texture_2d<f32>;
160
+ #endif
161
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
162
+ @group(1) @binding(30) var clearcoatNormalSampler : sampler;
163
+ @group(1) @binding(31) var clearcoatNormalTexture : texture_2d<f32>;
164
+ #endif
165
+ #ifdef ANISOTROPY_TEXTURE_AVAILABLE
166
+ @group(1) @binding(32) var anisotropySampler : sampler;
167
+ @group(1) @binding(33) var anisotropyTexture : texture_2d<f32>;
168
+ #endif
169
+ #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
170
+ @group(1) @binding(34) var sheenColorSampler : sampler;
171
+ @group(1) @binding(35) var sheenColorTexture : texture_2d<f32>;
172
+ #endif
173
+ #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
174
+ @group(1) @binding(36) var sheenRoughnessSampler : sampler;
175
+ @group(1) @binding(37) var sheenRoughnessTexture : texture_2d<f32>;
176
+ #endif
177
+ #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
178
+ @group(1) @binding(38) var iridescenceSampler : sampler;
179
+ @group(1) @binding(39) var iridescenceTexture : texture_2d<f32>;
180
+ #endif
181
+ #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
182
+ @group(1) @binding(40) var iridescenceThicknessSampler : sampler;
183
+ @group(1) @binding(41) var iridescenceThicknessTexture : texture_2d<f32>;
184
+ #endif
185
+ #ifdef SPECULAR_TEXTURE_AVAILABLE
186
+ @group(1) @binding(42) var specularTextureSampler : sampler;
187
+ @group(1) @binding(43) var specularTexture : texture_2d<f32>;
188
+ #endif
189
+ #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
190
+ @group(1) @binding(44) var specularColorTextureSampler : sampler;
191
+ @group(1) @binding(45) var specularColorTexture : texture_2d<f32>;
192
+ #endif
169
193
 
170
194
  // Naga reflection does not retain filtering usage through the generic shared
171
195
  // helper. These compile-time-only witnesses retain the binding contract while
@@ -174,15 +198,52 @@ fn materialTextureFilteringWitness() {
174
198
  let base = baseColorTexture;
175
199
  let metallicRoughness = metallicRoughnessTexture;
176
200
  let normal = normalTexture;
177
- let specularTint = specularTintTexture;
178
201
  let emissive = emissiveTexture;
179
202
  let occlusion = occlusionTexture;
203
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
204
+ let clearcoat = clearcoatTexture;
205
+ #endif
206
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
207
+ let clearcoatRoughness = clearcoatRoughnessTexture;
208
+ #endif
209
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
210
+ let clearcoatNormal = clearcoatNormalTexture;
211
+ #endif
180
212
  let baseWitness = textureSample(base, baseColorSampler, vec2<f32>(0.0));
181
213
  let metallicRoughnessWitness = textureSample(metallicRoughness, metallicRoughnessSampler, vec2<f32>(0.0));
182
214
  let normalWitness = textureSample(normal, normalSampler, vec2<f32>(0.0));
183
- let specularTintWitness = textureSample(specularTint, specularTintSampler, vec2<f32>(0.0));
184
215
  let emissiveWitness = textureSample(emissive, emissiveSampler, vec2<f32>(0.0));
185
216
  let occlusionWitness = textureSample(occlusion, occlusionSampler, vec2<f32>(0.0));
217
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
218
+ let clearcoatWitness = textureSample(clearcoat, clearcoatSampler, vec2<f32>(0.0));
219
+ #endif
220
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
221
+ let clearcoatRoughnessWitness = textureSample(clearcoatRoughness, clearcoatRoughnessSampler, vec2<f32>(0.0));
222
+ #endif
223
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
224
+ let clearcoatNormalWitness = textureSample(clearcoatNormal, clearcoatNormalSampler, vec2<f32>(0.0));
225
+ #endif
226
+ #ifdef ANISOTROPY_TEXTURE_AVAILABLE
227
+ let anisotropyWitness = textureSample(anisotropyTexture, anisotropySampler, vec2<f32>(0.0));
228
+ #endif
229
+ #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
230
+ let sheenColorWitness = textureSample(sheenColorTexture, sheenColorSampler, vec2<f32>(0.0));
231
+ #endif
232
+ #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
233
+ let sheenRoughnessWitness = textureSample(sheenRoughnessTexture, sheenRoughnessSampler, vec2<f32>(0.0));
234
+ #endif
235
+ #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
236
+ let iridescenceWitness = textureSample(iridescenceTexture, iridescenceSampler, vec2<f32>(0.0));
237
+ #endif
238
+ #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
239
+ let iridescenceThicknessWitness = textureSample(iridescenceThicknessTexture, iridescenceThicknessSampler, vec2<f32>(0.0));
240
+ #endif
241
+ #ifdef SPECULAR_TEXTURE_AVAILABLE
242
+ let specularWeightWitness = textureSample(specularTexture, specularTextureSampler, vec2<f32>(0.0));
243
+ #endif
244
+ #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
245
+ let specularColorWitness = textureSample(specularColorTexture, specularColorTextureSampler, vec2<f32>(0.0));
246
+ #endif
186
247
  #ifdef TRANSMISSION_AVAILABLE
187
248
  let transmission = transmissionTexture;
188
249
  let thickness = thicknessTexture;
@@ -223,6 +284,14 @@ struct SkylightUniforms {
223
284
  @group(1) @binding(22) var brdfLutSampler : sampler;
224
285
  @group(1) @binding(23) var<uniform> skylight : SkylightUniforms;
225
286
 
287
+ #ifdef PROBE_BLEND_AVAILABLE
288
+ // Optional consumer lane. The host provides the retained ProbeBlendRecord
289
+ // storage for the object selected by this draw. Keeping it in group(3)
290
+ // preserves the mesh/instance ownership of groups (2)/(3) without inventing a
291
+ // second probe bind group.
292
+ @group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
293
+ #endif
294
+
226
295
  // feat-20260612-hdrp-ssao M7 (round 2) D-B + D-C, scope-amend-webgl2-ubo:
227
296
  // SSAO sampling lives on the HDRP unified BGL @group(2) alongside the
228
297
  // cluster bindings (binding 7 = ssao texture, binding 8 = sampler). The
@@ -266,6 +335,10 @@ struct VsOut {
266
335
  // Keep mesh and instance storage vertex-only; transmission receives the
267
336
  // transform basis from the vertex stage below.
268
337
  @location(4) @interpolate(flat) transmissionBasis0 : vec4<f32>,
338
+ #else
339
+ // WebGL2 exposes only inter-stage locations 0..14. Pack the object-space
340
+ // Surface position with its cluster/CSM depth in one varying.
341
+ @location(7) positionOSAndViewZ : vec4<f32>,
269
342
  #endif
270
343
  // feat-city-glb multi-UV tiling: second UV set inter-stage varying. Uses the
271
344
  // previously-vacant @location(5) so @location(6)/(7) stay byte-stable with
@@ -275,8 +348,9 @@ struct VsOut {
275
348
  @location(9) uv3 : vec2<f32>,
276
349
  @location(10) uv4 : vec2<f32>,
277
350
  @location(11) uv5 : vec2<f32>,
278
- @location(12) uv6 : vec2<f32>,
279
- @location(13) uv7 : vec2<f32>,
351
+ // UV6/UV7 share one vec4 slot so the transmission basis tail can stay
352
+ // inside WebGL2's 14 user varying locations.
353
+ @location(12) uv6And7 : vec4<f32>,
280
354
  #ifdef VERTEX_COLOR_AVAILABLE
281
355
  @location(14) color : vec4<f32>,
282
356
  #endif
@@ -288,23 +362,14 @@ struct VsOut {
288
362
  // surfaces -- whose cluster cells were unrelated to the floor's slice 0
289
363
  // hot zone -- received zero light. Forward view_z explicitly. M5 / w19:
290
364
  // also feeds CSM cascade selection in evalDirectional.
291
- @location(7) viewZ : f32,
365
+ // Surface position and clustered/CSM view depth share one varying so the
366
+ // composed Standard shader stays within WebGL2's 14 inter-stage locations.
292
367
  #ifdef TRANSMISSION_AVAILABLE
293
- @location(15) @interpolate(flat) transmissionBasis1 : vec4<f32>,
368
+ @location(7) viewZ : f32,
369
+ @location(13) @interpolate(flat) transmissionBasis1 : vec4<f32>,
294
370
  #endif
295
371
  };
296
372
 
297
- fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
298
- // Branch-free channel pick. WGSL has no array<f32, 4>(rgba) addressable
299
- // helper; manual switch keeps the path uniform.
300
- switch (channelIndex) {
301
- case 0u: { return rgba.r; }
302
- case 1u: { return rgba.g; }
303
- case 2u: { return rgba.b; }
304
- default: { return rgba.a; }
305
- }
306
- }
307
-
308
373
  // Light evaluators live in forgeax_pbr::lighting_directional +
309
374
  // forgeax_pbr::lighting_punctual (M5 / T02). evalDirectional consumes the
310
375
  // host's view UBO + shadowMap (LO 3.1.3 slope-scaled-bias 3x3 PCF;
@@ -315,8 +380,7 @@ fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
315
380
  // Charter P4: spot light is a thin cone-multiplier on top of the punctual
316
381
  // body, point light is the body unchanged -- no magic-value collapse.
317
382
 
318
- @vertex
319
- fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
383
+ fn vs_main_impl(in : VsIn, meshIndex : u32, instanceIndex : u32) -> VsOut {
320
384
  // feat-20260604-instances-per-instance-transform-shader-group3-bin M1 / w4:
321
385
  // Entity world from meshes[0] — the @group(2) dynamic-offset window has
322
386
  // already been aimed at this entity's slot (render-system-record.ts:2996
@@ -324,12 +388,15 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
324
388
  // local from instances[idx] — @group(3) is a flat per-instance buffer indexed
325
389
  // directly by instance_index (firstInstance=0, render-system-record.ts:2898).
326
390
  // Combine: entity_world * per_instance_local.
327
- let instanceLocal = instances[idx].localFromInstance;
328
- let entityWorld = meshes[0].worldFromLocal;
391
+ let instanceLocal = instances[instanceIndex].localFromInstance;
392
+ let entityWorld = meshes[meshIndex].worldFromLocal;
329
393
  let localToWorld = entityWorld * instanceLocal;
330
394
  let world = localToWorld * vec4<f32>(in.pos, 1.0);
331
395
  var out : VsOut;
332
396
  out.clip = view.worldViewProj * world;
397
+ #ifndef TRANSMISSION_AVAILABLE
398
+ out.positionOSAndViewZ = vec4<f32>(in.pos, sceneViewZ(out.clip, view.temporalProjection));
399
+ #endif
333
400
  out.worldPos = world.xyz;
334
401
  // Normal: derive the inverse-transpose from the world matrix columns. This
335
402
  // keeps non-uniform entity scale correct while avoiding the Mesh.normalMatrix
@@ -353,6 +420,16 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
353
420
  // (w=0); .w handedness preserved for bitangent reconstruction in fragment.
354
421
  let worldTangentXyz = normalize((entityWorld * instanceLocal * vec4<f32>(in.tangent.xyz, 0.0)).xyz);
355
422
  out.worldTangent = vec4<f32>(worldTangentXyz, in.tangent.w);
423
+ out.uv = in.uv;
424
+ out.uv1 = in.uv1;
425
+ out.uv2 = in.uv2;
426
+ out.uv3 = in.uv3;
427
+ out.uv4 = in.uv4;
428
+ out.uv5 = in.uv5;
429
+ out.uv6And7 = vec4<f32>(in.uv6, in.uv7);
430
+ #ifdef VERTEX_COLOR_AVAILABLE
431
+ out.color = in.color;
432
+ #endif
356
433
  #ifdef TRANSMISSION_AVAILABLE
357
434
  out.transmissionBasis0 = vec4<f32>(
358
435
  localToWorld[0].x,
@@ -366,17 +443,6 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
366
443
  localToWorld[2].x,
367
444
  localToWorld[2].y,
368
445
  );
369
- #endif
370
- out.uv = in.uv;
371
- out.uv1 = in.uv1;
372
- out.uv2 = in.uv2;
373
- out.uv3 = in.uv3;
374
- out.uv4 = in.uv4;
375
- out.uv5 = in.uv5;
376
- out.uv6 = in.uv6;
377
- out.uv7 = in.uv7;
378
- #ifdef VERTEX_COLOR_AVAILABLE
379
- out.color = in.color;
380
446
  #endif
381
447
  // feat-20260613-csm-cascaded-shadow-maps M5 / w19: the per-fragment
382
448
  // light-space position varying is gone; evalDirectional computes
@@ -386,16 +452,35 @@ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
386
452
  // Perspective divide on clip-space position; ndc.z retains depth-buffer value.
387
453
  let clipPos = out.clip;
388
454
  out.ndc = vec4(clipPos.xy / clipPos.w, clipPos.z / clipPos.w, localToWorld[2].z);
389
- // Standard WebGPU/GL perspective projection has clip.w = -view.z, so
390
- // view_z = -clip.w (negative in front of the camera). M4.5-followup:
391
- // ndc_position_to_cluster needs this for log-z slice mapping; passing
392
- // ndc.z (in [0,1]) instead made every fragment land in slice 0.
393
455
  // Keep the cluster depth exactly aligned with the CPU binner for both
394
456
  // perspective and off-axis orthographic projections.
457
+ #ifdef TRANSMISSION_AVAILABLE
395
458
  out.viewZ = sceneViewZ(clipPos, view.temporalProjection);
459
+ #endif
396
460
  return out;
397
461
  }
398
462
 
463
+ fn standardViewZ(in : VsOut) -> f32 {
464
+ #ifdef TRANSMISSION_AVAILABLE
465
+ return in.viewZ;
466
+ #else
467
+ return in.positionOSAndViewZ.w;
468
+ #endif
469
+ }
470
+
471
+ @vertex
472
+ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
473
+ return vs_main_impl(in, 0u, idx);
474
+ }
475
+
476
+ @vertex
477
+ fn vs_scene_index(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
478
+ // GPU-driven scene projection packs one Mesh row per visible candidate and
479
+ // uses the identity instance row. The scene index therefore selects the
480
+ // projected Mesh directly while retaining the Standard PBR varyings.
481
+ return vs_main_impl(in, idx, 0u);
482
+ }
483
+
399
484
  fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
400
485
  var source = in.uv;
401
486
  if (metadata.x >= 1.0) { source = in.uv1; }
@@ -403,8 +488,8 @@ fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut
403
488
  if (metadata.x >= 3.0) { source = in.uv3; }
404
489
  if (metadata.x >= 4.0) { source = in.uv4; }
405
490
  if (metadata.x >= 5.0) { source = in.uv5; }
406
- if (metadata.x >= 6.0) { source = in.uv6; }
407
- if (metadata.x >= 7.0) { source = in.uv7; }
491
+ if (metadata.x >= 6.0) { source = in.uv6And7.xy; }
492
+ if (metadata.x >= 7.0) { source = in.uv6And7.zw; }
408
493
  let scaled = source * transform.zw;
409
494
  let angle = metadata.y;
410
495
  let c = cos(angle);
@@ -412,10 +497,6 @@ fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut
412
497
  return vec2<f32>(scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c) + transform.xy;
413
498
  }
414
499
 
415
- fn materialAlpha(baseSample : vec4<f32>) -> f32 {
416
- return material.baseColor.a * baseSample.a;
417
- }
418
-
419
500
  fn materialVertexColor(in : VsOut) -> vec4<f32> {
420
501
  #ifdef VERTEX_COLOR_AVAILABLE
421
502
  return in.color;
@@ -424,6 +505,79 @@ fn materialVertexColor(in : VsOut) -> vec4<f32> {
424
505
  #endif
425
506
  }
426
507
 
508
+ // Keep the cooked artifact content-addressable per declared capability set.
509
+ // Some capability axes only change an imported helper's resource surface; the
510
+ // witness makes that distinction explicit in the composed source as well, so
511
+ // two valid manifest keys can never alias one artifact hash.
512
+ fn standardVariantIdentity() -> f32 {
513
+ var identity = 0.0;
514
+ #ifdef STORAGE_BUFFER_AVAILABLE
515
+ identity = identity + 1.0;
516
+ #endif
517
+ #ifdef CLUSTER_FORWARD_AVAILABLE
518
+ identity = identity + 2.0;
519
+ #endif
520
+ #ifdef VERTEX_COLOR_AVAILABLE
521
+ identity = identity + 4.0;
522
+ #endif
523
+ #ifdef PROBE_BLEND_AVAILABLE
524
+ identity = identity + 8.0;
525
+ #endif
526
+ #ifdef EXTENDED_LIGHTING_AVAILABLE
527
+ identity = identity + 16.0;
528
+ #endif
529
+ #ifdef TRANSMISSION_AVAILABLE
530
+ identity = identity + 32.0;
531
+ #endif
532
+ #ifdef DIRECTIONAL_PCSS_AVAILABLE
533
+ identity = identity + 64.0;
534
+ #endif
535
+ #ifdef PROJECTOR_AVAILABLE
536
+ identity = identity + 128.0;
537
+ #endif
538
+ #ifdef REFLECTION_FALLBACK_AVAILABLE
539
+ identity = identity + 256.0;
540
+ #endif
541
+ return identity;
542
+ }
543
+
544
+ // Standard owns the lighting and pass policy; the selected Surface owns only
545
+ // the base facts exchanged through surface_v1.
546
+ fn evaluateStandardSurface(in : VsOut, frontFacing : bool) -> SurfaceData {
547
+ let viewDirectionWS = normalize(view.cameraPos - in.worldPos);
548
+ #ifdef TRANSMISSION_AVAILABLE
549
+ let positionOS = in.worldPos;
550
+ #else
551
+ let positionOS = in.positionOSAndViewZ.xyz;
552
+ #endif
553
+ return evaluate_surface(SurfaceInput(
554
+ positionOS,
555
+ in.worldPos,
556
+ in.worldNormal,
557
+ in.worldTangent,
558
+ viewDirectionWS,
559
+ in.uv,
560
+ in.uv1,
561
+ materialVertexColor(in),
562
+ frontFacing,
563
+ ));
564
+ }
565
+
566
+ // The renderer supplies the retained ProbeBlendRecord at the per-object
567
+ // consumer boundary. This helper deliberately accepts only diffuse inputs;
568
+ // Skylight/ReflectionProbe continue to own specular.
569
+ fn composeProbeDiffuse(
570
+ shPreblend : array<vec4<f32>, 9>,
571
+ localBlendFraction : f32,
572
+ normal : vec3<f32>,
573
+ skyIrradiance : vec3<f32>,
574
+ kD : vec3<f32>,
575
+ albedo : vec3<f32>,
576
+ metallic : f32,
577
+ ) -> vec3<f32> {
578
+ return evaluateProbeDiffuse(shPreblend, localBlendFraction, normal, skyIrradiance, kD, albedo, metallic);
579
+ }
580
+
427
581
  fn finiteScalar(value : f32, fallback : f32) -> f32 {
428
582
  let bounded = clamp(value, -65504.0, 65504.0);
429
583
  return select(fallback, bounded, value == value);
@@ -451,69 +605,139 @@ fn blendLinearTransparent(
451
605
  // linearHdrColorDomain: fs_main writes linear HDR when its target is HDR.
452
606
  // toneStageInput: the value remains linear until the fullscreen tone stage.
453
607
 
454
- fn alphaTest(alpha : f32) {
455
- if (material.alphaCutoff > 0.0 && alpha <= material.alphaCutoff) {
608
+ fn alphaTestSurface(surface : SurfaceData) {
609
+ if (surface.alphaClipThreshold > 0.0 && surface.opacity <= surface.alphaClipThreshold) {
456
610
  discard;
457
611
  }
458
612
  }
459
613
 
460
- @fragment
461
- fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
462
- let baseUv = transformedMaterialUv(material.baseColorTextureCoordinatesTransform, material.baseColorTextureCoordinatesMetadata, in);
463
- let baseSample = sampleMaterialTexture(baseColorTexture, baseColorSampler, baseUv, material.baseColorTextureCoordinatesMetadata.zw);
464
- let vertexColor = materialVertexColor(in);
465
- alphaTest(material.baseColor.a * baseSample.a * vertexColor.a);
466
- let alpha = materialAlpha(baseSample) * vertexColor.a;
467
- let albedo = material.baseColor.rgb * baseSample.rgb * vertexColor.rgb;
468
-
469
- // Metallic-roughness texture sampling with per-field channel selectors
470
- // (D-8): glTF 2.0 default layout B=metallic, G=roughness, R=occlusion is
471
- // encoded by the host as 4 independent f32 selectors in the merged UBO
472
- // (metallicChannel/roughnessChannel/aoChannel/extraChannel). Cast to u32
473
- // at the pick_channel call site; values stay in {0,1,2,3}.
474
- let mrUv = transformedMaterialUv(material.metallicRoughnessTextureCoordinatesTransform, material.metallicRoughnessTextureCoordinatesMetadata, in);
475
- let mrSample = sampleMaterialTexture(metallicRoughnessTexture, metallicRoughnessSampler, mrUv, material.metallicRoughnessTextureCoordinatesMetadata.zw);
476
- let metallic = clamp(
477
- finiteScalar(material.metallic, 0.0) *
478
- finiteScalar(pick_channel(mrSample, u32(material.metallicChannel)), 1.0),
479
- 0.0,
480
- 1.0,
481
- );
482
- let roughnessTex = clamp(
483
- finiteScalar(pick_channel(mrSample, u32(material.roughnessChannel)), 1.0),
484
- 0.0,
485
- 1.0,
486
- );
487
-
488
- // Layer-2 fail-fast: shader internal clamp keeps D_GGX finite for
489
- // roughness=0 even if the asset somehow bypasses layer-1 register
490
- // fail-fast (plan-strategy section 5.3 + AC-02 (b)+(c)). Apply to the
491
- // material scalar prior to the channelMap-extracted texture multiplier
492
- // so the final roughness >= 0.04 floor is preserved (texture sample is
493
- // a 1.0-default white when metallicRoughnessTexture is absent, so
494
- // multiplying after the clamp keeps the floor intact).
495
- var a = max(finiteScalar(material.roughness, 0.5), 0.04);
496
- a = a * roughnessTex;
497
- a = a * a;
614
+ struct StandardPbrOutput {
615
+ @location(0) color : vec4<f32>,
616
+ #ifdef REFLECTION_FALLBACK_AVAILABLE
617
+ // Linear HDR environment contribution from this same Standard BRDF callsite.
618
+ // The detached producer names this second target S_fallback.
619
+ @location(1) reflectionFallback : vec4<f32>,
620
+ #endif
621
+ };
498
622
 
499
- // TBN basis composed via forgeax_pbr::tbn helpers; default fallback
500
- // (defaultNormalTextureView) RG=(128,128) -> tangent (0,0,1) -> world n
501
- // unchanged.
502
- let normalUv = transformedMaterialUv(material.normalTextureCoordinatesTransform, material.normalTextureCoordinatesMetadata, in);
503
- let normSampleRg = sampleMaterialTexture(normalTexture, normalSampler, normalUv, material.normalTextureCoordinatesMetadata.zw).rg;
504
- let normTangent = scaleTangentSpaceNormal(
505
- decodeTangentSpaceNormalRg(normSampleRg), material.normalScale,
506
- );
507
- let n = applyTBN(in.worldNormal, in.worldTangent, normTangent);
623
+ @fragment
624
+ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbrOutput {
625
+ let _variantIdentity = standardVariantIdentity();
626
+ let surface = evaluateStandardSurface(in, frontFacing);
627
+ alphaTestSurface(surface);
628
+ let alpha = surface.opacity;
629
+ let albedo = surface.baseColor;
630
+ let metallic = clamp(finiteScalar(surface.metallic, 0.0), 0.0, 1.0);
631
+ let iblRoughness = clamp(finiteScalar(surface.roughness, 0.5), 0.04, 1.0);
632
+ let a = iblRoughness * iblRoughness;
633
+ let n = normalize(surface.normalWS);
508
634
 
509
635
  let v = normalize(view.cameraPos - in.worldPos);
510
- let specularUv = transformedMaterialUv(material.specularTintTextureCoordinatesTransform, material.specularTintTextureCoordinatesMetadata, in);
511
- let specularTint = material.specularTint * sampleMaterialTexture(
512
- specularTintTexture, specularTintSampler, specularUv, material.specularTintTextureCoordinatesMetadata.zw,
636
+ var specularColor = material.specularColor;
637
+ #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
638
+ let specularUv = transformedMaterialUv(
639
+ material.specularColorTextureCoordinatesTransform,
640
+ material.specularColorTextureCoordinatesMetadata,
641
+ in,
642
+ );
643
+ specularColor = specularColor * sampleMaterialTexture(
644
+ specularColorTexture,
645
+ specularColorTextureSampler,
646
+ specularUv,
647
+ material.specularColorTextureCoordinatesMetadata.zw,
513
648
  ).rgb;
649
+ #endif
650
+ var specularWeight = clamp(finiteScalar(material.specular, 1.0), 0.0, 1.0);
651
+ #ifdef SPECULAR_TEXTURE_AVAILABLE
652
+ let specularWeightUv = transformedMaterialUv(
653
+ material.specularTextureCoordinatesTransform,
654
+ material.specularTextureCoordinatesMetadata,
655
+ in,
656
+ );
657
+ specularWeight = specularWeight * sampleMaterialTexture(
658
+ specularTexture,
659
+ specularTextureSampler,
660
+ specularWeightUv,
661
+ material.specularTextureCoordinatesMetadata.zw,
662
+ ).a;
663
+ #endif
514
664
  let safeIor = max(finiteScalar(material.ior, 1.5), 1.0);
515
665
  let dielectricF0 = pow((safeIor - 1.0) / (safeIor + 1.0), 2.0);
516
- let f0 = mix(vec3<f32>(dielectricF0) * specularTint, albedo, metallic);
666
+ var f0 = mix(vec3<f32>(dielectricF0) * specularColor * specularWeight, albedo, metallic);
667
+ var physicalNormal = n;
668
+ #ifdef ANISOTROPY_AVAILABLE
669
+ var anisotropyStrength = finiteScalar(material.anisotropyStrength, 0.0);
670
+ var anisotropyRotation = finiteScalar(material.anisotropyRotation, 0.0);
671
+ var anisotropyDirection = vec2<f32>(1.0, 0.0);
672
+ #ifdef ANISOTROPY_TEXTURE_AVAILABLE
673
+ let anisotropyUv = transformedMaterialUv(
674
+ material.anisotropyTextureCoordinatesTransform,
675
+ material.anisotropyTextureCoordinatesMetadata,
676
+ in,
677
+ );
678
+ let anisotropySample = sampleMaterialTexture(
679
+ anisotropyTexture,
680
+ anisotropySampler,
681
+ anisotropyUv,
682
+ material.anisotropyTextureCoordinatesMetadata.zw,
683
+ );
684
+ let encodedAnisotropyDirection = 2.0 * anisotropySample.rg - vec2<f32>(1.0, 1.0);
685
+ if (dot(encodedAnisotropyDirection, encodedAnisotropyDirection) >= 1e-6) {
686
+ anisotropyDirection = normalize(encodedAnisotropyDirection);
687
+ }
688
+ anisotropyStrength = anisotropyStrength * anisotropySample.b;
689
+ #endif
690
+ physicalNormal = evaluateAnisotropicNormal(
691
+ n,
692
+ in.worldTangent,
693
+ anisotropyStrength,
694
+ anisotropyRotation,
695
+ anisotropyDirection,
696
+ );
697
+ #endif
698
+ #ifdef IRIDESCENCE_AVAILABLE
699
+ var iridescenceStrength = finiteScalar(material.iridescence, 0.0);
700
+ var iridescenceThicknessFactor = 1.0;
701
+ #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
702
+ let iridescenceUv = transformedMaterialUv(
703
+ material.iridescenceTextureCoordinatesTransform,
704
+ material.iridescenceTextureCoordinatesMetadata,
705
+ in,
706
+ );
707
+ let iridescenceSample = sampleMaterialTexture(
708
+ iridescenceTexture,
709
+ iridescenceSampler,
710
+ iridescenceUv,
711
+ material.iridescenceTextureCoordinatesMetadata.zw,
712
+ );
713
+ iridescenceStrength = iridescenceStrength * iridescenceSample.r;
714
+ #endif
715
+ #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
716
+ let iridescenceThicknessUv = transformedMaterialUv(
717
+ material.iridescenceThicknessTextureCoordinatesTransform,
718
+ material.iridescenceThicknessTextureCoordinatesMetadata,
719
+ in,
720
+ );
721
+ let iridescenceThicknessSample = sampleMaterialTexture(
722
+ iridescenceThicknessTexture,
723
+ iridescenceThicknessSampler,
724
+ iridescenceThicknessUv,
725
+ material.iridescenceThicknessTextureCoordinatesMetadata.zw,
726
+ );
727
+ iridescenceThicknessFactor = clamp(iridescenceThicknessSample.g, 0.0, 1.0);
728
+ #endif
729
+ let filmThickness = mix(
730
+ finiteScalar(material.iridescenceThicknessMinimum, 100.0),
731
+ finiteScalar(material.iridescenceThicknessMaximum, 400.0),
732
+ iridescenceThicknessFactor,
733
+ );
734
+ f0 = evaluateIridescenceFresnel(
735
+ f0,
736
+ iridescenceStrength,
737
+ finiteScalar(material.iridescenceIor, 1.3),
738
+ filmThickness,
739
+ );
740
+ #endif
517
741
  var diffuseAlbedo = albedo;
518
742
  #ifdef TRANSMISSION_AVAILABLE
519
743
  let transmissionUv = transformedMaterialUv(
@@ -562,10 +786,6 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
562
786
  );
563
787
  diffuseAlbedo = albedo * (1.0 - transmittedEnergy);
564
788
  #endif
565
- let coatRoughness = max(material.clearcoatRoughness, 0.04);
566
- let coatAlpha = coatRoughness * coatRoughness;
567
- let coatF = f_schlick(max(dot(n, v), 0.0), vec3<f32>(0.04)) * material.clearcoat;
568
-
569
789
  // Ambient (IBL) + 1 + N + N accumulation
570
790
  // (feat-20260520-skylight-ibl-cubemap M3 / t48 +
571
791
  // feat-20260520-directional-light-shadow-mapping +
@@ -595,49 +815,83 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
595
815
  // diffuse form. The directional path additionally projects worldPos
596
816
  // through the per-cascade light-space matrix in the fragment stage for
597
817
  // shadow-map PCF lookup (CSM, feat-20260613).
598
- let kD = (vec3<f32>(1.0) - f_schlick(max(dot(n, v), 0.0), f0)) * (1.0 - metallic);
599
- let iblRoughness = clamp(max(finiteScalar(material.roughness, 0.5), 0.04) * roughnessTex, 0.04, 1.0);
818
+ let kD = (vec3<f32>(1.0) - f_schlick(max(dot(physicalNormal, v), 0.0), f0)) * (1.0 - metallic);
819
+ var coatRoughness = 0.04;
820
+ var coatAlpha = coatRoughness * coatRoughness;
821
+ var coatF = vec3<f32>(0.0);
822
+ #ifdef CLEARCOAT_AVAILABLE
823
+ coatRoughness = max(finiteScalar(material.clearcoatRoughness, 0.04), 0.04);
824
+ coatAlpha = coatRoughness * coatRoughness;
825
+ coatF = f_schlick(max(dot(physicalNormal, v), 0.0), vec3<f32>(0.04)) *
826
+ clamp(finiteScalar(material.clearcoat, 0.0), 0.0, 1.0);
827
+ #endif
600
828
  var irradiance = vec3<f32>(0.0);
601
829
  var specularIbl = vec3<f32>(0.0);
602
830
  if (skylight.intensity < 0.0) {
603
831
  // A negative intensity is the renderer-owned probe sentinel. The four
604
- // color/rotation lanes carry probe center and half extents in this mode;
605
- // Skylight resources remain unchanged and therefore preserve the ABI.
832
+ // color/rotation lanes carry the selected probe center and half extents;
833
+ // ordinary Skylight resources remain unchanged, preserving the ABI.
606
834
  specularIbl = sampleReflectionProbeSpecular(
607
- n, v, iblRoughness, f0, in.worldPos,
835
+ physicalNormal, v, iblRoughness, f0, in.worldPos,
608
836
  vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
609
837
  skylight.rotation.xyz, vec4<f32>(0.0, 0.0, 0.0, 1.0),
610
838
  prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
611
839
  );
612
840
  } else {
613
- irradiance = sampleIblDiffuse(n, skylight.rotation, irradianceMap, irradianceSampler);
841
+ irradiance = sampleIblDiffuse(physicalNormal, skylight.rotation, irradianceMap, irradianceSampler);
614
842
  specularIbl = sampleIblSpecular(
615
- n, v, iblRoughness, f0,
843
+ physicalNormal, v, iblRoughness, f0,
616
844
  skylight.rotation,
617
845
  prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
618
846
  );
619
847
  }
620
- let occlusionUv = transformedMaterialUv(material.occlusionTextureCoordinatesTransform, material.occlusionTextureCoordinatesMetadata, in);
621
- let aoSample = sampleMaterialTexture(occlusionTexture, occlusionSampler, occlusionUv, material.occlusionTextureCoordinatesMetadata.zw);
622
- let ao = mix(1.0, aoSample.r, material.occlusionStrength);
848
+ let ao = surface.occlusion;
623
849
  // feat-20260612-hdrp-ssao M7 round-2: `var` (mutable) so the
624
850
  // CLUSTER_FORWARD_AVAILABLE branch below can `ambient *=` the SSAO
625
851
  // factor. The non-HDRP path leaves ambient untouched.
626
852
  let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
627
- var ambient = (kD * irradiance * diffuseAlbedo + specularIbl) * (vec3<f32>(1.0) - coatF);
628
- if (material.clearcoat != 0.0) {
629
- let clearcoatIbl = sampleIblSpecular(
630
- n, v, coatRoughness, vec3<f32>(0.04),
631
- skylight.rotation,
632
- prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
633
- );
634
- ambient = ambient + clearcoatIbl * material.clearcoat;
635
- }
853
+ let skyFactor = skyColor * skylight.intensity;
854
+ var reflectionFallback = specularIbl * (vec3<f32>(1.0) - coatF);
855
+ var ambient = (kD * irradiance * diffuseAlbedo + specularIbl) *
856
+ (vec3<f32>(1.0) - coatF);
857
+ #ifdef PROBE_BLEND_AVAILABLE
858
+ // group(3) is the per-object bind group; record zero is the object selected
859
+ // by this draw, independent of instance count.
860
+ let probeShPreblend = array<vec4<f32>, 9>(
861
+ probeBlendRecords[1], probeBlendRecords[2], probeBlendRecords[3],
862
+ probeBlendRecords[4], probeBlendRecords[5], probeBlendRecords[6],
863
+ probeBlendRecords[7], probeBlendRecords[8], probeBlendRecords[9],
864
+ );
865
+ let probeLocalBlendFraction = probeBlendRecords[0].z;
866
+ let probeDiffuseK = kD / max(vec3<f32>(1.0 - metallic), vec3<f32>(0.0001));
867
+ let probeDiffuse = composeProbeDiffuse(probeShPreblend,
868
+ probeLocalBlendFraction,
869
+ physicalNormal,
870
+ irradiance * skyFactor,
871
+ probeDiffuseK,
872
+ diffuseAlbedo,
873
+ metallic,
874
+ );
875
+ ambient = (probeDiffuse + specularIbl * skyFactor) *
876
+ (vec3<f32>(1.0) - coatF);
877
+ #endif
878
+ #ifdef PROBE_BLEND_AVAILABLE
879
+ ambient = ambient * ao;
880
+ #else
881
+ // In the regular Skylight path tint/intensity are applied after sampling.
882
+ // In the probe-sentinel path intensity is encoded as -(probeIntensity + 1),
883
+ // so decode only the non-negative payload and keep zero-intensity probes
884
+ // distinct from an absent Skylight.
636
885
  if (skylight.intensity < 0.0) {
637
- ambient = ambient * (-skylight.intensity) * ao;
886
+ let environmentScale = max(-skylight.intensity - 1.0, 0.0) * ao;
887
+ ambient = ambient * environmentScale;
888
+ reflectionFallback = reflectionFallback * environmentScale;
638
889
  } else {
639
- ambient = ambient * skyColor * skylight.intensity * ao;
890
+ let environmentScale = skyColor * skylight.intensity * ao;
891
+ ambient = ambient * environmentScale;
892
+ reflectionFallback = reflectionFallback * environmentScale;
640
893
  }
894
+ #endif
641
895
  #ifdef CLUSTER_FORWARD_AVAILABLE
642
896
  // feat-20260612-hdrp-ssao M2 round-1 + M7 round-2 (plan-strategy D-7 + D-B + D-C):
643
897
  // SSAO ambient synthesis. Reads the half-res R8 `ssaoBlurredTexture` from
@@ -654,16 +908,15 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
654
908
  // scope-amend-webgl2-ubo: intensity packed into cluster_uniform.near_far_log.w.
655
909
  let ssaoIntensity = get_ssao_intensity();
656
910
  ambient *= mix(1.0, ssaoFactor * ao, ssaoIntensity);
911
+ let ssaoScale = mix(1.0, ssaoFactor * ao, ssaoIntensity);
912
+ reflectionFallback *= ssaoScale;
657
913
  #endif
658
914
  var color = ambient;
659
915
  #ifdef TRANSMISSION_AVAILABLE
660
916
  let screenUv = in.ndc.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
661
- // Read the same renderer-owned transform tables that the vertex stage uses
662
- // instead of adding six more interstage varyings. WebGPU guarantees only 16
663
- // user-defined vertex output locations; keeping the affine conversion in
664
- // the fragment stage preserves that contract and keeps thickness in the
665
- // glTF unit-scale mesh space before measuring the refracted segment in world
666
- // metres.
917
+ // Read the affine basis forwarded by the vertex stage. This keeps the
918
+ // fragment path within the inter-stage location budget and preserves the
919
+ // scene-index vertex entry used by the GPU-driven lane.
667
920
  let localToWorld0 = in.transmissionBasis0.xyz;
668
921
  let localToWorld1 = vec3<f32>(
669
922
  in.transmissionBasis0.w,
@@ -746,31 +999,164 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
746
999
  color = color + finiteColor(transmittedBackdrop, vec3<f32>(0.0)) *
747
1000
  transmittedEnergy * beerAttenuation;
748
1001
  #endif
749
- let directionalShadow = evalDirectionalShadowFactor(n, in.worldPos, in.viewZ);
750
- let directionalBase = evalDirectionalNoShadow(n, v, diffuseAlbedo, metallic, a, f0);
1002
+ let directionalShadow = evalDirectionalShadowFactor(
1003
+ physicalNormal,
1004
+ in.worldPos,
1005
+ standardViewZ(in),
1006
+ );
1007
+ let directionalBase = evalDirectionalNoShadow(physicalNormal, v, diffuseAlbedo, metallic, a, f0);
751
1008
  color = color + directionalShadow * directionalBase;
752
- if (material.clearcoat != 0.0) {
753
- let directionalClearcoat = evalDirectionalNoShadow(
754
- n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
755
- );
756
- color = color + directionalShadow * material.clearcoat * directionalClearcoat;
757
- }
758
1009
  #ifdef CLUSTER_FORWARD_AVAILABLE
759
1010
  // NDC from vertex shader (perspective-divided clip-space, interpolated).
760
1011
  // view_z: NDC depth for cluster Z-slice lookup.
761
1012
  color = color + evaluateStandardClusterLights(
762
- in.ndc.xyz, in.viewZ, in.worldPos, n, v, diffuseAlbedo, metallic, a, f0, false,
1013
+ in.ndc.xyz, standardViewZ(in), in.worldPos, physicalNormal, v,
1014
+ diffuseAlbedo, metallic, a, f0, false,
1015
+ );
1016
+ #endif // CLUSTER_FORWARD_AVAILABLE
1017
+ // Emissive is part of the lower-energy stack and is attenuated by the
1018
+ // topcoat just like diffuse/specular radiance.
1019
+ color = color + surface.emissive;
1020
+ #ifdef SHEEN_AVAILABLE
1021
+ var sheenColor = material.sheenColor;
1022
+ var sheenRoughnessFactor = 1.0;
1023
+ #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
1024
+ let sheenColorUv = transformedMaterialUv(
1025
+ material.sheenColorTextureCoordinatesTransform,
1026
+ material.sheenColorTextureCoordinatesMetadata,
1027
+ in,
1028
+ );
1029
+ sheenColor = sheenColor * sampleMaterialTexture(
1030
+ sheenColorTexture,
1031
+ sheenColorSampler,
1032
+ sheenColorUv,
1033
+ material.sheenColorTextureCoordinatesMetadata.zw,
1034
+ ).rgb;
1035
+ #endif
1036
+ #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
1037
+ let sheenRoughnessUv = transformedMaterialUv(
1038
+ material.sheenRoughnessTextureCoordinatesTransform,
1039
+ material.sheenRoughnessTextureCoordinatesMetadata,
1040
+ in,
1041
+ );
1042
+ sheenRoughnessFactor = sampleMaterialTexture(
1043
+ sheenRoughnessTexture,
1044
+ sheenRoughnessSampler,
1045
+ sheenRoughnessUv,
1046
+ material.sheenRoughnessTextureCoordinatesMetadata.zw,
1047
+ ).a;
1048
+ #endif
1049
+ let sheenRoughness = clamp(
1050
+ finiteScalar(material.sheenRoughness, 0.0) * sheenRoughnessFactor,
1051
+ 0.0,
1052
+ 1.0,
1053
+ );
1054
+ color = evaluateSheenLayer(color, sheenColor, sheenRoughness, dot(n, v));
1055
+ #endif
1056
+ #ifdef CLEARCOAT_AVAILABLE
1057
+ var clearcoatFactor = clamp(material.clearcoat, 0.0, 1.0);
1058
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
1059
+ let clearcoatUv = transformedMaterialUv(material.clearcoatTextureCoordinatesTransform, material.clearcoatTextureCoordinatesMetadata, in);
1060
+ clearcoatFactor = clamp(
1061
+ material.clearcoat * sampleMaterialTexture(
1062
+ clearcoatTexture,
1063
+ clearcoatSampler,
1064
+ clearcoatUv,
1065
+ material.clearcoatTextureCoordinatesMetadata.zw,
1066
+ ).r,
1067
+ 0.0,
1068
+ 1.0,
1069
+ );
1070
+ #endif
1071
+ var clearcoatRoughnessValue = clamp(max(material.clearcoatRoughness, 0.04), 0.04, 1.0);
1072
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
1073
+ let clearcoatRoughnessUv = transformedMaterialUv(material.clearcoatRoughnessTextureCoordinatesTransform, material.clearcoatRoughnessTextureCoordinatesMetadata, in);
1074
+ clearcoatRoughnessValue = clamp(
1075
+ max(material.clearcoatRoughness, 0.04) * sampleMaterialTexture(
1076
+ clearcoatRoughnessTexture,
1077
+ clearcoatRoughnessSampler,
1078
+ clearcoatRoughnessUv,
1079
+ material.clearcoatRoughnessTextureCoordinatesMetadata.zw,
1080
+ ).g,
1081
+ 0.04,
1082
+ 1.0,
763
1083
  );
764
- if (material.clearcoat != 0.0) {
765
- color = color + material.clearcoat * evaluateStandardClusterLights(
766
- in.ndc.xyz, in.viewZ, in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04), false,
1084
+ #endif
1085
+ var clearcoatNormalValue = in.worldNormal;
1086
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
1087
+ let clearcoatNormalUv = transformedMaterialUv(material.clearcoatNormalTextureCoordinatesTransform, material.clearcoatNormalTextureCoordinatesMetadata, in);
1088
+ clearcoatNormalValue = applyTBN(
1089
+ in.worldNormal,
1090
+ in.worldTangent,
1091
+ scaleTangentSpaceNormal(
1092
+ decodeTangentSpaceNormalRg(sampleMaterialTexture(
1093
+ clearcoatNormalTexture,
1094
+ clearcoatNormalSampler,
1095
+ clearcoatNormalUv,
1096
+ material.clearcoatNormalTextureCoordinatesMetadata.zw,
1097
+ ).rg),
1098
+ material.clearcoatNormalScale,
1099
+ ),
1100
+ );
1101
+ #endif
1102
+ var clearcoatIbl = vec3<f32>(0.0);
1103
+ if (skylight.intensity < 0.0) {
1104
+ clearcoatIbl = sampleReflectionProbeSpecular(
1105
+ clearcoatNormalValue,
1106
+ v,
1107
+ clearcoatRoughnessValue,
1108
+ vec3<f32>(0.04),
1109
+ in.worldPos,
1110
+ vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
1111
+ skylight.rotation.xyz,
1112
+ vec4<f32>(0.0, 0.0, 0.0, 1.0),
1113
+ prefilterMap,
1114
+ prefilterSampler,
1115
+ brdfLut,
1116
+ brdfLutSampler,
1117
+ );
1118
+ } else {
1119
+ clearcoatIbl = sampleIblSpecular(
1120
+ clearcoatNormalValue,
1121
+ v,
1122
+ clearcoatRoughnessValue,
1123
+ vec3<f32>(0.04),
1124
+ skylight.rotation,
1125
+ prefilterMap,
1126
+ prefilterSampler,
1127
+ brdfLut,
1128
+ brdfLutSampler,
767
1129
  );
768
1130
  }
769
- #endif // CLUSTER_FORWARD_AVAILABLE
770
- let emissiveUv = transformedMaterialUv(material.emissiveTextureCoordinatesTransform, material.emissiveTextureCoordinatesMetadata, in);
771
- let emissiveSample = sampleMaterialTexture(emissiveTexture, emissiveSampler, emissiveUv, material.emissiveTextureCoordinatesMetadata.zw).rgb;
772
- color = color + material.emissive * material.emissiveIntensity * emissiveSample;
773
- return vec4<f32>(color, alpha);
1131
+ let clearcoatAlpha = clearcoatRoughnessValue * clearcoatRoughnessValue;
1132
+ let clearcoatDirect = directionalShadow * evalDirectionalNoShadow(
1133
+ clearcoatNormalValue,
1134
+ v,
1135
+ vec3<f32>(0.0),
1136
+ 1.0,
1137
+ clearcoatAlpha,
1138
+ vec3<f32>(0.04),
1139
+ );
1140
+ let directionalClearcoat = clearcoatDirect;
1141
+ let clearcoatContribution = clearcoatIbl * clearcoatFactor;
1142
+ var clearcoatEnvironment = clearcoatIbl * skyColor * skylight.intensity;
1143
+ if (skylight.intensity < 0.0) {
1144
+ clearcoatEnvironment = clearcoatIbl;
1145
+ }
1146
+ color = evaluateClearcoatLayer(
1147
+ color,
1148
+ clearcoatEnvironment + directionalClearcoat,
1149
+ dot(clearcoatNormalValue, v),
1150
+ clearcoatFactor,
1151
+ );
1152
+ reflectionFallback = reflectionFallback + clearcoatContribution;
1153
+ #endif
1154
+ var output : StandardPbrOutput;
1155
+ output.color = vec4<f32>(color, alpha);
1156
+ #ifdef REFLECTION_FALLBACK_AVAILABLE
1157
+ output.reflectionFallback = vec4<f32>(reflectionFallback, 1.0);
1158
+ #endif
1159
+ return output;
774
1160
  }
775
1161
 
776
1162
  // ── G-buffer output struct (feat-20260612-hdrp-deferred-shading M2 / w12) ──
@@ -800,49 +1186,18 @@ struct GBufferOutput {
800
1186
  /// pipeline's g-buffer render pass binds this entry point via the shader's
801
1187
  /// multi-entry support (passKind='deferred' selects `fs_gbuffer`).
802
1188
  @fragment
803
- fn fs_gbuffer(in : VsOut) -> GBufferOutput {
804
- let baseUv = transformedMaterialUv(
805
- material.baseColorTextureCoordinatesTransform,
806
- material.baseColorTextureCoordinatesMetadata,
807
- in,
808
- );
809
- let baseSample = sampleMaterialTexture(baseColorTexture, baseColorSampler, baseUv, material.baseColorTextureCoordinatesMetadata.zw);
810
- let vertexColor = materialVertexColor(in);
811
- alphaTest(material.baseColor.a * baseSample.a * vertexColor.a);
812
- let alpha = materialAlpha(baseSample) * vertexColor.a;
813
- let albedo = material.baseColor.rgb * baseSample.rgb * vertexColor.rgb;
814
-
815
- let mrUv = transformedMaterialUv(
816
- material.metallicRoughnessTextureCoordinatesTransform,
817
- material.metallicRoughnessTextureCoordinatesMetadata,
818
- in,
819
- );
820
- let mrSample = sampleMaterialTexture(metallicRoughnessTexture, metallicRoughnessSampler, mrUv, material.metallicRoughnessTextureCoordinatesMetadata.zw);
821
- let metallic = material.metallic * pick_channel(mrSample, u32(material.metallicChannel));
822
- let roughnessTex = pick_channel(mrSample, u32(material.roughnessChannel));
823
-
824
- var a = max(material.roughness, 0.04);
825
- a = a * roughnessTex;
826
-
827
- let normalUv = transformedMaterialUv(material.normalTextureCoordinatesTransform, material.normalTextureCoordinatesMetadata, in);
828
- let normSampleRg = sampleMaterialTexture(normalTexture, normalSampler, normalUv, material.normalTextureCoordinatesMetadata.zw).rg;
829
- let normTangent = scaleTangentSpaceNormal(
830
- decodeTangentSpaceNormalRg(normSampleRg), material.normalScale,
831
- );
832
- let n = applyTBN(in.worldNormal, in.worldTangent, normTangent);
833
-
834
- let emissiveUv = transformedMaterialUv(material.emissiveTextureCoordinatesTransform, material.emissiveTextureCoordinatesMetadata, in);
835
- let emissiveSample = sampleMaterialTexture(emissiveTexture, emissiveSampler, emissiveUv, material.emissiveTextureCoordinatesMetadata.zw).rgb;
836
- let emissive = material.emissive * material.emissiveIntensity * emissiveSample;
837
-
838
- let occlusionUv = transformedMaterialUv(material.occlusionTextureCoordinatesTransform, material.occlusionTextureCoordinatesMetadata, in);
839
- let aoSample = sampleMaterialTexture(occlusionTexture, occlusionSampler, occlusionUv, material.occlusionTextureCoordinatesMetadata.zw);
840
- let ao = mix(1.0, aoSample.r, material.occlusionStrength);
1189
+ fn fs_gbuffer(in : VsOut, @builtin(front_facing) frontFacing : bool) -> GBufferOutput {
1190
+ let surface = evaluateStandardSurface(in, frontFacing);
1191
+ alphaTestSurface(surface);
1192
+ let albedo = surface.baseColor;
1193
+ let metallic = clamp(surface.metallic, 0.0, 1.0);
1194
+ let roughness = clamp(surface.roughness, 0.04, 1.0);
1195
+ let n = normalize(surface.normalWS);
841
1196
 
842
1197
  var out : GBufferOutput;
843
- out.normal_roughness = vec4<f32>(n * 0.5 + 0.5, a);
1198
+ out.normal_roughness = vec4<f32>(n * 0.5 + 0.5, roughness);
844
1199
  out.albedo_metallic = vec4<f32>(albedo, metallic);
845
- out.emissive_ao = vec4<f32>(emissive, ao);
1200
+ out.emissive_ao = vec4<f32>(surface.emissive, surface.occlusion);
846
1201
  return out;
847
1202
  }
848
1203
 
@@ -858,7 +1213,6 @@ struct TemporalVsOut {
858
1213
  @location(7) uv7 : vec2<f32>,
859
1214
  @location(8) @interpolate(perspective) currentClip : vec4<f32>,
860
1215
  @location(9) @interpolate(perspective) previousClip : vec4<f32>,
861
- @location(10) @interpolate(flat) reactive : f32,
862
1216
  #ifdef VERTEX_COLOR_AVAILABLE
863
1217
  @location(14) color : vec4<f32>,
864
1218
  #endif
@@ -877,11 +1231,6 @@ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
877
1231
  out.currentClip = view.temporalCurrentViewProj * currentWorld;
878
1232
  out.clip = out.currentClip;
879
1233
  out.previousClip = view.temporalPreviousViewProj * previousWorld;
880
- #if STORAGE_BUFFER_AVAILABLE == true
881
- out.reactive = meshes[0].temporal.x;
882
- #else
883
- out.reactive = 1.0;
884
- #endif
885
1234
  out.uv = in.uv;
886
1235
  out.uv1 = in.uv1;
887
1236
  out.uv2 = in.uv2;
@@ -906,6 +1255,10 @@ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
906
1255
 
907
1256
  @fragment
908
1257
  fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
1258
+ var reactive = 0.0;
1259
+ #if STORAGE_BUFFER_AVAILABLE == true
1260
+ reactive = meshes[0].temporal.x;
1261
+ #endif
909
1262
  return projectPbrSceneTemporal(
910
1263
  material.baseColor.a * temporalVertexAlpha(in),
911
1264
  material.alphaCutoff,
@@ -916,7 +1269,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
916
1269
  in.currentClip,
917
1270
  in.previousClip,
918
1271
  view.temporalProjection,
919
- in.reactive,
1272
+ reactive,
920
1273
  in.uv, in.uv1, in.uv2, in.uv3,
921
1274
  in.uv4, in.uv5, in.uv6, in.uv7,
922
1275
  );