@forgeax/engine-shader 0.1.23 → 0.1.25

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 (60) hide show
  1. package/README.md +110 -4
  2. package/dist/ShaderRegistry.d.ts.map +1 -1
  3. package/dist/index.d.ts +10 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.mjs +59 -55
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/material/artifact-registry.d.ts +1 -0
  8. package/dist/material/artifact-registry.d.ts.map +1 -1
  9. package/dist/material-schemas.d.ts +31 -0
  10. package/dist/material-schemas.d.ts.map +1 -1
  11. package/package.json +4 -4
  12. package/src/ShaderRegistry.ts +0 -17
  13. package/src/__tests__/builtin-texture-sampling-contract.test.ts +10 -1
  14. package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +9 -3
  15. package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +48 -166
  16. package/src/__tests__/deferred-lighting-ssao.test.ts +7 -4
  17. package/src/__tests__/ibl-irradiance.unit.test.ts +9 -0
  18. package/src/__tests__/lighting-directional.unit.test.ts +36 -0
  19. package/src/__tests__/lighting-punctual.unit.test.ts +12 -4
  20. package/src/__tests__/material-builtins.unit.test.ts +21 -3
  21. package/src/__tests__/material-contract.unit.test.ts +131 -17
  22. package/src/__tests__/physical-clearcoat.unit.test.ts +65 -0
  23. package/src/__tests__/probe-lighting-composition.unit.test.ts +3 -2
  24. package/src/__tests__/shader-manifest-transmission.unit.test.ts +8 -10
  25. package/src/__tests__/shader.unit.test.ts +52 -87
  26. package/src/__tests__/spot-cookie-composition.unit.test.ts +2 -1
  27. package/src/__tests__/standard-pbr-webgl2-interface.unit.test.ts +33 -0
  28. package/src/__tests__/standard-physical-layer.unit.test.ts +16 -0
  29. package/src/__tests__/standard-surface-pass-evaluation.integration.test.ts +38 -0
  30. package/src/__tests__/standard-webgl2-varying-budget.unit.test.ts +17 -0
  31. package/src/__tests__/surface-lighting-provenance.integration.test.ts +59 -0
  32. package/src/__tests__/surface-pass-templates.integration.test.ts +28 -0
  33. package/src/__tests__/surface-v1-negative.unit.test.ts +37 -0
  34. package/src/__tests__/surface-v1.unit.test.ts +83 -0
  35. package/src/__tests__/transmission-thickness-scale.unit.test.ts +2 -6
  36. package/src/__tests__/vertex-color-variant.unit.test.ts +16 -5
  37. package/src/default-standard-pbr-skin.wgsl +704 -192
  38. package/src/default-standard-pbr.wgsl +563 -279
  39. package/src/default_standard_surface.wgsl +88 -0
  40. package/src/hdrp-cluster-forward.wgsl +225 -0
  41. package/src/ibl-irradiance.wgsl +43 -8
  42. package/src/ibl-prefilter.wgsl +13 -3
  43. package/src/ibl-shared.wgsl +9 -2
  44. package/src/index.ts +37 -3
  45. package/src/lighting-directional.wgsl +3 -1
  46. package/src/lighting-spot-modifiers.wgsl +8 -1
  47. package/src/lighting-spot-projector.wgsl +31 -0
  48. package/src/material/artifact-registry.ts +20 -1
  49. package/src/material/artifact-types.ts +2 -2
  50. package/src/material/physical/anisotropy.wgsl +26 -0
  51. package/src/material/physical/clearcoat.wgsl +24 -0
  52. package/src/material/physical/iridescence.wgsl +23 -0
  53. package/src/material/physical/sheen.wgsl +17 -0
  54. package/src/material/standard-physical-layer.wgsl +12 -0
  55. package/src/material-schemas.ts +65 -30
  56. package/src/register-default-standard-pbr-skin.ts +2 -2
  57. package/src/shadow_caster.wgsl +80 -6
  58. package/src/standard-cluster.wgsl +1 -36
  59. package/src/surface_v1.wgsl +26 -0
  60. package/src/unlit.wgsl +12 -0
@@ -1,13 +1,31 @@
1
+ #pragma material_slot surface
2
+ #import forgeax_material::slot::surface::{evaluate_surface}
3
+ #import forgeax_material::surface_v1::{SurfaceInput, SurfaceData}
1
4
  #import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, shadowMap, shadowSampler, sampleMaterialTexture}
2
5
  #import forgeax_scene_temporal::{sceneViewZ}
6
+ #ifdef EXTENDED_LIGHTING_AVAILABLE
7
+ #import forgeax_view::common::{spotModifierSampler, iesProfileTexture, cookieTexture, cookieMatrices}
8
+ #endif
3
9
  #import forgeax_pbr::temporal::{projectPbrSceneTemporal}
4
10
  #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
5
- #import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular}
11
+ #import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular, sampleReflectionProbeSpecular}
6
12
  #import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
7
13
  #import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
14
+ #ifdef CLEARCOAT_AVAILABLE
15
+ #import forgeax_pbr::clearcoat::{evaluateClearcoatLayer}
16
+ #endif
17
+ #ifdef ANISOTROPY_AVAILABLE
18
+ #import forgeax_pbr::anisotropy::{evaluateAnisotropicNormal}
19
+ #endif
20
+ #ifdef SHEEN_AVAILABLE
21
+ #import forgeax_pbr::sheen::{evaluateSheenLayer}
22
+ #endif
23
+ #ifdef IRIDESCENCE_AVAILABLE
24
+ #import forgeax_pbr::iridescence::{evaluateIridescenceFresnel}
25
+ #endif
8
26
  #import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
9
27
  #ifdef CLUSTER_FORWARD_AVAILABLE
10
- #import forgeax_standard::cluster::{evaluateStandardClusterLights}
28
+ #import forgeax_standard::cluster::{evaluateStandardClusterLights, get_ssao_intensity}
11
29
  #endif
12
30
 
13
31
  #define_import_path forgeax_material::pbr-skin
@@ -16,9 +34,23 @@
16
34
  #pragma variant_axis VERTEX_COLOR_AVAILABLE
17
35
  #pragma variant_axis PROBE_BLEND_AVAILABLE
18
36
  #pragma variant_axis EXTENDED_LIGHTING_AVAILABLE
19
-
37
+ #pragma variant_axis TRANSMISSION_AVAILABLE
20
38
  #pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
21
39
 
40
+ #ifdef PROBE_BLEND_AVAILABLE
41
+ fn composeProbeDiffuse(
42
+ shPreblend : array<vec4<f32>, 9>,
43
+ localBlendFraction : f32,
44
+ normal : vec3<f32>,
45
+ skyIrradiance : vec3<f32>,
46
+ kD : vec3<f32>,
47
+ albedo : vec3<f32>,
48
+ metallic : f32,
49
+ ) -> vec3<f32> {
50
+ return evaluateProbeDiffuse(shPreblend, localBlendFraction, normal, skyIrradiance, kD, albedo, metallic);
51
+ }
52
+ #endif
53
+
22
54
  // @forgeax/engine-shader - default-standard-pbr-skin.wgsl
23
55
  // (feat-20260523-skin-skeleton-animation M3 / T-29).
24
56
  //
@@ -41,7 +73,6 @@
41
73
  // @group(1) @binding(4) metallicRoughnessTexture texture_2d<f32>
42
74
  // @group(1) @binding(5) normalSampler sampler
43
75
  // @group(1) @binding(6) normalTexture texture_2d<f32>
44
- // @group(1) @binding(7) specularTintSampler + @binding(8) specularTintTexture
45
76
  // @group(1) @binding(9..15) Skylight (irradiance / prefilter / brdfLut +
46
77
  // samplers) + skylight uniform (intensity)
47
78
  // @group(2) @binding(0) meshes storage (worldFromLocal mat4
@@ -71,86 +102,128 @@
71
102
  // 4 joints max; weighted sum of skinned positions from the palette buffer
72
103
  // indexed by the per-vertex skinIndex vector.
73
104
 
74
- struct Material {
75
- baseColor : vec4<f32>,
76
- metallic : f32,
77
- roughness : f32,
78
- // Channel selectors (D-8): 4 independent f32 entries split out of the
79
- // legacy `channelMap : vec4<u32>`. See default-standard-pbr.wgsl for
80
- // the full rationale; the skin shader shares the same merged UBO shape.
81
- metallicChannel : f32,
82
- roughnessChannel : f32,
83
- aoChannel : f32,
84
- extraChannel : f32,
85
- // vec3 align=16 inserts implicit padding so emissive lands at offset 48;
86
- // the authored schema rounds to 112 B (matches default-standard-pbr SSOT).
87
- emissive : vec3<f32>,
88
- emissiveIntensity : f32,
89
- occlusionStrength : f32,
90
- alphaCutoff : f32,
91
- clearcoat : f32,
92
- clearcoatRoughness : f32,
93
- specularTint : vec3<f32>,
94
- normalScale : f32,
95
- transmission : f32,
96
- ior : f32,
97
- thickness : f32,
98
- attenuationColor : vec3<f32>,
99
- attenuationDistance : f32,
100
- baseColorTextureCoordinatesTransform : vec4<f32>,
101
- baseColorTextureCoordinatesMetadata : vec4<f32>,
102
- metallicRoughnessTextureCoordinatesTransform : vec4<f32>,
103
- metallicRoughnessTextureCoordinatesMetadata : vec4<f32>,
104
- normalTextureCoordinatesTransform : vec4<f32>,
105
- normalTextureCoordinatesMetadata : vec4<f32>,
106
- specularTintTextureCoordinatesTransform : vec4<f32>,
107
- specularTintTextureCoordinatesMetadata : vec4<f32>,
108
- emissiveTextureCoordinatesTransform : vec4<f32>,
109
- emissiveTextureCoordinatesMetadata : vec4<f32>,
110
- occlusionTextureCoordinatesTransform : vec4<f32>,
111
- occlusionTextureCoordinatesMetadata : vec4<f32>,
112
- transmissionTextureCoordinatesTransform : vec4<f32>,
113
- transmissionTextureCoordinatesMetadata : vec4<f32>,
114
- thicknessTextureCoordinatesTransform : vec4<f32>,
115
- thicknessTextureCoordinatesMetadata : vec4<f32>,
116
- };
117
-
118
- @group(1) @binding(0) var<uniform> material : Material;
105
+ // The MaterialParameters struct and its binding-0 declaration are generated
106
+ // from the root ParamSchema during material composition. Keeping the ABI out
107
+ // of this template prevents a second handwritten interface from drifting from
108
+ // the runtime UBO writer.
119
109
  @group(1) @binding(1) var baseColorSampler : sampler;
120
110
  @group(1) @binding(2) var baseColorTexture : texture_2d<f32>;
121
111
  @group(1) @binding(3) var metallicRoughnessSampler : sampler;
122
112
  @group(1) @binding(4) var metallicRoughnessTexture : texture_2d<f32>;
123
113
  @group(1) @binding(5) var normalSampler : sampler;
124
114
  @group(1) @binding(6) var normalTexture : texture_2d<f32>;
125
- @group(1) @binding(7) var specularTintSampler : sampler;
126
- @group(1) @binding(8) var specularTintTexture : texture_2d<f32>;
127
115
  @group(1) @binding(9) var emissiveSampler : sampler;
128
116
  @group(1) @binding(10) var emissiveTexture : texture_2d<f32>;
129
117
  @group(1) @binding(11) var occlusionSampler : sampler;
130
118
  @group(1) @binding(12) var occlusionTexture : texture_2d<f32>;
119
+ #ifdef TRANSMISSION_AVAILABLE
131
120
  @group(1) @binding(13) var transmissionSampler : sampler;
132
121
  @group(1) @binding(14) var transmissionTexture : texture_2d<f32>;
133
122
  @group(1) @binding(15) var thicknessSampler : sampler;
134
123
  @group(1) @binding(16) var thicknessTexture : texture_2d<f32>;
124
+ @group(1) @binding(24) var transmissionBackdropSampler : sampler;
125
+ @group(1) @binding(25) var transmissionBackdropTexture : texture_2d<f32>;
126
+ #endif
127
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
128
+ @group(1) @binding(26) var clearcoatSampler : sampler;
129
+ @group(1) @binding(27) var clearcoatTexture : texture_2d<f32>;
130
+ #endif
131
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
132
+ @group(1) @binding(28) var clearcoatRoughnessSampler : sampler;
133
+ @group(1) @binding(29) var clearcoatRoughnessTexture : texture_2d<f32>;
134
+ #endif
135
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
136
+ @group(1) @binding(30) var clearcoatNormalSampler : sampler;
137
+ @group(1) @binding(31) var clearcoatNormalTexture : texture_2d<f32>;
138
+ #endif
139
+ #ifdef ANISOTROPY_TEXTURE_AVAILABLE
140
+ @group(1) @binding(32) var anisotropySampler : sampler;
141
+ @group(1) @binding(33) var anisotropyTexture : texture_2d<f32>;
142
+ #endif
143
+ #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
144
+ @group(1) @binding(34) var sheenColorSampler : sampler;
145
+ @group(1) @binding(35) var sheenColorTexture : texture_2d<f32>;
146
+ #endif
147
+ #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
148
+ @group(1) @binding(36) var sheenRoughnessSampler : sampler;
149
+ @group(1) @binding(37) var sheenRoughnessTexture : texture_2d<f32>;
150
+ #endif
151
+ #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
152
+ @group(1) @binding(38) var iridescenceSampler : sampler;
153
+ @group(1) @binding(39) var iridescenceTexture : texture_2d<f32>;
154
+ #endif
155
+ #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
156
+ @group(1) @binding(40) var iridescenceThicknessSampler : sampler;
157
+ @group(1) @binding(41) var iridescenceThicknessTexture : texture_2d<f32>;
158
+ #endif
159
+ #ifdef SPECULAR_TEXTURE_AVAILABLE
160
+ @group(1) @binding(42) var specularTextureSampler : sampler;
161
+ @group(1) @binding(43) var specularTexture : texture_2d<f32>;
162
+ #endif
163
+ #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
164
+ @group(1) @binding(44) var specularColorTextureSampler : sampler;
165
+ @group(1) @binding(45) var specularColorTexture : texture_2d<f32>;
166
+ #endif
135
167
 
136
168
  // Preserve filtering reflection for resources passed to the shared sampler.
137
169
  fn materialTextureFilteringWitness() {
138
170
  let base = baseColorTexture;
139
171
  let metallicRoughness = metallicRoughnessTexture;
140
172
  let normal = normalTexture;
141
- let specularTint = specularTintTexture;
142
173
  let emissive = emissiveTexture;
143
174
  let occlusion = occlusionTexture;
175
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
176
+ let clearcoat = clearcoatTexture;
177
+ #endif
178
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
179
+ let clearcoatRoughness = clearcoatRoughnessTexture;
180
+ #endif
181
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
182
+ let clearcoatNormal = clearcoatNormalTexture;
183
+ #endif
184
+ #ifdef TRANSMISSION_AVAILABLE
144
185
  let transmission = transmissionTexture;
145
186
  let thickness = thicknessTexture;
187
+ #endif
146
188
  let baseWitness = textureSample(base, baseColorSampler, vec2<f32>(0.0));
147
189
  let metallicRoughnessWitness = textureSample(metallicRoughness, metallicRoughnessSampler, vec2<f32>(0.0));
148
190
  let normalWitness = textureSample(normal, normalSampler, vec2<f32>(0.0));
149
- let specularTintWitness = textureSample(specularTint, specularTintSampler, vec2<f32>(0.0));
150
191
  let emissiveWitness = textureSample(emissive, emissiveSampler, vec2<f32>(0.0));
151
192
  let occlusionWitness = textureSample(occlusion, occlusionSampler, vec2<f32>(0.0));
193
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
194
+ let clearcoatWitness = textureSample(clearcoat, clearcoatSampler, vec2<f32>(0.0));
195
+ #endif
196
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
197
+ let clearcoatRoughnessWitness = textureSample(clearcoatRoughness, clearcoatRoughnessSampler, vec2<f32>(0.0));
198
+ #endif
199
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
200
+ let clearcoatNormalWitness = textureSample(clearcoatNormal, clearcoatNormalSampler, vec2<f32>(0.0));
201
+ #endif
202
+ #ifdef ANISOTROPY_TEXTURE_AVAILABLE
203
+ let anisotropyWitness = textureSample(anisotropyTexture, anisotropySampler, vec2<f32>(0.0));
204
+ #endif
205
+ #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
206
+ let sheenColorWitness = textureSample(sheenColorTexture, sheenColorSampler, vec2<f32>(0.0));
207
+ #endif
208
+ #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
209
+ let sheenRoughnessWitness = textureSample(sheenRoughnessTexture, sheenRoughnessSampler, vec2<f32>(0.0));
210
+ #endif
211
+ #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
212
+ let iridescenceWitness = textureSample(iridescenceTexture, iridescenceSampler, vec2<f32>(0.0));
213
+ #endif
214
+ #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
215
+ let iridescenceThicknessWitness = textureSample(iridescenceThicknessTexture, iridescenceThicknessSampler, vec2<f32>(0.0));
216
+ #endif
217
+ #ifdef SPECULAR_TEXTURE_AVAILABLE
218
+ let specularWeightWitness = textureSample(specularTexture, specularTextureSampler, vec2<f32>(0.0));
219
+ #endif
220
+ #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
221
+ let specularColorWitness = textureSample(specularColorTexture, specularColorTextureSampler, vec2<f32>(0.0));
222
+ #endif
223
+ #ifdef TRANSMISSION_AVAILABLE
152
224
  let transmissionWitness = textureSample(transmission, transmissionSampler, vec2<f32>(0.0));
153
225
  let thicknessWitness = textureSample(thickness, thicknessSampler, vec2<f32>(0.0));
226
+ #endif
154
227
  }
155
228
 
156
229
  struct SkylightUniforms {
@@ -176,34 +249,16 @@ struct SkylightUniforms {
176
249
  @group(1) @binding(23) var<uniform> skylight : SkylightUniforms;
177
250
 
178
251
  #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
252
  @group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
182
253
  #endif
183
254
 
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>>;
187
- #else
188
- @group(2) @binding(1) var<uniform> paletteUniform : array<mat4x4<f32>, 255>;
189
- @group(2) @binding(2) var<uniform> previousPaletteUniform : array<mat4x4<f32>, 255>;
190
- #endif
191
-
192
- fn skinPalette(index : u32) -> mat4x4<f32> {
193
- #ifdef STORAGE_BUFFER_AVAILABLE
194
- return paletteStorage[index];
255
+ #if STORAGE_BUFFER_AVAILABLE == true
256
+ @group(2) @binding(1) var<storage, read> palette : array<mat4x4<f32>>;
257
+ @group(2) @binding(2) var<storage, read> previousPalette : array<mat4x4<f32>>;
195
258
  #else
196
- return paletteUniform[index];
259
+ @group(2) @binding(1) var<uniform> palette : array<mat4x4<f32>, 255>;
260
+ @group(2) @binding(2) var<uniform> previousPalette : array<mat4x4<f32>, 255>;
197
261
  #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
262
 
208
263
  struct VsIn {
209
264
  @location(0) pos : vec3<f32>,
@@ -227,6 +282,13 @@ struct VsIn {
227
282
  };
228
283
  struct VsOut {
229
284
  @builtin(position) clip : vec4<f32>,
285
+ #ifdef TRANSMISSION_AVAILABLE
286
+ @location(4) @interpolate(flat) transmissionBasis0 : vec4<f32>,
287
+ #else
288
+ // WebGL2 exposes only inter-stage locations 0..14. Pack the object-space
289
+ // Surface position with its cluster/CSM depth in one varying.
290
+ @location(7) positionOSAndViewZ : vec4<f32>,
291
+ #endif
230
292
  @location(0) worldPos : vec3<f32>,
231
293
  @location(1) worldNormal : vec3<f32>,
232
294
  @location(2) uv : vec2<f32>,
@@ -238,27 +300,20 @@ struct VsOut {
238
300
  @location(9) uv3 : vec2<f32>,
239
301
  @location(10) uv4 : vec2<f32>,
240
302
  @location(11) uv5 : vec2<f32>,
241
- @location(12) uv6 : vec2<f32>,
242
- @location(13) uv7 : vec2<f32>,
303
+ // UV6/UV7 share one vec4 slot so the transmission basis tail can stay
304
+ // inside WebGL2's 14 user varying locations.
305
+ @location(12) uv6And7 : vec4<f32>,
243
306
  #ifdef VERTEX_COLOR_AVAILABLE
244
307
  @location(14) color : vec4<f32>,
245
308
  #endif
246
- // Vertex-produced Standard cluster coordinates. Fragment builtin position
247
- // is framebuffer-space and cannot recover the original clip-space NDC.
248
- @location(6) ndc : vec3<f32>,
309
+ @location(6) ndc : vec4<f32>,
310
+ #ifdef TRANSMISSION_AVAILABLE
249
311
  @location(7) viewZ : f32,
312
+ @location(13) @interpolate(flat) transmissionBasis1 : vec4<f32>,
313
+ #endif
250
314
  };
251
315
 
252
- fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
253
- switch (channelIndex) {
254
- case 0u: { return rgba.r; }
255
- case 1u: { return rgba.g; }
256
- case 2u: { return rgba.b; }
257
- default: { return rgba.a; }
258
- }
259
- }
260
-
261
- fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
316
+ fn vs_main_impl(in : VsIn, meshIndex : u32, instanceIndex : u32) -> VsOut {
262
317
  // 4-bone weighted skinning (plan-strategy D-3 / D-3a).
263
318
  // The host pre-computes each joint matrix as worldFromJoint * inverseBindMatrix
264
319
  // (CPU-side pre-multiplication, per D-4) and writes them into the palette
@@ -270,10 +325,10 @@ fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
270
325
  // The host sets the BindGroup dynamic offset to (byteOffset / 64) so the
271
326
  // first palette entry visible to this draw is palette[0]. The shader
272
327
  // indexes into palette directly using the per-vertex skinIndex values.
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;
328
+ let skinMatrix = palette[in.skinIndex.x] * in.skinWeight.x +
329
+ palette[in.skinIndex.y] * in.skinWeight.y +
330
+ palette[in.skinIndex.z] * in.skinWeight.z +
331
+ palette[in.skinIndex.w] * in.skinWeight.w;
277
332
 
278
333
  // glTF 2.0 sec.Skins Implementation Note: when a mesh node has a skin
279
334
  // property, the joint matrices already encode the global transform of each
@@ -285,7 +340,7 @@ fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
285
340
  // skinnedLocal IS the world position -- no additional left-multiply by
286
341
  // meshes[0].worldFromLocal or instanceLocal is needed.
287
342
  //
288
- // This removes the implicit contract "Skin entity GlobalTransform.world must be
343
+ // This removes the implicit contract "Skin entity Transform.world must be
289
344
  // identity" -- a skin entity can be parented under any Transform chain and
290
345
  // the skinned mesh will rigidly follow via joint propagation alone.
291
346
  let skinnedLocal = skinMatrix * vec4<f32>(in.pos, 1.0);
@@ -307,10 +362,13 @@ fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
307
362
  // globals. The host-side BGL shape must remain compatible with non-skin
308
363
  // PBR pipeline layout (buildPbrSkinLayouts declares 2-entry mesh-array
309
364
  // slot + separate instances slot). Without these keep-alive references,
310
- // createRenderPipeline would fail on binding-count mismatch.
311
- _ = meshes[0].worldFromLocal;
312
- _ = instances[0].localFromInstance;
365
+ // createRenderPipeline would fail at binding-count validation.
366
+ _ = meshes[meshIndex].worldFromLocal;
367
+ _ = instances[instanceIndex].localFromInstance;
313
368
  out.clip = view.worldViewProj * skinnedLocal;
369
+ #ifndef TRANSMISSION_AVAILABLE
370
+ out.positionOSAndViewZ = vec4<f32>(in.pos, sceneViewZ(out.clip, view.temporalProjection));
371
+ #endif
314
372
  out.worldPos = skinnedLocal.xyz;
315
373
  out.worldNormal = normalize(skinNormal3x3 * in.normal);
316
374
  let worldTangentXyz = normalize(skinNormal3x3 * in.tangent.xyz);
@@ -321,30 +379,46 @@ fn vs_main_impl(in : VsIn, idx : u32) -> VsOut {
321
379
  out.uv3 = in.uv3;
322
380
  out.uv4 = in.uv4;
323
381
  out.uv5 = in.uv5;
324
- out.uv6 = in.uv6;
325
- out.uv7 = in.uv7;
382
+ out.uv6And7 = vec4<f32>(in.uv6, in.uv7);
326
383
  #ifdef VERTEX_COLOR_AVAILABLE
327
384
  out.color = in.color;
328
385
  #endif
386
+ #ifdef TRANSMISSION_AVAILABLE
387
+ // Skin palette already contains the world-space transform. Forward its
388
+ // affine basis so transmission does not reread Mesh/Instance storage in
389
+ // the fragment stage.
390
+ out.transmissionBasis0 = vec4<f32>(skinMatrix[0].xyz, skinMatrix[1].x);
391
+ out.transmissionBasis1 = vec4<f32>(skinMatrix[1].y, skinMatrix[1].z, skinMatrix[2].x, skinMatrix[2].y);
392
+ #endif
393
+ let clipPos = out.clip;
394
+ out.ndc = vec4(clipPos.xy / clipPos.w, clipPos.z / clipPos.w, skinMatrix[2].z);
329
395
  // feat-20260613-csm-cascaded-shadow-maps M5 / w19: viewZ replaces the
330
396
  // prior light-space-position varying; evalDirectional picks the cascade
331
397
  // matrix per fragment from viewZ + worldPos.
332
- let clipPos = out.clip;
333
- out.ndc = vec3(clipPos.xy / clipPos.w, clipPos.z / clipPos.w);
334
398
  // Keep the cluster depth exactly aligned with the CPU binner for both
335
399
  // perspective and off-axis orthographic projections.
336
- out.viewZ = sceneViewZ(out.clip, view.temporalProjection);
400
+ #ifdef TRANSMISSION_AVAILABLE
401
+ out.viewZ = sceneViewZ(clipPos, view.temporalProjection);
402
+ #endif
337
403
  return out;
338
404
  }
339
405
 
406
+ fn standardViewZ(in : VsOut) -> f32 {
407
+ #ifdef TRANSMISSION_AVAILABLE
408
+ return in.viewZ;
409
+ #else
410
+ return in.positionOSAndViewZ.w;
411
+ #endif
412
+ }
413
+
340
414
  @vertex
341
415
  fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
342
- return vs_main_impl(in, idx);
416
+ return vs_main_impl(in, 0u, idx);
343
417
  }
344
418
 
345
419
  @vertex
346
420
  fn vs_scene_index(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
347
- return vs_main_impl(in, idx);
421
+ return vs_main_impl(in, idx, 0u);
348
422
  }
349
423
 
350
424
  fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
@@ -354,8 +428,8 @@ fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut
354
428
  if (metadata.x >= 3.0) { source = in.uv3; }
355
429
  if (metadata.x >= 4.0) { source = in.uv4; }
356
430
  if (metadata.x >= 5.0) { source = in.uv5; }
357
- if (metadata.x >= 6.0) { source = in.uv6; }
358
- if (metadata.x >= 7.0) { source = in.uv7; }
431
+ if (metadata.x >= 6.0) { source = in.uv6And7.xy; }
432
+ if (metadata.x >= 7.0) { source = in.uv6And7.zw; }
359
433
  let scaled = source * transform.zw;
360
434
  let angle = metadata.y;
361
435
  let c = cos(angle);
@@ -371,58 +445,269 @@ fn materialVertexColor(in : VsOut) -> vec4<f32> {
371
445
  #endif
372
446
  }
373
447
 
374
- @fragment
375
- fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
376
- let baseUv = transformedMaterialUv(material.baseColorTextureCoordinatesTransform, material.baseColorTextureCoordinatesMetadata, in);
377
- let baseSample = sampleMaterialTexture(baseColorTexture, baseColorSampler, baseUv, material.baseColorTextureCoordinatesMetadata.zw);
378
- let vertexColor = materialVertexColor(in);
379
- let alpha = material.baseColor.a * baseSample.a * vertexColor.a;
380
- if (material.alphaCutoff > 0.0 && alpha <= material.alphaCutoff) {
381
- discard;
382
- }
383
- let albedo = material.baseColor.rgb * baseSample.rgb * vertexColor.rgb;
448
+ // Keep the cooked skinned artifact content-addressable per declared
449
+ // capability set, including axes whose imported helper/resource surface is
450
+ // otherwise identical after lowering.
451
+ fn standardSkinVariantIdentity() -> f32 {
452
+ var identity = 0.0;
453
+ #ifdef STORAGE_BUFFER_AVAILABLE
454
+ identity = identity + 1.0;
455
+ #endif
456
+ #ifdef CLUSTER_FORWARD_AVAILABLE
457
+ identity = identity + 2.0;
458
+ #endif
459
+ #ifdef VERTEX_COLOR_AVAILABLE
460
+ identity = identity + 4.0;
461
+ #endif
462
+ #ifdef PROBE_BLEND_AVAILABLE
463
+ identity = identity + 8.0;
464
+ #endif
465
+ #ifdef EXTENDED_LIGHTING_AVAILABLE
466
+ identity = identity + 16.0;
467
+ #endif
468
+ #ifdef TRANSMISSION_AVAILABLE
469
+ identity = identity + 32.0;
470
+ #endif
471
+ #ifdef DIRECTIONAL_PCSS_AVAILABLE
472
+ identity = identity + 64.0;
473
+ #endif
474
+ return identity;
475
+ }
384
476
 
385
- let mrUv = transformedMaterialUv(material.metallicRoughnessTextureCoordinatesTransform, material.metallicRoughnessTextureCoordinatesMetadata, in);
386
- let mrSample = sampleMaterialTexture(metallicRoughnessTexture, metallicRoughnessSampler, mrUv, material.metallicRoughnessTextureCoordinatesMetadata.zw);
387
- let metallic = material.metallic * pick_channel(mrSample, u32(material.metallicChannel));
388
- let roughnessTex = pick_channel(mrSample, u32(material.roughnessChannel));
477
+ fn evaluateStandardSurface(in : VsOut, frontFacing : bool) -> SurfaceData {
478
+ let viewDirectionWS = normalize(view.cameraPos - in.worldPos);
479
+ #ifdef TRANSMISSION_AVAILABLE
480
+ let positionOS = in.worldPos;
481
+ #else
482
+ let positionOS = in.positionOSAndViewZ.xyz;
483
+ #endif
484
+ return evaluate_surface(SurfaceInput(
485
+ positionOS,
486
+ in.worldPos,
487
+ in.worldNormal,
488
+ in.worldTangent,
489
+ viewDirectionWS,
490
+ in.uv,
491
+ in.uv1,
492
+ materialVertexColor(in),
493
+ frontFacing,
494
+ ));
495
+ }
389
496
 
390
- var a = max(material.roughness, 0.04);
391
- a = a * roughnessTex;
392
- a = a * a;
497
+ fn finiteScalar(value : f32, fallback : f32) -> f32 {
498
+ let bounded = clamp(value, -65504.0, 65504.0);
499
+ return select(fallback, bounded, value == value);
500
+ }
393
501
 
394
- let normalUv = transformedMaterialUv(material.normalTextureCoordinatesTransform, material.normalTextureCoordinatesMetadata, in);
395
- let normSampleRg = sampleMaterialTexture(normalTexture, normalSampler, normalUv, material.normalTextureCoordinatesMetadata.zw).rg;
396
- let normTangent = scaleTangentSpaceNormal(
397
- decodeTangentSpaceNormalRg(normSampleRg), material.normalScale,
502
+ fn finiteColor(value : vec3<f32>, fallback : vec3<f32>) -> vec3<f32> {
503
+ return vec3<f32>(
504
+ finiteScalar(value.x, fallback.x),
505
+ finiteScalar(value.y, fallback.y),
506
+ finiteScalar(value.z, fallback.z),
398
507
  );
399
- let n = applyTBN(in.worldNormal, in.worldTangent, normTangent);
508
+ }
509
+
510
+ fn alphaTestSurface(surface : SurfaceData) {
511
+ if (surface.alphaClipThreshold > 0.0 && surface.opacity <= surface.alphaClipThreshold) {
512
+ discard;
513
+ }
514
+ }
515
+
516
+ @fragment
517
+ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> @location(0) vec4<f32> {
518
+ let _variantIdentity = standardSkinVariantIdentity();
519
+ let surface = evaluateStandardSurface(in, frontFacing);
520
+ alphaTestSurface(surface);
521
+ let alpha = surface.opacity;
522
+ let albedo = surface.baseColor;
523
+ let metallic = clamp(finiteScalar(surface.metallic, 0.0), 0.0, 1.0);
524
+ let iblRoughness = clamp(finiteScalar(surface.roughness, 0.5), 0.04, 1.0);
525
+ let a = iblRoughness * iblRoughness;
526
+ let n = normalize(surface.normalWS);
400
527
 
401
528
  let v = normalize(view.cameraPos - in.worldPos);
402
- let specularTint = material.specularTint * sampleMaterialTexture(
403
- specularTintTexture, specularTintSampler,
404
- transformedMaterialUv(material.specularTintTextureCoordinatesTransform, material.specularTintTextureCoordinatesMetadata, in),
405
- material.specularTintTextureCoordinatesMetadata.zw,
529
+ var specularColor = material.specularColor;
530
+ #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
531
+ specularColor = specularColor * sampleMaterialTexture(
532
+ specularColorTexture,
533
+ specularColorTextureSampler,
534
+ transformedMaterialUv(
535
+ material.specularColorTextureCoordinatesTransform,
536
+ material.specularColorTextureCoordinatesMetadata,
537
+ in,
538
+ ),
539
+ material.specularColorTextureCoordinatesMetadata.zw,
406
540
  ).rgb;
407
- let f0 = mix(vec3<f32>(0.04) * specularTint, albedo, metallic);
408
- let coatRoughness = max(material.clearcoatRoughness, 0.04);
409
- let coatAlpha = coatRoughness * coatRoughness;
410
- let coatF = f_schlick(max(dot(n, v), 0.0), vec3<f32>(0.04)) * material.clearcoat;
411
-
412
- let kD = (vec3<f32>(1.0) - f_schlick(max(dot(n, v), 0.0), f0)) * (1.0 - metallic);
413
- let iblRoughness = max(material.roughness, 0.04) * roughnessTex;
414
- let irradiance = sampleIblDiffuse(n, skylight.rotation, irradianceMap, irradianceSampler);
415
- let specularIbl = sampleIblSpecular(
416
- n, v, iblRoughness, f0,
417
- skylight.rotation,
418
- prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
541
+ #endif
542
+ var specularWeight = clamp(finiteScalar(material.specular, 1.0), 0.0, 1.0);
543
+ #ifdef SPECULAR_TEXTURE_AVAILABLE
544
+ let specularWeightUv = transformedMaterialUv(
545
+ material.specularTextureCoordinatesTransform,
546
+ material.specularTextureCoordinatesMetadata,
547
+ in,
548
+ );
549
+ specularWeight = specularWeight * sampleMaterialTexture(
550
+ specularTexture,
551
+ specularTextureSampler,
552
+ specularWeightUv,
553
+ material.specularTextureCoordinatesMetadata.zw,
554
+ ).a;
555
+ #endif
556
+ let safeIor = max(finiteScalar(material.ior, 1.5), 1.0);
557
+ let dielectricF0 = pow((safeIor - 1.0) / (safeIor + 1.0), 2.0);
558
+ var f0 = mix(vec3<f32>(dielectricF0) * specularColor * specularWeight, albedo, metallic);
559
+ var physicalNormal = n;
560
+ #ifdef ANISOTROPY_AVAILABLE
561
+ var anisotropyStrength = finiteScalar(material.anisotropyStrength, 0.0);
562
+ var anisotropyRotation = finiteScalar(material.anisotropyRotation, 0.0);
563
+ var anisotropyDirection = vec2<f32>(1.0, 0.0);
564
+ #ifdef ANISOTROPY_TEXTURE_AVAILABLE
565
+ let anisotropyUv = transformedMaterialUv(
566
+ material.anisotropyTextureCoordinatesTransform,
567
+ material.anisotropyTextureCoordinatesMetadata,
568
+ in,
569
+ );
570
+ let anisotropySample = sampleMaterialTexture(
571
+ anisotropyTexture,
572
+ anisotropySampler,
573
+ anisotropyUv,
574
+ material.anisotropyTextureCoordinatesMetadata.zw,
575
+ );
576
+ let encodedAnisotropyDirection = 2.0 * anisotropySample.rg - vec2<f32>(1.0, 1.0);
577
+ if (dot(encodedAnisotropyDirection, encodedAnisotropyDirection) >= 1e-6) {
578
+ anisotropyDirection = normalize(encodedAnisotropyDirection);
579
+ }
580
+ anisotropyStrength = anisotropyStrength * anisotropySample.b;
581
+ #endif
582
+ physicalNormal = evaluateAnisotropicNormal(
583
+ n,
584
+ in.worldTangent,
585
+ anisotropyStrength,
586
+ anisotropyRotation,
587
+ anisotropyDirection,
588
+ );
589
+ #endif
590
+ #ifdef IRIDESCENCE_AVAILABLE
591
+ var iridescenceStrength = finiteScalar(material.iridescence, 0.0);
592
+ var iridescenceThicknessFactor = 1.0;
593
+ #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
594
+ let iridescenceUv = transformedMaterialUv(
595
+ material.iridescenceTextureCoordinatesTransform,
596
+ material.iridescenceTextureCoordinatesMetadata,
597
+ in,
598
+ );
599
+ let iridescenceSample = sampleMaterialTexture(
600
+ iridescenceTexture,
601
+ iridescenceSampler,
602
+ iridescenceUv,
603
+ material.iridescenceTextureCoordinatesMetadata.zw,
604
+ );
605
+ iridescenceStrength = iridescenceStrength * iridescenceSample.r;
606
+ #endif
607
+ #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
608
+ let iridescenceThicknessUv = transformedMaterialUv(
609
+ material.iridescenceThicknessTextureCoordinatesTransform,
610
+ material.iridescenceThicknessTextureCoordinatesMetadata,
611
+ in,
612
+ );
613
+ let iridescenceThicknessSample = sampleMaterialTexture(
614
+ iridescenceThicknessTexture,
615
+ iridescenceThicknessSampler,
616
+ iridescenceThicknessUv,
617
+ material.iridescenceThicknessTextureCoordinatesMetadata.zw,
419
618
  );
619
+ iridescenceThicknessFactor = clamp(iridescenceThicknessSample.g, 0.0, 1.0);
620
+ #endif
621
+ let filmThickness = mix(
622
+ finiteScalar(material.iridescenceThicknessMinimum, 100.0),
623
+ finiteScalar(material.iridescenceThicknessMaximum, 400.0),
624
+ iridescenceThicknessFactor,
625
+ );
626
+ f0 = evaluateIridescenceFresnel(
627
+ f0,
628
+ iridescenceStrength,
629
+ finiteScalar(material.iridescenceIor, 1.3),
630
+ filmThickness,
631
+ );
632
+ #endif
633
+ var diffuseAlbedo = albedo;
634
+ #ifdef TRANSMISSION_AVAILABLE
635
+ let transmissionUv = transformedMaterialUv(
636
+ material.transmissionTextureCoordinatesTransform,
637
+ material.transmissionTextureCoordinatesMetadata,
638
+ in,
639
+ );
640
+ let thicknessUv = transformedMaterialUv(
641
+ material.thicknessTextureCoordinatesTransform,
642
+ material.thicknessTextureCoordinatesMetadata,
643
+ in,
644
+ );
645
+ let transmissionSample = sampleMaterialTexture(
646
+ transmissionTexture,
647
+ transmissionSampler,
648
+ transmissionUv,
649
+ material.transmissionTextureCoordinatesMetadata.zw,
650
+ ).r;
651
+ let thicknessSample = sampleMaterialTexture(
652
+ thicknessTexture,
653
+ thicknessSampler,
654
+ thicknessUv,
655
+ material.thicknessTextureCoordinatesMetadata.zw,
656
+ ).g;
657
+ let transmissionFactor = clamp(
658
+ finiteScalar(material.transmission, 0.0) * finiteScalar(transmissionSample, 1.0),
659
+ 0.0,
660
+ 1.0,
661
+ );
662
+ let viewDot = finiteScalar(dot(n, v), 0.0);
663
+ let refractionFromInside = viewDot < 0.0;
664
+ let refractionNormal = select(n, -n, refractionFromInside);
665
+ let refractionEta = select(1.0 / safeIor, safeIor, refractionFromInside);
666
+ let incident = -v;
667
+ let refracted = refract(incident, refractionNormal, refractionEta);
668
+ let refractedLengthSquared = dot(refracted, refracted);
669
+ let viewCos = clamp(abs(viewDot), 0.0, 1.0);
670
+ let fresnel = clamp(f_schlick(viewCos, vec3<f32>(dielectricF0)).x, 0.0, 1.0);
671
+ let transmittedEnergy = select(
672
+ 0.0,
673
+ transmissionFactor * (1.0 - metallic) * (1.0 - fresnel),
674
+ refractedLengthSquared > 1e-6,
675
+ );
676
+ diffuseAlbedo = albedo * (1.0 - transmittedEnergy);
677
+ #endif
678
+ let kD = (vec3<f32>(1.0) - f_schlick(max(dot(physicalNormal, v), 0.0), f0)) * (1.0 - metallic);
679
+ var coatRoughness = 0.04;
680
+ var coatAlpha = coatRoughness * coatRoughness;
681
+ var coatF = vec3<f32>(0.0);
682
+ #ifdef CLEARCOAT_AVAILABLE
683
+ coatRoughness = max(finiteScalar(material.clearcoatRoughness, 0.04), 0.04);
684
+ coatAlpha = coatRoughness * coatRoughness;
685
+ coatF = f_schlick(max(dot(physicalNormal, v), 0.0), vec3<f32>(0.04)) *
686
+ clamp(finiteScalar(material.clearcoat, 0.0), 0.0, 1.0);
687
+ #endif
688
+ var irradiance = vec3<f32>(0.0);
689
+ var specularIbl = vec3<f32>(0.0);
690
+ if (skylight.intensity < 0.0) {
691
+ specularIbl = sampleReflectionProbeSpecular(
692
+ physicalNormal, v, iblRoughness, f0, in.worldPos,
693
+ vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
694
+ skylight.rotation.xyz, vec4<f32>(0.0, 0.0, 0.0, 1.0),
695
+ prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
696
+ );
697
+ } else {
698
+ irradiance = sampleIblDiffuse(physicalNormal, skylight.rotation, irradianceMap, irradianceSampler);
699
+ specularIbl = sampleIblSpecular(
700
+ physicalNormal, v, iblRoughness, f0,
701
+ skylight.rotation,
702
+ prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
703
+ );
704
+ }
705
+ let ao = surface.occlusion;
420
706
  let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
421
707
  let skyFactor = skyColor * skylight.intensity;
422
- var ambient = (kD * irradiance * albedo + specularIbl) * (vec3<f32>(1.0) - coatF);
708
+ var ambient = (kD * irradiance * diffuseAlbedo + specularIbl) *
709
+ (vec3<f32>(1.0) - coatF);
423
710
  #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
711
  let probeShPreblend = array<vec4<f32>, 9>(
427
712
  probeBlendRecords[1], probeBlendRecords[2], probeBlendRecords[3],
428
713
  probeBlendRecords[4], probeBlendRecords[5], probeBlendRecords[6],
@@ -430,46 +715,278 @@ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
430
715
  );
431
716
  let probeLocalBlendFraction = probeBlendRecords[0].z;
432
717
  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);
718
+ let probeDiffuse = composeProbeDiffuse(probeShPreblend,
719
+ probeLocalBlendFraction,
720
+ physicalNormal,
721
+ irradiance * skyFactor,
722
+ probeDiffuseK,
723
+ diffuseAlbedo,
724
+ metallic,
725
+ );
726
+ ambient = (probeDiffuse + specularIbl * skyFactor) *
727
+ (vec3<f32>(1.0) - coatF);
435
728
  #endif
436
- if (material.clearcoat != 0.0) {
437
- let clearcoatIbl = sampleIblSpecular(
438
- n, v, coatRoughness, vec3<f32>(0.04),
439
- skylight.rotation,
440
- prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
441
- );
442
- ambient = ambient + clearcoatIbl * material.clearcoat * skyFactor;
443
- }
444
729
  #ifdef PROBE_BLEND_AVAILABLE
445
- ambient = ambient;
730
+ ambient = ambient * ao;
446
731
  #else
447
- ambient = ambient * skyFactor;
732
+ if (skylight.intensity < 0.0) {
733
+ let environmentScale = max(-skylight.intensity - 1.0, 0.0) * ao;
734
+ ambient = ambient * environmentScale;
735
+ } else {
736
+ let environmentScale = skyColor * skylight.intensity * ao;
737
+ ambient = ambient * environmentScale;
738
+ }
448
739
  #endif
449
740
  var color = ambient;
450
- let directionalShadow = evalDirectionalShadowFactor(n, in.worldPos, in.viewZ);
451
- let directionalBase = evalDirectionalNoShadow(n, v, albedo, metallic, a, f0);
452
- color = color + directionalShadow * directionalBase;
453
- if (material.clearcoat != 0.0) {
454
- let directionalClearcoat = evalDirectionalNoShadow(
455
- n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
456
- );
457
- color = color + directionalShadow * material.clearcoat * directionalClearcoat;
741
+ #ifdef TRANSMISSION_AVAILABLE
742
+ let screenUv = in.ndc.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
743
+ let localToWorld0 = in.transmissionBasis0.xyz;
744
+ let localToWorld1 = vec3<f32>(
745
+ in.transmissionBasis0.w,
746
+ in.transmissionBasis1.x,
747
+ in.transmissionBasis1.y,
748
+ );
749
+ let localToWorld2 = vec3<f32>(
750
+ in.transmissionBasis1.z,
751
+ in.transmissionBasis1.w,
752
+ in.ndc.w,
753
+ );
754
+ let inverseCofactor0 = cross(localToWorld1, localToWorld2);
755
+ let inverseCofactor1 = cross(localToWorld2, localToWorld0);
756
+ let inverseCofactor2 = cross(localToWorld0, localToWorld1);
757
+ let localToWorldDet = dot(localToWorld0, inverseCofactor0);
758
+ let safeLocalToWorldDet = select(1.0, localToWorldDet, abs(localToWorldDet) >= 1e-6);
759
+ let worldToLocal0 = vec3<f32>(
760
+ inverseCofactor0.x,
761
+ inverseCofactor1.x,
762
+ inverseCofactor2.x,
763
+ ) / safeLocalToWorldDet;
764
+ let worldToLocal1 = vec3<f32>(
765
+ inverseCofactor0.y,
766
+ inverseCofactor1.y,
767
+ inverseCofactor2.y,
768
+ ) / safeLocalToWorldDet;
769
+ let worldToLocal2 = vec3<f32>(
770
+ inverseCofactor0.z,
771
+ inverseCofactor1.z,
772
+ inverseCofactor2.z,
773
+ ) / safeLocalToWorldDet;
774
+ let refractedLocal = normalize(
775
+ worldToLocal0 * refracted.x +
776
+ worldToLocal1 * refracted.y +
777
+ worldToLocal2 * refracted.z,
778
+ );
779
+ let worldRefractedDirection =
780
+ localToWorld0 * refractedLocal.x +
781
+ localToWorld1 * refractedLocal.y +
782
+ localToWorld2 * refractedLocal.z;
783
+ let worldThickness = max(
784
+ finiteScalar(material.thickness, 0.0) * length(worldRefractedDirection) *
785
+ finiteScalar(thicknessSample, 1.0),
786
+ 0.0,
787
+ );
788
+ let refractedUv = screenUv + (refracted.xy - incident.xy) * worldThickness * 0.25;
789
+ let guardBand = 0.02;
790
+ let insideGuardBand = all(refractedUv >= vec2<f32>(guardBand)) &&
791
+ all(refractedUv <= vec2<f32>(1.0 - guardBand));
792
+ let backdropMipCount = textureNumLevels(transmissionBackdropTexture);
793
+ let backdropMaxLod = max(f32(backdropMipCount) - 1.0, 0.0);
794
+ let backdropLod = clamp(iblRoughness * iblRoughness * backdropMaxLod, 0.0, backdropMaxLod);
795
+ let unrefractedBackdrop = textureSampleLevel(
796
+ transmissionBackdropTexture,
797
+ transmissionBackdropSampler,
798
+ clamp(screenUv, vec2<f32>(0.0), vec2<f32>(1.0)),
799
+ backdropLod,
800
+ ).rgb;
801
+ var transmittedBackdrop = unrefractedBackdrop;
802
+ if (refractedLengthSquared > 1e-6 && insideGuardBand) {
803
+ transmittedBackdrop = textureSampleLevel(
804
+ transmissionBackdropTexture,
805
+ transmissionBackdropSampler,
806
+ clamp(refractedUv, vec2<f32>(0.0), vec2<f32>(1.0)),
807
+ backdropLod,
808
+ ).rgb;
458
809
  }
810
+ let safeAttenuationColor = clamp(
811
+ finiteColor(material.attenuationColor, vec3<f32>(1.0)),
812
+ vec3<f32>(0.0),
813
+ vec3<f32>(1.0),
814
+ );
815
+ let safeAttenuationDistance = max(finiteScalar(material.attenuationDistance, 0.0), 0.0);
816
+ let attenuationExponent = worldThickness / max(safeAttenuationDistance, 1e-6);
817
+ let beerAttenuation = select(
818
+ vec3<f32>(1.0),
819
+ pow(safeAttenuationColor, vec3<f32>(attenuationExponent)),
820
+ safeAttenuationDistance > 1e-6 && worldThickness > 0.0,
821
+ );
822
+ color = color + finiteColor(transmittedBackdrop, vec3<f32>(0.0)) *
823
+ transmittedEnergy * beerAttenuation;
824
+ #endif
825
+ color = color + surface.emissive;
826
+ let directionalShadow = evalDirectionalShadowFactor(
827
+ physicalNormal,
828
+ in.worldPos,
829
+ standardViewZ(in),
830
+ );
831
+ let directionalBase = evalDirectionalNoShadow(physicalNormal, v, diffuseAlbedo, metallic, a, f0);
832
+ color = color + directionalShadow * directionalBase;
459
833
  #ifdef CLUSTER_FORWARD_AVAILABLE
460
834
  color = color + evaluateStandardClusterLights(
461
- in.ndc, in.viewZ, in.worldPos, n, v, albedo, metallic, a, f0, false,
835
+ in.ndc.xyz, standardViewZ(in), in.worldPos, physicalNormal, v,
836
+ diffuseAlbedo, metallic, a, f0, false,
837
+ );
838
+ #endif // CLUSTER_FORWARD_AVAILABLE
839
+ #ifdef SHEEN_AVAILABLE
840
+ var sheenColor = material.sheenColor;
841
+ var sheenRoughnessFactor = 1.0;
842
+ #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
843
+ sheenColor = sheenColor * sampleMaterialTexture(
844
+ sheenColorTexture,
845
+ sheenColorSampler,
846
+ transformedMaterialUv(material.sheenColorTextureCoordinatesTransform, material.sheenColorTextureCoordinatesMetadata, in),
847
+ material.sheenColorTextureCoordinatesMetadata.zw,
848
+ ).rgb;
849
+ #endif
850
+ #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
851
+ sheenRoughnessFactor = sampleMaterialTexture(
852
+ sheenRoughnessTexture,
853
+ sheenRoughnessSampler,
854
+ transformedMaterialUv(material.sheenRoughnessTextureCoordinatesTransform, material.sheenRoughnessTextureCoordinatesMetadata, in),
855
+ material.sheenRoughnessTextureCoordinatesMetadata.zw,
856
+ ).a;
857
+ #endif
858
+ let sheenRoughness = clamp(
859
+ material.sheenRoughness * sheenRoughnessFactor,
860
+ 0.0,
861
+ 1.0,
862
+ );
863
+ color = evaluateSheenLayer(color, sheenColor, sheenRoughness, dot(n, v));
864
+ #endif
865
+ #ifdef CLEARCOAT_AVAILABLE
866
+ var clearcoatFactor = clamp(material.clearcoat, 0.0, 1.0);
867
+ #ifdef CLEARCOAT_TEXTURE_AVAILABLE
868
+ let clearcoatUv = transformedMaterialUv(material.clearcoatTextureCoordinatesTransform, material.clearcoatTextureCoordinatesMetadata, in);
869
+ clearcoatFactor = clamp(
870
+ material.clearcoat * sampleMaterialTexture(
871
+ clearcoatTexture,
872
+ clearcoatSampler,
873
+ clearcoatUv,
874
+ material.clearcoatTextureCoordinatesMetadata.zw,
875
+ ).r,
876
+ 0.0,
877
+ 1.0,
878
+ );
879
+ #endif
880
+ var clearcoatRoughnessValue = clamp(max(material.clearcoatRoughness, 0.04), 0.04, 1.0);
881
+ #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
882
+ let clearcoatRoughnessUv = transformedMaterialUv(material.clearcoatRoughnessTextureCoordinatesTransform, material.clearcoatRoughnessTextureCoordinatesMetadata, in);
883
+ clearcoatRoughnessValue = clamp(
884
+ max(material.clearcoatRoughness, 0.04) * sampleMaterialTexture(
885
+ clearcoatRoughnessTexture,
886
+ clearcoatRoughnessSampler,
887
+ clearcoatRoughnessUv,
888
+ material.clearcoatRoughnessTextureCoordinatesMetadata.zw,
889
+ ).g,
890
+ 0.04,
891
+ 1.0,
462
892
  );
463
- if (material.clearcoat != 0.0) {
464
- color = color + material.clearcoat * evaluateStandardClusterLights(
465
- in.ndc, in.viewZ, in.worldPos, n, v,
466
- vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04), false,
893
+ #endif
894
+ var clearcoatNormalValue = in.worldNormal;
895
+ #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
896
+ let clearcoatNormalUv = transformedMaterialUv(material.clearcoatNormalTextureCoordinatesTransform, material.clearcoatNormalTextureCoordinatesMetadata, in);
897
+ clearcoatNormalValue = applyTBN(
898
+ in.worldNormal,
899
+ in.worldTangent,
900
+ scaleTangentSpaceNormal(
901
+ decodeTangentSpaceNormalRg(sampleMaterialTexture(
902
+ clearcoatNormalTexture,
903
+ clearcoatNormalSampler,
904
+ clearcoatNormalUv,
905
+ material.clearcoatNormalTextureCoordinatesMetadata.zw,
906
+ ).rg),
907
+ material.clearcoatNormalScale,
908
+ ),
909
+ );
910
+ #endif
911
+ var clearcoatIbl = vec3<f32>(0.0);
912
+ if (skylight.intensity < 0.0) {
913
+ clearcoatIbl = sampleReflectionProbeSpecular(
914
+ clearcoatNormalValue,
915
+ v,
916
+ clearcoatRoughnessValue,
917
+ vec3<f32>(0.04),
918
+ in.worldPos,
919
+ vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB),
920
+ skylight.rotation.xyz,
921
+ vec4<f32>(0.0, 0.0, 0.0, 1.0),
922
+ prefilterMap,
923
+ prefilterSampler,
924
+ brdfLut,
925
+ brdfLutSampler,
926
+ );
927
+ } else {
928
+ clearcoatIbl = sampleIblSpecular(
929
+ clearcoatNormalValue,
930
+ v,
931
+ clearcoatRoughnessValue,
932
+ vec3<f32>(0.04),
933
+ skylight.rotation,
934
+ prefilterMap,
935
+ prefilterSampler,
936
+ brdfLut,
937
+ brdfLutSampler,
467
938
  );
468
939
  }
940
+ let clearcoatAlpha = clearcoatRoughnessValue * clearcoatRoughnessValue;
941
+ let clearcoatDirect = directionalShadow * evalDirectionalNoShadow(
942
+ clearcoatNormalValue,
943
+ v,
944
+ vec3<f32>(0.0),
945
+ 1.0,
946
+ clearcoatAlpha,
947
+ vec3<f32>(0.04),
948
+ );
949
+ let directionalClearcoat = clearcoatDirect;
950
+ var clearcoatEnvironment = clearcoatIbl * skyColor * skylight.intensity;
951
+ if (skylight.intensity < 0.0) {
952
+ clearcoatEnvironment = clearcoatIbl;
953
+ }
954
+ color = evaluateClearcoatLayer(
955
+ color,
956
+ clearcoatEnvironment + directionalClearcoat,
957
+ dot(clearcoatNormalValue, v),
958
+ clearcoatFactor,
959
+ );
469
960
  #endif
470
961
  return vec4<f32>(color, alpha);
471
962
  }
472
963
 
964
+ // Keep the skinned Standard shader on the same multi-entry pass contract as
965
+ // the rigid Standard shader. Deferred draws select this entry point from the
966
+ // existing `forgeax::pbr-skin` artifact; lighting is performed by the deferred
967
+ // pass after these base surface facts are written to the shared G-buffer.
968
+ struct GBufferOutput {
969
+ @location(0) normal_roughness : vec4<f32>,
970
+ @location(1) albedo_metallic : vec4<f32>,
971
+ @location(2) emissive_ao : vec4<f32>,
972
+ };
973
+
974
+ @fragment
975
+ fn fs_gbuffer(in : VsOut, @builtin(front_facing) frontFacing : bool) -> GBufferOutput {
976
+ let surface = evaluateStandardSurface(in, frontFacing);
977
+ alphaTestSurface(surface);
978
+ let albedo = surface.baseColor;
979
+ let metallic = clamp(surface.metallic, 0.0, 1.0);
980
+ let roughness = clamp(surface.roughness, 0.04, 1.0);
981
+ let n = normalize(surface.normalWS);
982
+
983
+ var out : GBufferOutput;
984
+ out.normal_roughness = vec4<f32>(n * 0.5 + 0.5, roughness);
985
+ out.albedo_metallic = vec4<f32>(albedo, metallic);
986
+ out.emissive_ao = vec4<f32>(surface.emissive, surface.occlusion);
987
+ return out;
988
+ }
989
+
473
990
  struct TemporalVsOut {
474
991
  @builtin(position) clip : vec4<f32>,
475
992
  @location(0) uv : vec2<f32>,
@@ -482,7 +999,6 @@ struct TemporalVsOut {
482
999
  @location(7) uv7 : vec2<f32>,
483
1000
  @location(8) @interpolate(perspective) currentClip : vec4<f32>,
484
1001
  @location(9) @interpolate(perspective) previousClip : vec4<f32>,
485
- @location(10) @interpolate(flat) reactive : f32,
486
1002
  #ifdef VERTEX_COLOR_AVAILABLE
487
1003
  @location(14) color : vec4<f32>,
488
1004
  #endif
@@ -490,25 +1006,20 @@ struct TemporalVsOut {
490
1006
 
491
1007
  @vertex
492
1008
  fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
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;
1009
+ let currentSkin = palette[in.skinIndex.x] * in.skinWeight.x +
1010
+ palette[in.skinIndex.y] * in.skinWeight.y +
1011
+ palette[in.skinIndex.z] * in.skinWeight.z +
1012
+ palette[in.skinIndex.w] * in.skinWeight.w;
1013
+ let previousSkin = previousPalette[in.skinIndex.x] * in.skinWeight.x +
1014
+ previousPalette[in.skinIndex.y] * in.skinWeight.y +
1015
+ previousPalette[in.skinIndex.z] * in.skinWeight.z +
1016
+ previousPalette[in.skinIndex.w] * in.skinWeight.w;
501
1017
  let currentWorld = currentSkin * vec4<f32>(in.pos, 1.0);
502
1018
  let previousWorld = previousSkin * vec4<f32>(in.pos, 1.0);
503
1019
  var out : TemporalVsOut;
504
1020
  out.currentClip = view.temporalCurrentViewProj * currentWorld;
505
1021
  out.clip = out.currentClip;
506
1022
  out.previousClip = view.temporalPreviousViewProj * previousWorld;
507
- #if STORAGE_BUFFER_AVAILABLE == true
508
- out.reactive = meshes[0].temporal.x;
509
- #else
510
- out.reactive = 1.0;
511
- #endif
512
1023
  out.uv = in.uv;
513
1024
  out.uv1 = in.uv1;
514
1025
  out.uv2 = in.uv2;
@@ -534,9 +1045,10 @@ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
534
1045
 
535
1046
  @fragment
536
1047
  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;
1048
+ #if STORAGE_BUFFER_AVAILABLE == true
1049
+ let reactive = meshes[0].temporal.x;
1050
+ #else
1051
+ let reactive = 1.0;
540
1052
  #endif
541
1053
  return projectPbrSceneTemporal(
542
1054
  material.baseColor.a * temporalVertexAlpha(in),
@@ -548,7 +1060,7 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
548
1060
  in.currentClip,
549
1061
  in.previousClip,
550
1062
  view.temporalProjection,
551
- in.reactive,
1063
+ reactive,
552
1064
  in.uv, in.uv1, in.uv2, in.uv3,
553
1065
  in.uv4, in.uv5, in.uv6, in.uv7,
554
1066
  );