@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,129 @@
1
+ #define_import_path forgeax_view::taa_resolve
2
+
3
+ #import forgeax_view::common::fullscreen_triangle
4
+ #import forgeax_view::common::FullscreenOutput
5
+
6
+ struct TaaResolveParams {
7
+ currentJitterUv : vec2<f32>,
8
+ historyValid : u32,
9
+ temporalFrameIndex : u32,
10
+ };
11
+
12
+ struct TaaResolveOutput {
13
+ @location(0) color : vec4<f32>,
14
+ @location(1) temporal : vec4<f32>,
15
+ };
16
+
17
+ @group(0) @binding(0) var currentColor : texture_2d<f32>;
18
+ @group(0) @binding(1) var currentTemporal : texture_2d<f32>;
19
+ @group(0) @binding(2) var historyColor : texture_2d<f32>;
20
+ @group(0) @binding(3) var historyTemporal : texture_2d<f32>;
21
+ @group(0) @binding(4) var historySampler : sampler;
22
+ @group(0) @binding(5) var<uniform> params : TaaResolveParams;
23
+
24
+ fn rgbToYCoCg(rgb : vec3<f32>) -> vec3<f32> {
25
+ return vec3<f32>(
26
+ dot(rgb, vec3<f32>(0.25, 0.5, 0.25)),
27
+ dot(rgb, vec3<f32>(0.5, 0.0, -0.5)),
28
+ dot(rgb, vec3<f32>(-0.25, 0.5, -0.25)),
29
+ );
30
+ }
31
+
32
+ fn yCoCgToRgb(value : vec3<f32>) -> vec3<f32> {
33
+ return vec3<f32>(value.x + value.y - value.z, value.x + value.z, value.x - value.y - value.z);
34
+ }
35
+
36
+ fn luminance(rgb : vec3<f32>) -> f32 {
37
+ return dot(rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
38
+ }
39
+
40
+ fn jitterCorrectedCurrent(uv : vec2<f32>) -> vec4<f32> {
41
+ // The resolve and history live in the unjittered output domain. The main
42
+ // scene raster is the only jittered consumer, so re-center its color here;
43
+ // packed motion is already unjittered currentUv - previousUv.
44
+ return textureSampleLevel(currentColor, historySampler, uv + params.currentJitterUv, 0.0);
45
+ }
46
+
47
+ fn closestCurrentTemporal(pixel : vec2<i32>, dimensions : vec2<i32>) -> vec4<f32> {
48
+ var closest = vec4<f32>(0.0, 0.0, -1.0, 1.0);
49
+ var closestDepth = 1e20;
50
+ for (var y = -1; y <= 1; y += 1) {
51
+ for (var x = -1; x <= 1; x += 1) {
52
+ let samplePixel = clamp(pixel + vec2<i32>(x, y), vec2<i32>(0), dimensions - vec2<i32>(1));
53
+ let candidate = textureLoad(currentTemporal, samplePixel, 0);
54
+ if candidate.z >= 0.0 && candidate.z < closestDepth {
55
+ closest = candidate;
56
+ closestDepth = candidate.z;
57
+ }
58
+ }
59
+ }
60
+ return closest;
61
+ }
62
+
63
+ @vertex
64
+ fn vs_taa_resolve(@builtin(vertex_index) vertexIndex : u32) -> FullscreenOutput {
65
+ return fullscreen_triangle(vertexIndex);
66
+ }
67
+
68
+ @fragment
69
+ fn fs_taa_resolve(in : FullscreenOutput) -> TaaResolveOutput {
70
+ let dimensions = vec2<i32>(textureDimensions(currentColor));
71
+ let pixel = clamp(vec2<i32>(in.position.xy), vec2<i32>(0), dimensions - vec2<i32>(1));
72
+ let current = jitterCorrectedCurrent(in.uv);
73
+ let temporal = closestCurrentTemporal(pixel, dimensions);
74
+
75
+ var output : TaaResolveOutput;
76
+ output.temporal = temporal;
77
+ output.color = current;
78
+
79
+ let historyUv = in.uv - temporal.xy;
80
+ let historyInBounds = all(historyUv >= vec2<f32>(0.0)) && all(historyUv < vec2<f32>(1.0));
81
+ if params.historyValid == 0u || !historyInBounds || temporal.z < 0.0 {
82
+ return output;
83
+ }
84
+
85
+ let previousTemporal = textureSampleLevel(historyTemporal, historySampler, historyUv, 0.0);
86
+ let depthDelta = abs(previousTemporal.z - temporal.z);
87
+ let depthThreshold = max(0.01, temporal.z * 0.01);
88
+ if previousTemporal.z < 0.0 || depthDelta > depthThreshold {
89
+ return output;
90
+ }
91
+
92
+ var neighborhoodMin = vec3<f32>(1e20);
93
+ var neighborhoodMax = vec3<f32>(-1e20);
94
+ var neighborhoodMean = vec3<f32>(0.0);
95
+ var neighborhoodSquareMean = vec3<f32>(0.0);
96
+ for (var y = -1; y <= 1; y += 1) {
97
+ for (var x = -1; x <= 1; x += 1) {
98
+ let sampleUv = in.uv + vec2<f32>(f32(x), f32(y)) / vec2<f32>(dimensions);
99
+ let sampleValue = rgbToYCoCg(jitterCorrectedCurrent(sampleUv).rgb);
100
+ neighborhoodMin = min(neighborhoodMin, sampleValue);
101
+ neighborhoodMax = max(neighborhoodMax, sampleValue);
102
+ neighborhoodMean += sampleValue;
103
+ neighborhoodSquareMean += sampleValue * sampleValue;
104
+ }
105
+ }
106
+ neighborhoodMean /= 9.0;
107
+ neighborhoodSquareMean /= 9.0;
108
+ let sigma = sqrt(max(neighborhoodSquareMean - neighborhoodMean * neighborhoodMean, vec3<f32>(0.0)));
109
+ let clipMin = max(neighborhoodMin, neighborhoodMean - sigma * 1.25);
110
+ let clipMax = min(neighborhoodMax, neighborhoodMean + sigma * 1.25);
111
+
112
+ let sampledHistory = textureSampleLevel(historyColor, historySampler, historyUv, 0.0);
113
+ let clippedHistoryRgb = yCoCgToRgb(clamp(rgbToYCoCg(sampledHistory.rgb), clipMin, clipMax));
114
+ let velocityFactor = 1.0 - clamp(length(temporal.xy) * 64.0, 0.0, 1.0);
115
+ let reactiveFactor = 1.0 - clamp(temporal.w, 0.0, 1.0);
116
+ let depthFactor = 1.0 - clamp(depthDelta / depthThreshold, 0.0, 1.0);
117
+ let currentLuma = luminance(current.rgb);
118
+ let historyLuma = luminance(clippedHistoryRgb);
119
+ let lumaDelta = abs(historyLuma - currentLuma);
120
+ let unbiasedLumaDelta = clamp(lumaDelta / max(max(currentLuma, historyLuma), 0.2), 0.0, 1.0);
121
+ let lumaFactor = (1.0 - unbiasedLumaDelta) * (1.0 - unbiasedLumaDelta);
122
+ let fixedPresetWeight = mix(0.88, 0.97, lumaFactor);
123
+ let startupWeight = f32(params.temporalFrameIndex) / f32(params.temporalFrameIndex + 1u);
124
+ let progressiveWeight = select(fixedPresetWeight, min(startupWeight, fixedPresetWeight), params.temporalFrameIndex < 8u);
125
+ let rejectionFactor = min(reactiveFactor, min(velocityFactor, depthFactor));
126
+ let historyWeight = progressiveWeight * rejectionFactor;
127
+ output.color = vec4<f32>(mix(current.rgb, clippedHistoryRgb, historyWeight), current.a);
128
+ return output;
129
+ }
package/src/tbn.wgsl ADDED
@@ -0,0 +1,54 @@
1
+ #define_import_path forgeax_pbr::tbn
2
+
3
+ // @forgeax/engine-shader - tbn.wgsl (feat-20260523-shader-template-instance-split
4
+ // M5 / T01).
5
+ //
6
+ // Tangent-space normal decode + TBN basis composition helpers extracted from
7
+ // pbr.wgsl fs_main body. Pure-function module (zero @group / @binding) so it
8
+ // composes cleanly into any material shader that wants tangent-space normal
9
+ // mapping with the host's per-vertex tangent (vec4 with handedness in .w).
10
+ //
11
+ // RG-only tangent normal decode mirrors pbr.wgsl pre-split semantics
12
+ // (charter P5 byte-equivalent extraction). The default 1x1 normal fallback
13
+ // texture RG=(128,128) decodes to tangent (0,0,1) -- zero perturbation when
14
+ // normalTexture is absent (host-side defaultNormalTextureView). RG encoding
15
+ // also matches BC5 / RG normal maps and tolerates RGB normal maps (b is
16
+ // dropped, z is recomputed -- equivalent for unit vectors).
17
+ //
18
+ // Exports:
19
+ // - decodeTangentSpaceNormalRg(rg) -> vec3<f32> (pure)
20
+ // - scaleTangentSpaceNormal(tn, scale) -> vec3<f32> (pure)
21
+ // - applyTBN(worldNormal, worldTangent, tn) -> vec3<f32> (pure)
22
+
23
+ // Decode a tangent-space normal from the RG channels of a normal-map sample.
24
+ // Z is reconstructed via z = sqrt(saturate(1 - x^2 - y^2)); saturate guards
25
+ // against numerically-out-of-unit (x,y) producing NaN.
26
+ fn decodeTangentSpaceNormalRg(rg: vec2<f32>) -> vec3<f32> {
27
+ let xy = rg * 2.0 - vec2<f32>(1.0);
28
+ let z = sqrt(saturate(1.0 - dot(xy, xy)));
29
+ return vec3<f32>(xy, z);
30
+ }
31
+
32
+ // Scale the XY perturbation while reconstructing Z so normalScale=0 restores
33
+ // the flat tangent normal and normalScale=1 preserves the sampled normal.
34
+ fn scaleTangentSpaceNormal(tn: vec3<f32>, scale: f32) -> vec3<f32> {
35
+ let xy = tn.xy * scale;
36
+ let z = sqrt(saturate(1.0 - dot(xy, xy)));
37
+ return vec3<f32>(xy, z);
38
+ }
39
+
40
+ // Build the TBN basis from interpolated world-space normal + per-vertex
41
+ // tangent (xyz with handedness sign in .w), then transform a tangent-space
42
+ // normal `tn` into world-space. Re-normalises the input world normal +
43
+ // re-orthogonalises the tangent against the normal so that small interpolation
44
+ // drift across triangle barycentrics does not skew the basis.
45
+ fn applyTBN(
46
+ worldNormal : vec3<f32>,
47
+ worldTangent : vec4<f32>,
48
+ tn : vec3<f32>,
49
+ ) -> vec3<f32> {
50
+ let n0 = normalize(worldNormal);
51
+ let t0 = normalize(worldTangent.xyz - dot(worldTangent.xyz, n0) * n0);
52
+ let b0 = cross(n0, t0) * worldTangent.w;
53
+ return normalize(t0 * tn.x + b0 * tn.y + n0 * tn.z);
54
+ }
package/src/tonemap.ts ADDED
@@ -0,0 +1,13 @@
1
+ /** Numeric mode IDs consumed by the built-in tonemap WGSL module. */
2
+ export const TONEMAP_SHADER_MODE = {
3
+ none: 0,
4
+ reinhardExtended: 1,
5
+ linear: 2,
6
+ cineon: 3,
7
+ acesFilmic: 4,
8
+ agx: 5,
9
+ neutral: 6,
10
+ reinhard: 7,
11
+ } as const;
12
+
13
+ export type TonemapShaderMode = (typeof TONEMAP_SHADER_MODE)[keyof typeof TONEMAP_SHADER_MODE];
@@ -0,0 +1,177 @@
1
+ #define_import_path forgeax_view::tonemap
2
+
3
+ // @forgeax/engine-shader - Three r184 tone mapping output module.
4
+ //
5
+ // The public modes and formulas are the Three.js r184 oracle. The numeric
6
+ // values are the binding contract mirrored by TONEMAP_SHADER_MODE in tonemap.ts.
7
+ // `reinhard-extended` remains Forgeax's existing luminance-domain curve; the
8
+ // distinct `reinhard` mode is Three's per-channel Reinhard curve.
9
+
10
+ #import forgeax_view::common::FullscreenOutput
11
+ #import forgeax_view::common::fullscreen_triangle
12
+ #import forgeax_view::common::linearToSrgbOetf
13
+
14
+ const TONEMAP_LUMINANCE_EPSILON : f32 = 1e-5;
15
+
16
+ struct TonemapParams {
17
+ exposure : f32,
18
+ whitePoint : f32,
19
+ mode : u32,
20
+ pad1 : f32,
21
+ };
22
+
23
+ @group(1) @binding(0) var hdr : texture_2d<f32>;
24
+ @group(1) @binding(1) var samp : sampler;
25
+ @group(1) @binding(2) var<uniform> params : TonemapParams;
26
+
27
+ @vertex
28
+ fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
29
+ return fullscreen_triangle(vertex_index);
30
+ }
31
+
32
+ fn tonemapReinhardExtended(color : vec3<f32>) -> vec3<f32> {
33
+ let luma = dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
34
+ let lw_sq = params.whitePoint * params.whitePoint;
35
+ let luma_prime = (luma * (1.0 + luma / lw_sq)) / (1.0 + luma);
36
+ let scale = luma_prime / max(luma, TONEMAP_LUMINANCE_EPSILON);
37
+ return color * scale;
38
+ }
39
+
40
+ fn tonemapLinear(color : vec3<f32>) -> vec3<f32> {
41
+ return clamp(color, vec3<f32>(0.0), vec3<f32>(1.0));
42
+ }
43
+
44
+ fn tonemapReinhard(color : vec3<f32>) -> vec3<f32> {
45
+ return clamp(color / (color + vec3<f32>(1.0)), vec3<f32>(0.0), vec3<f32>(1.0));
46
+ }
47
+
48
+ fn tonemapCineon(color : vec3<f32>) -> vec3<f32> {
49
+ let x = max(color - vec3<f32>(0.004), vec3<f32>(0.0));
50
+ let a = x * (6.2 * x + vec3<f32>(0.5));
51
+ let b = x * (6.2 * x + vec3<f32>(1.7)) + vec3<f32>(0.06);
52
+ return pow(a / b, vec3<f32>(2.2));
53
+ }
54
+
55
+ fn acesInput(color : vec3<f32>) -> vec3<f32> {
56
+ return vec3<f32>(
57
+ dot(vec3<f32>(0.59719, 0.35458, 0.04823), color),
58
+ dot(vec3<f32>(0.07600, 0.90834, 0.01566), color),
59
+ dot(vec3<f32>(0.02840, 0.13383, 0.83777), color),
60
+ );
61
+ }
62
+
63
+ fn acesOutput(color : vec3<f32>) -> vec3<f32> {
64
+ return vec3<f32>(
65
+ dot(vec3<f32>(1.60475, -0.53108, -0.07367), color),
66
+ dot(vec3<f32>(-0.10208, 1.10813, -0.00605), color),
67
+ dot(vec3<f32>(-0.00327, -0.07276, 1.07602), color),
68
+ );
69
+ }
70
+
71
+ fn tonemapAcesFilmic(color : vec3<f32>) -> vec3<f32> {
72
+ let input = acesInput(color / vec3<f32>(0.6));
73
+ let a = input * (input + vec3<f32>(0.0245786)) - vec3<f32>(0.000090537);
74
+ let b = input * ((input + vec3<f32>(0.4329510)) * vec3<f32>(0.983729)) + vec3<f32>(0.238081);
75
+ return clamp(acesOutput(a / b), vec3<f32>(0.0), vec3<f32>(1.0));
76
+ }
77
+
78
+ fn rec2020FromSrgb(color : vec3<f32>) -> vec3<f32> {
79
+ return vec3<f32>(
80
+ dot(vec3<f32>(0.6274, 0.3293, 0.0433), color),
81
+ dot(vec3<f32>(0.0691, 0.9195, 0.0113), color),
82
+ dot(vec3<f32>(0.0164, 0.0880, 0.8956), color),
83
+ );
84
+ }
85
+
86
+ fn srgbFromRec2020(color : vec3<f32>) -> vec3<f32> {
87
+ return vec3<f32>(
88
+ dot(vec3<f32>(1.6605, -0.5876, -0.0728), color),
89
+ dot(vec3<f32>(-0.1246, 1.1329, -0.0083), color),
90
+ dot(vec3<f32>(-0.0182, -0.1006, 1.1187), color),
91
+ );
92
+ }
93
+
94
+ fn agxContrastApprox(x : vec3<f32>) -> vec3<f32> {
95
+ let x2 = x * x;
96
+ let x4 = x2 * x2;
97
+ return 15.5 * x4 * x2
98
+ - 40.14 * x4 * x
99
+ + 31.96 * x4
100
+ - 6.868 * x2 * x
101
+ + 0.4298 * x2
102
+ + 0.1191 * x
103
+ - vec3<f32>(0.00232);
104
+ }
105
+
106
+ fn agxInset(color : vec3<f32>) -> vec3<f32> {
107
+ return vec3<f32>(
108
+ dot(vec3<f32>(0.856627153315983, 0.0951212405381588, 0.0482516061458583), color),
109
+ dot(vec3<f32>(0.137318972929847, 0.761241990602591, 0.101439036467562), color),
110
+ dot(vec3<f32>(0.11189821299995, 0.0767994186031903, 0.811302368396859), color),
111
+ );
112
+ }
113
+
114
+ fn agxOutset(color : vec3<f32>) -> vec3<f32> {
115
+ return vec3<f32>(
116
+ dot(vec3<f32>(1.1271005818144368, -0.11060664309660323, -0.016493938717834573), color),
117
+ dot(vec3<f32>(-0.1413297634984383, 1.157823702216272, -0.016493938717834257), color),
118
+ dot(vec3<f32>(-0.14132976349843826, -0.11060664309660294, 1.2519364065950405), color),
119
+ );
120
+ }
121
+
122
+ fn tonemapAgx(color : vec3<f32>) -> vec3<f32> {
123
+ let min_ev = -12.47393;
124
+ let max_ev = 4.026069;
125
+ let rec2020 = agxInset(rec2020FromSrgb(color));
126
+ let log_color = log2(max(rec2020, vec3<f32>(1e-10)));
127
+ let normalized = clamp(
128
+ (log_color - vec3<f32>(min_ev)) / vec3<f32>(max_ev - min_ev),
129
+ vec3<f32>(0.0),
130
+ vec3<f32>(1.0),
131
+ );
132
+ let contrasted = agxContrastApprox(normalized);
133
+ let encoded = pow(max(agxOutset(contrasted), vec3<f32>(0.0)), vec3<f32>(2.2));
134
+ return clamp(srgbFromRec2020(encoded), vec3<f32>(0.0), vec3<f32>(1.0));
135
+ }
136
+
137
+ fn tonemapNeutral(color : vec3<f32>) -> vec3<f32> {
138
+ let start_compression = 0.8 - 0.04;
139
+ let desaturation = 0.15;
140
+ let x = min(color.r, min(color.g, color.b));
141
+ let offset = select(0.04, x - 6.25 * x * x, x < 0.08);
142
+ var compressed = color - vec3<f32>(offset);
143
+ let peak = max(compressed.r, max(compressed.g, compressed.b));
144
+ if (peak < start_compression) {
145
+ return compressed;
146
+ }
147
+ let d = 1.0 - start_compression;
148
+ let new_peak = 1.0 - d * d / (peak + d - start_compression);
149
+ compressed *= new_peak / peak;
150
+ let g = 1.0 - 1.0 / (desaturation * (peak - new_peak) + 1.0);
151
+ return mix(compressed, vec3<f32>(new_peak), g);
152
+ }
153
+
154
+ @fragment
155
+ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
156
+ // linearHdrColorDomain: hdr is sampled before the only HDR-to-LDR stage.
157
+ // linearLdrColorDomain: the output pass emits the display-target domain.
158
+ let source = textureSample(hdr, samp, in.uv);
159
+ let sample : vec3<f32> = source.rgb;
160
+ let exposed : vec3<f32> = sample * params.exposure;
161
+ var mapped : vec3<f32>;
162
+ switch (params.mode) {
163
+ case 1u: { mapped = tonemapReinhardExtended(exposed); }
164
+ case 2u: { mapped = tonemapLinear(exposed); }
165
+ case 3u: { mapped = tonemapCineon(exposed); }
166
+ case 4u: { mapped = tonemapAcesFilmic(exposed); }
167
+ case 5u: { mapped = tonemapAgx(exposed); }
168
+ case 6u: { mapped = tonemapNeutral(exposed); }
169
+ case 7u: { mapped = tonemapReinhard(exposed); }
170
+ // Mode 0 is the linear-LDR output pass used when tone mapping is disabled.
171
+ // It targets the raw canvas format and mirrors Three r184's output OETF.
172
+ default: { mapped = linearToSrgbOetf(exposed); }
173
+ }
174
+ // The mode-0 output pass is display-encoded; other modes retain their
175
+ // existing tonemap output contract.
176
+ return vec4<f32>(mapped, source.a);
177
+ }
package/src/types.ts ADDED
@@ -0,0 +1,59 @@
1
+ // @forgeax/engine-shader/types — shared variant types consumed by the runtime
2
+ // variant resolution layer (createRenderer.ts M3). The SSOT for the manifest
3
+ // schema stays in @forgeax/engine-vite-plugin-shader; this file re-declares the
4
+ // runtime-visible subset so the engine-shader package avoids a physical import
5
+ // of the build-time plugin (physical-isolation grep gate).
6
+
7
+ /**
8
+ * Single variant within a material-shader manifest entry.
9
+ * `definesKey` is the canonical defines string (sorted `key=value` pairs joined
10
+ * with `+`). Empty key `""` denotes the default variant (all axes `true`).
11
+ *
12
+ * Authored by vite-plugin-shader Cartesian compile at buildStart; consumed by
13
+ * createRenderer variant resolution at engine boot (plan-strategy D-6).
14
+ *
15
+ * feat-20260613 fix-issue-2 (D-1 / D-2 bindingLayout cut): the runtime
16
+ * derives the BGL from `derive(paramSchema).bglEntries`; manifest entries
17
+ * no longer carry a bindingLayout sidecar (§Change stance: no v1/v2
18
+ * dual-path, no incremental drop window).
19
+ */
20
+ export interface MaterialShaderManifestVariant {
21
+ readonly definesKey: string;
22
+ readonly defines: Record<string, boolean>;
23
+ readonly composedWgsl: string;
24
+ }
25
+
26
+ /**
27
+ * A material-shader entry in manifest.json carrying zero or more variants.
28
+ *
29
+ * When `variants` is empty the entry is single-variant (no `#pragma variant_axis`
30
+ * in the source). Otherwise `composedWgsl` carries the default
31
+ * (all-true) variant for backward compat; the `variants[]` array contains every
32
+ * compiled combination.
33
+ *
34
+ * For Engine.create() internal use only; AI users never see this type.
35
+ */
36
+ export interface MaterialShaderManifestEntry {
37
+ readonly identifier: string;
38
+ readonly sourcePath: string;
39
+ readonly composedWgsl: string;
40
+ readonly paramSchema: string;
41
+ readonly variants: readonly MaterialShaderManifestVariant[];
42
+ /**
43
+ * feat-20260629 M4: uvSetCount from naga vertex @location reflection.
44
+ * Populated by vite-plugin-shader at build time from compileShader output.
45
+ * The runtime reads this for clamp-to-last alias in deriveVertexBufferLayout.
46
+ */
47
+ readonly uvSetCount?: number;
48
+ }
49
+
50
+ /**
51
+ * Look up a variant by canonical defines key.
52
+ * Returns `undefined` when no variant matches the requested key.
53
+ */
54
+ export function findVariantByKey(
55
+ entry: MaterialShaderManifestEntry,
56
+ definesKey: string,
57
+ ): MaterialShaderManifestVariant | undefined {
58
+ return entry.variants.find((v) => v.definesKey === definesKey);
59
+ }
package/src/unlit.wgsl ADDED
@@ -0,0 +1,186 @@
1
+ #define_import_path forgeax_material::unlit
2
+ #import forgeax_view::common::{View, FogViewParams, FogRay, Mesh, InstanceData, view, meshes, instances, sampleMaterialTextureLinear, packSceneTemporal}
3
+ #import forgeax_view::fog::{apply_fog}
4
+
5
+ #pragma variant_axis STORAGE_BUFFER_AVAILABLE
6
+ #pragma variant_axis VERTEX_COLOR_AVAILABLE
7
+
8
+ // @forgeax/engine-shader - unlit.wgsl (M5 feat-20260511-asset-system-v1;
9
+ // refactored M5 T-18 feat-20260512-naga-oil-composition-hmr to pull View +
10
+ // Mesh via naga_oil #import; expanded feat-20260518-pbr-direct-lighting-mvp
11
+ // M2 / w8 to share binding 0-6 layout + 12-floats VsIn with pbr.wgsl).
12
+ //
13
+ // Minimal unlit material shader: world * view * proj transform + flat
14
+ // fragment output of `material.baseColor * sample(baseColorTexture)`. No
15
+ // lighting, no normal mapping, no metallic/roughness. Consumed by
16
+ // RenderSystem when the material dispatch tag resolves to 'unlit'
17
+ // (plan-strategy D-P4 / requirements AC-07). The pipeline binds:
18
+ //
19
+ // @group(0) @binding(0) view uniform (see common.wgsl;
20
+ // unlit only reads
21
+ // worldViewProj)
22
+ // @group(1) @binding(0) material uniform (vec4 baseColor;
23
+ // metallic/roughness
24
+ // unused on this path)
25
+ // @group(1) @binding(1) baseColorSampler sampler
26
+ // @group(1) @binding(2) baseColorTexture texture_2d<f32>
27
+ // @group(2) @binding(0) meshes storage (see common.wgsl;
28
+ // normalMatrix not
29
+ // consumed in unlit)
30
+ // @group(3) @binding(0) instances storage (per-instance
31
+ // localFromInstance mat4;
32
+ // indexed by @builtin
33
+ // (instance_index);
34
+ // see common.wgsl)
35
+ //
36
+ // Material bindings are derived from the built-in paramSchema. Procedural
37
+ // geometry (M4) emits 12-floats vertex stride
38
+ // (pos+normal+uv+tangent); BUILTIN_CUBE / TRIANGLE keep 6-floats stride and
39
+ // route to a dedicated unlit pipeline branch wired by RenderSystem (M3 w22).
40
+ // This shader file consumes the 12-floats path; the 6-floats path is the
41
+ // vertex pipeline branch's responsibility.
42
+
43
+ struct Material {
44
+ baseColor : vec4<f32>,
45
+ alphaCutoff : f32,
46
+ baseColorTextureCoordinatesTransform : vec4<f32>,
47
+ baseColorTextureCoordinatesMetadata : vec4<f32>,
48
+ };
49
+
50
+ @group(1) @binding(0) var<uniform> material : Material;
51
+ @group(1) @binding(1) var baseColorSampler : sampler;
52
+ @group(1) @binding(2) var baseColorTexture : texture_2d<f32>;
53
+
54
+ // Preserve filtering reflection for the bound texture passed to the helper.
55
+ fn materialTextureFilteringWitness() {
56
+ let base = baseColorTexture;
57
+ let baseWitness = textureSample(base, baseColorSampler, vec2<f32>(0.0));
58
+ }
59
+
60
+ struct VsIn {
61
+ @location(0) pos : vec3<f32>,
62
+ @location(1) normal : vec3<f32>,
63
+ @location(2) uv : vec2<f32>,
64
+ @location(3) tangent : vec4<f32>,
65
+ #ifdef VERTEX_COLOR_AVAILABLE
66
+ @location(13) color : vec4<f32>,
67
+ #endif
68
+ };
69
+ struct VsOut {
70
+ @builtin(position) clip : vec4<f32>,
71
+ @location(0) uv : vec2<f32>,
72
+ @location(1) worldPos : vec3<f32>,
73
+ #ifdef VERTEX_COLOR_AVAILABLE
74
+ @location(14) color : vec4<f32>,
75
+ #endif
76
+ };
77
+
78
+ @vertex
79
+ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
80
+ // feat-20260604-instances-per-instance-transform-shader-group3-bin M1 / w5:
81
+ // entity world from meshes[0] (dynamic-offset window), per-instance local
82
+ // from instances[idx] (flat @group(3) buffer indexed by instance_index).
83
+ // Combine: entity_world * per_instance_local.
84
+ let world = meshes[0].worldFromLocal * instances[idx].localFromInstance * vec4<f32>(in.pos, 1.0);
85
+ var out : VsOut;
86
+ out.clip = view.worldViewProj * world;
87
+ out.uv = in.uv;
88
+ out.worldPos = world.xyz;
89
+ #ifdef VERTEX_COLOR_AVAILABLE
90
+ out.color = in.color;
91
+ #endif
92
+ return out;
93
+ }
94
+
95
+ fn materialVertexColor(in : VsOut) -> vec4<f32> {
96
+ #ifdef VERTEX_COLOR_AVAILABLE
97
+ return in.color;
98
+ #else
99
+ return vec4<f32>(1.0);
100
+ #endif
101
+ }
102
+
103
+ fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
104
+ var origin = viewParams.cameraPos;
105
+ var direction = normalize(worldPos - origin);
106
+ var rayDistance = length(worldPos - origin);
107
+ if (viewParams.temporalProjection.z >= 0.5) {
108
+ let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
109
+ let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
110
+ let nearPoint = nearH.xyz / nearH.w;
111
+ let farPoint = farH.xyz / farH.w;
112
+ direction = normalize(farPoint - nearPoint);
113
+ origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
114
+ rayDistance = max(dot(worldPos - origin, direction), 0.0);
115
+ }
116
+ return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
117
+ }
118
+
119
+ @fragment
120
+ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
121
+ let texSample = sampleMaterialTextureLinear(baseColorTexture, baseColorSampler, in.uv, material.baseColorTextureCoordinatesMetadata.zw);
122
+ let vertexColor = materialVertexColor(in);
123
+ let alpha = material.baseColor.a * texSample.a * vertexColor.a;
124
+ if (material.alphaCutoff > 0.0 && alpha < material.alphaCutoff) {
125
+ discard;
126
+ }
127
+ return applySceneFog(
128
+ view,
129
+ material.baseColor.rgb * texSample.rgb * vertexColor.rgb,
130
+ alpha,
131
+ in.worldPos,
132
+ );
133
+ }
134
+
135
+ struct TemporalVsOut {
136
+ @builtin(position) clip : vec4<f32>,
137
+ @location(0) uv : vec2<f32>,
138
+ @location(1) @interpolate(linear) currentClip : vec4<f32>,
139
+ @location(2) @interpolate(linear) previousClip : vec4<f32>,
140
+ #ifdef VERTEX_COLOR_AVAILABLE
141
+ @location(14) color : vec4<f32>,
142
+ #endif
143
+ };
144
+
145
+ @vertex
146
+ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
147
+ let currentWorld =
148
+ meshes[0].worldFromLocal * instances[idx].localFromInstance * vec4<f32>(in.pos, 1.0);
149
+ var previousWorld = currentWorld;
150
+ #if STORAGE_BUFFER_AVAILABLE == true
151
+ previousWorld = meshes[0].previousWorldFromLocal *
152
+ instances[idx].previousLocalFromInstance * vec4<f32>(in.pos, 1.0);
153
+ #endif
154
+ var out : TemporalVsOut;
155
+ out.currentClip = view.temporalCurrentViewProj * currentWorld;
156
+ out.clip = out.currentClip;
157
+ out.previousClip = view.temporalPreviousViewProj * previousWorld;
158
+ out.uv = in.uv;
159
+ #ifdef VERTEX_COLOR_AVAILABLE
160
+ out.color = in.color;
161
+ #endif
162
+ return out;
163
+ }
164
+
165
+ fn temporalVertexColor(in : TemporalVsOut) -> vec4<f32> {
166
+ #ifdef VERTEX_COLOR_AVAILABLE
167
+ return in.color;
168
+ #else
169
+ return vec4<f32>(1.0);
170
+ #endif
171
+ }
172
+
173
+ @fragment
174
+ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
175
+ let texSample = sampleMaterialTextureLinear(baseColorTexture, baseColorSampler, in.uv, material.baseColorTextureCoordinatesMetadata.zw);
176
+ let vertexColor = temporalVertexColor(in);
177
+ let alpha = material.baseColor.a * texSample.a * vertexColor.a;
178
+ if (material.alphaCutoff > 0.0 && alpha < material.alphaCutoff) {
179
+ discard;
180
+ }
181
+ var reactive = 1.0;
182
+ #if STORAGE_BUFFER_AVAILABLE == true
183
+ reactive = meshes[0].temporal.x;
184
+ #endif
185
+ return packSceneTemporal(in.currentClip, in.previousClip, reactive);
186
+ }