@forgeax/engine-render 0.1.23 → 0.1.25

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 (322) hide show
  1. package/README.md +231 -10
  2. package/dist/assembly/bundler-contract.d.ts +9 -0
  3. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  4. package/dist/assembly/device-feature-admission.d.ts +8 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  7. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +7 -243
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +3 -2
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  13. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  14. package/dist/assembly/material/surface-projection.d.ts +24 -0
  15. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  16. package/dist/assembly/material-shader-policy.d.ts +188 -0
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  18. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  19. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  20. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  21. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  22. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  23. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  24. package/dist/assembly/recovery/generation.d.ts +46 -0
  25. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  26. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  27. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  28. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  29. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  30. package/dist/assembly/renderer-facade.d.ts +10 -0
  31. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  32. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-helpers.d.ts +19 -0
  35. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  36. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  37. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  38. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  39. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  41. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-ready.d.ts +55 -0
  43. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  45. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  47. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  48. package/dist/assets/asset-decoders.d.ts.map +1 -1
  49. package/dist/authoring.mjs +1 -1
  50. package/dist/{chunk-FF7K3FSO.mjs → chunk-ADHHVYLW.mjs} +89 -89
  51. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  52. package/dist/{chunk-YALPS44A.mjs → chunk-E34VL5VI.mjs} +47 -47
  53. package/dist/chunk-E34VL5VI.mjs.map +1 -0
  54. package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
  55. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  56. package/dist/{chunk-HZUPSYW7.mjs → chunk-HKXTW355.mjs} +105 -15
  57. package/dist/chunk-HKXTW355.mjs.map +1 -0
  58. package/dist/chunk-JSEDTZYS.mjs +413 -0
  59. package/dist/chunk-JSEDTZYS.mjs.map +1 -0
  60. package/dist/{chunk-FBC4R44J.mjs → chunk-KM2NOX2I.mjs} +57 -57
  61. package/dist/chunk-KM2NOX2I.mjs.map +1 -0
  62. package/dist/{chunk-DIYW3M5D.mjs → chunk-N3RT2EUR.mjs} +2288 -2238
  63. package/dist/chunk-N3RT2EUR.mjs.map +1 -0
  64. package/dist/{chunk-ZUWKAZ2I.mjs → chunk-TCLDX7SZ.mjs} +1167 -368
  65. package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
  66. package/dist/{chunk-QODA5UKU.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  67. package/dist/chunk-ZZ4YQ474.mjs.map +1 -0
  68. package/dist/components/instances.d.ts +13 -31
  69. package/dist/components/instances.d.ts.map +1 -1
  70. package/dist/construct-renderer.mjs +20688 -16314
  71. package/dist/construct-renderer.mjs.map +1 -1
  72. package/dist/device/device-scope.d.ts +2 -0
  73. package/dist/device/device-scope.d.ts.map +1 -1
  74. package/dist/device/gpu-residency.d.ts +24 -4
  75. package/dist/device/gpu-residency.d.ts.map +1 -1
  76. package/dist/environment/frame.d.ts +5 -0
  77. package/dist/environment/frame.d.ts.map +1 -1
  78. package/dist/environment/inspection.d.ts +7 -0
  79. package/dist/environment/inspection.d.ts.map +1 -1
  80. package/dist/environment/lifecycle.d.ts +20 -2
  81. package/dist/environment/lifecycle.d.ts.map +1 -1
  82. package/dist/errors/recover.d.ts +58 -2
  83. package/dist/errors/recover.d.ts.map +1 -1
  84. package/dist/errors/render.d.ts +13 -0
  85. package/dist/errors/render.d.ts.map +1 -1
  86. package/dist/extract/contracts.d.ts +899 -0
  87. package/dist/extract/contracts.d.ts.map +1 -0
  88. package/dist/features/host.d.ts +2 -0
  89. package/dist/features/host.d.ts.map +1 -1
  90. package/dist/features/prepared-gpu-work.d.ts +3 -0
  91. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  92. package/dist/features/prepared-graphics-store.d.ts +2 -0
  93. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  94. package/dist/gpu-driven/production-raster.d.ts +34 -1
  95. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  96. package/dist/gpu-scene.d.ts +5 -0
  97. package/dist/gpu-scene.d.ts.map +1 -1
  98. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  99. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  100. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  101. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  102. package/dist/index.d.ts +2 -1
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.mjs +27 -9
  105. package/dist/index.mjs.map +1 -1
  106. package/dist/instance-buffer-cache.d.ts +7 -1
  107. package/dist/instance-buffer-cache.d.ts.map +1 -1
  108. package/dist/instances.d.ts +101 -0
  109. package/dist/instances.d.ts.map +1 -0
  110. package/dist/internal.mjs +6 -6
  111. package/dist/light-buffer-layout.d.ts +3 -0
  112. package/dist/light-buffer-layout.d.ts.map +1 -1
  113. package/dist/materials.d.ts +96 -11
  114. package/dist/materials.d.ts.map +1 -1
  115. package/dist/mesh-material-bindings.d.ts +56 -0
  116. package/dist/mesh-material-bindings.d.ts.map +1 -1
  117. package/dist/occlusion-proxy.d.ts +4 -0
  118. package/dist/occlusion-proxy.d.ts.map +1 -0
  119. package/dist/pbr-pipeline.d.ts +32 -9
  120. package/dist/pbr-pipeline.d.ts.map +1 -1
  121. package/dist/pipeline-builder.d.ts.map +1 -1
  122. package/dist/pipeline-spec.d.ts.map +1 -1
  123. package/dist/points-lines/prepare.d.ts +3 -0
  124. package/dist/points-lines/prepare.d.ts.map +1 -1
  125. package/dist/points-lines/standard-owner.d.ts +33 -0
  126. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  127. package/dist/record/frame-snapshot.d.ts +18 -2
  128. package/dist/record/frame-snapshot.d.ts.map +1 -1
  129. package/dist/record/frame-targets.d.ts.map +1 -1
  130. package/dist/record/frame.d.ts +14 -2
  131. package/dist/record/frame.d.ts.map +1 -1
  132. package/dist/record/main-pass-geometry.d.ts +36 -4
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +7 -17
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass.d.ts.map +1 -1
  137. package/dist/record/mesh-ssbo.d.ts +11 -3
  138. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  139. package/dist/record/recovery-pipeline.d.ts +30 -0
  140. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  141. package/dist/record/render-context.d.ts +28 -5
  142. package/dist/record/render-context.d.ts.map +1 -1
  143. package/dist/record/shadow-pass.d.ts +11 -1
  144. package/dist/record/shadow-pass.d.ts.map +1 -1
  145. package/dist/record/typed-frame-graph.d.ts +1 -1
  146. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  147. package/dist/recovery/render-system-candidate.d.ts +65 -0
  148. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  149. package/dist/render-contract.d.ts +68 -7
  150. package/dist/render-contract.d.ts.map +1 -1
  151. package/dist/render-system-extract-tail.d.ts +16 -0
  152. package/dist/render-system-extract-tail.d.ts.map +1 -0
  153. package/dist/render-system-extract.d.ts +180 -22
  154. package/dist/render-system-extract.d.ts.map +1 -1
  155. package/dist/render-system.d.ts +131 -4
  156. package/dist/render-system.d.ts.map +1 -1
  157. package/dist/scene/render-scene-types.d.ts +2 -0
  158. package/dist/scene/render-scene-types.d.ts.map +1 -1
  159. package/dist/scene/render-scene.d.ts +31 -1
  160. package/dist/scene/render-scene.d.ts.map +1 -1
  161. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  162. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  163. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  164. package/dist/temporal/index.mjs +6 -6
  165. package/dist/temporal/taa-history-store.d.ts +15 -1
  166. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  167. package/dist/temporal/transaction.d.ts +5 -0
  168. package/dist/temporal/transaction.d.ts.map +1 -1
  169. package/package.json +20 -20
  170. package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
  171. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  172. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  173. package/src/__tests__/environment-generation.integration.test.ts +23 -0
  174. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  175. package/src/__tests__/factory-contract.integration.test.ts +122 -7
  176. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  177. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  178. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  179. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  180. package/src/__tests__/instances-store.unit.test.ts +46 -0
  181. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  182. package/src/__tests__/material-contract-inventory.unit.test.ts +29 -22
  183. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  184. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  185. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  186. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  187. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  188. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  189. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  190. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  191. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -5
  192. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  193. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
  194. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  195. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  196. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  197. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +127 -18
  198. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  199. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  200. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  201. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  202. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  203. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  204. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  205. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  206. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  207. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  208. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  209. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  210. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  211. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  212. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  213. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  214. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  215. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  216. package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
  217. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  218. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  219. package/src/assembly/bundler-contract.ts +9 -0
  220. package/src/assembly/device-feature-admission.ts +15 -5
  221. package/src/assembly/extended-lighting-fallback.ts +216 -0
  222. package/src/assembly/factory.ts +83 -10088
  223. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  224. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  225. package/src/assembly/material/assembly.ts +18 -2
  226. package/src/assembly/material/standard-layer-projection.ts +16 -0
  227. package/src/assembly/material/surface-projection.ts +90 -0
  228. package/src/assembly/material-shader-policy.ts +932 -0
  229. package/src/assembly/material-shader-prewarm.ts +88 -0
  230. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  231. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  232. package/src/assembly/recovery/generation.ts +141 -0
  233. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  234. package/src/assembly/recovery/renderer-recover.ts +906 -0
  235. package/src/assembly/renderer-facade.ts +342 -0
  236. package/src/assembly/renderer-frame-transaction.ts +45 -0
  237. package/src/assembly/renderer-helpers.ts +99 -0
  238. package/src/assembly/renderer-lifecycle.ts +159 -0
  239. package/src/assembly/shader-prewarm-policy.ts +157 -0
  240. package/src/assembly/webgpu-ready-contract.ts +14 -0
  241. package/src/assembly/webgpu-ready.ts +4066 -0
  242. package/src/assembly/webgpu-renderer.ts +4027 -0
  243. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  244. package/src/assets/asset-decoders.ts +27 -4
  245. package/src/components/camera.ts +1 -1
  246. package/src/components/instances.ts +14 -197
  247. package/src/debug-draw-glue.ts +1 -1
  248. package/src/device/device-scope.ts +35 -6
  249. package/src/device/gpu-residency.ts +194 -26
  250. package/src/environment/frame.ts +6 -0
  251. package/src/environment/inspection.ts +10 -0
  252. package/src/environment/lifecycle.ts +114 -8
  253. package/src/errors/recover.ts +76 -5
  254. package/src/errors/render.ts +17 -0
  255. package/src/extract/contracts.ts +974 -0
  256. package/src/features/host.ts +16 -0
  257. package/src/features/prepared-gpu-work.ts +20 -2
  258. package/src/features/prepared-graphics-store.ts +17 -0
  259. package/src/gpu-driven/production-raster.ts +131 -3
  260. package/src/gpu-scene.ts +87 -31
  261. package/src/ibl/IblPipelineCache.ts +134 -50
  262. package/src/ibl/skylight-bind-group.ts +26 -0
  263. package/src/index.ts +10 -1
  264. package/src/instance-buffer-cache.ts +14 -1
  265. package/src/instances.ts +288 -0
  266. package/src/light-buffer-layout.ts +3 -0
  267. package/src/materials.ts +365 -53
  268. package/src/mesh-material-bindings.ts +59 -0
  269. package/src/occlusion-proxy.ts +64 -0
  270. package/src/pbr-pipeline.ts +139 -15
  271. package/src/pipeline-builder.ts +14 -10
  272. package/src/pipeline-spec.ts +6 -14
  273. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  274. package/src/points-lines/prepare.ts +9 -0
  275. package/src/points-lines/standard-owner.ts +403 -0
  276. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  277. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  278. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  279. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  280. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  281. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  282. package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
  283. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
  284. package/src/record/frame-snapshot.ts +34 -2
  285. package/src/record/frame-targets.ts +11 -1
  286. package/src/record/frame.ts +37 -4
  287. package/src/record/helpers.ts +1 -1
  288. package/src/record/main-pass-geometry.ts +727 -63
  289. package/src/record/main-pass-material.ts +316 -209
  290. package/src/record/main-pass-sprite-draws.ts +7 -8
  291. package/src/record/main-pass.ts +94 -6
  292. package/src/record/mesh-ssbo.ts +32 -10
  293. package/src/record/recovery-pipeline.ts +695 -0
  294. package/src/record/render-context.ts +39 -4
  295. package/src/record/shadow-pass.ts +46 -99
  296. package/src/record/typed-frame-graph.ts +45 -1
  297. package/src/record/view-ubo.ts +3 -3
  298. package/src/recovery/render-system-candidate.ts +876 -0
  299. package/src/render-contract.ts +69 -7
  300. package/src/render-system-extract-tail.ts +3039 -0
  301. package/src/render-system-extract.ts +177 -3003
  302. package/src/render-system.ts +906 -662
  303. package/src/scene/render-scene-types.ts +2 -0
  304. package/src/scene/render-scene.ts +703 -37
  305. package/src/systems/skin-palette-allocator.ts +129 -7
  306. package/src/temporal/frame-coordinator.ts +4 -1
  307. package/src/temporal/taa-history-store.ts +46 -2
  308. package/src/temporal/temporal-view.ts +1 -1
  309. package/src/temporal/transaction.ts +9 -0
  310. package/src/tilemap-chunk-extract-system.ts +1 -1
  311. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  312. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  313. package/dist/chunk-4L3P6DT5.mjs.map +0 -1
  314. package/dist/chunk-4NPUASZR.mjs +0 -203
  315. package/dist/chunk-4NPUASZR.mjs.map +0 -1
  316. package/dist/chunk-DIYW3M5D.mjs.map +0 -1
  317. package/dist/chunk-FBC4R44J.mjs.map +0 -1
  318. package/dist/chunk-FF7K3FSO.mjs.map +0 -1
  319. package/dist/chunk-HZUPSYW7.mjs.map +0 -1
  320. package/dist/chunk-QODA5UKU.mjs.map +0 -1
  321. package/dist/chunk-YALPS44A.mjs.map +0 -1
  322. package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
package/src/materials.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { color } from '@forgeax/engine-math';
1
2
  import { DEFAULT_STANDARD_PBR_PARAM_SCHEMA } from '@forgeax/engine-shader';
2
3
  import type {
3
4
  MaterialAsset,
@@ -7,6 +8,11 @@ import type {
7
8
  MaterialRenderState,
8
9
  MaterialValue,
9
10
  } from '@forgeax/engine-types';
11
+ import { deriveStandardLayerPlan } from '@forgeax/engine-types';
12
+ import {
13
+ DEFAULT_STANDARD_SURFACE_MODULE,
14
+ projectStandardSurfacePasses,
15
+ } from './assembly/material/surface-projection';
10
16
 
11
17
  export type MaterialTransmissionValidationReason =
12
18
  | 'non-finite'
@@ -56,10 +62,52 @@ export const SPRITE_PREMULTIPLIED_ALPHA_BLEND: GPUBlendState = {
56
62
  alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
57
63
  };
58
64
 
59
- const STANDARD_MODULE = 'forgeax_material::standard';
60
65
  const UNLIT_MODULE = 'forgeax_material::unlit';
61
66
  const SPRITE_MODULE = 'forgeax_material::sprite';
62
67
 
68
+ export type MaterialColorTuple3 = readonly [number, number, number];
69
+ export type MaterialColorTuple4 = readonly [number, number, number, number];
70
+ export type MaterialColorInput3 = MaterialColorTuple3 | string | number;
71
+ export type MaterialColorInput4 = MaterialColorTuple4 | string | number;
72
+
73
+ function colorHexFromNumber(value: number): string {
74
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffff) {
75
+ throw new Error(`material color number must be an integer in [0, 0xffffff], got ${value}`);
76
+ }
77
+ return `#${value.toString(16).padStart(6, '0')}`;
78
+ }
79
+
80
+ function linearColorFromInput(
81
+ input: MaterialColorInput3 | MaterialColorInput4,
82
+ channels: 3 | 4,
83
+ ): readonly number[] {
84
+ if (typeof input === 'number' || typeof input === 'string') {
85
+ const parsed = color.create();
86
+ color.fromCss(parsed, typeof input === 'number' ? colorHexFromNumber(input) : input);
87
+ return channels === 3
88
+ ? [parsed[0] as number, parsed[1] as number, parsed[2] as number]
89
+ : Array.from(parsed);
90
+ }
91
+ if (input.length !== channels) {
92
+ throw new Error(`material color tuple must contain ${channels} channels, got ${input.length}`);
93
+ }
94
+ return [...input];
95
+ }
96
+
97
+ /** Convert an explicit sRGB numeric tuple into the linear tuple factories store. */
98
+ export function srgb(input: MaterialColorTuple3): MaterialColorTuple3;
99
+ export function srgb(input: MaterialColorTuple4): MaterialColorTuple4;
100
+ export function srgb(input: readonly number[]): readonly number[] {
101
+ if (input.length !== 3 && input.length !== 4) {
102
+ throw new Error(`sRGB material color tuple must contain 3 or 4 channels, got ${input.length}`);
103
+ }
104
+ const parsed = color.create(input[0] ?? 0, input[1] ?? 0, input[2] ?? 0, input[3] ?? 1);
105
+ color.srgbToLinear(parsed, parsed);
106
+ return input.length === 3
107
+ ? [parsed[0] as number, parsed[1] as number, parsed[2] as number]
108
+ : [parsed[0] as number, parsed[1] as number, parsed[2] as number, parsed[3] as number];
109
+ }
110
+
63
111
  function authoredColorParameter(
64
112
  name: string,
65
113
  type: 'color' | 'vec3',
@@ -74,27 +122,148 @@ function authoredColorParameter(
74
122
  };
75
123
  }
76
124
 
77
- function standardParameters(colorSpace: MaterialColorSpace): readonly MaterialParameter[] {
78
- return DEFAULT_STANDARD_PBR_PARAM_SCHEMA.map((entry) => {
79
- const type = entry.type.startsWith('texture') ? 'texture' : entry.type;
80
- const isRequired =
81
- entry.name === 'baseColor' || entry.name === 'metallic' || entry.name === 'roughness';
82
- const authoredColor =
83
- entry.type === 'color' || (entry.type === 'vec3' && entry.name !== 'attenuationColor');
84
- const schemaColorSpace = 'colorSpace' in entry ? entry.colorSpace : undefined;
85
- const colorSpaceProjection: { colorSpace?: MaterialColorSpace } = {};
86
- if (authoredColor && !(type === 'color' && colorSpace === 'srgb')) {
87
- colorSpaceProjection.colorSpace = colorSpace;
88
- } else if (!authoredColor && schemaColorSpace !== undefined) {
89
- colorSpaceProjection.colorSpace = schemaColorSpace;
125
+ function standardParameters(
126
+ colorSpace: MaterialColorSpace,
127
+ opts: StandardOpts,
128
+ ): readonly MaterialParameter[] {
129
+ const physicalNames = new Set<string>();
130
+ const addPhysical = (...names: readonly string[]) => {
131
+ for (const name of names) physicalNames.add(name);
132
+ };
133
+ if (
134
+ opts.clearcoat !== undefined ||
135
+ opts.clearcoatRoughness !== undefined ||
136
+ opts.clearcoatNormalScale !== undefined ||
137
+ opts.clearcoatTexture !== undefined ||
138
+ opts.clearcoatRoughnessTexture !== undefined ||
139
+ opts.clearcoatNormalTexture !== undefined
140
+ ) {
141
+ addPhysical(
142
+ 'clearcoat',
143
+ 'clearcoatRoughness',
144
+ 'clearcoatNormalScale',
145
+ ...(opts.clearcoatTexture === undefined ? [] : ['clearcoatTexture']),
146
+ ...(opts.clearcoatRoughnessTexture === undefined ? [] : ['clearcoatRoughnessTexture']),
147
+ ...(opts.clearcoatNormalTexture === undefined ? [] : ['clearcoatNormalTexture']),
148
+ );
149
+ }
150
+ if (
151
+ opts.anisotropyStrength !== undefined ||
152
+ opts.anisotropyRotation !== undefined ||
153
+ opts.anisotropyTexture !== undefined
154
+ ) {
155
+ addPhysical('anisotropyStrength', 'anisotropyRotation');
156
+ if (opts.anisotropyTexture !== undefined) addPhysical('anisotropyTexture');
157
+ }
158
+ if (
159
+ opts.sheenColor !== undefined ||
160
+ opts.sheenRoughness !== undefined ||
161
+ opts.sheenColorTexture !== undefined ||
162
+ opts.sheenRoughnessTexture !== undefined
163
+ ) {
164
+ addPhysical('sheenColor', 'sheenRoughness');
165
+ if (opts.sheenColorTexture !== undefined) addPhysical('sheenColorTexture');
166
+ if (opts.sheenRoughnessTexture !== undefined) addPhysical('sheenRoughnessTexture');
167
+ }
168
+ if (
169
+ opts.iridescence !== undefined ||
170
+ opts.iridescenceIor !== undefined ||
171
+ opts.iridescenceThicknessMinimum !== undefined ||
172
+ opts.iridescenceThicknessMaximum !== undefined ||
173
+ opts.iridescenceTexture !== undefined ||
174
+ opts.iridescenceThicknessTexture !== undefined
175
+ ) {
176
+ addPhysical(
177
+ 'iridescence',
178
+ 'iridescenceIor',
179
+ 'iridescenceThicknessMinimum',
180
+ 'iridescenceThicknessMaximum',
181
+ ...(opts.iridescenceTexture === undefined ? [] : ['iridescenceTexture']),
182
+ ...(opts.iridescenceThicknessTexture === undefined ? [] : ['iridescenceThicknessTexture']),
183
+ );
184
+ }
185
+ const baseNames = new Set([
186
+ 'baseColor',
187
+ 'metallic',
188
+ 'roughness',
189
+ 'metallicChannel',
190
+ 'roughnessChannel',
191
+ 'aoChannel',
192
+ 'extraChannel',
193
+ 'emissive',
194
+ 'emissiveIntensity',
195
+ 'occlusionStrength',
196
+ 'alphaCutoff',
197
+ 'normalScale',
198
+ 'baseColorTexture',
199
+ 'metallicRoughnessTexture',
200
+ 'normalTexture',
201
+ 'emissiveTexture',
202
+ 'occlusionTexture',
203
+ 'specular',
204
+ 'specularColor',
205
+ // IOR is part of the always-present dielectric F0 contract. The
206
+ // transmission layer may be absent, but the Standard shader still
207
+ // consumes this scalar for the base reflection path.
208
+ 'ior',
209
+ ]);
210
+ if (opts.specularTexture !== undefined) baseNames.add('specularTexture');
211
+ if (opts.specularColorTexture !== undefined) baseNames.add('specularColorTexture');
212
+ if (
213
+ opts.transmission !== undefined ||
214
+ opts.ior !== undefined ||
215
+ opts.thickness !== undefined ||
216
+ opts.transmissionTexture !== undefined ||
217
+ opts.thicknessTexture !== undefined ||
218
+ opts.attenuationColor !== undefined ||
219
+ opts.attenuationDistance !== undefined
220
+ ) {
221
+ for (const name of [
222
+ 'transmission',
223
+ 'ior',
224
+ 'thickness',
225
+ 'attenuationColor',
226
+ 'attenuationDistance',
227
+ 'transmissionTexture',
228
+ 'thicknessTexture',
229
+ ]) {
230
+ baseNames.add(name);
90
231
  }
91
- return {
92
- name: entry.name,
93
- type,
94
- ...colorSpaceProjection,
95
- ...(isRequired ? {} : { optional: true }),
96
- } as MaterialParameter;
97
- });
232
+ }
233
+ // All Standard fields come from one root schema. Physical fields are
234
+ // projected by the authored option membership above; appending a second
235
+ // physical schema would create a parallel ABI authority.
236
+ const availableSchema = DEFAULT_STANDARD_PBR_PARAM_SCHEMA;
237
+ // Keep the generated Standard UBO's numeric run contiguous before any
238
+ // texture coordinate records. The built-in WGSL places the complete
239
+ // physical root (including clearcoat scalars) after the base numeric run
240
+ // and before all coordinate pairs; appending physical entries after the
241
+ // base texture records would otherwise derive a different ABI.
242
+ const numericSchema = availableSchema.filter((entry) => !entry.type.startsWith('texture'));
243
+ const textureSchema = availableSchema.filter((entry) => entry.type.startsWith('texture'));
244
+ const orderedSchema = [...numericSchema, ...textureSchema];
245
+ return orderedSchema
246
+ .filter((entry) => baseNames.has(entry.name) || physicalNames.has(entry.name))
247
+ .map((entry) => {
248
+ const type = entry.type.startsWith('texture') ? 'texture' : entry.type;
249
+ const isRequired =
250
+ entry.name === 'baseColor' || entry.name === 'metallic' || entry.name === 'roughness';
251
+ const authoredColor =
252
+ entry.type === 'color' || (entry.type === 'vec3' && entry.name !== 'attenuationColor');
253
+ const schemaColorSpace = 'colorSpace' in entry ? entry.colorSpace : undefined;
254
+ const colorSpaceProjection: { colorSpace?: MaterialColorSpace } = {};
255
+ if (authoredColor && !(type === 'color' && colorSpace === 'srgb')) {
256
+ colorSpaceProjection.colorSpace = colorSpace;
257
+ } else if (!authoredColor && schemaColorSpace !== undefined) {
258
+ colorSpaceProjection.colorSpace = schemaColorSpace;
259
+ }
260
+ return {
261
+ name: entry.name,
262
+ type,
263
+ ...colorSpaceProjection,
264
+ ...(isRequired ? {} : { optional: true }),
265
+ } as MaterialParameter;
266
+ });
98
267
  }
99
268
 
100
269
  function unlitParameters(colorSpace: MaterialColorSpace): readonly MaterialParameter[] {
@@ -141,8 +310,6 @@ function shadowCasterRenderState(
141
310
  }
142
311
 
143
312
  interface UnlitOpts {
144
- /** Asset-side authored color space. Defaults to sRGB. */
145
- readonly colorSpace?: MaterialColorSpace;
146
313
  readonly castShadow?: boolean;
147
314
  readonly baseColorTexture?: MaterialValue;
148
315
  readonly alphaCutoff?: number;
@@ -150,11 +317,11 @@ interface UnlitOpts {
150
317
  readonly queue?: number;
151
318
  }
152
319
 
153
- function unlit(rgba: readonly [number, number, number, number], opts?: UnlitOpts): MaterialAsset {
320
+ function unlit(rgba: MaterialColorInput4, opts?: UnlitOpts): MaterialAsset {
154
321
  if (opts?.alphaCutoff !== undefined && (opts.alphaCutoff < 0 || opts.alphaCutoff > 1)) {
155
322
  throw new Error(`Materials.unlit: alphaCutoff must be in [0, 1], got ${opts.alphaCutoff}`);
156
323
  }
157
- const values: Record<string, MaterialValue> = { baseColor: rgba };
324
+ const values: Record<string, MaterialValue> = { baseColor: linearColorFromInput(rgba, 4) };
158
325
  if (opts?.baseColorTexture !== undefined) values.baseColorTexture = opts.baseColorTexture;
159
326
  if (opts?.alphaCutoff !== undefined) values.alphaCutoff = opts.alphaCutoff;
160
327
  const passes: [MaterialPass, ...MaterialPass[]] = [
@@ -165,19 +332,16 @@ function unlit(rgba: readonly [number, number, number, number], opts?: UnlitOpts
165
332
  }
166
333
  return {
167
334
  kind: 'material',
168
- ...(opts?.colorSpace === undefined || opts.colorSpace === 'srgb'
169
- ? {}
170
- : { colorSpace: opts.colorSpace }),
335
+ colorSpace: 'linear',
171
336
  passes,
172
- parameters: unlitParameters(opts?.colorSpace ?? 'srgb'),
337
+ parameters: unlitParameters('linear'),
173
338
  values,
174
339
  };
175
340
  }
176
341
 
177
342
  interface StandardOpts {
178
- /** Asset-side authored color space for baseColor/emissive/specularTint. Defaults to sRGB. */
179
- readonly colorSpace?: MaterialColorSpace;
180
- readonly baseColor: readonly [number, number, number, number];
343
+ /** Numeric tuples are linear; Hex/CSS strings and numbers are sRGB inputs. */
344
+ readonly baseColor: MaterialColorInput4;
181
345
  readonly metallic?: number;
182
346
  readonly roughness?: number;
183
347
  /** Texture channel used for the metallic factor; glTF defaults to B (2). */
@@ -186,9 +350,28 @@ interface StandardOpts {
186
350
  readonly roughnessChannel?: number;
187
351
  readonly clearcoat?: number;
188
352
  readonly clearcoatRoughness?: number;
189
- readonly specularTint?: readonly [number, number, number];
190
- readonly specularTintTexture?: MaterialValue;
191
- readonly emissive?: readonly [number, number, number];
353
+ readonly clearcoatTexture?: MaterialValue;
354
+ readonly clearcoatRoughnessTexture?: MaterialValue;
355
+ readonly clearcoatNormalTexture?: MaterialValue;
356
+ readonly clearcoatNormalScale?: number;
357
+ readonly anisotropyStrength?: number;
358
+ readonly anisotropyRotation?: number;
359
+ readonly anisotropyTexture?: MaterialValue;
360
+ readonly sheenColor?: MaterialColorInput3;
361
+ readonly sheenRoughness?: number;
362
+ readonly sheenColorTexture?: MaterialValue;
363
+ readonly sheenRoughnessTexture?: MaterialValue;
364
+ readonly iridescence?: number;
365
+ readonly iridescenceIor?: number;
366
+ readonly iridescenceThicknessMinimum?: number;
367
+ readonly iridescenceThicknessMaximum?: number;
368
+ readonly iridescenceTexture?: MaterialValue;
369
+ readonly iridescenceThicknessTexture?: MaterialValue;
370
+ readonly specular?: number;
371
+ readonly specularColor?: MaterialColorInput3;
372
+ readonly specularTexture?: MaterialValue;
373
+ readonly specularColorTexture?: MaterialValue;
374
+ readonly emissive?: MaterialColorInput3;
192
375
  readonly emissiveIntensity?: number;
193
376
  readonly emissiveTexture?: MaterialValue;
194
377
  readonly baseColorTexture?: MaterialValue;
@@ -209,6 +392,70 @@ interface StandardOpts {
209
392
  readonly queue?: number;
210
393
  }
211
394
 
395
+ /** Built-in Standard authoring surface with engine-owned parameter defaults. */
396
+ export interface DefaultStandardOptions extends StandardOpts {
397
+ readonly surfaceModule?: never;
398
+ readonly parameters?: never;
399
+ readonly values?: never;
400
+ }
401
+
402
+ /** Import-first Standard Surface authoring; the root parameter contract stays caller-owned. */
403
+ export interface CustomStandardSurfaceOptions {
404
+ readonly surfaceModule: string;
405
+ readonly parameters: readonly MaterialParameter[];
406
+ readonly values: Readonly<Record<string, MaterialValue>>;
407
+ readonly colorSpace?: MaterialColorSpace;
408
+ readonly renderState?: MaterialRenderState;
409
+ readonly castShadow?: boolean;
410
+ readonly queue?: number;
411
+ readonly baseColor?: never;
412
+ readonly metallic?: never;
413
+ readonly roughness?: never;
414
+ readonly metallicChannel?: never;
415
+ readonly roughnessChannel?: never;
416
+ readonly clearcoat?: never;
417
+ readonly clearcoatRoughness?: never;
418
+ readonly clearcoatTexture?: never;
419
+ readonly clearcoatRoughnessTexture?: never;
420
+ readonly clearcoatNormalTexture?: never;
421
+ readonly clearcoatNormalScale?: never;
422
+ readonly anisotropyStrength?: never;
423
+ readonly anisotropyRotation?: never;
424
+ readonly anisotropyTexture?: never;
425
+ readonly sheenColor?: never;
426
+ readonly sheenRoughness?: never;
427
+ readonly sheenColorTexture?: never;
428
+ readonly sheenRoughnessTexture?: never;
429
+ readonly iridescence?: never;
430
+ readonly iridescenceIor?: never;
431
+ readonly iridescenceThicknessMinimum?: never;
432
+ readonly iridescenceThicknessMaximum?: never;
433
+ readonly iridescenceTexture?: never;
434
+ readonly iridescenceThicknessTexture?: never;
435
+ readonly specular?: never;
436
+ readonly specularColor?: never;
437
+ readonly specularTexture?: never;
438
+ readonly specularColorTexture?: never;
439
+ readonly emissive?: never;
440
+ readonly emissiveIntensity?: never;
441
+ readonly emissiveTexture?: never;
442
+ readonly baseColorTexture?: never;
443
+ readonly metallicRoughnessTexture?: never;
444
+ readonly normalTexture?: never;
445
+ readonly occlusionTexture?: never;
446
+ readonly occlusionStrength?: never;
447
+ readonly alphaCutoff?: never;
448
+ readonly transmission?: never;
449
+ readonly transmissionTexture?: never;
450
+ readonly ior?: never;
451
+ readonly thickness?: never;
452
+ readonly thicknessTexture?: never;
453
+ readonly attenuationColor?: never;
454
+ readonly attenuationDistance?: never;
455
+ }
456
+
457
+ export type StandardOptions = DefaultStandardOptions | CustomStandardSurfaceOptions;
458
+
212
459
  function validateChannel(
213
460
  name: 'metallicChannel' | 'roughnessChannel',
214
461
  value: number | undefined,
@@ -233,7 +480,7 @@ function validateFiniteRange(
233
480
  }
234
481
  }
235
482
 
236
- function standard(opts: StandardOpts): MaterialAsset {
483
+ function standardDefault(opts: DefaultStandardOptions): MaterialAsset {
237
484
  const occlusionStrength = opts.occlusionStrength ?? 1;
238
485
  if (occlusionStrength < 0 || occlusionStrength > 1) {
239
486
  throw new Error(
@@ -266,11 +513,13 @@ function standard(opts: StandardOpts): MaterialAsset {
266
513
  throw new MaterialTransmissionContractError('transmission', 'depth-write', true);
267
514
  }
268
515
  const values: Record<string, MaterialValue> = {
269
- baseColor: opts.baseColor,
516
+ baseColor: linearColorFromInput(opts.baseColor, 4),
270
517
  metallic: opts.metallic ?? 0,
271
518
  roughness: opts.roughness ?? 0.5,
272
519
  occlusionStrength,
273
- specularTint: opts.specularTint ?? [1, 1, 1],
520
+ specular: opts.specular ?? 1,
521
+ specularColor:
522
+ opts.specularColor === undefined ? [1, 1, 1] : linearColorFromInput(opts.specularColor, 3),
274
523
  transmission,
275
524
  ior,
276
525
  thickness,
@@ -280,7 +529,37 @@ function standard(opts: StandardOpts): MaterialAsset {
280
529
  if (opts.roughnessChannel !== undefined) values.roughnessChannel = opts.roughnessChannel;
281
530
  if (opts.clearcoat !== undefined) values.clearcoat = opts.clearcoat;
282
531
  if (opts.clearcoatRoughness !== undefined) values.clearcoatRoughness = opts.clearcoatRoughness;
283
- if (opts.emissive !== undefined) values.emissive = opts.emissive;
532
+ if (opts.clearcoatTexture !== undefined) values.clearcoatTexture = opts.clearcoatTexture;
533
+ if (opts.clearcoatRoughnessTexture !== undefined) {
534
+ values.clearcoatRoughnessTexture = opts.clearcoatRoughnessTexture;
535
+ }
536
+ if (opts.clearcoatNormalTexture !== undefined) {
537
+ values.clearcoatNormalTexture = opts.clearcoatNormalTexture;
538
+ }
539
+ if (opts.clearcoatNormalScale !== undefined)
540
+ values.clearcoatNormalScale = opts.clearcoatNormalScale;
541
+ if (opts.anisotropyStrength !== undefined) values.anisotropyStrength = opts.anisotropyStrength;
542
+ if (opts.anisotropyRotation !== undefined) values.anisotropyRotation = opts.anisotropyRotation;
543
+ if (opts.anisotropyTexture !== undefined) values.anisotropyTexture = opts.anisotropyTexture;
544
+ if (opts.sheenColor !== undefined) values.sheenColor = linearColorFromInput(opts.sheenColor, 3);
545
+ if (opts.sheenRoughness !== undefined) values.sheenRoughness = opts.sheenRoughness;
546
+ if (opts.sheenColorTexture !== undefined) values.sheenColorTexture = opts.sheenColorTexture;
547
+ if (opts.sheenRoughnessTexture !== undefined) {
548
+ values.sheenRoughnessTexture = opts.sheenRoughnessTexture;
549
+ }
550
+ if (opts.iridescence !== undefined) values.iridescence = opts.iridescence;
551
+ if (opts.iridescenceIor !== undefined) values.iridescenceIor = opts.iridescenceIor;
552
+ if (opts.iridescenceThicknessMinimum !== undefined) {
553
+ values.iridescenceThicknessMinimum = opts.iridescenceThicknessMinimum;
554
+ }
555
+ if (opts.iridescenceThicknessMaximum !== undefined) {
556
+ values.iridescenceThicknessMaximum = opts.iridescenceThicknessMaximum;
557
+ }
558
+ if (opts.iridescenceTexture !== undefined) values.iridescenceTexture = opts.iridescenceTexture;
559
+ if (opts.iridescenceThicknessTexture !== undefined) {
560
+ values.iridescenceThicknessTexture = opts.iridescenceThicknessTexture;
561
+ }
562
+ if (opts.emissive !== undefined) values.emissive = linearColorFromInput(opts.emissive, 3);
284
563
  if (opts.emissiveIntensity !== undefined) values.emissiveIntensity = opts.emissiveIntensity;
285
564
  if (opts.emissiveTexture !== undefined) values.emissiveTexture = opts.emissiveTexture;
286
565
  if (opts.baseColorTexture !== undefined) values.baseColorTexture = opts.baseColorTexture;
@@ -289,8 +568,9 @@ function standard(opts: StandardOpts): MaterialAsset {
289
568
  }
290
569
  if (opts.normalTexture !== undefined) values.normalTexture = opts.normalTexture;
291
570
  if (opts.occlusionTexture !== undefined) values.occlusionTexture = opts.occlusionTexture;
292
- if (opts.specularTintTexture !== undefined) {
293
- values.specularTintTexture = opts.specularTintTexture;
571
+ if (opts.specularTexture !== undefined) values.specularTexture = opts.specularTexture;
572
+ if (opts.specularColorTexture !== undefined) {
573
+ values.specularColorTexture = opts.specularColorTexture;
294
574
  }
295
575
  if (opts.alphaCutoff !== undefined) values.alphaCutoff = opts.alphaCutoff;
296
576
  if (opts.transmissionTexture !== undefined) values.transmissionTexture = opts.transmissionTexture;
@@ -298,26 +578,58 @@ function standard(opts: StandardOpts): MaterialAsset {
298
578
  if (opts.attenuationDistance !== undefined) values.attenuationDistance = opts.attenuationDistance;
299
579
  const forwardRenderState =
300
580
  transmission > 0 ? { ...(opts.renderState ?? {}), depthWriteEnabled: false } : opts.renderState;
301
- const passes: [MaterialPass, ...MaterialPass[]] = [
302
- pass('forward', STANDARD_MODULE, forwardRenderState, 'fs_main', opts.queue),
303
- ];
304
- if (transmission === 0) {
305
- passes.push(pass('deferred', STANDARD_MODULE, opts.renderState, 'fs_gbuffer', opts.queue));
306
- }
307
- if (opts.castShadow !== false) {
308
- passes.push(pass('shadow-caster', STANDARD_MODULE, shadowCasterRenderState(opts.renderState)));
581
+ const parameters = standardParameters('linear', opts);
582
+ const declaredNames = new Set(parameters.map((parameter) => parameter.name));
583
+ for (const name of Object.keys(values)) {
584
+ if (!declaredNames.has(name)) delete values[name];
309
585
  }
586
+ const layerPlan = deriveStandardLayerPlan(parameters);
587
+ const projected = projectStandardSurfacePasses({
588
+ surfaceModule: DEFAULT_STANDARD_SURFACE_MODULE,
589
+ values,
590
+ ...(forwardRenderState === undefined ? {} : { renderState: forwardRenderState }),
591
+ ...(opts.castShadow === undefined ? {} : { castShadow: opts.castShadow }),
592
+ ...(opts.queue === undefined ? {} : { queue: opts.queue }),
593
+ layerPlan,
594
+ });
595
+ const passes = (
596
+ transmission > 0 ? projected.filter((entry) => entry.name !== 'deferred') : projected
597
+ ) as [MaterialPass, ...MaterialPass[]];
598
+ return {
599
+ kind: 'material',
600
+ colorSpace: 'linear',
601
+ passes,
602
+ parameters,
603
+ values,
604
+ };
605
+ }
606
+
607
+ function standardCustom(opts: CustomStandardSurfaceOptions): MaterialAsset {
608
+ const layerPlan = deriveStandardLayerPlan(
609
+ opts.parameters.filter((parameter) => parameter.optional !== true),
610
+ );
310
611
  return {
311
612
  kind: 'material',
312
613
  ...(opts.colorSpace === undefined || opts.colorSpace === 'srgb'
313
614
  ? {}
314
615
  : { colorSpace: opts.colorSpace }),
315
- passes,
316
- parameters: standardParameters(opts.colorSpace ?? 'srgb'),
317
- values,
616
+ passes: projectStandardSurfacePasses({
617
+ surfaceModule: opts.surfaceModule,
618
+ values: opts.values,
619
+ ...(opts.renderState === undefined ? {} : { renderState: opts.renderState }),
620
+ ...(opts.castShadow === undefined ? {} : { castShadow: opts.castShadow }),
621
+ ...(opts.queue === undefined ? {} : { queue: opts.queue }),
622
+ layerPlan,
623
+ }),
624
+ parameters: opts.parameters,
625
+ values: opts.values,
318
626
  };
319
627
  }
320
628
 
321
- export const Materials = { unlit, standard } as const;
629
+ export function standard(options: StandardOptions): MaterialAsset {
630
+ return options.surfaceModule === undefined ? standardDefault(options) : standardCustom(options);
631
+ }
632
+
633
+ export const Materials = { unlit, standard, srgb } as const;
322
634
 
323
635
  export { SPRITE_MODULE };
@@ -12,6 +12,65 @@ export interface MeshMaterialBindingDiagnostic {
12
12
  readonly detail?: Readonly<AssetErrorDetail>;
13
13
  }
14
14
 
15
+ /** Detached final-frame receipt for the Standard material IBL binding chain. */
16
+ export interface IblBindingResourceInspection {
17
+ readonly viewIdentity: number;
18
+ readonly samplerIdentity: number;
19
+ readonly deviceGeneration: number;
20
+ }
21
+
22
+ export interface IblBindingEntryInspection {
23
+ readonly binding: number;
24
+ readonly kind: 'textureView' | 'sampler' | 'buffer';
25
+ readonly resourceIdentity: number;
26
+ }
27
+
28
+ export interface IblBindingInspection {
29
+ readonly status: 'binding-chain-consistent' | 'binding-chain-mismatch';
30
+ readonly frameId: number;
31
+ readonly deviceGeneration: number;
32
+ readonly active: 'active' | 'fallback';
33
+ readonly cache: {
34
+ readonly identity: number;
35
+ readonly generation: number;
36
+ readonly prefilterMipCount: number;
37
+ readonly prefilterViewMipCount: number;
38
+ };
39
+ readonly sampler: {
40
+ readonly expected: {
41
+ readonly magFilter: 'linear';
42
+ readonly minFilter: 'linear';
43
+ readonly mipmapFilter: 'linear';
44
+ readonly addressModeU: 'clamp-to-edge';
45
+ readonly addressModeV: 'clamp-to-edge';
46
+ readonly addressModeW: 'clamp-to-edge';
47
+ };
48
+ readonly identities: readonly number[];
49
+ };
50
+ readonly resources: {
51
+ readonly irradiance: IblBindingResourceInspection;
52
+ readonly prefilter: IblBindingResourceInspection;
53
+ readonly brdfLut: IblBindingResourceInspection;
54
+ readonly intensityBufferIdentity: number;
55
+ };
56
+ readonly material?: {
57
+ readonly bindGroupIdentity: number;
58
+ readonly materialBglIdentity: number;
59
+ readonly cache: 'hit' | 'miss';
60
+ readonly skylightBindingStart: number;
61
+ readonly entries: readonly IblBindingEntryInspection[];
62
+ readonly reflectionBindings: readonly number[];
63
+ };
64
+ readonly pipeline?: {
65
+ readonly pipelineIdentity: number;
66
+ readonly effectiveMaterialLayoutIdentity: string | undefined;
67
+ readonly materialBglIdentity: number;
68
+ readonly bindGroupIdentity: number;
69
+ readonly drawFrameId: number;
70
+ };
71
+ readonly errors: readonly string[];
72
+ }
73
+
15
74
  export interface ResolvedMeshMaterialBinding {
16
75
  readonly handle: number;
17
76
  readonly source: MeshMaterialBindingSource;
@@ -0,0 +1,64 @@
1
+ import { mat4 } from '@forgeax/engine-math';
2
+ import { computeProjectionMatrix, computeViewMatrix } from './record/helpers';
3
+ import type { CameraSnapshot } from './render-contract';
4
+
5
+ /** Build the camera-facing proxy geometry used by GPU occlusion queries. */
6
+ export function buildOcclusionProxyVertices(
7
+ aabb: ArrayLike<number>,
8
+ world: ArrayLike<number>,
9
+ camera: CameraSnapshot,
10
+ ): Float32Array {
11
+ const view = computeViewMatrix(camera);
12
+ const projection = computeProjectionMatrix(camera);
13
+ const viewProjection = mat4.create();
14
+ mat4.multiply(viewProjection, projection, view);
15
+ const corners = [
16
+ [aabb[0] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
17
+ [aabb[3] ?? 0, aabb[1] ?? 0, aabb[2] ?? 0],
18
+ [aabb[3] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
19
+ [aabb[0] ?? 0, aabb[4] ?? 0, aabb[2] ?? 0],
20
+ [aabb[0] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
21
+ [aabb[3] ?? 0, aabb[1] ?? 0, aabb[5] ?? 0],
22
+ [aabb[3] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
23
+ [aabb[0] ?? 0, aabb[4] ?? 0, aabb[5] ?? 0],
24
+ ];
25
+ const triangles = [
26
+ 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 4, 5, 0, 5, 1, 3, 2, 6, 3, 6, 7, 0, 3, 7, 0, 7, 4, 1, 5,
27
+ 6, 1, 6, 2,
28
+ ];
29
+ const output = new Float32Array(triangles.length * 4);
30
+ for (let index = 0; index < triangles.length; index += 1) {
31
+ const corner = corners[triangles[index] ?? 0] ?? [0, 0, 0];
32
+ const cx = corner[0] ?? 0;
33
+ const cy = corner[1] ?? 0;
34
+ const cz = corner[2] ?? 0;
35
+ const x = (world[0] ?? 0) * cx + (world[4] ?? 0) * cy + (world[8] ?? 0) * cz + (world[12] ?? 0);
36
+ const y = (world[1] ?? 0) * cx + (world[5] ?? 0) * cy + (world[9] ?? 0) * cz + (world[13] ?? 0);
37
+ const z =
38
+ (world[2] ?? 0) * cx + (world[6] ?? 0) * cy + (world[10] ?? 0) * cz + (world[14] ?? 0);
39
+ const w =
40
+ (world[3] ?? 0) * cx + (world[7] ?? 0) * cy + (world[11] ?? 0) * cz + (world[15] ?? 1);
41
+ const base = index * 4;
42
+ output[base] =
43
+ (viewProjection[0] ?? 0) * x +
44
+ (viewProjection[4] ?? 0) * y +
45
+ (viewProjection[8] ?? 0) * z +
46
+ (viewProjection[12] ?? 0) * w;
47
+ output[base + 1] =
48
+ (viewProjection[1] ?? 0) * x +
49
+ (viewProjection[5] ?? 0) * y +
50
+ (viewProjection[9] ?? 0) * z +
51
+ (viewProjection[13] ?? 0) * w;
52
+ output[base + 2] =
53
+ (viewProjection[2] ?? 0) * x +
54
+ (viewProjection[6] ?? 0) * y +
55
+ (viewProjection[10] ?? 0) * z +
56
+ (viewProjection[14] ?? 0) * w;
57
+ output[base + 3] =
58
+ (viewProjection[3] ?? 0) * x +
59
+ (viewProjection[7] ?? 0) * y +
60
+ (viewProjection[11] ?? 0) * z +
61
+ (viewProjection[15] ?? 0) * w;
62
+ }
63
+ return output;
64
+ }