@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,538 @@
1
+ #import forgeax_view::common::{View, FogViewParams, FogRay, Mesh, InstanceData, view, meshes, instances, PointLight, SpotLight, pointLightsBuffer, spotLightsBuffer, shadowMap, shadowSampler, sampleMaterialTexture}
2
+ #import forgeax_pbr::temporal::{projectPbrSceneTemporal}
3
+ #import forgeax_pbr::brdf::{f_schlick, v_smith, d_ggx}
4
+ #import forgeax_pbr::ibl_sampling::{sampleIblDiffuse, sampleIblSpecular}
5
+ #import forgeax_pbr::tbn::{decodeTangentSpaceNormalRg, scaleTangentSpaceNormal, applyTBN}
6
+ #import forgeax_pbr::lighting_directional::{evalDirectionalNoShadow, evalDirectionalShadowFactor}
7
+ #import forgeax_pbr::lighting_punctual::{evalPoint, evalSpot}
8
+ #import forgeax_view::fog::{apply_fog}
9
+ #ifdef POINT_SHADOW_AVAILABLE
10
+ #import forgeax_pbr::lighting_punctual::{evalPointShadowed}
11
+ #import forgeax_view::common::{shadowParams}
12
+ #endif
13
+
14
+ #define_import_path forgeax_material::pbr-skin
15
+ #pragma variant_axis STORAGE_BUFFER_AVAILABLE
16
+ #pragma variant_axis VERTEX_COLOR_AVAILABLE
17
+
18
+ // @forgeax/engine-shader - default-standard-pbr-skin.wgsl
19
+ // (feat-20260523-skin-skeleton-animation M3 / T-29).
20
+ //
21
+ // Engine-shipped default standard PBR material shader with GPU skinning,
22
+ // registered under the reserved path identifier `forgeax::pbr-skin`
23
+ // (plan-strategy D-3). Fragment stage is byte-for-byte identical to
24
+ // default-standard-pbr.wgsl — the two shaders share the same PBR/IBL/TBN/
25
+ // lighting helpers via #import. The vertex stage adds 4-bone weighted
26
+ // skinning before the worldFromLocal transform.
27
+ //
28
+ // Bindings (4 BG layout slots; @group(0) View / @group(1) Material+Texture
29
+ // / @group(2) Meshes+Palette — identical to default-standard-pbr except
30
+ // @group(2)@binding(1) adds the skin palette storage buffer):
31
+ //
32
+ // @group(0) @binding(0) view uniform (see common.wgsl)
33
+ // @group(1) @binding(0) material uniform
34
+ // @group(1) @binding(1) baseColorSampler sampler
35
+ // @group(1) @binding(2) baseColorTexture texture_2d<f32>
36
+ // @group(1) @binding(3) metallicRoughnessSampler sampler
37
+ // @group(1) @binding(4) metallicRoughnessTexture texture_2d<f32>
38
+ // @group(1) @binding(5) normalSampler sampler
39
+ // @group(1) @binding(6) normalTexture texture_2d<f32>
40
+ // @group(1) @binding(7) specularTintSampler + @binding(8) specularTintTexture
41
+ // @group(1) @binding(9..15) Skylight (irradiance / prefilter / brdfLut +
42
+ // samplers) + skylight uniform (intensity)
43
+ // @group(2) @binding(0) meshes storage (worldFromLocal mat4
44
+ // + normalMatrix mat3,
45
+ // see common.wgsl)
46
+ // @group(2) @binding(1) palette storage (array of joint
47
+ // skinning mat4x4,
48
+ // CPU-precomputed
49
+ // world * IBM)
50
+ // @group(3) @binding(0) instances storage (per-instance
51
+ // localFromInstance
52
+ // mat4; see
53
+ // common.wgsl —
54
+ // preventive
55
+ // structural
56
+ // alignment:
57
+ // SkinInstances-
58
+ // CoexistForbidden
59
+ // blocks skin +
60
+ // instances, so
61
+ // instances[idx]
62
+ // = I identity)
63
+ //
64
+ // Skinning formula (plan-strategy D-3 / D-3a):
65
+ // world_pos = Sum(w_i * palette[base + skinIndex[i]] * local_pos)
66
+ // world_norm = transpose(inverse(mat3x3(skin_matrix))) * local_normal
67
+ // 4 joints max; weighted sum of skinned positions from the palette buffer
68
+ // indexed by the per-vertex skinIndex vector.
69
+
70
+ struct Material {
71
+ baseColor : vec4<f32>,
72
+ metallic : f32,
73
+ roughness : f32,
74
+ // Channel selectors (D-8): 4 independent f32 entries split out of the
75
+ // legacy `channelMap : vec4<u32>`. See default-standard-pbr.wgsl for
76
+ // the full rationale; the skin shader shares the same merged UBO shape.
77
+ metallicChannel : f32,
78
+ roughnessChannel : f32,
79
+ aoChannel : f32,
80
+ extraChannel : f32,
81
+ // vec3 align=16 inserts implicit padding so emissive lands at offset 48;
82
+ // the authored schema rounds to 112 B (matches default-standard-pbr SSOT).
83
+ emissive : vec3<f32>,
84
+ emissiveIntensity : f32,
85
+ occlusionStrength : f32,
86
+ alphaCutoff : f32,
87
+ clearcoat : f32,
88
+ clearcoatRoughness : f32,
89
+ specularTint : vec3<f32>,
90
+ normalScale : f32,
91
+ baseColorTextureCoordinatesTransform : vec4<f32>,
92
+ baseColorTextureCoordinatesMetadata : vec4<f32>,
93
+ metallicRoughnessTextureCoordinatesTransform : vec4<f32>,
94
+ metallicRoughnessTextureCoordinatesMetadata : vec4<f32>,
95
+ normalTextureCoordinatesTransform : vec4<f32>,
96
+ normalTextureCoordinatesMetadata : vec4<f32>,
97
+ specularTintTextureCoordinatesTransform : vec4<f32>,
98
+ specularTintTextureCoordinatesMetadata : vec4<f32>,
99
+ emissiveTextureCoordinatesTransform : vec4<f32>,
100
+ emissiveTextureCoordinatesMetadata : vec4<f32>,
101
+ occlusionTextureCoordinatesTransform : vec4<f32>,
102
+ occlusionTextureCoordinatesMetadata : vec4<f32>,
103
+ };
104
+
105
+ @group(1) @binding(0) var<uniform> material : Material;
106
+ @group(1) @binding(1) var baseColorSampler : sampler;
107
+ @group(1) @binding(2) var baseColorTexture : texture_2d<f32>;
108
+ @group(1) @binding(3) var metallicRoughnessSampler : sampler;
109
+ @group(1) @binding(4) var metallicRoughnessTexture : texture_2d<f32>;
110
+ @group(1) @binding(5) var normalSampler : sampler;
111
+ @group(1) @binding(6) var normalTexture : texture_2d<f32>;
112
+ @group(1) @binding(7) var specularTintSampler : sampler;
113
+ @group(1) @binding(8) var specularTintTexture : texture_2d<f32>;
114
+ @group(1) @binding(9) var emissiveSampler : sampler;
115
+ @group(1) @binding(10) var emissiveTexture : texture_2d<f32>;
116
+ @group(1) @binding(11) var occlusionSampler : sampler;
117
+ @group(1) @binding(12) var occlusionTexture : texture_2d<f32>;
118
+
119
+ // Preserve filtering reflection for resources passed to the shared sampler.
120
+ fn materialTextureFilteringWitness() {
121
+ let base = baseColorTexture;
122
+ let metallicRoughness = metallicRoughnessTexture;
123
+ let normal = normalTexture;
124
+ let specularTint = specularTintTexture;
125
+ let emissive = emissiveTexture;
126
+ let occlusion = occlusionTexture;
127
+ let baseWitness = textureSample(base, baseColorSampler, vec2<f32>(0.0));
128
+ let metallicRoughnessWitness = textureSample(metallicRoughness, metallicRoughnessSampler, vec2<f32>(0.0));
129
+ let normalWitness = textureSample(normal, normalSampler, vec2<f32>(0.0));
130
+ let specularTintWitness = textureSample(specularTint, specularTintSampler, vec2<f32>(0.0));
131
+ let emissiveWitness = textureSample(emissive, emissiveSampler, vec2<f32>(0.0));
132
+ let occlusionWitness = textureSample(occlusion, occlusionSampler, vec2<f32>(0.0));
133
+ }
134
+
135
+ struct SkylightUniforms {
136
+ intensity : f32,
137
+ // 16 B (WebGL2 / GLES 3.0 uniform-buffer 16-byte-multiple rule). The former
138
+ // pad0/1/2 lanes now carry the linear-space ambient `color` tint
139
+ // (downstream integration #4). Kept as three scalars (NOT vec3<f32>) so the
140
+ // struct stays exactly 16 B -- a vec3 has 16-byte std140 alignment and would
141
+ // grow the UBO to 32 B. Host writes `[intensity, colorR, colorG, colorB]`;
142
+ // color defaults to white so the multiply is identity for intensity-only
143
+ // callers.
144
+ colorR : f32,
145
+ colorG : f32,
146
+ colorB : f32,
147
+ rotation : vec4<f32>,
148
+ };
149
+ @group(1) @binding(13) var irradianceMap : texture_cube<f32>;
150
+ @group(1) @binding(14) var irradianceSampler : sampler;
151
+ @group(1) @binding(15) var prefilterMap : texture_cube<f32>;
152
+ @group(1) @binding(16) var prefilterSampler : sampler;
153
+ @group(1) @binding(17) var brdfLut : texture_2d<f32>;
154
+ @group(1) @binding(18) var brdfLutSampler : sampler;
155
+ @group(1) @binding(19) var<uniform> skylight : SkylightUniforms;
156
+
157
+ #if STORAGE_BUFFER_AVAILABLE == true
158
+ @group(2) @binding(1) var<storage, read> palette : array<mat4x4<f32>>;
159
+ @group(2) @binding(2) var<storage, read> previousPalette : array<mat4x4<f32>>;
160
+ #else
161
+ @group(2) @binding(1) var<uniform> palette : array<mat4x4<f32>, 255>;
162
+ @group(2) @binding(2) var<uniform> previousPalette : array<mat4x4<f32>, 255>;
163
+ #endif
164
+
165
+ struct VsIn {
166
+ @location(0) pos : vec3<f32>,
167
+ @location(1) normal : vec3<f32>,
168
+ @location(2) uv : vec2<f32>,
169
+ @location(3) tangent : vec4<f32>,
170
+ @location(4) skinIndex : vec4<u32>,
171
+ @location(5) skinWeight : vec4<f32>,
172
+ // MaterialAsset per-slot texCoord: reserve the canonical UV0-UV7 inputs.
173
+ // Missing mesh sets are supplied by the pipeline's clamp-to-last aliases.
174
+ @location(6) uv1 : vec2<f32>,
175
+ @location(7) uv2 : vec2<f32>,
176
+ @location(8) uv3 : vec2<f32>,
177
+ @location(9) uv4 : vec2<f32>,
178
+ @location(10) uv5 : vec2<f32>,
179
+ @location(11) uv6 : vec2<f32>,
180
+ @location(12) uv7 : vec2<f32>,
181
+ #ifdef VERTEX_COLOR_AVAILABLE
182
+ @location(13) color : vec4<f32>,
183
+ #endif
184
+ };
185
+ struct VsOut {
186
+ @builtin(position) clip : vec4<f32>,
187
+ @location(0) worldPos : vec3<f32>,
188
+ @location(1) worldNormal : vec3<f32>,
189
+ @location(2) uv : vec2<f32>,
190
+ @location(3) worldTangent : vec4<f32>,
191
+ @location(4) @interpolate(flat) instanceIdx : u32,
192
+ // feat-city-glb multi-UV tiling: second UV set varying at location 5
193
+ // (parity with default-standard-pbr.wgsl).
194
+ @location(5) uv1 : vec2<f32>,
195
+ @location(8) uv2 : vec2<f32>,
196
+ @location(9) uv3 : vec2<f32>,
197
+ @location(10) uv4 : vec2<f32>,
198
+ @location(11) uv5 : vec2<f32>,
199
+ @location(12) uv6 : vec2<f32>,
200
+ @location(13) uv7 : vec2<f32>,
201
+ #ifdef VERTEX_COLOR_AVAILABLE
202
+ @location(14) color : vec4<f32>,
203
+ #endif
204
+ @location(7) viewZ : f32,
205
+ };
206
+
207
+ fn pick_channel(rgba : vec4<f32>, channelIndex : u32) -> f32 {
208
+ switch (channelIndex) {
209
+ case 0u: { return rgba.r; }
210
+ case 1u: { return rgba.g; }
211
+ case 2u: { return rgba.b; }
212
+ default: { return rgba.a; }
213
+ }
214
+ }
215
+
216
+ fn applySceneFog(viewParams : View, color : vec3<f32>, alpha : f32, worldPos : vec3<f32>) -> vec4<f32> {
217
+ var origin = viewParams.cameraPos;
218
+ var direction = normalize(worldPos - origin);
219
+ var rayDistance = length(worldPos - origin);
220
+ if (viewParams.temporalProjection.z >= 0.5) {
221
+ let nearH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 0.0, 1.0);
222
+ let farH = viewParams.inverseViewProj * vec4<f32>(0.0, 0.0, 1.0, 1.0);
223
+ let nearPoint = nearH.xyz / nearH.w;
224
+ let farPoint = farH.xyz / farH.w;
225
+ direction = normalize(farPoint - nearPoint);
226
+ origin = worldPos - direction * dot(worldPos - viewParams.cameraPos, direction);
227
+ rayDistance = max(dot(worldPos - origin, direction), 0.0);
228
+ }
229
+ return apply_fog(viewParams.fog, FogRay(origin, direction, rayDistance), vec4<f32>(color, alpha));
230
+ }
231
+
232
+ @vertex
233
+ fn vs_main(in : VsIn, @builtin(instance_index) idx : u32) -> VsOut {
234
+ // 4-bone weighted skinning (plan-strategy D-3 / D-3a).
235
+ // The host pre-computes each joint matrix as worldFromJoint * inverseBindMatrix
236
+ // (CPU-side pre-multiplication, per D-4) and writes them into the palette
237
+ // storage buffer. The BindGroup dynamic offset selects the per-entity slice
238
+ // so palette[0] is the first joint of this draw.
239
+
240
+ // Accumulate the weighted 4-joint skinning matrix: sum(w_i * M_i).
241
+ // Each palette entry is a mat4x4<f32> (world * IBM per joint).
242
+ // The host sets the BindGroup dynamic offset to (byteOffset / 64) so the
243
+ // first palette entry visible to this draw is palette[0]. The shader
244
+ // indexes into palette directly using the per-vertex skinIndex values.
245
+ let skinMatrix = palette[in.skinIndex.x] * in.skinWeight.x +
246
+ palette[in.skinIndex.y] * in.skinWeight.y +
247
+ palette[in.skinIndex.z] * in.skinWeight.z +
248
+ palette[in.skinIndex.w] * in.skinWeight.w;
249
+
250
+ // glTF 2.0 sec.Skins Implementation Note: when a mesh node has a skin
251
+ // property, the joint matrices already encode the global transform of each
252
+ // joint relative to the scene root. The transform of the mesh node itself
253
+ // must be ignored when rendering the skinned mesh.
254
+ //
255
+ // The host pre-computes palette[i] = jointWorld_i * IBM_i (full world-space
256
+ // transform, including the entire ancestor chain via propagateTransforms).
257
+ // skinnedLocal IS the world position -- no additional left-multiply by
258
+ // meshes[0].worldFromLocal or instanceLocal is needed.
259
+ //
260
+ // This removes the implicit contract "Skin entity Transform.world must be
261
+ // identity" -- a skin entity can be parented under any Transform chain and
262
+ // the skinned mesh will rigidly follow via joint propagation alone.
263
+ let skinnedLocal = skinMatrix * vec4<f32>(in.pos, 1.0);
264
+
265
+ // Extract the upper-left 3x3 for normal/tangent transformation
266
+ // (plan-strategy D-3a). WGSL mat4x4 columns are vec4:
267
+ // col0 = palette[i][0], col1 = palette[i][1], col2 = palette[i][2].
268
+ // We sum the weighted columns across the 4 joints to build the 3x3.
269
+ let m0 = skinMatrix[0].xyz;
270
+ let m1 = skinMatrix[1].xyz;
271
+ let m2 = skinMatrix[2].xyz;
272
+ let skinNormal3x3 = mat3x3<f32>(m0, m1, m2);
273
+
274
+ // world = skinnedLocal (position), no extra left-multiply --
275
+ // palette = jointWorld * IBM is already full world-space.
276
+ var out : VsOut;
277
+ // Keep meshes[0] and instances bindings referenced so naga_oil does not
278
+ // dead-code-eliminate the @group(2)@binding(0) and @group(3)@binding(0)
279
+ // globals. The host-side BGL shape must remain compatible with non-skin
280
+ // PBR pipeline layout (buildPbrSkinLayouts declares 2-entry mesh-array
281
+ // slot + separate instances slot). Without these keep-alive references,
282
+ // createRenderPipeline would fail on binding-count mismatch.
283
+ _ = meshes[0].worldFromLocal;
284
+ _ = instances[0].localFromInstance;
285
+ out.clip = view.worldViewProj * skinnedLocal;
286
+ out.worldPos = skinnedLocal.xyz;
287
+ out.worldNormal = normalize(skinNormal3x3 * in.normal);
288
+ let worldTangentXyz = normalize(skinNormal3x3 * in.tangent.xyz);
289
+ out.worldTangent = vec4<f32>(worldTangentXyz, in.tangent.w);
290
+ out.uv = in.uv;
291
+ out.uv1 = in.uv1;
292
+ out.uv2 = in.uv2;
293
+ out.uv3 = in.uv3;
294
+ out.uv4 = in.uv4;
295
+ out.uv5 = in.uv5;
296
+ out.uv6 = in.uv6;
297
+ out.uv7 = in.uv7;
298
+ #ifdef VERTEX_COLOR_AVAILABLE
299
+ out.color = in.color;
300
+ #endif
301
+ out.instanceIdx = idx;
302
+ // feat-20260613-csm-cascaded-shadow-maps M5 / w19: viewZ replaces the
303
+ // prior light-space-position varying; evalDirectional picks the cascade
304
+ // matrix per fragment from viewZ + worldPos.
305
+ out.viewZ = -out.clip.w;
306
+ return out;
307
+ }
308
+
309
+ fn transformedMaterialUv(transform : vec4<f32>, metadata : vec4<f32>, in : VsOut) -> vec2<f32> {
310
+ var source = in.uv;
311
+ if (metadata.x >= 1.0) { source = in.uv1; }
312
+ if (metadata.x >= 2.0) { source = in.uv2; }
313
+ if (metadata.x >= 3.0) { source = in.uv3; }
314
+ if (metadata.x >= 4.0) { source = in.uv4; }
315
+ if (metadata.x >= 5.0) { source = in.uv5; }
316
+ if (metadata.x >= 6.0) { source = in.uv6; }
317
+ if (metadata.x >= 7.0) { source = in.uv7; }
318
+ let scaled = source * transform.zw;
319
+ let angle = metadata.y;
320
+ let c = cos(angle);
321
+ let s = sin(angle);
322
+ return vec2<f32>(scaled.x * c - scaled.y * s, scaled.x * s + scaled.y * c) + transform.xy;
323
+ }
324
+
325
+ fn materialVertexColor(in : VsOut) -> vec4<f32> {
326
+ #ifdef VERTEX_COLOR_AVAILABLE
327
+ return in.color;
328
+ #else
329
+ return vec4<f32>(1.0);
330
+ #endif
331
+ }
332
+
333
+ @fragment
334
+ fn fs_main(in : VsOut) -> @location(0) vec4<f32> {
335
+ let baseUv = transformedMaterialUv(material.baseColorTextureCoordinatesTransform, material.baseColorTextureCoordinatesMetadata, in);
336
+ let baseSample = sampleMaterialTexture(baseColorTexture, baseColorSampler, baseUv, material.baseColorTextureCoordinatesMetadata.zw);
337
+ let vertexColor = materialVertexColor(in);
338
+ let alpha = material.baseColor.a * baseSample.a * vertexColor.a;
339
+ if (material.alphaCutoff > 0.0 && alpha <= material.alphaCutoff) {
340
+ discard;
341
+ }
342
+ let albedo = material.baseColor.rgb * baseSample.rgb * vertexColor.rgb;
343
+
344
+ let mrUv = transformedMaterialUv(material.metallicRoughnessTextureCoordinatesTransform, material.metallicRoughnessTextureCoordinatesMetadata, in);
345
+ let mrSample = sampleMaterialTexture(metallicRoughnessTexture, metallicRoughnessSampler, mrUv, material.metallicRoughnessTextureCoordinatesMetadata.zw);
346
+ let metallic = material.metallic * pick_channel(mrSample, u32(material.metallicChannel));
347
+ let roughnessTex = pick_channel(mrSample, u32(material.roughnessChannel));
348
+
349
+ var a = max(material.roughness, 0.04);
350
+ a = a * roughnessTex;
351
+ a = a * a;
352
+
353
+ let normalUv = transformedMaterialUv(material.normalTextureCoordinatesTransform, material.normalTextureCoordinatesMetadata, in);
354
+ let normSampleRg = sampleMaterialTexture(normalTexture, normalSampler, normalUv, material.normalTextureCoordinatesMetadata.zw).rg;
355
+ let normTangent = scaleTangentSpaceNormal(
356
+ decodeTangentSpaceNormalRg(normSampleRg), material.normalScale,
357
+ );
358
+ let n = applyTBN(in.worldNormal, in.worldTangent, normTangent);
359
+
360
+ let v = normalize(view.cameraPos - in.worldPos);
361
+ let specularTint = material.specularTint * sampleMaterialTexture(
362
+ specularTintTexture, specularTintSampler,
363
+ transformedMaterialUv(material.specularTintTextureCoordinatesTransform, material.specularTintTextureCoordinatesMetadata, in),
364
+ material.specularTintTextureCoordinatesMetadata.zw,
365
+ ).rgb;
366
+ let f0 = mix(vec3<f32>(0.04) * specularTint, albedo, metallic);
367
+ let coatRoughness = max(material.clearcoatRoughness, 0.04);
368
+ let coatAlpha = coatRoughness * coatRoughness;
369
+ let coatF = f_schlick(max(dot(n, v), 0.0), vec3<f32>(0.04)) * material.clearcoat;
370
+
371
+ let kD = (vec3<f32>(1.0) - f_schlick(max(dot(n, v), 0.0), f0)) * (1.0 - metallic);
372
+ let iblRoughness = max(material.roughness, 0.04) * roughnessTex;
373
+ let irradiance = sampleIblDiffuse(n, skylight.rotation, irradianceMap, irradianceSampler);
374
+ let specularIbl = sampleIblSpecular(
375
+ n, v, iblRoughness, f0,
376
+ skylight.rotation,
377
+ prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
378
+ );
379
+ let skyColor = vec3<f32>(skylight.colorR, skylight.colorG, skylight.colorB);
380
+ var ambient = (kD * irradiance * albedo + specularIbl) * (vec3<f32>(1.0) - coatF);
381
+ if (material.clearcoat != 0.0) {
382
+ let clearcoatIbl = sampleIblSpecular(
383
+ n, v, coatRoughness, vec3<f32>(0.04),
384
+ skylight.rotation,
385
+ prefilterMap, prefilterSampler, brdfLut, brdfLutSampler,
386
+ );
387
+ ambient = ambient + clearcoatIbl * material.clearcoat;
388
+ }
389
+ ambient = ambient * skyColor * skylight.intensity;
390
+ var color = ambient;
391
+ let directionalShadow = evalDirectionalShadowFactor(n, in.worldPos, in.viewZ);
392
+ let directionalBase = evalDirectionalNoShadow(n, v, albedo, metallic, a, f0);
393
+ color = color + directionalShadow * directionalBase;
394
+ if (material.clearcoat != 0.0) {
395
+ let directionalClearcoat = evalDirectionalNoShadow(
396
+ n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
397
+ );
398
+ color = color + directionalShadow * material.clearcoat * directionalClearcoat;
399
+ }
400
+ let pointCount = pointLightsBuffer.count;
401
+ for (var i: u32 = 0u; i < pointCount; i = i + 1u) {
402
+ let p = pointLightsBuffer.slots[i];
403
+ #ifdef POINT_SHADOW_AVAILABLE
404
+ if (p.shadowAtlasLayer >= 0) {
405
+ let lane = shadowParams[p.shadowAtlasLayer];
406
+ color = color + evalPointShadowed(
407
+ p.position, p.colorTimesIntensity, p.invRangeSquared,
408
+ in.worldPos, n, v, albedo, metallic, a, f0,
409
+ p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
410
+ );
411
+ if (material.clearcoat != 0.0) {
412
+ color = color + material.clearcoat * evalPointShadowed(
413
+ p.position, p.colorTimesIntensity, p.invRangeSquared,
414
+ in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
415
+ p.shadowAtlasLayer, lane.x, lane.y, 0.005, 0.05,
416
+ );
417
+ }
418
+ } else {
419
+ color = color + evalPoint(
420
+ p.position, p.colorTimesIntensity, p.invRangeSquared,
421
+ in.worldPos, n, v, albedo, metallic, a, f0,
422
+ );
423
+ if (material.clearcoat != 0.0) {
424
+ color = color + material.clearcoat * evalPoint(
425
+ p.position, p.colorTimesIntensity, p.invRangeSquared,
426
+ in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
427
+ );
428
+ }
429
+ }
430
+ #else
431
+ color = color + evalPoint(
432
+ p.position, p.colorTimesIntensity, p.invRangeSquared,
433
+ in.worldPos, n, v, albedo, metallic, a, f0,
434
+ );
435
+ if (material.clearcoat != 0.0) {
436
+ color = color + material.clearcoat * evalPoint(
437
+ p.position, p.colorTimesIntensity, p.invRangeSquared,
438
+ in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
439
+ );
440
+ }
441
+ #endif
442
+ }
443
+ let spotCount = spotLightsBuffer.count;
444
+ for (var i: u32 = 0u; i < spotCount; i = i + 1u) {
445
+ let s = spotLightsBuffer.slots[i];
446
+ color = color + evalSpot(
447
+ s.position, s.direction, s.colorTimesIntensity,
448
+ s.cosInner, s.cosOuter, s.invRangeSquared,
449
+ in.worldPos, n, v, albedo, metallic, a, f0,
450
+ );
451
+ if (material.clearcoat != 0.0) {
452
+ color = color + material.clearcoat * evalSpot(
453
+ s.position, s.direction, s.colorTimesIntensity,
454
+ s.cosInner, s.cosOuter, s.invRangeSquared,
455
+ in.worldPos, n, v, vec3<f32>(0.0), 1.0, coatAlpha, vec3<f32>(0.04),
456
+ );
457
+ }
458
+ }
459
+ return applySceneFog(view, color, alpha, in.worldPos);
460
+ }
461
+
462
+ struct TemporalVsOut {
463
+ @builtin(position) clip : vec4<f32>,
464
+ @location(0) uv : vec2<f32>,
465
+ @location(1) uv1 : vec2<f32>,
466
+ @location(2) uv2 : vec2<f32>,
467
+ @location(3) uv3 : vec2<f32>,
468
+ @location(4) uv4 : vec2<f32>,
469
+ @location(5) uv5 : vec2<f32>,
470
+ @location(6) uv6 : vec2<f32>,
471
+ @location(7) uv7 : vec2<f32>,
472
+ @location(8) @interpolate(linear) currentClip : vec4<f32>,
473
+ @location(9) @interpolate(linear) previousClip : vec4<f32>,
474
+ #ifdef VERTEX_COLOR_AVAILABLE
475
+ @location(14) color : vec4<f32>,
476
+ #endif
477
+ };
478
+
479
+ @vertex
480
+ fn vs_temporal(in : VsIn, @builtin(instance_index) idx : u32) -> TemporalVsOut {
481
+ let currentSkin = palette[in.skinIndex.x] * in.skinWeight.x +
482
+ palette[in.skinIndex.y] * in.skinWeight.y +
483
+ palette[in.skinIndex.z] * in.skinWeight.z +
484
+ palette[in.skinIndex.w] * in.skinWeight.w;
485
+ let previousSkin = previousPalette[in.skinIndex.x] * in.skinWeight.x +
486
+ previousPalette[in.skinIndex.y] * in.skinWeight.y +
487
+ previousPalette[in.skinIndex.z] * in.skinWeight.z +
488
+ previousPalette[in.skinIndex.w] * in.skinWeight.w;
489
+ let currentWorld = currentSkin * vec4<f32>(in.pos, 1.0);
490
+ let previousWorld = previousSkin * vec4<f32>(in.pos, 1.0);
491
+ var out : TemporalVsOut;
492
+ out.currentClip = view.temporalCurrentViewProj * currentWorld;
493
+ out.clip = out.currentClip;
494
+ out.previousClip = view.temporalPreviousViewProj * previousWorld;
495
+ out.uv = in.uv;
496
+ out.uv1 = in.uv1;
497
+ out.uv2 = in.uv2;
498
+ out.uv3 = in.uv3;
499
+ out.uv4 = in.uv4;
500
+ out.uv5 = in.uv5;
501
+ out.uv6 = in.uv6;
502
+ out.uv7 = in.uv7;
503
+ #ifdef VERTEX_COLOR_AVAILABLE
504
+ out.color = in.color;
505
+ #endif
506
+ _ = idx;
507
+ return out;
508
+ }
509
+
510
+ fn temporalVertexAlpha(in : TemporalVsOut) -> f32 {
511
+ #ifdef VERTEX_COLOR_AVAILABLE
512
+ return in.color.a;
513
+ #else
514
+ return 1.0;
515
+ #endif
516
+ }
517
+
518
+ @fragment
519
+ fn fs_temporal(in : TemporalVsOut) -> @location(0) vec4<f32> {
520
+ #if STORAGE_BUFFER_AVAILABLE == true
521
+ let reactive = meshes[0].temporal.x;
522
+ #else
523
+ let reactive = 1.0;
524
+ #endif
525
+ return projectPbrSceneTemporal(
526
+ material.baseColor.a * temporalVertexAlpha(in),
527
+ material.alphaCutoff,
528
+ baseColorTexture,
529
+ baseColorSampler,
530
+ material.baseColorTextureCoordinatesTransform,
531
+ material.baseColorTextureCoordinatesMetadata,
532
+ in.currentClip,
533
+ in.previousClip,
534
+ reactive,
535
+ in.uv, in.uv1, in.uv2, in.uv3,
536
+ in.uv4, in.uv5, in.uv6, in.uv7,
537
+ );
538
+ }