@forgeax/engine-shader 0.1.2

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 (162) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +99 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/ShaderRegistry.d.ts +208 -0
  5. package/dist/ShaderRegistry.d.ts.map +1 -0
  6. package/dist/__tests__/atmosphere-background.test.d.ts +2 -0
  7. package/dist/__tests__/atmosphere-background.test.d.ts.map +1 -0
  8. package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +2 -0
  9. package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +1 -0
  10. package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +2 -0
  11. package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +1 -0
  12. package/dist/__tests__/atmosphere-preetham-red.test.d.ts +2 -0
  13. package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +1 -0
  14. package/dist/__tests__/atmosphere-preetham.test.d.ts +2 -0
  15. package/dist/__tests__/atmosphere-preetham.test.d.ts.map +1 -0
  16. package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +2 -0
  17. package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +1 -0
  18. package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +2 -0
  19. package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +2 -0
  21. package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +1 -0
  22. package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +2 -0
  23. package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/deferred-lighting-ssao.test.d.ts +2 -0
  25. package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +1 -0
  26. package/dist/__tests__/fog-oracle.unit.test.d.ts +2 -0
  27. package/dist/__tests__/fog-oracle.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +2 -0
  29. package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +2 -0
  31. package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +1 -0
  32. package/dist/__tests__/hdrp-ssao-compile.test.d.ts +2 -0
  33. package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +1 -0
  34. package/dist/__tests__/ibl-irradiance.unit.test.d.ts +2 -0
  35. package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/ibl-sampling.unit.test.d.ts +2 -0
  37. package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/lighting-punctual.unit.test.d.ts +2 -0
  39. package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +2 -0
  41. package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/material-builtins.unit.test.d.ts +2 -0
  43. package/dist/__tests__/material-builtins.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/material-contract.unit.test.d.ts +2 -0
  45. package/dist/__tests__/material-contract.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/material-derived-builtins.integration.test.d.ts +2 -0
  47. package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +1 -0
  48. package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
  49. package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/scene-temporal.unit.test.d.ts +2 -0
  51. package/dist/__tests__/scene-temporal.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +2 -0
  53. package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/shader.unit.test.d.ts +2 -0
  55. package/dist/__tests__/shader.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/sprite-lit-shader.test.d.ts +2 -0
  57. package/dist/__tests__/sprite-lit-shader.test.d.ts.map +1 -0
  58. package/dist/__tests__/sprite-variants.unit.test.d.ts +2 -0
  59. package/dist/__tests__/sprite-variants.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/taa-resolve.unit.test.d.ts +2 -0
  61. package/dist/__tests__/taa-resolve.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/transparent-pbr.unit.test.d.ts +2 -0
  63. package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/vertex-color-variant.unit.test.d.ts +2 -0
  65. package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +1 -0
  66. package/dist/errors.d.ts +96 -0
  67. package/dist/errors.d.ts.map +1 -0
  68. package/dist/index.d.ts +44 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.mjs +560 -0
  71. package/dist/index.mjs.map +1 -0
  72. package/dist/material/artifact-registry.d.ts +31 -0
  73. package/dist/material/artifact-registry.d.ts.map +1 -0
  74. package/dist/material/artifact-types.d.ts +13 -0
  75. package/dist/material/artifact-types.d.ts.map +1 -0
  76. package/dist/material-schemas.d.ts +8 -0
  77. package/dist/material-schemas.d.ts.map +1 -0
  78. package/dist/register-default-sprite-lit.d.ts +40 -0
  79. package/dist/register-default-sprite-lit.d.ts.map +1 -0
  80. package/dist/register-default-standard-pbr-skin.d.ts +34 -0
  81. package/dist/register-default-standard-pbr-skin.d.ts.map +1 -0
  82. package/dist/tonemap.d.ts +13 -0
  83. package/dist/tonemap.d.ts.map +1 -0
  84. package/dist/types.d.ts +47 -0
  85. package/dist/types.d.ts.map +1 -0
  86. package/package.json +58 -0
  87. package/src/ShaderRegistry.ts +519 -0
  88. package/src/__tests__/atmosphere-background.test.ts +33 -0
  89. package/src/__tests__/atmosphere-cubemap-orientation.test.ts +68 -0
  90. package/src/__tests__/atmosphere-preetham-numeric.test.ts +122 -0
  91. package/src/__tests__/atmosphere-preetham-red.test.ts +52 -0
  92. package/src/__tests__/atmosphere-preetham.test.ts +132 -0
  93. package/src/__tests__/atmosphere-three-r184-reference.test.ts +122 -0
  94. package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +31 -0
  95. package/src/__tests__/builtin-texture-sampling-contract.test.ts +56 -0
  96. package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +19 -0
  97. package/src/__tests__/deferred-lighting-ssao.test.ts +114 -0
  98. package/src/__tests__/fog-oracle.unit.test.ts +132 -0
  99. package/src/__tests__/fog-orthographic-ray.unit.test.ts +62 -0
  100. package/src/__tests__/fog-producer-matrix.integration.test.ts +77 -0
  101. package/src/__tests__/hdrp-ssao-compile.test.ts +185 -0
  102. package/src/__tests__/ibl-irradiance.unit.test.ts +43 -0
  103. package/src/__tests__/ibl-sampling.unit.test.ts +40 -0
  104. package/src/__tests__/lighting-punctual.unit.test.ts +66 -0
  105. package/src/__tests__/manifest.fixture.json +22 -0
  106. package/src/__tests__/material-artifact-dedup.unit.test.ts +21 -0
  107. package/src/__tests__/material-builtins.unit.test.ts +111 -0
  108. package/src/__tests__/material-contract.unit.test.ts +79 -0
  109. package/src/__tests__/material-derived-builtins.integration.test.ts +30 -0
  110. package/src/__tests__/public-surface.unit.test.ts +9 -0
  111. package/src/__tests__/scene-temporal.unit.test.ts +32 -0
  112. package/src/__tests__/shader-registry-transaction.unit.test.ts +116 -0
  113. package/src/__tests__/shader.unit.test.ts +1909 -0
  114. package/src/__tests__/sprite-lit-shader.test.ts +240 -0
  115. package/src/__tests__/sprite-variants.unit.test.ts +209 -0
  116. package/src/__tests__/taa-resolve.unit.test.ts +32 -0
  117. package/src/__tests__/transparent-pbr.unit.test.ts +21 -0
  118. package/src/__tests__/vertex-color-variant.unit.test.ts +52 -0
  119. package/src/atmosphere-background.wgsl +80 -0
  120. package/src/atmosphere-cubemap.wgsl +72 -0
  121. package/src/atmosphere-ibl.wgsl +127 -0
  122. package/src/atmosphere-preetham.wgsl +214 -0
  123. package/src/bloom-blur.wgsl +89 -0
  124. package/src/bloom-bright.wgsl +50 -0
  125. package/src/bloom-composite.wgsl +54 -0
  126. package/src/brdf.wgsl +25 -0
  127. package/src/common.wgsl +523 -0
  128. package/src/default-standard-pbr-skin.wgsl +538 -0
  129. package/src/default-standard-pbr.wgsl +794 -0
  130. package/src/errors.ts +169 -0
  131. package/src/fog.wgsl +27 -0
  132. package/src/fxaa.wgsl +259 -0
  133. package/src/hdrp-cluster-forward.wgsl +186 -0
  134. package/src/hdrp-deferred-lighting.wgsl +69 -0
  135. package/src/hdrp-ssao.wgsl +174 -0
  136. package/src/ibl-brdf-lut.wgsl +92 -0
  137. package/src/ibl-equirect-to-cube.wgsl +56 -0
  138. package/src/ibl-irradiance.wgsl +89 -0
  139. package/src/ibl-prefilter.wgsl +101 -0
  140. package/src/ibl-sampling.wgsl +58 -0
  141. package/src/ibl-shared.wgsl +118 -0
  142. package/src/index.ts +166 -0
  143. package/src/lighting-directional.wgsl +471 -0
  144. package/src/lighting-punctual.wgsl +274 -0
  145. package/src/material/artifact-registry.ts +78 -0
  146. package/src/material/artifact-types.ts +26 -0
  147. package/src/material-schemas.ts +48 -0
  148. package/src/msdf-text.wgsl +208 -0
  149. package/src/pbr-temporal.wgsl +73 -0
  150. package/src/register-default-sprite-lit.ts +87 -0
  151. package/src/register-default-standard-pbr-skin.ts +74 -0
  152. package/src/shadow-pcf.wgsl +120 -0
  153. package/src/shadow_caster.wgsl +54 -0
  154. package/src/skybox.wgsl +75 -0
  155. package/src/sprite-lit.wgsl +307 -0
  156. package/src/sprite.wgsl +365 -0
  157. package/src/taa-resolve.wgsl +129 -0
  158. package/src/tbn.wgsl +54 -0
  159. package/src/tonemap.ts +13 -0
  160. package/src/tonemap.wgsl +177 -0
  161. package/src/types.ts +59 -0
  162. package/src/unlit.wgsl +186 -0
@@ -0,0 +1,73 @@
1
+ #define_import_path forgeax_pbr::temporal
2
+
3
+ #import forgeax_view::common::{sampleMaterialTexture, packSceneTemporal}
4
+
5
+ fn transformedPbrTemporalUv(
6
+ transform : vec4<f32>,
7
+ metadata : vec4<f32>,
8
+ uv0 : vec2<f32>,
9
+ uv1 : vec2<f32>,
10
+ uv2 : vec2<f32>,
11
+ uv3 : vec2<f32>,
12
+ uv4 : vec2<f32>,
13
+ uv5 : vec2<f32>,
14
+ uv6 : vec2<f32>,
15
+ uv7 : vec2<f32>,
16
+ ) -> vec2<f32> {
17
+ var source = uv0;
18
+ if (metadata.x >= 1.0) { source = uv1; }
19
+ if (metadata.x >= 2.0) { source = uv2; }
20
+ if (metadata.x >= 3.0) { source = uv3; }
21
+ if (metadata.x >= 4.0) { source = uv4; }
22
+ if (metadata.x >= 5.0) { source = uv5; }
23
+ if (metadata.x >= 6.0) { source = uv6; }
24
+ if (metadata.x >= 7.0) { source = uv7; }
25
+ let scaled = source * transform.zw;
26
+ let angle = metadata.y;
27
+ let c = cos(angle);
28
+ let s = sin(angle);
29
+ return vec2<f32>(scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c) + transform.xy;
30
+ }
31
+
32
+ fn projectPbrSceneTemporal(
33
+ baseColorAlpha : f32,
34
+ alphaCutoff : f32,
35
+ baseColorTexture : texture_2d<f32>,
36
+ baseColorSampler : sampler,
37
+ transform : vec4<f32>,
38
+ metadata : vec4<f32>,
39
+ currentClip : vec4<f32>,
40
+ previousClip : vec4<f32>,
41
+ reactive : f32,
42
+ uv0 : vec2<f32>,
43
+ uv1 : vec2<f32>,
44
+ uv2 : vec2<f32>,
45
+ uv3 : vec2<f32>,
46
+ uv4 : vec2<f32>,
47
+ uv5 : vec2<f32>,
48
+ uv6 : vec2<f32>,
49
+ uv7 : vec2<f32>,
50
+ ) -> vec4<f32> {
51
+ let baseUv = transformedPbrTemporalUv(
52
+ transform,
53
+ metadata,
54
+ uv0,
55
+ uv1,
56
+ uv2,
57
+ uv3,
58
+ uv4,
59
+ uv5,
60
+ uv6,
61
+ uv7,
62
+ );
63
+ let baseSample = sampleMaterialTexture(
64
+ baseColorTexture,
65
+ baseColorSampler,
66
+ baseUv,
67
+ metadata.zw,
68
+ );
69
+ if (alphaCutoff > 0.0 && baseColorAlpha * baseSample.a <= alphaCutoff) {
70
+ discard;
71
+ }
72
+ return packSceneTemporal(currentClip, previousClip, reactive);
73
+ }
@@ -0,0 +1,87 @@
1
+ // @forgeax/engine-shader - registerDefaultSpriteLit helper.
2
+ //
3
+ // feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / w4
4
+ // (plan-strategy D-10 application registration path).
5
+ //
6
+ // Mirrors registerDefaultStandardPbrSkin (same package; identical
7
+ // registration shape). paramSchema mirrors sprite.material.json minus
8
+ // PBR-only fields; the BGL is derived from paramSchema via
9
+ // `derive(paramSchema).bglEntries` inside the runtime pipeline-builder.
10
+ //
11
+ // Anchors:
12
+ // - requirements section 2 #2 / #10 (sprite-lit.wgsl new + 4 BGL byte-identical)
13
+ // - plan-strategy D-3 (sprite-lit single Half-Lambert formula lock)
14
+ // - plan-strategy D-5 (STORAGE_BUFFER_AVAILABLE variant axis day-1)
15
+ // - plan-strategy D-6 (4 PBR-unused slots default sampler + white tex)
16
+ // - plan-strategy D-10 (application registration path)
17
+
18
+ import type { ShaderRegistry } from './index.js';
19
+ import { DEFAULT_SPRITE_PARAM_SCHEMA } from './material-schemas.js';
20
+
21
+ /**
22
+ * paramSchema for forgeax::sprite-lit -- mirrors sprite.material.json
23
+ * exactly (same 5 fields: colorTint / region / pivotAndSize /
24
+ * slicesAndMode / baseColorTexture). sprite-lit and sprite share the
25
+ * same Material UBO layout byte-for-byte so the host-side
26
+ * SpriteMaterialAsset writer + 4 BGL JSON are byte-identical (AC-07).
27
+ *
28
+ * OOS-1 (feat-future-sprite-lit-normal-map) will extend this with
29
+ * normalTexture + normalStrength. M1' deliberately keeps the schema
30
+ * byte-identical to sprite so AC-13 "1 string-change between sprite
31
+ * and sprite-lit materials" is provably the only delta required.
32
+ */
33
+ const RESERVED_ID = 'forgeax::sprite-lit' as const;
34
+
35
+ /**
36
+ * Caps shape exposed for sprite-lit registration. Currently only
37
+ * `storageBuffer` matters (STORAGE_BUFFER_AVAILABLE variant axis); the
38
+ * per-shader BGL is derived from paramSchema at pipeline-build time, so
39
+ * the runtime layout factory consumes this caps shape downstream.
40
+ *
41
+ * Kept as a separate type rather than reusing `SkinCaps` to preserve
42
+ * a clear contract surface; the structural shape is currently identical
43
+ * but future divergence (sprite-lit may opt in to OOS-7 cluster-forward
44
+ * later) should not silently leak via a shared type.
45
+ */
46
+ export interface SpriteLitCaps {
47
+ readonly storageBuffer: boolean;
48
+ }
49
+
50
+ /**
51
+ * Register the engine-shipped `forgeax::sprite-lit` material shader in
52
+ * the ShaderRegistry. Must be called once at engine boot (createRenderer),
53
+ * after `ShaderRegistry.loadManifest()` has completed.
54
+ *
55
+ * The `composedWgsl` parameter is the post-naga_oil composed WGSL source,
56
+ * compiled by @forgeax/engine-vite-plugin-shader at build time and surfaced
57
+ * in manifest.json. The caller reads it from the manifest entries.
58
+ *
59
+ * `caps.storageBuffer` switches the runtime BGL buffer types for
60
+ * @group(0) bindings 1+2 (pointLightsBuffer / spotLightsBuffer):
61
+ * `false` -> uniform fallback (AC-10 WebGL2 path); `true` -> read-only-storage.
62
+ * The BGL itself is built downstream in runtime/pbr-pipeline.ts (sprite-lit
63
+ * reuses pbr-view / pbr-mesh-array / pbr-instances unchanged; AC-07
64
+ * BGL byte-identical).
65
+ *
66
+ * @param registry ShaderRegistry instance (engine boot path).
67
+ * @param composedWgsl Post-naga_oil composed WGSL source string.
68
+ * @param caps sprite-lit caps (storageBuffer boolean) for BGL entry type selection.
69
+ *
70
+ * Throws if the identifier is already registered (fail-fast per
71
+ * ShaderRegistry.installMaterialArtifact contract).
72
+ */
73
+ export function registerDefaultSpriteLit(
74
+ registry: ShaderRegistry,
75
+ composedWgsl: string,
76
+ caps: SpriteLitCaps,
77
+ ): void {
78
+ // The caps argument is retained for API symmetry with
79
+ // registerDefaultStandardPbrSkin -- equivalent caps gating lives in
80
+ // buildPbrPipelineLayouts since the per-shader BGL is paramSchema-derived
81
+ // and the storage-vs-uniform branching is a runtime-level concern.
82
+ void caps;
83
+ registry.installMaterialArtifact(RESERVED_ID, {
84
+ source: composedWgsl,
85
+ paramSchema: DEFAULT_SPRITE_PARAM_SCHEMA,
86
+ });
87
+ }
@@ -0,0 +1,74 @@
1
+ // @forgeax/engine-shader - registerDefaultStandardPbrSkin helper.
2
+ //
3
+ // feat-20260523-skin-skeleton-animation M3 / T-30; updated by
4
+ // feat-20260613-material-paramschema-driven-binding M3 / w13: the
5
+ // bindingLayout sidecar field has been removed from MaterialShaderEntry;
6
+ // the BGL is derived from paramSchema via `derive(paramSchema).bglEntries`
7
+ // inside the runtime pipeline-builder. paramSchema remains shared with
8
+ // default-standard-pbr (identical PBR material interface).
9
+ //
10
+ // Anchors:
11
+ // - plan-strategy D-3: register independent WGSL template for skin variant
12
+ // - plan-strategy D-1 / D-2: paramSchema is the single source of truth
13
+ // - R-10: paramSchema reuses default-standard-pbr schema (same params)
14
+
15
+ import type { ShaderRegistry } from './index.js';
16
+ import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from './material-schemas.js';
17
+
18
+ /**
19
+ * paramSchema for forgeax::pbr-skin — mirrors default-standard-pbr 8 fields.
20
+ * Previously sourced from default-standard-pbr.schema.json; now inlined because
21
+ * the .schema.json file has been deleted (feat-20260528-material-shader-registration-unification M1/w4).
22
+ * The SSOT is default-standard-pbr.material.json paramSchema[].
23
+ */
24
+ const RESERVED_ID = 'forgeax::pbr-skin' as const;
25
+
26
+ /**
27
+ * Caps shape historically consumed by the pbr-skin BGL builder. The
28
+ * BGL builder itself moved to runtime/pbr-pipeline.ts (buildPbrSkinLayouts);
29
+ * this type is preserved as the public registration-API contract so
30
+ * existing callers compile unchanged. M3 / w13: per-shader BGL is
31
+ * derived from paramSchema; the storage-buffer-vs-uniform branching
32
+ * the BGL needs lives entirely in the runtime layout factory.
33
+ */
34
+ export interface SkinCaps {
35
+ readonly storageBuffer: boolean;
36
+ }
37
+
38
+ /**
39
+ * Register the engine-shipped `forgeax::pbr-skin` material shader in the
40
+ * ShaderRegistry. Must be called once at engine boot (createRenderer), after
41
+ * `ShaderRegistry.loadManifest()` has completed.
42
+ *
43
+ * The `composedWgsl` parameter is the post-naga_oil composed WGSL source,
44
+ * compiled by @forgeax/engine-vite-plugin-shader at build time and surfaced
45
+ * in manifest.json. The caller reads it from the manifest entries.
46
+ *
47
+ * `caps.storageBuffer` switches the runtime BGL buffer types:
48
+ * `false` -> uniform fallback; `true` -> read-only-storage. The BGL itself
49
+ * is built downstream in runtime/pbr-pipeline.ts (M3 / w13).
50
+ *
51
+ * @param registry ShaderRegistry instance (engine boot path).
52
+ * @param composedWgsl Post-naga_oil composed WGSL source string.
53
+ * @param caps PBR caps (storageBuffer boolean) for BGL entry type selection.
54
+ *
55
+ * Throws if the identifier is already registered (fail-fast per
56
+ * ShaderRegistry.installMaterialArtifact contract).
57
+ */
58
+ export function registerDefaultStandardPbrSkin(
59
+ registry: ShaderRegistry,
60
+ composedWgsl: string,
61
+ caps: SkinCaps,
62
+ ): void {
63
+ // M3 / w13 (D-1 / D-2): bindingLayout sidecar deleted from
64
+ // MaterialShaderEntry; paramSchema is the SSOT and downstream
65
+ // pipeline-builder reads `derive(paramSchema).bglEntries` on demand.
66
+ // The `caps` argument is retained for API compat — historically it
67
+ // selected the storage-buffer vs uniform-buffer skin variant of the
68
+ // BGL; equivalent caps gating now lives in buildPbrSkinLayouts.
69
+ void caps;
70
+ registry.installMaterialArtifact(RESERVED_ID, {
71
+ source: composedWgsl,
72
+ paramSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
73
+ });
74
+ }
@@ -0,0 +1,120 @@
1
+ #define_import_path forgeax_pbr::shadow_pcf
2
+
3
+ // @forgeax/engine-shader - shadow-pcf.wgsl
4
+ // feat-20260612-point-light-shadows-urp-hdrp M2 / T-M2-1 (plan-strategy D-4).
5
+ //
6
+ // Shared PCF (percentage-closer filtering) for directional and point-light
7
+ // shadow sampling. Single SSOT for the slope-scaled bias formula and 9-tap 3x3
8
+ // kernel-offset table. Directional shadows (2D) and point-light shadows
9
+ // (cube_array) each have their own wrapper function; the bias formula and
10
+ // offset table are shared.
11
+ //
12
+ // Exports:
13
+ // - PCF_OFFSETS: const array<vec2<i32>, 9> — 3x3 kernel offset table
14
+ // - sample_shadow_2d(...) -> f32 — directional 2D 9-tap software PCF
15
+ // - sample_shadow_cube_hw2x2(...) -> f32 — point cube hardware 2x2 PCF
16
+ //
17
+ // Bias formula (D-1 directional convention -- normalBias is the slope
18
+ // coefficient, depthBias the floor; feat-20260621 m3-t5 rename):
19
+ // bias = max(normalBias * (1.0 - dot(N, L)), depthBias / 1000.0)
20
+ //
21
+ // Return: 1.0 = fully lit, 0.0 = fully shadowed.
22
+
23
+ // 3x3 integer offset table for PCF kernel (9 taps).
24
+ // Extracted from lighting-directional.wgsl inline loop (T-M2-4);
25
+ // `sample_shadow_2d` walks all 9 taps; `sample_shadow_cube_hw2x2` does NOT
26
+ // use this table (cubemap sampling has no UV parameterization across face
27
+ // seams — see Round-2 F-2 finding).
28
+ const PCF_OFFSETS = array<vec2<i32>, 9>(
29
+ vec2<i32>(-1, -1), vec2<i32>( 0, -1), vec2<i32>( 1, -1),
30
+ vec2<i32>(-1, 0), vec2<i32>( 0, 0), vec2<i32>( 1, 0),
31
+ vec2<i32>(-1, 1), vec2<i32>( 0, 1), vec2<i32>( 1, 1),
32
+ );
33
+
34
+ // === 2D directional-shadow wrapper ============================================
35
+ //
36
+ // `shadowMap` and `shadowSampler` are @group(0) @binding(3/4) from
37
+ // forgeax_view::common. `uv` is the [0,1]^2 projected light-clip coordinate.
38
+ // `texel` is 1.0 / textureDimensions(shadowMap, 0) precomputed by the caller.
39
+ //
40
+ // The caller must perform the OOB gate `uv in [0,1] && depthRef <= 1.0` before
41
+ // calling this function; OOB returns fully lit (1.0) upstream.
42
+
43
+ fn sample_shadow_2d(
44
+ shadowMap : texture_depth_2d,
45
+ shadowSampler: sampler_comparison,
46
+ uv : vec2<f32>,
47
+ texel : vec2<f32>,
48
+ depthRef : f32,
49
+ normalBias : f32,
50
+ depthBias : f32,
51
+ nDotL : f32,
52
+ ) -> f32 {
53
+ // feat-20260621-merge-directionallightshadow-into-directionallight M3 / m3-t5:
54
+ // param names aligned to the D-1 directional convention -- the slope
55
+ // coefficient is `normalBias` (scales the 1 - dot(N,L) term) and the floor is
56
+ // `depthBias`. Pure formal-parameter rename: the two names are swapped in BOTH
57
+ // the signature and the formula, so each positional argument keeps its prior
58
+ // role and all call sites are unaffected. The /1000.0 floor scaling is the
59
+ // existing point-light convention and is preserved.
60
+ let bias = max(normalBias * (1.0 - nDotL), depthBias / 1000.0);
61
+ let adjustedDepth = depthRef - bias;
62
+
63
+ // 9-tap 3x3 PCF kernel. textureSampleCompareLevel returns 1.0 when
64
+ // adjustedDepth < sampledDepth (sampler compare op = 'less'), 0.0 otherwise.
65
+ // Sampler uses clamp-to-edge wrap so OOB texels return the nearest border value.
66
+ var blocked = 0.0;
67
+ for (var i = 0u; i < 9u; i++) {
68
+ let off = PCF_OFFSETS[i];
69
+ let offsetUv = uv + vec2<f32>(f32(off.x), f32(off.y)) * texel;
70
+ let lit = textureSampleCompareLevel(shadowMap, shadowSampler, offsetUv, adjustedDepth);
71
+ blocked = blocked + (1.0 - lit);
72
+ }
73
+ return 1.0 - blocked / 9.0;
74
+ }
75
+
76
+ // === Cube point-light-shadow wrapper =========================================
77
+ //
78
+ // `shadowAtlas` is @group(0) @binding(5) texture_depth_cube_array.
79
+ // `shadowSampler` is @group(0) @binding(4) sampler_comparison (shared with
80
+ // directional shadows — same sampler type, no dimension distinction).
81
+ // `lightLocal` is the fragment-to-light direction vector in the cubemap's
82
+ // coordinate system (Bevy convention: left-handed cubemap; caller applies
83
+ // flip_z = vec3(1,1,-1) to convert right-hand world to left-hand cubemap).
84
+ // `layer` is the cube_array layer index (i32; 0..3 for 4 shadow-casting
85
+ // point lights; sentinel -1 must be gated upstream — this function assumes
86
+ // a valid layer).
87
+ // `depthRef` is the reconstructed [0,1] NDC depth (largest-axis projection,
88
+ // research L0.5 Bevy fetch_point_shadow pattern).
89
+ //
90
+ // Hardware 2x2 PCF only: 2D texel offsets from PCF_OFFSETS have no direct 3D
91
+ // cubemap mapping (no UV parameterization across face seams). A previous
92
+ // 9-tap loop using the same cubemap direction every iteration was algorithmic
93
+ // dead code — every iteration returned the same `lit` value, so 9-tap = 1-tap
94
+ // (Reviewer F-2). This single call lets the GPU's silicon-level 2x2 PCF
95
+ // resolve the local neighborhood within each face natively. Matches Bevy's
96
+ // HARDWARE_2X2 path (research L0.6) and README §AC-11 default. Software 9-tap
97
+ // on cubemaps requires face-basis vector offsets and is OOS-3 here.
98
+
99
+ fn sample_shadow_cube_hw2x2(
100
+ shadowAtlas : texture_depth_cube_array,
101
+ shadowSampler : sampler_comparison,
102
+ lightLocal : vec3<f32>,
103
+ layer : i32,
104
+ depthRef : f32,
105
+ normalBias : f32,
106
+ depthBias : f32,
107
+ nDotL : f32,
108
+ ) -> f32 {
109
+ // Slope-scaled bias: same formula as 2D path, shared SSOT. feat-20260621 M3 /
110
+ // m3-t5: param names aligned to D-1 (normalBias = slope coefficient,
111
+ // depthBias = floor). Pure rename -- each positional arg keeps its prior role.
112
+ let bias = max(normalBias * (1.0 - nDotL), depthBias / 1000.0);
113
+ let adjustedDepth = depthRef - bias;
114
+
115
+ // Single textureSampleCompareLevel call: the comparison sampler resolves a
116
+ // 2x2 PCF neighborhood at the silicon level; clamp-to-edge address mode
117
+ // handles face-boundary texel fetches. Returns 1.0 = fully lit, 0.0 = fully
118
+ // shadowed.
119
+ return textureSampleCompareLevel(shadowAtlas, shadowSampler, lightLocal, layer, adjustedDepth);
120
+ }
@@ -0,0 +1,54 @@
1
+ #pragma variant_axis STORAGE_BUFFER_AVAILABLE
2
+
3
+ // @forgeax/engine-shader shadow_caster.wgsl
4
+ // feat-20260520-directional-light-shadow-mapping M1c / w9 (D-9 / AC-09):
5
+ // vertex-only depth pass for directional shadow map. No fragment stage --
6
+ // the GPU writes depth automatically from gl_Position.z (depth32float RT).
7
+ //
8
+ // feat-20260613-csm-cascaded-shadow-maps M5 / w28: per-cascade
9
+ // lightViewProj selection. Each cascade pass writes a different
10
+ // `shadowCasterCascade.index` (0..3) before encoder submit; the vertex
11
+ // shader reads it to pick `view.lightViewProj_A..D`. The atlas tile UV
12
+ // inset is already baked into each lightViewProj host-side
13
+ // (render-system-extract.ts), so the per-cascade viewport on the depth
14
+ // pass clips rasterization to the correct atlas tile while the matrix
15
+ // itself maps NDC straight into atlas-space [0,1]^2.
16
+ //
17
+ // Reuses:
18
+ // @group(0) binding(0) view : View -- common.wgsl
19
+ // @group(0) binding(5) shadowCasterCascade -- common.wgsl
20
+ // @group(2) binding(0) meshes : array<Mesh> -- common.wgsl
21
+ // @group(3) binding(0) instances : array<InstanceData> -- common.wgsl
22
+ //
23
+ // Vertex layout: matches the 12F procedural layout of every mesh
24
+ // (position vec3, normal vec3, uv vec2, tangent vec4; 48 B stride).
25
+
26
+ #import forgeax_view::common::{View, Mesh, InstanceData, ShadowCasterCascade, view, shadowCasterCascade, meshes, instances}
27
+
28
+ struct VsInput {
29
+ @location(0) position : vec3<f32>,
30
+ };
31
+
32
+ fn _cascadeLightViewProj(layer : u32) -> mat4x4<f32> {
33
+ switch (layer) {
34
+ case 0u: { return view.lightViewProj_A; }
35
+ case 1u: { return view.lightViewProj_B; }
36
+ case 2u: { return view.lightViewProj_C; }
37
+ default: { return view.lightViewProj_D; }
38
+ }
39
+ }
40
+
41
+ @vertex
42
+ fn vs_main(in : VsInput, @builtin(instance_index) idx : u32) -> @builtin(position) vec4<f32> {
43
+ let worldPos = meshes[0].worldFromLocal * instances[idx].localFromInstance * vec4<f32>(in.position, 1.0);
44
+ // feat-20260625-spot-light-shadow-mapping M2 / w10 (D-1): spot shadow passes
45
+ // set `isSpot = 1u` and write their perspective matrix into
46
+ // `spotLightViewProj`; directional cascade passes keep `isSpot = 0u` and read
47
+ // `view.lightViewProj_A..D` via `index`. Routing on the discriminant keeps
48
+ // the spot matrix out of the directional View UBO (no same-frame contention).
49
+ if (shadowCasterCascade.isSpot == 1u) {
50
+ return shadowCasterCascade.spotLightViewProj * worldPos;
51
+ }
52
+ let lvp = _cascadeLightViewProj(shadowCasterCascade.index);
53
+ return lvp * worldPos;
54
+ }
@@ -0,0 +1,75 @@
1
+ #define_import_path forgeax_view::skybox
2
+
3
+ // @forgeax/engine-shader - skybox.wgsl
4
+ // (feat-20260531-skybox-env-background / M3 / w13).
5
+ //
6
+ // Skybox fullscreen pass: vertex stage imports the SSOT fullscreen_triangle()
7
+ // from forgeax_view::common (same single-large-triangle pattern as tonemap +
8
+ // fxaa); fragment stage reconstructs world-space view direction from the
9
+ // screen-space UV + inverseViewProj (View UBO, w3), samples the cubemap
10
+ // texture, and writes HDR colour to the hdrColor rgba16float render target
11
+ // (plan-strategy D-2/D-7).
12
+ //
13
+ // Y-negation: the equirect-to-cube render passes used during cubemap upload
14
+ // follow the WebGL/OpenGL convention (Y-up in world space maps to V=0 at
15
+ // the bottom of the cubemap face). WebGPU's texture coordinate origin is
16
+ // top-left (V=0 at the top), so sampled directions must negate Y to match
17
+ // the same convention used by ibl-sampling.wgsl (plan-strategy R-4, D-7).
18
+ //
19
+ // Bindings (group 0):
20
+ // @binding(0) cubemap : texture_cube<f32> // skybox environment map
21
+ // @binding(1) cubemapSampler : sampler // linear filter + clamp-to-edge
22
+ // @binding(2) view : View (UBO) // carries inverseViewProj (w3)
23
+ // @binding(3) rotation : SkyboxRotation (UBO)
24
+
25
+ #import forgeax_view::common::FullscreenOutput
26
+ #import forgeax_view::common::{View, FogViewParams, FogRay}
27
+ #import forgeax_view::common::fullscreen_triangle
28
+ #import forgeax_view::fog::{apply_fog}
29
+ #import forgeax_pbr::ibl_shared::{inverseRotateEnvironment}
30
+
31
+ @group(0) @binding(0) var cubemap : texture_cube<f32>;
32
+ @group(0) @binding(1) var cubemapSampler : sampler;
33
+ @group(0) @binding(2) var<uniform> view : View;
34
+
35
+ struct SkyboxRotation {
36
+ rotation: vec4<f32>,
37
+ };
38
+
39
+ @group(0) @binding(3) var<uniform> environment : SkyboxRotation;
40
+
41
+ @vertex
42
+ fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
43
+ return fullscreen_triangle(vertex_index);
44
+ }
45
+
46
+ // Reconstruct world-space view direction from screen UV + inverseViewProj.
47
+ // clipPos.xy derived from screen-space UV, clipPos.w = 1, apply
48
+ // inverseViewProj to get world-space direction. Negate Y to match the
49
+ // IBL cubemap Y convention (ibl-sampling.wgsl:30,47).
50
+ //
51
+ // bug-20260608-skybox-cubemaps-upside-down: ndc.y must UNDO the V-flip
52
+ // `fullscreen_triangle()` bakes in (`v = 1 - (y + 1) * 0.5`, common.wgsl:204);
53
+ // the prior `uv.y * 2 - 1` produced the OPPOSITE sign of the actual NDC.y at
54
+ // each fragment, so `inverseViewProj * ndc` returned the world ray for the
55
+ // MIRRORED screen position, and the final `-dir.y` (kept for the IBL bake
56
+ // convention) ended up rendering the skybox upside-down. Reverse the V-flip
57
+ // here with `1.0 - uv.y * 2.0`; see skybox-direction.test.ts.
58
+ fn skyboxDirection(uv : vec2<f32>) -> vec3<f32> {
59
+ let ndc = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 1.0, 1.0);
60
+ let worldDir = view.inverseViewProj * ndc;
61
+ let dir = normalize(worldDir.xyz / worldDir.w);
62
+ let rotated = inverseRotateEnvironment(dir, environment.rotation);
63
+ return vec3<f32>(rotated.x, -rotated.y, rotated.z);
64
+ }
65
+
66
+ @fragment
67
+ fn skybox_fs(in : FullscreenOutput) -> @location(0) vec4<f32> {
68
+ let dir = skyboxDirection(in.uv);
69
+ let color = textureSample(cubemap, cubemapSampler, dir).rgb;
70
+ return apply_fog(
71
+ view.fog,
72
+ FogRay(view.cameraPos, dir, max(view.temporalProjection.y, 0.0)),
73
+ vec4<f32>(color, 1.0),
74
+ );
75
+ }