@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,118 @@
1
+ #define_import_path forgeax_pbr::ibl_shared
2
+
3
+ // @forgeax/engine-shader - ibl-shared.wgsl (feat-20260520-skylight-ibl-cubemap M3 / t42).
4
+ //
5
+ // Pure-function math shared across the 6-module ibl-* family. Zero binding
6
+ // declarations -- this module never touches @group/@binding so it composes
7
+ // cleanly with any host that has its own @group(0)/@group(1) layout.
8
+ //
9
+ // Origin: round-1's monolithic ibl.wgsl collided @group(1) @binding(0)
10
+ // across texture_2d (equirect) and texture_cube (irradiance, prefilter),
11
+ // which the WGSL spec forbids inside a single module. round-2 splits the
12
+ // math out into this file so the 4 precompute modules + 2 sampling helpers
13
+ // can each declare their own (group, binding) namespace without overlap.
14
+ //
15
+ // Exports (no entry points):
16
+ // - PI, INV_ATAN constants
17
+ // - sampleSphericalMap(v)
18
+ // - radicalInverseVdC(bits)
19
+ // - hammersley(i, N)
20
+ // - iblDGGX(nDotH, roughness)
21
+ // - iblGeometrySchlickGGX(NdotV, roughness) (IBL k = roughness^2 / 2)
22
+ // - iblGeometrySmith(NdotV, NdotL, roughness)
23
+ // - importanceSampleGGX(Xi, N, roughness)
24
+ // - fresnelSchlickRoughness(cosTheta, F0, roughness)
25
+ //
26
+ // References: LearnOpenGL §6.2 IBL pipeline; Karis 2013 split-sum.
27
+
28
+ const INV_ATAN: vec2<f32> = vec2<f32>(0.1591, 0.3183);
29
+ const PI: f32 = 3.14159265;
30
+
31
+ // Map a direction vector to equirectangular UV.
32
+ fn sampleSphericalMap(v: vec3<f32>) -> vec2<f32> {
33
+ let uv = vec2<f32>(atan2(v.z, v.x), asin(v.y));
34
+ return uv * INV_ATAN + 0.5;
35
+ }
36
+
37
+ // Rotate a sampling direction by the inverse of an environment quaternion.
38
+ // This keeps the baked cubemap reusable while rotating the source map in
39
+ // world space at the final sampling boundary.
40
+ fn inverseRotateEnvironment(direction: vec3<f32>, rotation: vec4<f32>) -> vec3<f32> {
41
+ let q = normalize(rotation);
42
+ let t = 2.0 * cross(q.xyz, direction);
43
+ return direction - q.w * t + cross(q.xyz, t);
44
+ }
45
+
46
+ // Van der Corput radical inverse in base 2.
47
+ fn radicalInverseVdC(bits: u32) -> f32 {
48
+ var b: u32 = bits;
49
+ b = (b << 16u) | (b >> 16u);
50
+ b = ((b & 0x55555555u) << 1u) | ((b & 0xAAAAAAAAu) >> 1u);
51
+ b = ((b & 0x33333333u) << 2u) | ((b & 0xCCCCCCCCu) >> 2u);
52
+ b = ((b & 0x0F0F0F0Fu) << 4u) | ((b & 0xF0F0F0F0u) >> 4u);
53
+ b = ((b & 0x00FF00FFu) << 8u) | ((b & 0xFF00FF00u) >> 8u);
54
+ return f32(b) * 2.3283064365386963e-10;
55
+ }
56
+
57
+ // Hammersley low-discrepancy 2D sequence: (i/N, radicalInverseVdC(i)).
58
+ fn hammersley(i: u32, N: u32) -> vec2<f32> {
59
+ return vec2<f32>(f32(i) / f32(N), radicalInverseVdC(i));
60
+ }
61
+
62
+ // GGX NDF.
63
+ fn iblDGGX(nDotH: f32, roughness: f32) -> f32 {
64
+ let a = roughness * roughness;
65
+ let a2 = a * a;
66
+ let f = (nDotH * a2 - nDotH) * nDotH + 1.0;
67
+ return a2 / (max(PI * f * f, 1e-7));
68
+ }
69
+
70
+ // Smith-GGX G1 for IBL: k = roughness^2 / 2.
71
+ // DIFFERENT from direct-light's k = ((roughness+1)^2)/8 (in brdf.wgsl).
72
+ fn iblGeometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 {
73
+ let k = (roughness * roughness) / 2.0;
74
+ return NdotV / max(NdotV * (1.0 - k) + k, 1e-5);
75
+ }
76
+
77
+ // Smith-GGX height-correlated geometry for IBL.
78
+ fn iblGeometrySmith(NdotV: f32, NdotL: f32, roughness: f32) -> f32 {
79
+ let ggxV = iblGeometrySchlickGGX(NdotV, roughness);
80
+ let ggxL = iblGeometrySchlickGGX(NdotL, roughness);
81
+ return ggxV * ggxL;
82
+ }
83
+
84
+ // GGX importance sampling: maps uniform 2D sample Xi to a half-vector H
85
+ // in the local frame around N.
86
+ fn importanceSampleGGX(Xi: vec2<f32>, N: vec3<f32>, roughness: f32) -> vec3<f32> {
87
+ let a = roughness * roughness;
88
+
89
+ let phi = 2.0 * PI * Xi.x;
90
+ let cosTheta = sqrt(max((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y), 0.0));
91
+ let sinTheta = sqrt(max(1.0 - cosTheta * cosTheta, 0.0));
92
+
93
+ // GGX half-vector in tangent space.
94
+ let Ht = vec3<f32>(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
95
+
96
+ // Tangent frame construction (avoids singularity at N parallel to z).
97
+ // WGSL `select(falseVal, trueVal, cond)` returns trueVal when cond is
98
+ // true; the LearnOpenGL reference is `cond ? vec3(0,0,1) : vec3(1,0,0)`.
99
+ // When N is (near-)parallel to z (e.g. the BRDF-LUT canonical frame
100
+ // N = (0,0,1)) we need up = (1,0,0); otherwise up = (0,0,1). Swapping the
101
+ // operands here fixes the brdf-lut bake silently producing all-zero output
102
+ // (cross(up, N) = 0 when up == N, normalize(0) = NaN, GPU flushes to 0).
103
+ let up = select(
104
+ vec3<f32>(1.0, 0.0, 0.0),
105
+ vec3<f32>(0.0, 0.0, 1.0),
106
+ abs(N.z) < 0.999,
107
+ );
108
+ let tangent = normalize(cross(up, N));
109
+ let bitangent = cross(N, tangent);
110
+
111
+ return normalize(tangent * Ht.x + bitangent * Ht.y + N * Ht.z);
112
+ }
113
+
114
+ // Fresnel-Schlick with roughness dampening for IBL specular.
115
+ fn fresnelSchlickRoughness(cosTheta: f32, F0: vec3<f32>, roughness: f32) -> vec3<f32> {
116
+ let oneMinusRough = max(vec3<f32>(1.0 - roughness), F0);
117
+ return F0 + (oneMinusRough - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
118
+ }
package/src/index.ts ADDED
@@ -0,0 +1,166 @@
1
+ // @forgeax/engine-shader — runtime shader registry public surface.
2
+ //
3
+ // Shape rules (plan-strategy §S-10 / D-R10 / OQ-5 close):
4
+ // - instance-per-engine — exposed through the lazy `engine.shader:
5
+ // ShaderRegistry` property; module-level singletons / static methods are
6
+ // forbidden (aligned with `Engine.create({ rhi })`'s instance-based style).
7
+ // - Physical isolation — this package's deps only contain `@forgeax/engine-rhi` +
8
+ // `@forgeax/engine-types`; importing `@forgeax/engine-shader-compiler` / `@forgeax/engine-naga`
9
+ // / `@forgeax/engine-wgpu-wasm` directly or transitively is **forbidden**
10
+ // (guarded by the AC-06 triple-grep gate; feat-20260511-naga-rhi-wgpu-merge
11
+ // M4 replaced the legacy single-shim ban with the merged ban triple above).
12
+ // - Result model — expected failures go through
13
+ // `Result.err(RhiError | ShaderError)` and **never throw** (AGENTS.md
14
+ // "Errors are structured" / charter proposition 4: explicit failure).
15
+ //
16
+ // Top-level surface (charter proposition 1: progressive disclosure):
17
+ // - ShaderRegistry / ShaderRegistryOptions / ShaderRegistryDevice — main class
18
+ // + injection interface
19
+ // - ShaderError / ShaderErrorCode / 2 factories — runtime error types
20
+ // - Result<T, E> + ok / err — binary result type and constructors
21
+ // - ManifestEntry — re-exported from `@forgeax/engine-types` (manifest schema SSOT)
22
+
23
+ export type { ManifestEntry, ParamSchemaEntry } from '@forgeax/engine-types';
24
+
25
+ import type { MaterialAsset, MaterialParameter, MaterialValue } from '@forgeax/engine-types';
26
+ import { STANDARD_PBR_ALPHA_CUTOFF_DEFAULT } from './material-schemas.js';
27
+
28
+ export { MATERIAL_PARAM_TYPES } from '@forgeax/engine-types';
29
+ export {
30
+ err,
31
+ manifestMalformed,
32
+ materialShaderNotFound,
33
+ ok,
34
+ type Result,
35
+ type ResultErr,
36
+ type ResultOk,
37
+ ShaderError,
38
+ type ShaderErrorCode,
39
+ type ShaderErrorDetail,
40
+ shaderNotFound,
41
+ } from './errors.js';
42
+ export {
43
+ type MaterialArtifactConflictError,
44
+ type MaterialArtifactInspection,
45
+ MaterialArtifactRegistry,
46
+ type MaterialRuntimeArtifact,
47
+ registerMaterialArtifact,
48
+ } from './material/artifact-registry.js';
49
+ export {
50
+ isMaterialShaderArtifact,
51
+ type MaterialShaderArtifact,
52
+ } from './material/artifact-types.js';
53
+ export {
54
+ DEFAULT_MSDF_TEXT_PARAM_SCHEMA,
55
+ DEFAULT_SPRITE_PARAM_SCHEMA,
56
+ DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
57
+ DEFAULT_UNLIT_PARAM_SCHEMA,
58
+ STANDARD_PBR_ALPHA_CUTOFF_DEFAULT,
59
+ } from './material-schemas.js';
60
+ export {
61
+ registerDefaultSpriteLit,
62
+ type SpriteLitCaps,
63
+ } from './register-default-sprite-lit.js';
64
+ export { registerDefaultStandardPbrSkin } from './register-default-standard-pbr-skin.js';
65
+ export {
66
+ FORGEAX_RESERVED_PATH_PREFIX,
67
+ type MaterialShaderEntry,
68
+ type RegisteredMaterialShaderEntry,
69
+ ShaderRegistry,
70
+ type ShaderRegistryDevice,
71
+ type ShaderRegistryOptions,
72
+ } from './ShaderRegistry.js';
73
+ export {
74
+ findVariantByKey,
75
+ type MaterialShaderManifestEntry,
76
+ type MaterialShaderManifestVariant,
77
+ } from './types.js';
78
+
79
+ export const BUILTIN_MATERIAL_MODULES = {
80
+ standard: 'forgeax_material::standard',
81
+ unlit: 'forgeax_material::unlit',
82
+ sprite: 'forgeax_material::sprite',
83
+ } as const;
84
+
85
+ /**
86
+ * Material modules whose shader source and parameter contract are owned by
87
+ * the Engine. They live in the runtime ShaderRegistry, so a Pack containing
88
+ * one of these materials carries authored values only and does not require a
89
+ * project material-cook artifact.
90
+ */
91
+ export const ENGINE_MATERIAL_MODULES = [
92
+ 'forgeax::default-standard-pbr',
93
+ 'forgeax::pbr-skin',
94
+ 'forgeax::default-standard-pbr-skin',
95
+ 'forgeax::default-unlit',
96
+ 'forgeax::default-shadow-caster',
97
+ 'forgeax::sprite',
98
+ 'forgeax::sprite-lit',
99
+ 'forgeax::msdf-text',
100
+ BUILTIN_MATERIAL_MODULES.standard,
101
+ BUILTIN_MATERIAL_MODULES.unlit,
102
+ BUILTIN_MATERIAL_MODULES.sprite,
103
+ 'forgeax_material::sprite-lit',
104
+ ] as const;
105
+
106
+ export function isEngineMaterialModule(module: string): boolean {
107
+ return (ENGINE_MATERIAL_MODULES as readonly string[]).includes(module);
108
+ }
109
+
110
+ export function isEngineMaterial(material: Pick<MaterialAsset, 'passes'>): boolean {
111
+ const passes = material.passes ?? [];
112
+ return passes.length > 0 && passes.every((pass) => isEngineMaterialModule(pass.program.module));
113
+ }
114
+
115
+ export type BuiltinMaterialKind = keyof typeof BUILTIN_MATERIAL_MODULES;
116
+
117
+ const BUILTIN_PARAMETERS: Readonly<Record<BuiltinMaterialKind, readonly MaterialParameter[]>> = {
118
+ standard: [
119
+ { name: 'baseColor', type: 'color' },
120
+ { name: 'metallic', type: 'f32' },
121
+ { name: 'roughness', type: 'f32' },
122
+ { name: 'alphaCutoff', type: 'f32', optional: true },
123
+ ],
124
+ unlit: [{ name: 'baseColor', type: 'color' }],
125
+ sprite: [{ name: 'colorTint', type: 'vec4', colorSpace: 'srgb' }],
126
+ };
127
+
128
+ const BUILTIN_VALUES: Readonly<
129
+ Record<BuiltinMaterialKind, Readonly<Record<string, MaterialValue>>>
130
+ > = {
131
+ standard: {
132
+ baseColor: [1, 1, 1, 1],
133
+ metallic: 0,
134
+ roughness: 0.5,
135
+ alphaCutoff: STANDARD_PBR_ALPHA_CUTOFF_DEFAULT,
136
+ },
137
+ unlit: { baseColor: [1, 1, 1, 1] },
138
+ sprite: { colorTint: [1, 1, 1, 1] },
139
+ };
140
+
141
+ export function createBuiltinMaterialAsset(kind: BuiltinMaterialKind): MaterialAsset {
142
+ return {
143
+ kind: 'material',
144
+ passes: [{ name: 'forward', program: { module: BUILTIN_MATERIAL_MODULES[kind] } }],
145
+ parameters: BUILTIN_PARAMETERS[kind],
146
+ values: BUILTIN_VALUES[kind],
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Shared luminance epsilon floor for the extended Reinhard tone-map
152
+ * (feat-20260519-tonemap-reinhard-mvp / D-O3).
153
+ *
154
+ * Both the TS port at `packages/runtime/src/systems/tonemap.ts` and the WGSL
155
+ * fragment stage in `packages/shader/src/tonemap.wgsl` apply
156
+ * `max(Y, TONEMAP_LUMINANCE_EPSILON)` before dividing the luminance ratio.
157
+ * The floor keeps the divisor finite at degenerate inputs (`Y = 0` from black
158
+ * pixels, `Y < 0` from rare numerical artefacts). Single SSOT here so a
159
+ * single `import { TONEMAP_LUMINANCE_EPSILON } from '@forgeax/engine-shader'`
160
+ * keeps TS / WGSL byte-equivalent.
161
+ *
162
+ * Value: `1e-5` — small enough to not perturb any plausible HDR luminance.
163
+ */
164
+ export const TONEMAP_LUMINANCE_EPSILON = 1e-5;
165
+
166
+ export { TONEMAP_SHADER_MODE, type TonemapShaderMode } from './tonemap.js';