@forgeax/engine-shader 0.1.27 → 0.1.29

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 (73) hide show
  1. package/NOTICE +35 -0
  2. package/README.md +39 -0
  3. package/dist/ShaderRegistry.d.ts +8 -0
  4. package/dist/ShaderRegistry.d.ts.map +1 -1
  5. package/dist/index.d.ts +6 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.mjs +312 -71
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/material/artifact-types.d.ts +12 -2
  10. package/dist/material/artifact-types.d.ts.map +1 -1
  11. package/dist/material-schemas.d.ts +5 -0
  12. package/dist/material-schemas.d.ts.map +1 -1
  13. package/package.json +5 -4
  14. package/src/ShaderRegistry.ts +10 -0
  15. package/src/__tests__/auto-exposure-graph.unit.test.ts +59 -0
  16. package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +30 -15
  17. package/src/__tests__/builtin-texture-sampling-contract.test.ts +1 -1
  18. package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +2 -1
  19. package/src/__tests__/default-standard-pbr-transmission.unit.test.ts +167 -6
  20. package/src/__tests__/direct-light-layout.unit.test.ts +1 -1
  21. package/src/__tests__/lighting-punctual.unit.test.ts +29 -1
  22. package/src/__tests__/ltc-provenance.unit.test.ts +13 -2
  23. package/src/__tests__/material-builtins.unit.test.ts +1 -0
  24. package/src/__tests__/material-contract.unit.test.ts +42 -9
  25. package/src/__tests__/material-derived-builtins.integration.test.ts +18 -2
  26. package/src/__tests__/probe-lighting-composition.unit.test.ts +1 -1
  27. package/src/__tests__/reflection-probe-sampling.unit.test.ts +23 -2
  28. package/src/__tests__/scene-temporal.unit.test.ts +10 -0
  29. package/src/__tests__/shader.unit.test.ts +1 -0
  30. package/src/__tests__/sprite-variants.unit.test.ts +1 -0
  31. package/src/__tests__/ssr-artifact.unit.test.ts +141 -0
  32. package/src/__tests__/ssr-bgl.integration.test.ts +185 -0
  33. package/src/__tests__/standard-output-domain.unit.test.ts +36 -0
  34. package/src/__tests__/standard-pbr-artifact-receipt.unit.test.ts +93 -0
  35. package/src/__tests__/standard-surface-pass-evaluation.integration.test.ts +16 -4
  36. package/src/__tests__/surface-v1.unit.test.ts +6 -0
  37. package/src/__tests__/taa-resolve.unit.test.ts +20 -9
  38. package/src/__tests__/transmission-thickness-scale.unit.test.ts +6 -1
  39. package/src/__tests__/transparent-pbr.unit.test.ts +1 -1
  40. package/src/__tests__/vertex-color-variant.unit.test.ts +4 -1
  41. package/src/atmosphere-background.wgsl +3 -3
  42. package/src/auto-exposure-meter.wgsl +179 -0
  43. package/src/color-lut.wgsl +17 -0
  44. package/src/common.wgsl +7 -5
  45. package/src/default-standard-pbr-skin.wgsl +142 -40
  46. package/src/default-standard-pbr.wgsl +198 -82
  47. package/src/default_standard_surface.wgsl +56 -19
  48. package/src/fxaa.wgsl +9 -5
  49. package/src/ibl-sampling.wgsl +30 -6
  50. package/src/index.ts +189 -0
  51. package/src/lighting-punctual.wgsl +5 -6
  52. package/src/material/artifact-types.ts +109 -63
  53. package/src/material-schemas.ts +84 -8
  54. package/src/output-encoding.wgsl +10 -0
  55. package/src/pbr-temporal.wgsl +9 -1
  56. package/src/scene-temporal.wgsl +2 -0
  57. package/src/shadow-pcf.wgsl +6 -8
  58. package/src/shadow-surface.wgsl +16 -0
  59. package/src/shadow_caster.wgsl +99 -61
  60. package/src/sprite-lit.wgsl +4 -4
  61. package/src/sprite.wgsl +3 -3
  62. package/src/ssr-compose.wgsl +129 -0
  63. package/src/ssr-hiz-reduce.wgsl +53 -0
  64. package/src/ssr-hiz.wgsl +66 -0
  65. package/src/ssr-temporal.wgsl +305 -0
  66. package/src/ssr-trace.wgsl +784 -0
  67. package/src/standard-cluster.wgsl +6 -4
  68. package/src/standard-surface.wgsl +696 -0
  69. package/src/surface_v1.wgsl +6 -0
  70. package/src/taa-resolve.wgsl +222 -28
  71. package/src/tbn.wgsl +1 -1
  72. package/src/tonemap.wgsl +37 -18
  73. package/src/unlit.wgsl +3 -3
@@ -1,6 +1,6 @@
1
1
  #define_import_path forgeax_material::standard
2
2
  #pragma material_slot surface
3
- #import forgeax_material::slot::surface::{evaluate_surface}
3
+ #import forgeax_material::slot::surface::{evaluate_surface, evaluate_standard_surface}
4
4
  #import forgeax_material::surface_v1::{SurfaceInput, SurfaceData}
5
5
  #import forgeax_view::common::{View, Mesh, InstanceData, view, meshes, instances, shadowMap, shadowSampler, sampleMaterialTexture}
6
6
  #import forgeax_scene_temporal::{sceneViewZ}
@@ -14,7 +14,7 @@
14
14
  #endif
15
15
  #import forgeax_pbr::temporal::{projectPbrSceneTemporal}
16
16
  #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
17
- #import forgeax_pbr::ibl_sampling::{box_project, sampleIblDiffuse, sampleIblSpecular, sampleReflectionProbeSpecular}
17
+ #import forgeax_pbr::ibl_sampling::{box_project, decodeSpecularEnvironmentScale, sampleIblDiffuse, sampleIblSpecular, sampleReflectionProbeSpecular, projectSpecularRadiance}
18
18
  #import forgeax_pbr::lighting_probe::{evaluateProbeDiffuse}
19
19
  #import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
20
20
  #ifdef CLEARCOAT_AVAILABLE
@@ -42,25 +42,9 @@
42
42
  #pragma variant_axis TRANSMISSION_AVAILABLE
43
43
  #pragma variant_axis DIRECTIONAL_PCSS_AVAILABLE
44
44
  #pragma variant_axis PROJECTOR_AVAILABLE
45
+ #pragma variant_axis GPU_DRIVEN_SCENE_INDEX_AVAILABLE
45
46
  #pragma variant_axis REFLECTION_FALLBACK_AVAILABLE
46
47
 
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
54
- fn reflection_probe_sampling_contract(
55
- worldPosition: vec3<f32>,
56
- direction: vec3<f32>,
57
- boxCenter: vec3<f32>,
58
- boxExtents: vec3<f32>,
59
- ) -> vec3<f32> {
60
- return box_project(worldPosition, direction, boxCenter, boxExtents);
61
- }
62
- #endif
63
-
64
48
  // @forgeax/engine-shader - default-standard-pbr.wgsl
65
49
  // (feat-20260523-shader-template-instance-split M5 / T04).
66
50
  //
@@ -120,7 +104,7 @@ fn reflection_probe_sampling_contract(
120
104
  // plan-strategy D-5). RG-only tangent-space normal: sample.rg encodes
121
105
  // (x,y) of the unit-length tangent normal, z is reconstructed via
122
106
  // z = sqrt(1 - x^2 - y^2). Default 1x1 normal fallback texture is
123
- // RG=(128,128) which decodes to tangent (0,0,1) -- zero perturbation when
107
+ // half-float RG=(0.5,0.5), which decodes exactly to tangent (0,0,1) when
124
108
  // normalTexture is absent (host-side pipelineState.defaultNormalTextureView,
125
109
  // distinct from the white fallback used by baseColor / metallicRoughness
126
110
  // slots so a missing normal does not pollute the white-on-missing semantics
@@ -132,21 +116,35 @@ fn reflection_probe_sampling_contract(
132
116
  // from the root ParamSchema during composition. Keeping the ABI out of this
133
117
  // template prevents a second handwritten interface from drifting from the
134
118
  // runtime UBO writer.
119
+ #ifdef BASE_COLOR_TEXTURE_AVAILABLE
135
120
  @group(1) @binding(1) var baseColorTexture_sampler : sampler;
136
121
  @group(1) @binding(2) var baseColorTexture : texture_2d<f32>;
122
+ #endif
123
+ #ifdef METALLIC_ROUGHNESS_TEXTURE_AVAILABLE
137
124
  @group(1) @binding(3) var metallicRoughnessTexture_sampler : sampler;
138
125
  @group(1) @binding(4) var metallicRoughnessTexture : texture_2d<f32>;
126
+ #endif
127
+ #ifdef NORMAL_TEXTURE_AVAILABLE
139
128
  @group(1) @binding(5) var normalTexture_sampler : sampler;
140
129
  @group(1) @binding(6) var normalTexture : texture_2d<f32>;
130
+ #endif
131
+ #ifdef EMISSIVE_TEXTURE_AVAILABLE
141
132
  @group(1) @binding(9) var emissiveTexture_sampler : sampler;
142
133
  @group(1) @binding(10) var emissiveTexture : texture_2d<f32>;
134
+ #endif
135
+ #ifdef OCCLUSION_TEXTURE_AVAILABLE
143
136
  @group(1) @binding(11) var occlusionTexture_sampler : sampler;
144
137
  @group(1) @binding(12) var occlusionTexture : texture_2d<f32>;
138
+ #endif
145
139
  #ifdef TRANSMISSION_AVAILABLE
140
+ #ifdef TRANSMISSION_TEXTURE_AVAILABLE
146
141
  @group(1) @binding(13) var transmissionSampler : sampler;
147
142
  @group(1) @binding(14) var transmissionTexture : texture_2d<f32>;
143
+ #endif
144
+ #ifdef THICKNESS_TEXTURE_AVAILABLE
148
145
  @group(1) @binding(15) var thicknessSampler : sampler;
149
146
  @group(1) @binding(16) var thicknessTexture : texture_2d<f32>;
147
+ #endif
150
148
  @group(1) @binding(24) var transmissionBackdropSampler : sampler;
151
149
  @group(1) @binding(25) var transmissionBackdropTexture : texture_2d<f32>;
152
150
  #endif
@@ -195,11 +193,21 @@ fn reflection_probe_sampling_contract(
195
193
  // helper. These compile-time-only witnesses retain the binding contract while
196
194
  // every runtime material sample still goes through sampleMaterialTexture.
197
195
  fn materialTextureFilteringWitness() {
196
+ #ifdef BASE_COLOR_TEXTURE_AVAILABLE
198
197
  let base = baseColorTexture;
198
+ #endif
199
+ #ifdef METALLIC_ROUGHNESS_TEXTURE_AVAILABLE
199
200
  let metallicRoughness = metallicRoughnessTexture;
201
+ #endif
202
+ #ifdef NORMAL_TEXTURE_AVAILABLE
200
203
  let normal = normalTexture;
204
+ #endif
205
+ #ifdef EMISSIVE_TEXTURE_AVAILABLE
201
206
  let emissive = emissiveTexture;
207
+ #endif
208
+ #ifdef OCCLUSION_TEXTURE_AVAILABLE
202
209
  let occlusion = occlusionTexture;
210
+ #endif
203
211
  #ifdef CLEARCOAT_TEXTURE_AVAILABLE
204
212
  let clearcoat = clearcoatTexture;
205
213
  #endif
@@ -209,11 +217,21 @@ fn materialTextureFilteringWitness() {
209
217
  #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
210
218
  let clearcoatNormal = clearcoatNormalTexture;
211
219
  #endif
220
+ #ifdef BASE_COLOR_TEXTURE_AVAILABLE
212
221
  let baseWitness = textureSample(base, baseColorTexture_sampler, vec2<f32>(0.0));
222
+ #endif
223
+ #ifdef METALLIC_ROUGHNESS_TEXTURE_AVAILABLE
213
224
  let metallicRoughnessWitness = textureSample(metallicRoughness, metallicRoughnessTexture_sampler, vec2<f32>(0.0));
225
+ #endif
226
+ #ifdef NORMAL_TEXTURE_AVAILABLE
214
227
  let normalWitness = textureSample(normal, normalTexture_sampler, vec2<f32>(0.0));
228
+ #endif
229
+ #ifdef EMISSIVE_TEXTURE_AVAILABLE
215
230
  let emissiveWitness = textureSample(emissive, emissiveTexture_sampler, vec2<f32>(0.0));
231
+ #endif
232
+ #ifdef OCCLUSION_TEXTURE_AVAILABLE
216
233
  let occlusionWitness = textureSample(occlusion, occlusionTexture_sampler, vec2<f32>(0.0));
234
+ #endif
217
235
  #ifdef CLEARCOAT_TEXTURE_AVAILABLE
218
236
  let clearcoatWitness = textureSample(clearcoat, clearcoatSampler, vec2<f32>(0.0));
219
237
  #endif
@@ -245,11 +263,19 @@ fn materialTextureFilteringWitness() {
245
263
  let specularColorWitness = textureSample(specularColorTexture, specularColorTextureSampler, vec2<f32>(0.0));
246
264
  #endif
247
265
  #ifdef TRANSMISSION_AVAILABLE
266
+ #ifdef TRANSMISSION_TEXTURE_AVAILABLE
248
267
  let transmission = transmissionTexture;
268
+ #endif
269
+ #ifdef THICKNESS_TEXTURE_AVAILABLE
249
270
  let thickness = thicknessTexture;
271
+ #endif
272
+ #ifdef TRANSMISSION_TEXTURE_AVAILABLE
250
273
  let transmissionWitness = textureSample(transmission, transmissionSampler, vec2<f32>(0.0));
274
+ #endif
275
+ #ifdef THICKNESS_TEXTURE_AVAILABLE
251
276
  let thicknessWitness = textureSample(thickness, thicknessSampler, vec2<f32>(0.0));
252
277
  #endif
278
+ #endif
253
279
  }
254
280
 
255
281
  // Skylight bindings merged into the PBR material BGL
@@ -292,6 +318,14 @@ struct SkylightUniforms {
292
318
  @group(3) @binding(1) var<storage, read> probeBlendRecords : array<vec4<f32>>;
293
319
  #endif
294
320
 
321
+ #ifdef GPU_DRIVEN_SCENE_INDEX_AVAILABLE
322
+ // GPU-driven production binds the producer-owned Standard material table after
323
+ // the fixed physical texture range. The visible stream carries the projected
324
+ // mesh row and material row selected by the compute cull.
325
+ @group(1) @binding(46) var<storage, read> sceneMaterials : array<MaterialParameters>;
326
+ @group(3) @binding(2) var<storage, read> visibleItems : array<vec2<u32>>;
327
+ #endif
328
+
295
329
  // feat-20260612-hdrp-ssao M7 (round 2) D-B + D-C, scope-amend-webgl2-ubo:
296
330
  // SSAO sampling lives on the HDRP unified BGL @group(2) alongside the
297
331
  // cluster bindings (binding 7 = ssao texture, binding 8 = sampler). The
@@ -326,7 +360,7 @@ struct VsIn {
326
360
  #endif
327
361
  };
328
362
  struct VsOut {
329
- @builtin(position) clip : vec4<f32>,
363
+ @builtin(position) @invariant clip : vec4<f32>,
330
364
  @location(0) worldPos : vec3<f32>,
331
365
  @location(1) worldNormal : vec3<f32>,
332
366
  @location(2) uv : vec2<f32>,
@@ -367,6 +401,10 @@ struct VsOut {
367
401
  #ifdef TRANSMISSION_AVAILABLE
368
402
  @location(7) viewZ : f32,
369
403
  @location(13) @interpolate(flat) transmissionBasis1 : vec4<f32>,
404
+ #else
405
+ #if GPU_DRIVEN_SCENE_INDEX_AVAILABLE == true
406
+ @location(15) @interpolate(flat) materialIndex : u32,
407
+ #endif
370
408
  #endif
371
409
  };
372
410
 
@@ -380,7 +418,7 @@ struct VsOut {
380
418
  // Charter P4: spot light is a thin cone-multiplier on top of the punctual
381
419
  // body, point light is the body unchanged -- no magic-value collapse.
382
420
 
383
- fn vs_main_impl(in : VsIn, meshIndex : u32, instanceIndex : u32) -> VsOut {
421
+ fn vs_main_impl(in : VsIn, meshIndex : u32, instanceIndex : u32, materialIndex : u32) -> VsOut {
384
422
  // feat-20260604-instances-per-instance-transform-shader-group3-bin M1 / w4:
385
423
  // Entity world from meshes[0] — the @group(2) dynamic-offset window has
386
424
  // already been aimed at this entity's slot (render-system-record.ts:2996
@@ -443,6 +481,11 @@ fn vs_main_impl(in : VsIn, meshIndex : u32, instanceIndex : u32) -> VsOut {
443
481
  localToWorld[2].x,
444
482
  localToWorld[2].y,
445
483
  );
484
+ #endif
485
+ #if GPU_DRIVEN_SCENE_INDEX_AVAILABLE == true
486
+ #if TRANSMISSION_AVAILABLE == false
487
+ out.materialIndex = materialIndex;
488
+ #endif
446
489
  #endif
447
490
  // feat-20260613-csm-cascaded-shadow-maps M5 / w19: the per-fragment
448
491
  // light-space position varying is gone; evalDirectional computes
@@ -470,17 +513,27 @@ fn standardViewZ(in : VsOut) -> f32 {
470
513
 
471
514
  @vertex
472
515
  fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
473
- return vs_main_impl(in, 0u, idx);
516
+ return vs_main_impl(in, 0u, idx, 0xffffffffu);
474
517
  }
475
518
 
519
+ #ifdef GPU_DRIVEN_SCENE_INDEX_AVAILABLE
476
520
  @vertex
477
521
  fn vs_scene_index(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
478
522
  // 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);
523
+ // carries the material row in the second visible-item lane.
524
+ let visible = visibleItems[idx];
525
+ return vs_main_impl(in, visible.x, 0u, visible.y);
526
+ }
527
+ #endif
528
+
529
+ #ifdef GPU_DRIVEN_SCENE_INDEX_AVAILABLE
530
+ fn selectedMaterial(index : u32) -> MaterialParameters {
531
+ return sceneMaterials[index];
482
532
  }
483
533
 
534
+
535
+ #endif
536
+
484
537
  fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
485
538
  var source = in.uv;
486
539
  if (metadata.x >= 1.0) { source = in.uv1; }
@@ -505,6 +558,7 @@ fn materialVertexColor(in : VsOut) -> vec4<f32> {
505
558
  #endif
506
559
  }
507
560
 
561
+
508
562
  // Keep the cooked artifact content-addressable per declared capability set.
509
563
  // Some capability axes only change an imported helper's resource surface; the
510
564
  // witness makes that distinction explicit in the composed source as well, so
@@ -550,17 +604,20 @@ fn evaluateStandardSurface(in : VsOut, frontFacing : bool) -> SurfaceData {
550
604
  #else
551
605
  let positionOS = in.positionOSAndViewZ.xyz;
552
606
  #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
- ));
607
+ let input = SurfaceInput(
608
+ positionOS, in.worldPos, in.worldNormal, in.worldTangent, viewDirectionWS,
609
+ in.uv, in.uv1, in.uv2, in.uv3, in.uv4, in.uv5,
610
+ in.uv6And7.xy, in.uv6And7.zw, materialVertexColor(in), frontFacing,
611
+ );
612
+ #if GPU_DRIVEN_SCENE_INDEX_AVAILABLE == true
613
+ #if TRANSMISSION_AVAILABLE == false
614
+ return evaluate_standard_surface(input, selectedMaterial(in.materialIndex));
615
+ #else
616
+ return evaluate_surface(input);
617
+ #endif
618
+ #else
619
+ return evaluate_surface(input);
620
+ #endif
564
621
  }
565
622
 
566
623
  // The renderer supplies the retained ProbeBlendRecord at the per-object
@@ -620,21 +677,12 @@ struct StandardPbrOutput {
620
677
  #endif
621
678
  };
622
679
 
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;
680
+ fn standardSurfaceF0(in : VsOut, surface : SurfaceData) -> vec3<f32> {
629
681
  let albedo = surface.baseColor;
630
682
  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);
634
-
635
- let v = normalize(view.cameraPos - in.worldPos);
636
683
  var specularColor = material.specularColor;
637
684
  #ifdef SPECULAR_COLOR_TEXTURE_AVAILABLE
685
+ if (standardUsesSpecularColorTexture()) {
638
686
  let specularUv = transformedMaterialUv(
639
687
  material.specularColorTextureCoordinatesTransform,
640
688
  material.specularColorTextureCoordinatesMetadata,
@@ -646,9 +694,11 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
646
694
  specularUv,
647
695
  material.specularColorTextureCoordinatesMetadata.zw,
648
696
  ).rgb;
697
+ }
649
698
  #endif
650
699
  var specularWeight = clamp(finiteScalar(material.specular, 1.0), 0.0, 1.0);
651
700
  #ifdef SPECULAR_TEXTURE_AVAILABLE
701
+ if (standardUsesSpecularTexture()) {
652
702
  let specularWeightUv = transformedMaterialUv(
653
703
  material.specularTextureCoordinatesTransform,
654
704
  material.specularTextureCoordinatesMetadata,
@@ -660,16 +710,57 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
660
710
  specularWeightUv,
661
711
  material.specularTextureCoordinatesMetadata.zw,
662
712
  ).a;
713
+ }
714
+ #endif
715
+ let safeIor = max(finiteScalar(material.ior, 1.5), 1.0);
716
+ let dielectricF0 = pow((safeIor - 1.0) / (safeIor + 1.0), 2.0);
717
+ return mix(vec3<f32>(dielectricF0) * specularColor * specularWeight, albedo, metallic);
718
+ }
719
+
720
+ // The lighting-owned fallback alpha carries this same material admission to
721
+ // the trace. Unsupported lobes neither receive nor contribute screen radiance.
722
+ fn standardSsrCoverage() -> f32 {
723
+ var coverage = 1.0;
724
+ #ifdef CLEARCOAT_AVAILABLE
725
+ coverage = 0.0;
726
+ #endif
727
+ #ifdef ANISOTROPY_AVAILABLE
728
+ coverage = 0.0;
663
729
  #endif
730
+ #ifdef IRIDESCENCE_AVAILABLE
731
+ coverage = 0.0;
732
+ #endif
733
+ return coverage;
734
+ }
735
+
736
+ @fragment
737
+ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbrOutput {
738
+ let _variantIdentity = standardVariantIdentity();
739
+ #if GPU_DRIVEN_SCENE_INDEX_AVAILABLE == true
740
+ #if TRANSMISSION_AVAILABLE == false
741
+ let material = selectedMaterial(in.materialIndex);
742
+ #endif
743
+ #endif
744
+ let surface = evaluateStandardSurface(in, frontFacing);
745
+ alphaTestSurface(surface);
746
+ let alpha = surface.opacity;
747
+ let albedo = surface.baseColor;
748
+ let metallic = clamp(finiteScalar(surface.metallic, 0.0), 0.0, 1.0);
749
+ let iblRoughness = clamp(finiteScalar(surface.roughness, 0.5), 0.04, 1.0);
750
+ let a = iblRoughness * iblRoughness;
751
+ let n = normalize(surface.normalWS);
752
+
753
+ let v = normalize(view.cameraPos - in.worldPos);
664
754
  let safeIor = max(finiteScalar(material.ior, 1.5), 1.0);
665
755
  let dielectricF0 = pow((safeIor - 1.0) / (safeIor + 1.0), 2.0);
666
- var f0 = mix(vec3<f32>(dielectricF0) * specularColor * specularWeight, albedo, metallic);
756
+ var f0 = standardSurfaceF0(in, surface);
667
757
  var physicalNormal = n;
668
758
  #ifdef ANISOTROPY_AVAILABLE
669
759
  var anisotropyStrength = finiteScalar(material.anisotropyStrength, 0.0);
670
760
  var anisotropyRotation = finiteScalar(material.anisotropyRotation, 0.0);
671
761
  var anisotropyDirection = vec2<f32>(1.0, 0.0);
672
762
  #ifdef ANISOTROPY_TEXTURE_AVAILABLE
763
+ if (standardUsesAnisotropyTexture()) {
673
764
  let anisotropyUv = transformedMaterialUv(
674
765
  material.anisotropyTextureCoordinatesTransform,
675
766
  material.anisotropyTextureCoordinatesMetadata,
@@ -686,6 +777,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
686
777
  anisotropyDirection = normalize(encodedAnisotropyDirection);
687
778
  }
688
779
  anisotropyStrength = anisotropyStrength * anisotropySample.b;
780
+ }
689
781
  #endif
690
782
  physicalNormal = evaluateAnisotropicNormal(
691
783
  n,
@@ -699,6 +791,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
699
791
  var iridescenceStrength = finiteScalar(material.iridescence, 0.0);
700
792
  var iridescenceThicknessFactor = 1.0;
701
793
  #ifdef IRIDESCENCE_TEXTURE_AVAILABLE
794
+ if (standardUsesIridescenceTexture()) {
702
795
  let iridescenceUv = transformedMaterialUv(
703
796
  material.iridescenceTextureCoordinatesTransform,
704
797
  material.iridescenceTextureCoordinatesMetadata,
@@ -711,8 +804,10 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
711
804
  material.iridescenceTextureCoordinatesMetadata.zw,
712
805
  );
713
806
  iridescenceStrength = iridescenceStrength * iridescenceSample.r;
807
+ }
714
808
  #endif
715
809
  #ifdef IRIDESCENCE_THICKNESS_TEXTURE_AVAILABLE
810
+ if (standardUsesIridescenceThicknessTexture()) {
716
811
  let iridescenceThicknessUv = transformedMaterialUv(
717
812
  material.iridescenceThicknessTextureCoordinatesTransform,
718
813
  material.iridescenceThicknessTextureCoordinatesMetadata,
@@ -725,6 +820,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
725
820
  material.iridescenceThicknessTextureCoordinatesMetadata.zw,
726
821
  );
727
822
  iridescenceThicknessFactor = clamp(iridescenceThicknessSample.g, 0.0, 1.0);
823
+ }
728
824
  #endif
729
825
  let filmThickness = mix(
730
826
  finiteScalar(material.iridescenceThicknessMinimum, 100.0),
@@ -740,28 +836,30 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
740
836
  #endif
741
837
  var diffuseAlbedo = albedo;
742
838
  #ifdef TRANSMISSION_AVAILABLE
743
- let transmissionUv = transformedMaterialUv(
744
- material.transmissionTextureCoordinatesTransform,
745
- material.transmissionTextureCoordinatesMetadata,
746
- in,
747
- );
748
- let thicknessUv = transformedMaterialUv(
749
- material.thicknessTextureCoordinatesTransform,
750
- material.thicknessTextureCoordinatesMetadata,
751
- in,
752
- );
753
- let transmissionSample = sampleMaterialTexture(
839
+ var transmissionSample = 1.0;
840
+ #ifdef TRANSMISSION_TEXTURE_AVAILABLE
841
+ if (standardUsesTransmissionTexture()) {
842
+ let transmissionUv = transformedMaterialUv(material.transmissionTextureCoordinatesTransform, material.transmissionTextureCoordinatesMetadata, in);
843
+ transmissionSample = sampleMaterialTexture(
754
844
  transmissionTexture,
755
845
  transmissionSampler,
756
846
  transmissionUv,
757
847
  material.transmissionTextureCoordinatesMetadata.zw,
758
848
  ).r;
759
- let thicknessSample = sampleMaterialTexture(
849
+ }
850
+ #endif
851
+ var thicknessSample = 1.0;
852
+ #ifdef THICKNESS_TEXTURE_AVAILABLE
853
+ if (standardUsesThicknessTexture()) {
854
+ let thicknessUv = transformedMaterialUv(material.thicknessTextureCoordinatesTransform, material.thicknessTextureCoordinatesMetadata, in);
855
+ thicknessSample = sampleMaterialTexture(
760
856
  thicknessTexture,
761
857
  thicknessSampler,
762
858
  thicknessUv,
763
859
  material.thicknessTextureCoordinatesMetadata.zw,
764
860
  ).g;
861
+ }
862
+ #endif
765
863
  let transmissionFactor = clamp(
766
864
  finiteScalar(material.transmission, 0.0) * finiteScalar(transmissionSample, 1.0),
767
865
  0.0,
@@ -851,6 +949,12 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
851
949
  // factor. The non-HDRP path leaves ambient untouched.
852
950
  let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
853
951
  let skyFactor = skyColor * skylight.intensity;
952
+ // ReflectionProbe sentinel metadata is decoded by the shared IBL owner;
953
+ // both ambient specular and the detached SSR fallback use the same scale.
954
+ let specularEnvironmentScale = decodeSpecularEnvironmentScale(
955
+ skyColor,
956
+ skylight.intensity,
957
+ );
854
958
  var reflectionFallback = specularIbl * (vec3<f32>(1.0) - coatF);
855
959
  var ambient = (kD * irradiance * diffuseAlbedo + specularIbl) *
856
960
  (vec3<f32>(1.0) - coatF);
@@ -872,25 +976,20 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
872
976
  diffuseAlbedo,
873
977
  metallic,
874
978
  );
875
- ambient = (probeDiffuse + specularIbl * skyFactor) *
979
+ ambient = (probeDiffuse + specularIbl * specularEnvironmentScale) *
876
980
  (vec3<f32>(1.0) - coatF);
877
981
  #endif
878
982
  #ifdef PROBE_BLEND_AVAILABLE
879
983
  ambient = ambient * ao;
984
+ reflectionFallback = reflectionFallback * specularEnvironmentScale * ao;
880
985
  #else
881
986
  // In the regular Skylight path tint/intensity are applied after sampling.
882
987
  // In the probe-sentinel path intensity is encoded as -(probeIntensity + 1),
883
988
  // so decode only the non-negative payload and keep zero-intensity probes
884
989
  // distinct from an absent Skylight.
885
- if (skylight.intensity < 0.0) {
886
- let environmentScale = max(-skylight.intensity - 1.0, 0.0) * ao;
887
- ambient = ambient * environmentScale;
888
- reflectionFallback = reflectionFallback * environmentScale;
889
- } else {
890
- let environmentScale = skyColor * skylight.intensity * ao;
891
- ambient = ambient * environmentScale;
892
- reflectionFallback = reflectionFallback * environmentScale;
893
- }
990
+ let environmentScale = specularEnvironmentScale * ao;
991
+ ambient = ambient * environmentScale;
992
+ reflectionFallback = reflectionFallback * environmentScale;
894
993
  #endif
895
994
  #ifdef CLUSTER_FORWARD_AVAILABLE
896
995
  // feat-20260612-hdrp-ssao M2 round-1 + M7 round-2 (plan-strategy D-7 + D-B + D-C):
@@ -1011,7 +1110,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1011
1110
  // view_z: NDC depth for cluster Z-slice lookup.
1012
1111
  color = color + evaluateStandardClusterLights(
1013
1112
  in.ndc.xyz, standardViewZ(in), in.worldPos, physicalNormal, v,
1014
- diffuseAlbedo, metallic, a, f0, false,
1113
+ diffuseAlbedo, metallic, a, f0, false, true,
1015
1114
  );
1016
1115
  #endif // CLUSTER_FORWARD_AVAILABLE
1017
1116
  // Emissive is part of the lower-energy stack and is attenuated by the
@@ -1021,6 +1120,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1021
1120
  var sheenColor = material.sheenColor;
1022
1121
  var sheenRoughnessFactor = 1.0;
1023
1122
  #ifdef SHEEN_COLOR_TEXTURE_AVAILABLE
1123
+ if (standardUsesSheenColorTexture()) {
1024
1124
  let sheenColorUv = transformedMaterialUv(
1025
1125
  material.sheenColorTextureCoordinatesTransform,
1026
1126
  material.sheenColorTextureCoordinatesMetadata,
@@ -1032,8 +1132,10 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1032
1132
  sheenColorUv,
1033
1133
  material.sheenColorTextureCoordinatesMetadata.zw,
1034
1134
  ).rgb;
1135
+ }
1035
1136
  #endif
1036
1137
  #ifdef SHEEN_ROUGHNESS_TEXTURE_AVAILABLE
1138
+ if (standardUsesSheenRoughnessTexture()) {
1037
1139
  let sheenRoughnessUv = transformedMaterialUv(
1038
1140
  material.sheenRoughnessTextureCoordinatesTransform,
1039
1141
  material.sheenRoughnessTextureCoordinatesMetadata,
@@ -1045,6 +1147,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1045
1147
  sheenRoughnessUv,
1046
1148
  material.sheenRoughnessTextureCoordinatesMetadata.zw,
1047
1149
  ).a;
1150
+ }
1048
1151
  #endif
1049
1152
  let sheenRoughness = clamp(
1050
1153
  finiteScalar(material.sheenRoughness, 0.0) * sheenRoughnessFactor,
@@ -1056,6 +1159,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1056
1159
  #ifdef CLEARCOAT_AVAILABLE
1057
1160
  var clearcoatFactor = clamp(material.clearcoat, 0.0, 1.0);
1058
1161
  #ifdef CLEARCOAT_TEXTURE_AVAILABLE
1162
+ if (standardUsesClearcoatTexture()) {
1059
1163
  let clearcoatUv = transformedMaterialUv(material.clearcoatTextureCoordinatesTransform, material.clearcoatTextureCoordinatesMetadata, in);
1060
1164
  clearcoatFactor = clamp(
1061
1165
  material.clearcoat * sampleMaterialTexture(
@@ -1067,9 +1171,11 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1067
1171
  0.0,
1068
1172
  1.0,
1069
1173
  );
1174
+ }
1070
1175
  #endif
1071
1176
  var clearcoatRoughnessValue = clamp(max(material.clearcoatRoughness, 0.04), 0.04, 1.0);
1072
1177
  #ifdef CLEARCOAT_ROUGHNESS_TEXTURE_AVAILABLE
1178
+ if (standardUsesClearcoatRoughnessTexture()) {
1073
1179
  let clearcoatRoughnessUv = transformedMaterialUv(material.clearcoatRoughnessTextureCoordinatesTransform, material.clearcoatRoughnessTextureCoordinatesMetadata, in);
1074
1180
  clearcoatRoughnessValue = clamp(
1075
1181
  max(material.clearcoatRoughness, 0.04) * sampleMaterialTexture(
@@ -1081,9 +1187,11 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1081
1187
  0.04,
1082
1188
  1.0,
1083
1189
  );
1190
+ }
1084
1191
  #endif
1085
1192
  var clearcoatNormalValue = in.worldNormal;
1086
1193
  #ifdef CLEARCOAT_NORMAL_TEXTURE_AVAILABLE
1194
+ if (standardUsesClearcoatNormalTexture()) {
1087
1195
  let clearcoatNormalUv = transformedMaterialUv(material.clearcoatNormalTextureCoordinatesTransform, material.clearcoatNormalTextureCoordinatesMetadata, in);
1088
1196
  clearcoatNormalValue = applyTBN(
1089
1197
  in.worldNormal,
@@ -1098,6 +1206,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1098
1206
  material.clearcoatNormalScale,
1099
1207
  ),
1100
1208
  );
1209
+ }
1101
1210
  #endif
1102
1211
  var clearcoatIbl = vec3<f32>(0.0);
1103
1212
  if (skylight.intensity < 0.0) {
@@ -1154,7 +1263,7 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1154
1263
  var output : StandardPbrOutput;
1155
1264
  output.color = vec4<f32>(color, alpha);
1156
1265
  #ifdef REFLECTION_FALLBACK_AVAILABLE
1157
- output.reflectionFallback = vec4<f32>(reflectionFallback, 1.0);
1266
+ output.reflectionFallback = vec4<f32>(reflectionFallback, standardSsrCoverage());
1158
1267
  #endif
1159
1268
  return output;
1160
1269
  }
@@ -1168,18 +1277,17 @@ fn fs_main(in : VsOut, @builtin(front_facing) frontFacing : bool) -> StandardPbr
1168
1277
  // D-2 / requirements §3.2 g-buffer schema:
1169
1278
  // @location(0) RT0 = normal.rgb + roughness.a → rgba16f
1170
1279
  // @location(1) RT1 = albedo.rgb + metallic.a → rgba8unorm
1171
- // @location(2) RT2 = emissive.rgb + ao.a → rgba16f
1280
+ // @location(2) RT2 = material-owned specular response.rgb + ao.a → rgba16f
1172
1281
 
1173
1282
  struct GBufferOutput {
1174
1283
  @location(0) normal_roughness : vec4<f32>,
1175
1284
  @location(1) albedo_metallic : vec4<f32>,
1176
- @location(2) emissive_ao : vec4<f32>,
1285
+ @location(2) specular_response_ao : vec4<f32>,
1177
1286
  };
1178
1287
 
1179
- /// Deferred g-buffer fragment entry: writes material properties (normal,
1180
- /// albedo, roughness, metallic, emissive, ao) to a 3-RT g-buffer for the
1181
- /// deferred lighting pass to decode. Lighting evaluation is deferred — this
1182
- /// entry does NOT compute GGX / directional / cluster lights.
1288
+ /// Material g-buffer entry: writes normal/roughness, albedo/metallic and the
1289
+ /// material-owned unit-radiance specular response/AO. Direct lighting and
1290
+ /// emissive color remain in the same material's forward lighting entry.
1183
1291
  ///
1184
1292
  /// Shares the same vertex shader `vs_main` and the same material UBO / texture
1185
1293
  /// bindings as `fs_main`; only the fragment output differs. The HDRP
@@ -1197,12 +1305,17 @@ fn fs_gbuffer(in : VsOut, @builtin(front_facing) frontFacing : bool) -> GBufferO
1197
1305
  var out : GBufferOutput;
1198
1306
  out.normal_roughness = vec4<f32>(n * 0.5 + 0.5, roughness);
1199
1307
  out.albedo_metallic = vec4<f32>(albedo, metallic);
1200
- out.emissive_ao = vec4<f32>(surface.emissive, surface.occlusion);
1308
+ // Preserve the material's own IOR/specular maps and BRDF projection. SSR
1309
+ // applies this response once per visible pixel without re-drawing geometry.
1310
+ let response = projectSpecularRadiance(vec3<f32>(1.0), n,
1311
+ normalize(view.cameraPos - in.worldPos), roughness,
1312
+ standardSurfaceF0(in, surface), brdfLut, brdfLutSampler) * surface.occlusion;
1313
+ out.specular_response_ao = vec4<f32>(response, surface.occlusion);
1201
1314
  return out;
1202
1315
  }
1203
1316
 
1204
1317
  struct TemporalVsOut {
1205
- @builtin(position) clip : vec4<f32>,
1318
+ @builtin(position) @invariant clip : vec4<f32>,
1206
1319
  @location(0) uv : vec2<f32>,
1207
1320
  @location(1) uv1 : vec2<f32>,
1208
1321
  @location(2) uv2 : vec2<f32>,
@@ -1229,7 +1342,7 @@ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
1229
1342
  #endif
1230
1343
  var out : TemporalVsOut;
1231
1344
  out.currentClip = view.temporalCurrentViewProj * currentWorld;
1232
- out.clip = out.currentClip;
1345
+ out.clip = view.worldViewProj * currentWorld;
1233
1346
  out.previousClip = view.temporalPreviousViewProj * previousWorld;
1234
1347
  out.uv = in.uv;
1235
1348
  out.uv1 = in.uv1;
@@ -1262,10 +1375,13 @@ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
1262
1375
  return projectPbrSceneTemporal(
1263
1376
  material.baseColor.a * temporalVertexAlpha(in),
1264
1377
  material.alphaCutoff,
1378
+ #ifdef BASE_COLOR_TEXTURE_AVAILABLE
1379
+ standardUsesBaseColorTexture(),
1265
1380
  baseColorTexture,
1266
1381
  baseColorTexture_sampler,
1267
1382
  material.baseColorTextureCoordinatesTransform,
1268
1383
  material.baseColorTextureCoordinatesMetadata,
1384
+ #endif
1269
1385
  in.currentClip,
1270
1386
  in.previousClip,
1271
1387
  view.temporalProjection,