@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,72 @@
1
+ #define_import_path forgeax_environment::cubemap
2
+
3
+ // @forgeax/engine-shader - atmosphere-cubemap.wgsl
4
+ //
5
+ // The sole production caller of preetham_sky_radiance. Background and IBL
6
+ // consumers sample this generated cube; neither owns another evaluator.
7
+
8
+ #import forgeax_environment::preetham::{preetham_sky_radiance}
9
+
10
+ struct AtmosphereCubeParams {
11
+ sunDirection: vec3<f32>,
12
+ sunIlluminance: f32,
13
+ sunColor: vec3<f32>,
14
+ _sunColorPad: f32,
15
+ turbidity: f32,
16
+ rayleigh: f32,
17
+ mieCoefficient: f32,
18
+ mieDirectionalG: f32,
19
+ sunAngularRadius: f32,
20
+ sunDiscEnabled: f32,
21
+ _tailPad: vec2<f32>,
22
+ };
23
+
24
+ struct AtmosphereCubeVsIn {
25
+ // xy is clip-space; z is a one-based cube-face tag supplied by the
26
+ // producer's six fullscreen triangles.
27
+ @location(0) faceVertex: vec3<f32>,
28
+ };
29
+
30
+ struct AtmosphereCubeVsOut {
31
+ @builtin(position) clip: vec4<f32>,
32
+ @location(0) direction: vec3<f32>,
33
+ };
34
+
35
+ @group(0) @binding(0) var<uniform> atmosphere: AtmosphereCubeParams;
36
+
37
+ @vertex
38
+ fn atmosphere_cubemap_vs(input: AtmosphereCubeVsIn) -> AtmosphereCubeVsOut {
39
+ var output: AtmosphereCubeVsOut;
40
+ let face = u32(input.faceVertex.z) - 1u;
41
+ let x = input.faceVertex.x;
42
+ let y = input.faceVertex.y;
43
+ output.clip = vec4<f32>(x, y, 0.5, 1.0);
44
+ switch face {
45
+ case 0u: { output.direction = vec3<f32>(1.0, -y, -x); }
46
+ case 1u: { output.direction = vec3<f32>(-1.0, -y, x); }
47
+ // Cube consumers negate Y for the shared OpenGL-authored convention.
48
+ // Clip-space Y is also inverted by the attachment viewport. The four
49
+ // side faces already compose both transforms through `-y`; the Y-axis
50
+ // faces must encode their world sign and Z orientation explicitly.
51
+ case 2u: { output.direction = vec3<f32>(x, -1.0, -y); }
52
+ case 3u: { output.direction = vec3<f32>(x, 1.0, y); }
53
+ case 4u: { output.direction = vec3<f32>(x, -y, 1.0); }
54
+ default: { output.direction = vec3<f32>(-x, -y, -1.0); }
55
+ }
56
+ return output;
57
+ }
58
+
59
+ @fragment
60
+ fn atmosphere_cubemap_fs(input: AtmosphereCubeVsOut) -> @location(0) vec4<f32> {
61
+ let radiance = preetham_sky_radiance(
62
+ normalize(input.direction),
63
+ atmosphere.sunDirection,
64
+ atmosphere.sunColor,
65
+ atmosphere.sunIlluminance,
66
+ atmosphere.turbidity,
67
+ atmosphere.rayleigh,
68
+ atmosphere.mieCoefficient,
69
+ atmosphere.mieDirectionalG,
70
+ );
71
+ return vec4<f32>(radiance, 1.0);
72
+ }
@@ -0,0 +1,127 @@
1
+ #define_import_path forgeax_environment::ibl
2
+
3
+ // @forgeax/engine-shader - atmosphere-ibl.wgsl
4
+ //
5
+ // The analytic atmosphere evaluator writes only the sky cube. Diffuse and
6
+ // specular consumers derive their own products from that cube here, keeping
7
+ // the Sun disc out of every IBL payload and preventing a second direct-Sun
8
+ // injection. The graph selects one entry point per producer kind.
9
+
10
+ #import forgeax_pbr::ibl_shared::{PI, hammersley, importanceSampleGGX}
11
+
12
+ struct AtmosphereIblParams {
13
+ sunDirection: vec3<f32>,
14
+ sunIlluminance: f32,
15
+ sunColor: vec3<f32>,
16
+ _sunColorPad: f32,
17
+ turbidity: f32,
18
+ rayleigh: f32,
19
+ mieCoefficient: f32,
20
+ mieDirectionalG: f32,
21
+ sunAngularRadius: f32,
22
+ sunDiscEnabled: f32,
23
+ prefilterRoughness: f32,
24
+ producerKind: f32,
25
+ };
26
+
27
+ struct AtmosphereIblVsIn {
28
+ // xy is clip-space; z is the one-based cube-face tag supplied by the
29
+ // producer's six fullscreen triangles.
30
+ @location(0) faceVertex: vec3<f32>,
31
+ };
32
+
33
+ struct AtmosphereIblVsOut {
34
+ @builtin(position) clip: vec4<f32>,
35
+ @location(0) direction: vec3<f32>,
36
+ };
37
+
38
+ @group(0) @binding(0) var<uniform> atmosphere: AtmosphereIblParams;
39
+ @group(1) @binding(0) var sky: texture_cube<f32>;
40
+ @group(1) @binding(1) var skySampler: sampler;
41
+
42
+ @vertex
43
+ fn atmosphere_ibl_vs(input: AtmosphereIblVsIn) -> AtmosphereIblVsOut {
44
+ var output: AtmosphereIblVsOut;
45
+ let face = u32(input.faceVertex.z) - 1u;
46
+ let x = input.faceVertex.x;
47
+ let y = input.faceVertex.y;
48
+ output.clip = vec4<f32>(x, y, 0.5, 1.0);
49
+ switch face {
50
+ case 0u: { output.direction = vec3<f32>(1.0, -y, -x); }
51
+ case 1u: { output.direction = vec3<f32>(-1.0, -y, x); }
52
+ case 2u: { output.direction = vec3<f32>(x, -1.0, -y); }
53
+ case 3u: { output.direction = vec3<f32>(x, 1.0, y); }
54
+ case 4u: { output.direction = vec3<f32>(x, -y, 1.0); }
55
+ default: { output.direction = vec3<f32>(-x, -y, -1.0); }
56
+ }
57
+ return output;
58
+ }
59
+
60
+ fn atmosphere_ibl_tangent_frame(normal: vec3<f32>) -> mat3x3<f32> {
61
+ let up = select(
62
+ vec3<f32>(0.0, 1.0, 0.0),
63
+ vec3<f32>(1.0, 0.0, 0.0),
64
+ abs(normal.y) > 0.999,
65
+ );
66
+ let tangent = normalize(cross(up, normal));
67
+ let bitangent = normalize(cross(normal, tangent));
68
+ return mat3x3<f32>(tangent, bitangent, normal);
69
+ }
70
+
71
+ @fragment
72
+ fn atmosphere_irradiance_fs(input: AtmosphereIblVsOut) -> @location(0) vec4<f32> {
73
+ let normal = normalize(input.direction);
74
+ let frame = atmosphere_ibl_tangent_frame(normal);
75
+ var irradiance = vec3<f32>(0.0);
76
+ var sampleCount: f32 = 0.0;
77
+ var phiIndex: u32 = 0u;
78
+ while (phiIndex < 16u) {
79
+ let phi = 2.0 * PI * f32(phiIndex) / 16.0;
80
+ var thetaIndex: u32 = 0u;
81
+ while (thetaIndex < 8u) {
82
+ let theta = 0.5 * PI * (f32(thetaIndex) + 0.5) / 8.0;
83
+ let tangentSample = vec3<f32>(
84
+ sin(theta) * cos(phi),
85
+ sin(theta) * sin(phi),
86
+ cos(theta),
87
+ );
88
+ let sampleDirection = frame * tangentSample;
89
+ let weight = cos(theta) * sin(theta);
90
+ let cubeSampleDirection = vec3<f32>(
91
+ sampleDirection.x,
92
+ -sampleDirection.y,
93
+ sampleDirection.z,
94
+ );
95
+ irradiance += textureSampleLevel(sky, skySampler, cubeSampleDirection, 0.0).rgb * weight;
96
+ sampleCount += 1.0;
97
+ thetaIndex = thetaIndex + 1u;
98
+ }
99
+ phiIndex = phiIndex + 1u;
100
+ }
101
+ return vec4<f32>(PI * irradiance / max(sampleCount, 1.0), 1.0);
102
+ }
103
+
104
+ @fragment
105
+ fn atmosphere_prefilter_fs(input: AtmosphereIblVsOut) -> @location(0) vec4<f32> {
106
+ let normal = normalize(input.direction);
107
+ let viewDirection = normal;
108
+ let roughness = clamp(atmosphere.prefilterRoughness, 0.04, 1.0);
109
+ var prefiltered = vec3<f32>(0.0);
110
+ var totalWeight: f32 = 0.0;
111
+ const sampleCount: u32 = 64u;
112
+ for (var index: u32 = 0u; index < sampleCount; index = index + 1u) {
113
+ let halfVector = importanceSampleGGX(hammersley(index, sampleCount), normal, roughness);
114
+ let lightDirection = normalize(2.0 * dot(viewDirection, halfVector) * halfVector - viewDirection);
115
+ let normalLight = max(dot(normal, lightDirection), 0.0);
116
+ if (normalLight > 0.0) {
117
+ let cubeLightDirection = vec3<f32>(
118
+ lightDirection.x,
119
+ -lightDirection.y,
120
+ lightDirection.z,
121
+ );
122
+ prefiltered += textureSampleLevel(sky, skySampler, cubeLightDirection, 0.0).rgb * normalLight;
123
+ totalWeight += normalLight;
124
+ }
125
+ }
126
+ return vec4<f32>(prefiltered / max(totalWeight, 0.001), 1.0);
127
+ }
@@ -0,0 +1,214 @@
1
+ #define_import_path forgeax_environment::preetham
2
+
3
+ // @forgeax/engine-shader - atmosphere-preetham.wgsl
4
+ //
5
+ // Build-time Preetham/Perez daylight evaluator. The cubemap producer is the
6
+ // only caller. Background composition owns the sun disc separately so this
7
+ // module cannot accidentally bake the disc into sky or IBL radiance.
8
+
9
+ const PREETHAM_ALGORITHM_REVISION : u32 = 1999u;
10
+ const PREETHAM_PI : f32 = 3.14159265359;
11
+ const PREETHAM_YXY_EPSILON : f32 = 1e-5;
12
+ const PREETHAM_RADIANCE_MAX : f32 = 65504.0;
13
+
14
+ // Each vec2 stores the turbidity slope and intercept for Perez A/B/C/D/E.
15
+ // Keeping Y, x and y independent is essential: they are three distributions,
16
+ // not one luminance curve followed by invented chromaticity.
17
+ const PREETHAM_PEREZ_Y_COEFFICIENTS : array<vec2<f32>, 5> = array<vec2<f32>, 5>(
18
+ vec2<f32>(0.1787, -1.4630),
19
+ vec2<f32>(-0.3554, 0.4275),
20
+ vec2<f32>(-0.0227, 5.3251),
21
+ vec2<f32>(0.1206, -2.5771),
22
+ vec2<f32>(-0.0670, 0.3703),
23
+ );
24
+
25
+ const PREETHAM_PEREZ_X_COEFFICIENTS : array<vec2<f32>, 5> = array<vec2<f32>, 5>(
26
+ vec2<f32>(-0.0193, -0.2592),
27
+ vec2<f32>(-0.0665, 0.0008),
28
+ vec2<f32>(-0.0004, 0.2125),
29
+ vec2<f32>(-0.0641, -0.8989),
30
+ vec2<f32>(-0.0033, 0.0452),
31
+ );
32
+
33
+ const PREETHAM_PEREZ_SMALL_Y_COEFFICIENTS : array<vec2<f32>, 5> = array<vec2<f32>, 5>(
34
+ vec2<f32>(-0.0167, -0.2608),
35
+ vec2<f32>(-0.0950, 0.0092),
36
+ vec2<f32>(-0.0079, 0.2102),
37
+ vec2<f32>(-0.0441, -1.6537),
38
+ vec2<f32>(-0.0109, 0.0529),
39
+ );
40
+
41
+ struct PreethamPerezCoefficients {
42
+ a: f32,
43
+ b: f32,
44
+ c: f32,
45
+ d: f32,
46
+ e: f32,
47
+ };
48
+
49
+ fn preetham_finite_guard(value: vec3<f32>) -> vec3<f32> {
50
+ // Inputs are validated before the producer reaches this module. Clamp the
51
+ // output to the finite half-float domain as a second shader-side guard.
52
+ return clamp(value, vec3<f32>(0.0), vec3<f32>(PREETHAM_RADIANCE_MAX));
53
+ }
54
+
55
+ fn preetham_yxy_to_linear_srgb(yxy: vec3<f32>) -> vec3<f32> {
56
+ let Y = max(yxy.x, 0.0);
57
+ let x = clamp(yxy.y, 0.0, 1.0);
58
+ let y = clamp(yxy.z, PREETHAM_YXY_EPSILON, 1.0);
59
+ let X = Y * x / y;
60
+ let Z = Y * max(1.0 - x - y, 0.0) / y;
61
+ let linear = vec3<f32>(
62
+ 3.2406 * X - 1.5372 * Y - 0.4986 * Z,
63
+ -0.9689 * X + 1.8758 * Y + 0.0415 * Z,
64
+ 0.0557 * X - 0.2040 * Y + 1.0570 * Z,
65
+ );
66
+ return preetham_finite_guard(max(linear, vec3<f32>(0.0)));
67
+ }
68
+
69
+ fn preetham_fit(fit: vec2<f32>, turbidity: f32) -> f32 {
70
+ return fit.x * turbidity + fit.y;
71
+ }
72
+
73
+ fn preetham_y_coefficients(turbidity: f32) -> PreethamPerezCoefficients {
74
+ return PreethamPerezCoefficients(
75
+ preetham_fit(PREETHAM_PEREZ_Y_COEFFICIENTS[0], turbidity),
76
+ preetham_fit(PREETHAM_PEREZ_Y_COEFFICIENTS[1], turbidity),
77
+ preetham_fit(PREETHAM_PEREZ_Y_COEFFICIENTS[2], turbidity),
78
+ preetham_fit(PREETHAM_PEREZ_Y_COEFFICIENTS[3], turbidity),
79
+ preetham_fit(PREETHAM_PEREZ_Y_COEFFICIENTS[4], turbidity),
80
+ );
81
+ }
82
+
83
+ fn preetham_x_coefficients(turbidity: f32) -> PreethamPerezCoefficients {
84
+ return PreethamPerezCoefficients(
85
+ preetham_fit(PREETHAM_PEREZ_X_COEFFICIENTS[0], turbidity),
86
+ preetham_fit(PREETHAM_PEREZ_X_COEFFICIENTS[1], turbidity),
87
+ preetham_fit(PREETHAM_PEREZ_X_COEFFICIENTS[2], turbidity),
88
+ preetham_fit(PREETHAM_PEREZ_X_COEFFICIENTS[3], turbidity),
89
+ preetham_fit(PREETHAM_PEREZ_X_COEFFICIENTS[4], turbidity),
90
+ );
91
+ }
92
+
93
+ fn preetham_small_y_coefficients(turbidity: f32) -> PreethamPerezCoefficients {
94
+ return PreethamPerezCoefficients(
95
+ preetham_fit(PREETHAM_PEREZ_SMALL_Y_COEFFICIENTS[0], turbidity),
96
+ preetham_fit(PREETHAM_PEREZ_SMALL_Y_COEFFICIENTS[1], turbidity),
97
+ preetham_fit(PREETHAM_PEREZ_SMALL_Y_COEFFICIENTS[2], turbidity),
98
+ preetham_fit(PREETHAM_PEREZ_SMALL_Y_COEFFICIENTS[3], turbidity),
99
+ preetham_fit(PREETHAM_PEREZ_SMALL_Y_COEFFICIENTS[4], turbidity),
100
+ );
101
+ }
102
+
103
+ fn preetham_perez(
104
+ theta: f32,
105
+ gamma: f32,
106
+ coefficients: PreethamPerezCoefficients,
107
+ ) -> f32 {
108
+ let safeCosTheta = max(cos(theta), 0.01);
109
+ let cosGamma = cos(max(gamma, 0.0));
110
+ let horizon = 1.0 + coefficients.a * exp(coefficients.b / safeCosTheta);
111
+ let circumsolar = 1.0 + coefficients.c * exp(coefficients.d * max(gamma, 0.0)) +
112
+ coefficients.e * cosGamma * cosGamma;
113
+ return max(horizon * circumsolar, PREETHAM_YXY_EPSILON);
114
+ }
115
+
116
+ fn preetham_relative_perez(
117
+ theta: f32,
118
+ gamma: f32,
119
+ sunTheta: f32,
120
+ coefficients: PreethamPerezCoefficients,
121
+ ) -> f32 {
122
+ let numerator = preetham_perez(theta, gamma, coefficients);
123
+ let zenith = preetham_perez(0.0, sunTheta, coefficients);
124
+ return numerator / max(zenith, PREETHAM_YXY_EPSILON);
125
+ }
126
+
127
+ fn preetham_zenith_yxy(sunTheta: f32, turbidity: f32) -> vec3<f32> {
128
+ let theta2 = sunTheta * sunTheta;
129
+ let theta3 = theta2 * sunTheta;
130
+ let turbidity2 = turbidity * turbidity;
131
+ let chi = (4.0 / 9.0 - turbidity / 120.0) * (PREETHAM_PI - 2.0 * sunTheta);
132
+ let Y = (4.0453 * turbidity - 4.9710) * tan(chi) -
133
+ 0.2155 * turbidity + 2.4192;
134
+ let x =
135
+ (0.00165 * theta3 - 0.00374 * theta2 + 0.00208 * sunTheta) * turbidity2 +
136
+ (-0.02902 * theta3 + 0.06377 * theta2 - 0.03202 * sunTheta + 0.00394) * turbidity +
137
+ (0.11693 * theta3 - 0.21196 * theta2 + 0.06052 * sunTheta + 0.25885);
138
+ let y =
139
+ (0.00275 * theta3 - 0.00610 * theta2 + 0.00316 * sunTheta) * turbidity2 +
140
+ (-0.04214 * theta3 + 0.08970 * theta2 - 0.04153 * sunTheta + 0.00515) * turbidity +
141
+ (0.15346 * theta3 - 0.26756 * theta2 + 0.06669 * sunTheta + 0.26688);
142
+ return vec3<f32>(max(Y, 0.0), clamp(x, 0.0, 1.0), clamp(y, PREETHAM_YXY_EPSILON, 1.0));
143
+ }
144
+
145
+ // Evaluate one HDR sky direction. Preetham Y is kcd/m2, so the 0.01 factor
146
+ // maps it to renderer radiance per a 100,000-lux daylight reference. The
147
+ // authoring Rayleigh/Mie controls remain bounded relative gains around the
148
+ // complete Perez Y/x/y distribution instead of replacing its chromaticity.
149
+ fn preetham_sky_radiance(
150
+ viewDirection: vec3<f32>,
151
+ sunDirection: vec3<f32>,
152
+ sunColor: vec3<f32>,
153
+ sunIlluminance: f32,
154
+ turbidity: f32,
155
+ rayleigh: f32,
156
+ mieCoefficient: f32,
157
+ mieDirectionalG: f32,
158
+ ) -> vec3<f32> {
159
+ let horizonClampedView = vec3<f32>(
160
+ viewDirection.x,
161
+ max(viewDirection.y, 0.0),
162
+ viewDirection.z,
163
+ );
164
+ let view = horizonClampedView /
165
+ max(length(horizonClampedView), PREETHAM_YXY_EPSILON);
166
+ let sun = sunDirection / max(length(sunDirection), PREETHAM_YXY_EPSILON);
167
+ let theta = acos(clamp(view.y, 0.0, 1.0));
168
+ let sunTheta = acos(clamp(sun.y, 0.0, 1.0));
169
+ let sunAlignment = clamp(dot(view, sun), -1.0, 1.0);
170
+ let gamma = acos(sunAlignment);
171
+ let safeTurbidity = clamp(turbidity, 1.0, 20.0);
172
+ let zenithYxy = preetham_zenith_yxy(sunTheta, safeTurbidity);
173
+ let relativeY = preetham_relative_perez(
174
+ theta,
175
+ gamma,
176
+ sunTheta,
177
+ preetham_y_coefficients(safeTurbidity),
178
+ );
179
+ let relativeX = preetham_relative_perez(
180
+ theta,
181
+ gamma,
182
+ sunTheta,
183
+ preetham_x_coefficients(safeTurbidity),
184
+ );
185
+ let relativeSmallY = preetham_relative_perez(
186
+ theta,
187
+ gamma,
188
+ sunTheta,
189
+ preetham_small_y_coefficients(safeTurbidity),
190
+ );
191
+ let forward = pow(max(sunAlignment, 0.0), mix(2.0, 32.0, clamp(mieDirectionalG, 0.0, 0.999)));
192
+ let mediumGain = max(rayleigh, 0.0) + max(mieCoefficient, 0.0) * 40.0 * (0.25 + forward);
193
+ let luminance = zenithYxy.x * relativeY * max(sunIlluminance, 0.0) * 0.01 * mediumGain;
194
+ let yxy = vec3<f32>(
195
+ max(luminance, 0.0),
196
+ clamp(zenithYxy.y * relativeX, 0.0, 1.0),
197
+ clamp(zenithYxy.z * relativeSmallY, PREETHAM_YXY_EPSILON, 1.0),
198
+ );
199
+ let tintStrength = 0.08 + 0.32 * pow(max(sunAlignment, 0.0), 4.0);
200
+ let skyTint = mix(
201
+ vec3<f32>(1.0),
202
+ max(sunColor, vec3<f32>(0.0)),
203
+ tintStrength,
204
+ );
205
+ let daylight = preetham_yxy_to_linear_srgb(yxy) * skyTint;
206
+ // Preetham is a daylight model. Below the civil-twilight boundary, blend to
207
+ // a bounded blue night gradient instead of extrapolating Perez into the
208
+ // characteristic green/purple failure that the model cannot represent.
209
+ let daylightWeight = smoothstep(-0.12, 0.02, sun.y);
210
+ let nightHorizon = vec3<f32>(0.008, 0.012, 0.024);
211
+ let nightZenith = vec3<f32>(0.0015, 0.004, 0.016);
212
+ let night = mix(nightHorizon, nightZenith, pow(clamp(view.y, 0.0, 1.0), 0.35));
213
+ return preetham_finite_guard(mix(night, daylight, daylightWeight));
214
+ }
@@ -0,0 +1,89 @@
1
+ #define_import_path forgeax_view::bloom_blur
2
+
3
+ // @forgeax/engine-shader - bloom-blur.wgsl
4
+ // (feat-20260531-bloom-first-declarative-render-graph-pass / w7).
5
+ //
6
+ // Fragment-stage separable Gaussian blur: 5-weight kernel (radius 4,
7
+ // 9 taps per pass -- centre + 4 left + 4 right) sampled via
8
+ // textureSampleLevel(tex, samp, uv, 0.0) for uniform-control-flow safety.
9
+ //
10
+ // Design (plan-strategy D-1, D-4): NO if(horizontal) branch in this
11
+ // shader. The axis direction is baked per-pipeline at assembly by the
12
+ // host: the H-pipeline passes texelSize=(1/textureW, 0), the V-pipeline
13
+ // passes texelSize=(0, 1/textureH). The shader always applies the same
14
+ // offset loop -- the direction emerges from the UBO texelSize parameter.
15
+ //
16
+ // Kernel weights (LO 5.7, radius 4, sigma ~1.0):
17
+ // centre: 0.227027
18
+ // +-1: 0.1945946
19
+ // +-2: 0.1216216
20
+ // +-3: 0.054054
21
+ // +-4: 0.016216
22
+ // Sum: ~0.99854 (truncated Gaussian slight darkening is normal).
23
+ //
24
+ // The radius parameter (1.0-4.0 clamped) controls how many taps are
25
+ // active: floor-clamp(radius) selects the range index [1,4], with the
26
+ // centre weight renormalised to keep the sum near 1.0.
27
+ //
28
+ // Vertex stage: imports the SSOT fullscreen_triangle() from
29
+ // forgeax_view::common (single large triangle).
30
+ //
31
+ // Bindings (group 0):
32
+ // @binding(0) src : texture_2d<f32> -- sampled input texture
33
+ // @binding(1) samp : sampler -- filterable sampler
34
+ // @binding(2) params : BloomBlurParams (UBO) -- texelSize + radius (16B std140)
35
+ //
36
+ // Content marker (marker triage): bloomBlurDir
37
+ // linearHdrColorDomain: bloom samples and writes linear HDR values only.
38
+
39
+ #import forgeax_view::common::FullscreenOutput
40
+ #import forgeax_view::common::fullscreen_triangle
41
+
42
+ struct BloomBlurParams {
43
+ texelSizeX : f32,
44
+ texelSizeY : f32,
45
+ radius : f32,
46
+ pad0 : f32,
47
+ };
48
+
49
+ @group(0) @binding(0) var src : texture_2d<f32>;
50
+ @group(0) @binding(1) var samp : sampler;
51
+ @group(0) @binding(2) var<uniform> params : BloomBlurParams;
52
+
53
+ @vertex
54
+ fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
55
+ return fullscreen_triangle(vertex_index);
56
+ }
57
+
58
+ @fragment
59
+ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
60
+ let texelSize : vec2<f32> = vec2<f32>(params.texelSizeX, params.texelSizeY);
61
+ let r : f32 = clamp(params.radius, 1.0, 4.0);
62
+ let center : vec3<f32> = textureSampleLevel(src, samp, in.uv, 0.0).rgb;
63
+ var result : vec3<f32> = center * 0.227027;
64
+ // bloomBlurDir
65
+ result += textureSampleLevel(src, samp, in.uv + texelSize * 1.0, 0.0).rgb * 0.1945946;
66
+ result += textureSampleLevel(src, samp, in.uv - texelSize * 1.0, 0.0).rgb * 0.1945946;
67
+ if r >= 2.0 {
68
+ result += textureSampleLevel(src, samp, in.uv + texelSize * 2.0, 0.0).rgb * 0.1216216;
69
+ result += textureSampleLevel(src, samp, in.uv - texelSize * 2.0, 0.0).rgb * 0.1216216;
70
+ } else {
71
+ result += center * 0.1216216;
72
+ result += center * 0.1216216;
73
+ }
74
+ if r >= 3.0 {
75
+ result += textureSampleLevel(src, samp, in.uv + texelSize * 3.0, 0.0).rgb * 0.054054;
76
+ result += textureSampleLevel(src, samp, in.uv - texelSize * 3.0, 0.0).rgb * 0.054054;
77
+ } else {
78
+ result += center * 0.054054;
79
+ result += center * 0.054054;
80
+ }
81
+ if r >= 4.0 {
82
+ result += textureSampleLevel(src, samp, in.uv + texelSize * 4.0, 0.0).rgb * 0.016216;
83
+ result += textureSampleLevel(src, samp, in.uv - texelSize * 4.0, 0.0).rgb * 0.016216;
84
+ } else {
85
+ result += center * 0.016216;
86
+ result += center * 0.016216;
87
+ }
88
+ return vec4<f32>(result, 1.0);
89
+ }
@@ -0,0 +1,50 @@
1
+ #define_import_path forgeax_view::bloom_bright
2
+
3
+ // @forgeax/engine-shader - bloom-bright.wgsl
4
+ // (feat-20260531-bloom-first-declarative-render-graph-pass / w6).
5
+ //
6
+ // Fragment-stage bright-pass: extracts pixels whose Rec.709 relative
7
+ // luminance exceeds the threshold parameter, outputting to a 1/2-res
8
+ // rgba16float intermediate texture. Pixels below threshold are zeroed.
9
+ //
10
+ // Vertex stage: imports the SSOT fullscreen_triangle() from
11
+ // forgeax_view::common (single large triangle).
12
+ //
13
+ // Fragment stage: computes Rec.709 luminance via
14
+ // L = dot(rgb, vec3(0.2126, 0.7152, 0.0722))
15
+ // and outputs rgb when L > threshold else vec3(0).
16
+ //
17
+ // Bindings (group 0):
18
+ // @binding(0) hdrColor : texture_2d<f32> -- sampled HDR color attachment
19
+ // @binding(1) samp : sampler -- filterable sampler
20
+ // @binding(2) params : BloomBrightParams (UBO) -- threshold + pad (16B std140)
21
+ //
22
+ // Content marker (marker triage): bloomBrightExtract
23
+
24
+ #import forgeax_view::common::FullscreenOutput
25
+ #import forgeax_view::common::fullscreen_triangle
26
+
27
+ struct BloomBrightParams {
28
+ threshold : f32,
29
+ pad0 : f32,
30
+ pad1 : f32,
31
+ pad2 : f32,
32
+ };
33
+
34
+ @group(0) @binding(0) var hdrColor : texture_2d<f32>;
35
+ @group(0) @binding(1) var samp : sampler;
36
+ @group(0) @binding(2) var<uniform> params : BloomBrightParams;
37
+
38
+ @vertex
39
+ fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
40
+ return fullscreen_triangle(vertex_index);
41
+ }
42
+
43
+ @fragment
44
+ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
45
+ let color : vec3<f32> = textureSampleLevel(hdrColor, samp, in.uv, 0.0).rgb;
46
+ let luma : f32 = dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
47
+ // bloomBrightExtract
48
+ let bright : vec3<f32> = select(vec3<f32>(0.0), color, luma > params.threshold);
49
+ return vec4<f32>(bright, 1.0);
50
+ }
@@ -0,0 +1,54 @@
1
+ #define_import_path forgeax_view::bloom_composite
2
+
3
+ // @forgeax/engine-shader - bloom-composite.wgsl
4
+ // (feat-20260531-bloom-first-declarative-render-graph-pass / w8).
5
+ //
6
+ // Fragment-stage additive HDR composite: blends the blurred bloom result
7
+ // back onto the original HDR scene color before tone mapping.
8
+ //
9
+ // Formula (LO 5.7 + intensity parameter):
10
+ // hdrOut = hdrScene + intensity * bloomBlurV
11
+ //
12
+ // This pass runs at full resolution. The blurred bloom texture is at 1/2
13
+ // resolution but textureSampleLevel with bilinear filtering naturally
14
+ // upsamples to the full-resolution render target.
15
+ //
16
+ // Vertex stage: imports the SSOT fullscreen_triangle() from
17
+ // forgeax_view::common (single large triangle).
18
+ //
19
+ // Bindings (group 0):
20
+ // @binding(0) hdrColor : texture_2d<f32> -- original HDR scene color
21
+ // @binding(1) bloomBlurV : texture_2d<f32> -- blurred bloom (V-pass output)
22
+ // @binding(2) samp : sampler -- filterable sampler
23
+ // @binding(3) params : BloomCompositeParams (UBO) -- intensity + pad (16B std140)
24
+ //
25
+ // Content marker (marker triage): bloomComposite
26
+
27
+ #import forgeax_view::common::FullscreenOutput
28
+ #import forgeax_view::common::fullscreen_triangle
29
+
30
+ struct BloomCompositeParams {
31
+ intensity : f32,
32
+ pad0 : f32,
33
+ pad1 : f32,
34
+ pad2 : f32,
35
+ };
36
+
37
+ @group(0) @binding(0) var hdrColor : texture_2d<f32>;
38
+ @group(0) @binding(1) var bloomBlurV : texture_2d<f32>;
39
+ @group(0) @binding(2) var samp : sampler;
40
+ @group(0) @binding(3) var<uniform> params : BloomCompositeParams;
41
+
42
+ @vertex
43
+ fn vs_main(@builtin(vertex_index) vertex_index : u32) -> FullscreenOutput {
44
+ return fullscreen_triangle(vertex_index);
45
+ }
46
+
47
+ @fragment
48
+ fn fs_main(in : FullscreenOutput) -> @location(0) vec4<f32> {
49
+ let scene : vec3<f32> = textureSampleLevel(hdrColor, samp, in.uv, 0.0).rgb;
50
+ let bloom : vec3<f32> = textureSampleLevel(bloomBlurV, samp, in.uv, 0.0).rgb;
51
+ // bloomComposite
52
+ let result : vec3<f32> = scene + params.intensity * bloom;
53
+ return vec4<f32>(result, 1.0);
54
+ }
package/src/brdf.wgsl ADDED
@@ -0,0 +1,25 @@
1
+ #define_import_path forgeax_pbr::brdf
2
+
3
+ // @forgeax/engine-shader - brdf.wgsl (M5 T-18 feat-20260512-naga-oil-composition-hmr).
4
+ //
5
+ // Cook-Torrance BRDF helpers (D_GGX + V_SmithCorrelated + F_Schlick)
6
+ // extracted from pbr.wgsl via naga_oil #define_import_path / #import
7
+ // (D-04 moduleId convention). Pure helpers, no bindings, no state -
8
+ // safe to reuse from any material shader that needs microfacet specular.
9
+
10
+ fn d_ggx(nDotH : f32, a : f32) -> f32 {
11
+ let a2 = a * a;
12
+ let f = (nDotH * a2 - nDotH) * nDotH + 1.0;
13
+ return a2 / (3.14159265 * f * f);
14
+ }
15
+
16
+ fn v_smith(nDotV : f32, nDotL : f32, a : f32) -> f32 {
17
+ let a2 = a * a;
18
+ let gv = nDotL * sqrt(nDotV * nDotV * (1.0 - a2) + a2);
19
+ let gl = nDotV * sqrt(nDotL * nDotL * (1.0 - a2) + a2);
20
+ return 0.5 / max(gv + gl, 1e-5);
21
+ }
22
+
23
+ fn f_schlick(vDotH : f32, f0 : vec3<f32>) -> vec3<f32> {
24
+ return f0 + (vec3<f32>(1.0) - f0) * pow(1.0 - vDotH, 5.0);
25
+ }