reze-engine 0.12.1 → 0.12.3

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 (120) hide show
  1. package/README.md +102 -61
  2. package/dist/engine.d.ts +9 -4
  3. package/dist/engine.d.ts.map +1 -1
  4. package/dist/engine.js +62 -18
  5. package/dist/shaders/body.d.ts +1 -1
  6. package/dist/shaders/body.d.ts.map +1 -1
  7. package/dist/shaders/body.js +7 -28
  8. package/dist/shaders/cloth_rough.d.ts +1 -1
  9. package/dist/shaders/cloth_rough.d.ts.map +1 -1
  10. package/dist/shaders/cloth_rough.js +4 -16
  11. package/dist/shaders/cloth_smooth.d.ts +1 -1
  12. package/dist/shaders/cloth_smooth.d.ts.map +1 -1
  13. package/dist/shaders/cloth_smooth.js +5 -17
  14. package/dist/shaders/default.d.ts +1 -1
  15. package/dist/shaders/default.d.ts.map +1 -1
  16. package/dist/shaders/eye.d.ts +1 -1
  17. package/dist/shaders/eye.d.ts.map +1 -1
  18. package/dist/shaders/face.d.ts +1 -1
  19. package/dist/shaders/face.d.ts.map +1 -1
  20. package/dist/shaders/face.js +21 -57
  21. package/dist/shaders/hair.d.ts +1 -1
  22. package/dist/shaders/hair.d.ts.map +1 -1
  23. package/dist/shaders/hair.js +7 -27
  24. package/dist/shaders/materials/body.d.ts +1 -1
  25. package/dist/shaders/materials/body.d.ts.map +1 -1
  26. package/dist/shaders/materials/body.js +86 -197
  27. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  28. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  29. package/dist/shaders/materials/cloth_rough.js +11 -122
  30. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  31. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  32. package/dist/shaders/materials/cloth_smooth.js +59 -172
  33. package/dist/shaders/materials/common.d.ts +6 -0
  34. package/dist/shaders/materials/common.d.ts.map +1 -0
  35. package/dist/shaders/materials/common.js +160 -0
  36. package/dist/shaders/materials/default.d.ts +1 -1
  37. package/dist/shaders/materials/default.d.ts.map +1 -1
  38. package/dist/shaders/materials/default.js +13 -146
  39. package/dist/shaders/materials/eye.d.ts +1 -1
  40. package/dist/shaders/materials/eye.d.ts.map +1 -1
  41. package/dist/shaders/materials/eye.js +13 -118
  42. package/dist/shaders/materials/face.d.ts +1 -1
  43. package/dist/shaders/materials/face.d.ts.map +1 -1
  44. package/dist/shaders/materials/face.js +85 -197
  45. package/dist/shaders/materials/hair.d.ts +1 -1
  46. package/dist/shaders/materials/hair.d.ts.map +1 -1
  47. package/dist/shaders/materials/hair.js +78 -183
  48. package/dist/shaders/materials/metal.d.ts +1 -1
  49. package/dist/shaders/materials/metal.d.ts.map +1 -1
  50. package/dist/shaders/materials/metal.js +16 -122
  51. package/dist/shaders/materials/nodes.d.ts +1 -1
  52. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  53. package/dist/shaders/materials/nodes.js +77 -0
  54. package/dist/shaders/materials/stockings.d.ts +1 -1
  55. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  56. package/dist/shaders/materials/stockings.js +26 -152
  57. package/dist/shaders/metal.d.ts +1 -1
  58. package/dist/shaders/metal.d.ts.map +1 -1
  59. package/dist/shaders/metal.js +4 -17
  60. package/dist/shaders/nodes.d.ts +1 -1
  61. package/dist/shaders/nodes.d.ts.map +1 -1
  62. package/dist/shaders/nodes.js +0 -9
  63. package/dist/shaders/passes/bloom.d.ts +1 -1
  64. package/dist/shaders/passes/bloom.d.ts.map +1 -1
  65. package/dist/shaders/passes/bloom.js +7 -4
  66. package/dist/shaders/passes/composite.d.ts +1 -1
  67. package/dist/shaders/passes/composite.d.ts.map +1 -1
  68. package/dist/shaders/passes/composite.js +11 -4
  69. package/dist/shaders/passes/ground.d.ts +1 -1
  70. package/dist/shaders/passes/ground.d.ts.map +1 -1
  71. package/dist/shaders/passes/ground.js +6 -2
  72. package/dist/shaders/passes/outline.d.ts +1 -1
  73. package/dist/shaders/passes/outline.d.ts.map +1 -1
  74. package/dist/shaders/passes/outline.js +2 -2
  75. package/dist/shaders/stockings.d.ts +1 -1
  76. package/dist/shaders/stockings.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/engine.ts +60 -18
  79. package/src/shaders/materials/body.ts +90 -201
  80. package/src/shaders/materials/cloth_rough.ts +11 -122
  81. package/src/shaders/materials/cloth_smooth.ts +63 -176
  82. package/src/shaders/materials/common.ts +171 -0
  83. package/src/shaders/materials/default.ts +13 -146
  84. package/src/shaders/materials/eye.ts +13 -118
  85. package/src/shaders/materials/face.ts +89 -201
  86. package/src/shaders/materials/hair.ts +82 -187
  87. package/src/shaders/materials/metal.ts +16 -122
  88. package/src/shaders/materials/nodes.ts +77 -0
  89. package/src/shaders/materials/stockings.ts +27 -153
  90. package/src/shaders/passes/bloom.ts +7 -4
  91. package/src/shaders/passes/composite.ts +11 -4
  92. package/src/shaders/passes/ground.ts +6 -2
  93. package/src/shaders/passes/outline.ts +2 -2
  94. package/dist/bezier-interpolate.d.ts +0 -15
  95. package/dist/bezier-interpolate.d.ts.map +0 -1
  96. package/dist/bezier-interpolate.js +0 -40
  97. package/dist/engine_ts.d.ts +0 -143
  98. package/dist/engine_ts.d.ts.map +0 -1
  99. package/dist/engine_ts.js +0 -1575
  100. package/dist/ik.d.ts +0 -32
  101. package/dist/ik.d.ts.map +0 -1
  102. package/dist/ik.js +0 -337
  103. package/dist/player.d.ts +0 -64
  104. package/dist/player.d.ts.map +0 -1
  105. package/dist/player.js +0 -220
  106. package/dist/pool-scene.d.ts +0 -52
  107. package/dist/pool-scene.d.ts.map +0 -1
  108. package/dist/pool-scene.js +0 -1122
  109. package/dist/pool.d.ts +0 -38
  110. package/dist/pool.d.ts.map +0 -1
  111. package/dist/pool.js +0 -422
  112. package/dist/rzm-converter.d.ts +0 -12
  113. package/dist/rzm-converter.d.ts.map +0 -1
  114. package/dist/rzm-converter.js +0 -40
  115. package/dist/rzm-loader.d.ts +0 -24
  116. package/dist/rzm-loader.d.ts.map +0 -1
  117. package/dist/rzm-loader.js +0 -488
  118. package/dist/rzm-writer.d.ts +0 -27
  119. package/dist/rzm-writer.d.ts.map +0 -1
  120. package/dist/rzm-writer.js +0 -701
package/README.md CHANGED
@@ -14,9 +14,10 @@ npm install reze-engine
14
14
 
15
15
  - **Anime/MMD-style hybrid renderer** — toon-ramp NPR diffuse mixed with PBR GGX specular (multi-scatter + LTC energy compensation)
16
16
  - **Per-material presets** — `face` / `hair` / `body` / `eye` / `stockings` / `metal` / `cloth_smooth` / `cloth_rough` / `default`, assigned by material name
17
- - **HDR pipeline** with bloom mip pyramid, Filmic tone mapping, 4× MSAA
17
+ - **HDR pipeline** with bloom mip pyramid, Filmic tone mapping, 4× MSAA, tile-memory-friendly on Apple Silicon
18
18
  - **Alpha-hashed transparency** (Wyman & McGuire 2017) for self-overlapping transparent meshes like stockings
19
19
  - **Screen-space outlines** on opaque + transparent materials
20
+ - **See-through hair over eyes** — stencil-gated MMD post-alpha-eye so eyes read at 50% through hair silhouettes
20
21
  - **VMD animation** with IK solver and Bullet physics
21
22
  - **Orbit camera** with bone-follow mode
22
23
  - **GPU picking** (double-click/tap)
@@ -26,7 +27,7 @@ npm install reze-engine
26
27
  ## Usage
27
28
 
28
29
  ```javascript
29
- import { Engine, Vec3 } from "reze-engine";
30
+ import { Engine, Vec3 } from "reze-engine"
30
31
 
31
32
  const engine = new Engine(canvas, {
32
33
  world: { color: new Vec3(0.4, 0.49, 0.65), strength: 1.0 },
@@ -36,16 +37,16 @@ const engine = new Engine(canvas, {
36
37
  direction: new Vec3(0, -0.5, 1),
37
38
  },
38
39
  camera: { distance: 31.5, target: new Vec3(0, 11.5, 0) }, // MMD units (1 unit = 8 cm)
39
- });
40
- await engine.init();
40
+ })
41
+ await engine.init()
41
42
 
42
43
  engine.setBloomOptions({
43
44
  color: new Vec3(0.9, 0.1, 0.8),
44
45
  intensity: 0.05,
45
46
  threshold: 0.5,
46
- });
47
+ })
47
48
 
48
- const model = await engine.loadModel("hero", "/models/hero/hero.pmx");
49
+ const model = await engine.loadModel("hero", "/models/hero/hero.pmx")
49
50
 
50
51
  // Map PMX material names to NPR presets (unlisted names fall back to `default`).
51
52
  engine.setMaterialPresets("hero", {
@@ -57,15 +58,15 @@ engine.setMaterialPresets("hero", {
57
58
  cloth_rough: ["jacket", "pants"],
58
59
  stockings: ["stockings"],
59
60
  metal: ["metal01", "earring"],
60
- });
61
+ })
61
62
 
62
- await model.loadVmd("idle", "/animations/idle.vmd");
63
- model.show("idle");
64
- model.play();
63
+ await model.loadVmd("idle", "/animations/idle.vmd")
64
+ model.show("idle")
65
+ model.play()
65
66
 
66
- engine.setCameraFollow(model, "センター", new Vec3(0, 3.5, 0));
67
- engine.addGround({ width: 160, height: 160 });
68
- engine.runRenderLoop();
67
+ engine.setCameraFollow(model, "センター", new Vec3(0, 3.5, 0))
68
+ engine.addGround({ width: 160, height: 160 })
69
+ engine.runRenderLoop()
69
70
  ```
70
71
 
71
72
  ## API
@@ -114,30 +115,26 @@ Use a hidden `<input type="file" webkitdirectory multiple>` (or drag/drop) and p
114
115
  2. **`engine.loadModel(name, { files, pmxFile })`** — `pmxFile` selects which `.pmx` when the folder contains several.
115
116
 
116
117
  ```javascript
117
- import {
118
- Engine,
119
- parsePmxFolderInput,
120
- pmxFileAtRelativePath,
121
- } from "reze-engine";
118
+ import { Engine, parsePmxFolderInput, pmxFileAtRelativePath } from "reze-engine"
122
119
 
123
120
  // In <input onChange>:
124
- const picked = parsePmxFolderInput(e.target.files);
125
- e.target.value = "";
121
+ const picked = parsePmxFolderInput(e.target.files)
122
+ e.target.value = ""
126
123
 
127
124
  if (picked.status === "single") {
128
125
  const model = await engine.loadModel("myModel", {
129
126
  files: picked.files,
130
127
  pmxFile: picked.pmxFile,
131
- });
128
+ })
132
129
  }
133
130
 
134
131
  if (picked.status === "multiple") {
135
132
  // Let the user choose `chosenPath` from picked.pmxRelativePaths, then:
136
- const pmxFile = pmxFileAtRelativePath(picked.files, chosenPath);
133
+ const pmxFile = pmxFileAtRelativePath(picked.files, chosenPath)
137
134
  const model = await engine.loadModel("myModel", {
138
135
  files: picked.files,
139
136
  pmxFile,
140
- });
137
+ })
141
138
  }
142
139
  ```
143
140
 
@@ -176,12 +173,12 @@ model.getBoneWorldPosition(name)
176
173
  `model.exportVmd(name)` serialises a loaded clip back to the VMD binary format and returns an `ArrayBuffer`. Bone and morph names are Shift-JIS encoded for compatibility with standard MMD tools.
177
174
 
178
175
  ```javascript
179
- const buffer = model.exportVmd("idle");
180
- const blob = new Blob([buffer], { type: "application/octet-stream" });
181
- const link = document.createElement("a");
182
- link.href = URL.createObjectURL(blob);
183
- link.download = "idle.vmd";
184
- link.click();
176
+ const buffer = model.exportVmd("idle")
177
+ const blob = new Blob([buffer], { type: "application/octet-stream" })
178
+ const link = document.createElement("a")
179
+ link.href = URL.createObjectURL(blob)
180
+ link.download = "idle.vmd"
181
+ link.click()
185
182
  ```
186
183
 
187
184
  #### Playback
@@ -227,55 +224,99 @@ The shadow map is cast from `sun.direction` — same vector the shader lights wi
227
224
 
228
225
  ## Rendering
229
226
 
230
- The renderer pairs stylised toon shading with a physically-based specular core, so anime characters keep their flat illustrated look while highlights and reflections still feel grounded. Each surface runs through a per-material preset that mixes an NPR closure with a Principled-style BSDF.
227
+ Each surface combines an NPR stack with a Principled-style BSDF, mixed per material — so anime characters keep their flat illustrated look while highlights and reflections stay grounded. Every per-material shader is ~40–120 lines of distinctive code standing on a small set of shared WGSL primitives (`engine/src/shaders/materials/`).
228
+
229
+ ### Anatomy of a material shader
230
+
231
+ Every material's fragment shader is the same 7-stage pipeline. The order is fixed; the content is per-material:
232
+
233
+ ```
234
+ (A) Fragment setup → n, v, l, sun, amb, shadow ← shared
235
+ (B) Texture + alpha → tex_rgb, discard ← shared shape
236
+ (C) NPR stack → toon + rim + warm + … ← UNIQUE per material
237
+ (D) Optional bump → noise → bump_lh ← 3 presets
238
+ (E) Principled BSDF → eval_principled(...) ← shared helper
239
+ (F) NPR ↔ PBR mix → mix(npr, principled, fac) ← per-material fac
240
+ (G) FSOut → color + bloom mask ← shared
241
+ ```
242
+
243
+ The simplest material (`default`) uses only A/B/E/G — no NPR stack at all:
244
+
245
+ ```wgsl
246
+ let color = eval_principled(
247
+ PrincipledIn(albedo, 0.0, 0.5, 0.5, 1e30, 0.0, 0.0), // metallic, spec, rough, clamp, sheen, sheen_tint
248
+ n, l, v, sun, amb, shadow
249
+ );
250
+ ```
251
+
252
+ NPR presets add stage C (and sometimes D) on top, and stage F chooses how NPR-leaning the surface is.
253
+
254
+ ### Shared WGSL foundations
255
+
256
+ - **`nodes.ts`** — WGSL mirrors of the Blender shader nodes the presets use: `hue_sat`, `bright_contrast`, `ramp_constant/linear/cardinal`, `mix_overlay/lighten/linear_light`, `fresnel`, `layer_weight_fresnel/facing`, `tex_noise`, `tex_voronoi`, `mapping_point`, `bump_lh`, `normal_map`. Plus a combined DFG + LTC LUT, `eval_principled(PrincipledIn, N, L, V, sun, amb, shadow)`, and `principled_sheen`.
257
+ - **`common.ts`** — Uniform structs, bind-group layout (same for every material pipeline), PCF shadow sampler, skinning vertex shader, shared `FSOut`.
258
+ - **Per-material files** — constants + NPR stack + optional bump + `eval_principled` call + final mix.
231
259
 
232
260
  ### PBR specular core
233
261
 
262
+ Inside `eval_principled`:
263
+
234
264
  - GGX microfacet specular with Schlick Fresnel and Walter–Smith G1
235
- - **Multi-scatter compensation** (Fdez-Agüera 2019, `F_brdf_multi_scatter`) — restores energy at high roughness so metals don't darken
236
- - **Split-sum DFG LUT** (Karis 2013) baked at `engine.init()` — drives indirect specular and acts as the denominator of the direct-spec energy correction
237
- - **LTC direct-spec scale** (Heitz 2016 magnitude LUT) — keeps analytic-light specular in the same energy budget as image-based lighting
238
- - Sheen coarse approximation (`f³·0.077 + f·0.01 + 0.00026`) on cloth and stockings
265
+ - **Multi-scatter compensation** (Fdez-Agüera 2019) — restores energy at high roughness so metals don't darken
266
+ - **Split-sum DFG LUT** (Karis 2013) — drives indirect specular
267
+ - **LTC direct-spec scale** (Heitz 2016) — keeps analytic-light specular in the same energy budget as image-based lighting
268
+ - **Sheen coarse curve** gated by the `sheen` field on `PrincipledIn`
239
269
 
240
- ### Stylised diffuse (NPR toolbox)
270
+ ### NPR toolbox
241
271
 
242
- Every preset is built out of the same NPR primitives:
272
+ Every preset's stage C is built from these primitives:
243
273
 
244
- - **Toon ramps** — quantised NdotL through constant or anti-aliased step ramps for hard cel-shaded transitions
245
- - **HSV remaps** — separate hue/sat/value tints for shadow vs lit zones, then layered with mix-overlay against AO masks for warm-shadow / cool-light shifts
246
- - **Fresnel rim & layer-weight wrap** — Fresnel × layer-weight feeds a MixShader against an emissive backdrop for anime-style back-light
247
- - **Procedural micro-detail** — fBM noise (PCG hash, three octaves) plus bump-from-height for fabric weave, skin micro-roughness, and metallic Voronoi sparkle in reflection-coord space
248
- - **Selective emission** — texture-gated emission boosts (eye iris, stockings pattern) that survive into bloom
274
+ - **Toon ramps** — quantised NdotL through constant or `ramp_constant_edge_aa` (fwidth-based step anti-alias) for cel-shaded shadow terminators
275
+ - **HSV remaps** — separate hue/sat/value tints for shadow vs lit zones, layered with mix-overlay against the lit texture for warm-shadow / cool-light shifts
276
+ - **Fresnel rim & layer-weight wrap** — `fresnel × layer_weight_facing` (or two stacked fresnels) feeds a MixShader against an emissive backdrop for anime back-light
277
+ - **Procedural micro-detail** — 3-octave value noise (PCG hash, fully unrolled) drives bump-from-height for skin and fabric; 3D Voronoi in reflection-coord space drives metallic sparkle
278
+ - **Selective emission** — BT.601-luminance-gated boosts (eye iris, face highlights, stockings pattern) that survive into bloom
249
279
 
250
- ### Material presets
280
+ ### Per-material NPR stacks
251
281
 
252
- Each PMX material is dispatched to one of these shaders:
282
+ Each PMX material is assigned to one of these shaders. The NPR stack column is what's actually in stage C of that file; the Principled column is what gets passed to `eval_principled`.
253
283
 
254
- | Preset | Look |
255
- | -------------- | ------------------------------------------------------------------------------ |
256
- | `face` | toon ramp + rim + warm subsurface bleed + Principled mix |
257
- | `hair` | layered hair toon + Fresnel rim + Principled spec mix |
258
- | `body` | toon ramp + AO modulation + rim + Principled mix |
259
- | `eye` | iris with emission boost (drives bloom) |
260
- | `stockings` | NPR-tinted Principled with sheen, alpha-hashed |
261
- | `metal` | full-metallic Principled + reflection-coord Voronoi sparkle + NPR toon overlay |
262
- | `cloth_smooth` | Principled cloth with sheen, smoother variant |
263
- | `cloth_rough` | rougher cloth variant |
264
- | `default` | plain Principled BSDF (unmapped fallback) |
284
+ | Preset | NPR stack (stage C) | Principled (stage E) |
285
+ | -------------- | ----------------------------------------------------------- | ------------------------------------------------------------- |
286
+ | `default` | — | metallic=0, spec=0.5, rough=0.5 |
287
+ | `eye` | (emission = albedo × 1.5 added post-eval) | same as default |
288
+ | `face` | toon + warm rim + dual-fresnel rim + BT.601 bright-tex gate | spec=0.5, rough=0.3, noise bump, spec clamp=10 |
289
+ | `body` | toon + warm rim + fresnel rim + facing rim | spec=0.5, rough=0.3, noise bump, spec clamp=10 |
290
+ | `hair` | toon + fresnel rim + bevel (n.y) + bright-tex gate | spec=1.0, rough=0.3, mixed at 20% PBR |
291
+ | `cloth_smooth` | toon + bevel + mix-overlay emission (×18) | spec=0.8, rough=0.5 |
292
+ | `cloth_rough` | same NPR as `cloth_smooth` | spec=0.8, rough=0.82, live noise bump, spec clamp=10 |
293
+ | `metal` | toon + mix-overlay emission (×8) | metallic=1, voronoi-driven base (reflection-coord), rough=0.3 |
294
+ | `stockings` | gradient × facing mask + HSV-boosted emission (×5) | metallic=0.1, spec=1, rough=0.5, **sheen=0.7**, hashed alpha |
295
+
296
+ Assign presets per-model with `engine.setMaterialPresets(name, map)` (see the [Usage](#usage) example). Material names not listed fall through to `default`.
265
297
 
266
298
  ### Shadows, post, output
267
299
 
268
- - Directional shadow map (2048², depth32float, 3×3 PCF unrolled, normal + depth bias)
269
- - HDR (rgba16f) main pass with 4× MSAA, resolved before tonemap
270
- - Bloom via threshold + downsample/upsample mip pyramid, gated by an MRT mask channel emitted from emissive presets
271
- - Filmic tone mapping (LUT sampled from the same view-transform curve used by "Filmic / Medium High Contrast"), exposure baked in
300
+ - Directional shadow map (2048², depth32float, PCF, normal + depth bias)
301
+ - HDR main pass with 4× MSAA. Color is `rg11b10ufloat` paired with an `rg8unorm` aux MRT carrying bloom mask (`.r`) and accumulated alpha (`.g`). The combined footprint fits Apple Silicon TBDR tile memory, so the 4× MSAA buffer resolves in-tile rather than spilling to system memory every frame. Falls back to `rgba16float` when the device does not expose `rg11b10ufloat-renderable`.
302
+ - Bloom via threshold + downsample/upsample mip pyramid, gated by the aux bloom-mask channel
303
+ - Filmic tone mapping (LUT extracted from Blender 3.6 OCIO "Filmic / Medium High Contrast")
272
304
  - Screen-space outline pass (inverted-hull) on opaque and transparent materials
273
305
 
274
- Assign presets per-model with `engine.setMaterialPresets(name, map)` (see the [Usage](#usage) example). Material names not listed fall through to the `default` Principled BSDF.
275
-
276
306
  ### Alpha-hashed transparency
277
307
 
278
- `stockings` uses the Wyman & McGuire 2017 derivative-aware stochastic discard instead of alpha blend. Self-overlapping transparent meshes (stockings wrap the leg — front and back surfaces share screen pixels) can't be sorted per-fragment in one draw call, so blend produces "cracks". Hashed alpha keeps opaque-style depth writes and resolves cleanly under MSAA.
308
+ `stockings` uses the Wyman & McGuire 2017 derivative-aware stochastic discard so self-overlapping transparent meshes (e.g. the front and back of a stocking wrapped around a leg) resolve cleanly under MSAA with opaque-style depth writes. The hash is derived from world-space position, so the dither pattern does not swim when the camera moves.
309
+
310
+ ### See-through hair over eyes (MMD post-alpha-eye)
311
+
312
+ The classic MMD effect where hair strands covering the eye are rendered at 50% so the iris stays readable — implemented as a single extra pass driven by the stencil buffer, not a two-texture composite.
313
+
314
+ - **Eye pipeline** stamps `stencil = EYE_VALUE` on every fragment it writes, with `cullMode: "front"` and a small negative `depthBias` so only the back half of the eye mesh renders (the MMD trick that keeps eyes from leaking through the back of the head).
315
+ - **Main hair pipeline** stencil-tests `not-equal EYE_VALUE` and skips those fragments.
316
+ - **Hair-over-eyes pipeline** re-issues the hair draws with `IS_OVER_EYES = true`, stencil-tests `equal EYE_VALUE`, disables depth writes, and alpha blends at 50% — eye-stamped pixels end up `0.5·hair + 0.5·eye` in linear HDR before tonemap.
317
+ - **Outline pipeline** stencil-tests `not-equal EYE_VALUE` so edge color does not overwrite the see-through region.
318
+
319
+ Draw order within a model: non-eye/non-hair opaque → eye (stamp) → hair (skip stamp) → outlines (skip stamp) → hair-over-eyes (match stamp).
279
320
 
280
321
  ## Projects Using This Engine
281
322
 
package/dist/engine.d.ts CHANGED
@@ -128,13 +128,18 @@ export declare class Engine {
128
128
  private multisampleTexture;
129
129
  private hdrResolveTexture;
130
130
  private static readonly MULTISAMPLE_COUNT;
131
- private static readonly HDR_FORMAT;
131
+ private hdrFormat;
132
132
  /** Stencil value stamped by eye draws so hair can stencil-test against it and
133
133
  * alpha-blend a second pass over eye silhouette pixels (see-through-hair effect). */
134
134
  private static readonly STENCIL_EYE_VALUE;
135
- /** Single-channel mask written alongside HDR color 1 = model geometry (contributes
136
- * to bloom), 0 = ground (never blooms). Sampled by the bloom blit pass to gate the
137
- * prefilter so ground brightness can't halo the scene. */
135
+ /** Aux MRT alongside HDR color. Two channels:
136
+ * .r bloom mask (1 = model geometry, 0 = ground; sampled by bloom blit to gate prefilter).
137
+ * .g accumulated alpha (the channel that used to live in hdr.a before the HDR format
138
+ * switched to rg11b10ufloat, which has no alpha). Sampled by composite/bloom to
139
+ * un-premultiply color for tonemap and to produce the canvas-drawable alpha used by
140
+ * the premultiplied alphaMode compositor (so the page background still shows through
141
+ * cleared / edge-faded regions like before).
142
+ * rg8unorm at 4× MSAA is 8 bytes/texel — still fits Apple TBDR tile memory comfortably. */
138
143
  private static readonly BLOOM_MASK_FORMAT;
139
144
  private multisampleMaskTexture;
140
145
  private maskResolveTexture;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -65,6 +65,23 @@ export class Engine {
65
65
  this.lightData = new Float32Array(64);
66
66
  this.lightCount = 0;
67
67
  this.resizeObserver = null;
68
+ // HDR intermediate format. rg11b10ufloat when the adapter exposes the
69
+ // `rg11b10ufloat-renderable` feature (Chrome + Safari on Apple Silicon both
70
+ // do), else fall back to rgba16float.
71
+ //
72
+ // Why it matters — Apple TBDR tile memory: rgba16float is 8 bytes/texel, so
73
+ // 4× MSAA is 32 bytes/texel and does not fit Apple Silicon's tile memory at
74
+ // useful tile sizes. The driver then stores the full MSAA buffer to system
75
+ // memory every frame and resolves from there — ~300 MB/frame of extra
76
+ // bandwidth at 1920×1200 DPR=2, which is the dominant frame-pacing hit on
77
+ // Safari (visibly: shrinking the window made Safari smooth; Chrome was
78
+ // always smooth because Dawn apparently amortizes it). rg11b10ufloat at
79
+ // 4 bytes/texel → 16 bytes/texel at 4× MSAA → fits tile memory like
80
+ // rgba8unorm does, resolves in-tile, no system-memory round-trip. No alpha
81
+ // channel (the HDR path never needed one — alpha blending reads src.a from
82
+ // the fragment shader and treats missing dst.a as 1, so the blend math is
83
+ // unchanged).
84
+ this.hdrFormat = "rgba16float";
68
85
  // [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
69
86
  this.compositeUniformData = new Float32Array(8);
70
87
  this.bloomBlitUniformData = new Float32Array(4);
@@ -276,11 +293,19 @@ export class Engine {
276
293
  // Step 1: Get WebGPU device and context
277
294
  async init() {
278
295
  const adapter = await navigator.gpu?.requestAdapter();
279
- const device = await adapter?.requestDevice();
296
+ if (!adapter)
297
+ throw new Error("WebGPU is not supported in this browser.");
298
+ const wantFeature = "rg11b10ufloat-renderable";
299
+ const hasRg11b10 = adapter.features.has(wantFeature);
300
+ const device = await adapter.requestDevice({
301
+ requiredFeatures: hasRg11b10 ? [wantFeature] : [],
302
+ });
280
303
  if (!device) {
281
304
  throw new Error("WebGPU is not supported in this browser.");
282
305
  }
283
306
  this.device = device;
307
+ if (hasRg11b10)
308
+ this.hdrFormat = "rg11b10ufloat";
284
309
  const context = this.canvas.getContext("webgpu");
285
310
  if (!context) {
286
311
  throw new Error("Failed to get WebGPU context.");
@@ -438,7 +463,7 @@ export class Engine {
438
463
  // composite pass writes the swapchain. Tonemap moved to composite so bloom
439
464
  // (added next) can run on linear HDR.
440
465
  const standardBlend = {
441
- format: Engine.HDR_FORMAT,
466
+ format: this.hdrFormat,
442
467
  blend: {
443
468
  color: {
444
469
  srcFactor: "src-alpha",
@@ -452,10 +477,21 @@ export class Engine {
452
477
  },
453
478
  },
454
479
  };
455
- // Bloom mask target r8unorm has no alpha channel, so src-alpha blending is invalid.
456
- // Use replace mode: depth test already rejects occluded fragments, so last-writer-wins
457
- // on surviving pixels gives the right result (ground writes 0; models/outlines write 1).
458
- const maskBlend = { format: Engine.BLOOM_MASK_FORMAT };
480
+ // Aux target carrying (bloom mask, alpha). Src-alpha blend so the .g channel
481
+ // accumulates proper alpha-over (same semantic the old rgba16f hdr.a had).
482
+ // Materials write vec2f(mask, 1.0); ground writes vec2f(0.0, 1.0). With src.a
483
+ // coming from the fragment color.a, the blend equation produces
484
+ // out.g = 1·src.a + dst.g·(1-src.a) → premultiplied over operator on alpha.
485
+ // .r gets weighted by src.a too, which is fine: opaque pixels (α=1) give full
486
+ // mask, partially translucent fragments dilute mask proportionally — acceptable
487
+ // for the bloom-gate use.
488
+ const maskBlend = {
489
+ format: Engine.BLOOM_MASK_FORMAT,
490
+ blend: {
491
+ color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha", operation: "add" },
492
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
493
+ },
494
+ };
459
495
  const sceneTargets = [standardBlend, maskBlend];
460
496
  const shaderModule = this.device.createShaderModule({
461
497
  label: "default model shader",
@@ -889,21 +925,21 @@ export class Engine {
889
925
  label: "bloom blit pipeline",
890
926
  layout: bloomBlitLayout,
891
927
  vertex: { module: bloomBlitShader, entryPoint: "vs" },
892
- fragment: { module: bloomBlitShader, entryPoint: "fs", targets: [{ format: Engine.HDR_FORMAT }] },
928
+ fragment: { module: bloomBlitShader, entryPoint: "fs", targets: [{ format: this.hdrFormat }] },
893
929
  primitive: { topology: "triangle-list" },
894
930
  });
895
931
  this.bloomDownsamplePipeline = this.device.createRenderPipeline({
896
932
  label: "bloom downsample pipeline",
897
933
  layout: bloomDownLayout,
898
934
  vertex: { module: bloomDownsampleShader, entryPoint: "vs" },
899
- fragment: { module: bloomDownsampleShader, entryPoint: "fs", targets: [{ format: Engine.HDR_FORMAT }] },
935
+ fragment: { module: bloomDownsampleShader, entryPoint: "fs", targets: [{ format: this.hdrFormat }] },
900
936
  primitive: { topology: "triangle-list" },
901
937
  });
902
938
  this.bloomUpsamplePipeline = this.device.createRenderPipeline({
903
939
  label: "bloom upsample pipeline",
904
940
  layout: bloomUpLayout,
905
941
  vertex: { module: bloomUpsampleShader, entryPoint: "vs" },
906
- fragment: { module: bloomUpsampleShader, entryPoint: "fs", targets: [{ format: Engine.HDR_FORMAT }] },
942
+ fragment: { module: bloomUpsampleShader, entryPoint: "fs", targets: [{ format: this.hdrFormat }] },
907
943
  primitive: { topology: "triangle-list" },
908
944
  });
909
945
  // ─── Composite: HDR + bloom → Filmic → swapchain (premultiplied) ───
@@ -921,6 +957,9 @@ export class Engine {
921
957
  { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: {} },
922
958
  { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
923
959
  { binding: 3, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
960
+ // Aux mask/alpha texture — composite reads .g to reconstruct the alpha that
961
+ // used to live in the HDR target before the rg11b10ufloat switch.
962
+ { binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: {} },
924
963
  ],
925
964
  });
926
965
  const compositeShader = this.device.createShaderModule({
@@ -1029,13 +1068,13 @@ export class Engine {
1029
1068
  label: "multisample HDR render target",
1030
1069
  size: [width, height],
1031
1070
  sampleCount: Engine.MULTISAMPLE_COUNT,
1032
- format: Engine.HDR_FORMAT,
1071
+ format: this.hdrFormat,
1033
1072
  usage: GPUTextureUsage.RENDER_ATTACHMENT,
1034
1073
  });
1035
1074
  this.hdrResolveTexture = this.device.createTexture({
1036
1075
  label: "HDR resolve target",
1037
1076
  size: [width, height],
1038
- format: Engine.HDR_FORMAT,
1077
+ format: this.hdrFormat,
1039
1078
  usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
1040
1079
  });
1041
1080
  // Bloom-mask MRT attachments — same dims + MSAA as HDR so they share the render pass.
@@ -1064,14 +1103,14 @@ export class Engine {
1064
1103
  label: "bloom down pyramid",
1065
1104
  size: [bw, bh],
1066
1105
  mipLevelCount: this.bloomMipCount,
1067
- format: Engine.HDR_FORMAT,
1106
+ format: this.hdrFormat,
1068
1107
  usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
1069
1108
  });
1070
1109
  this.bloomUpTexture = this.device.createTexture({
1071
1110
  label: "bloom up pyramid",
1072
1111
  size: [bw, bh],
1073
1112
  mipLevelCount: Math.max(1, this.bloomMipCount - 1),
1074
- format: Engine.HDR_FORMAT,
1113
+ format: this.hdrFormat,
1075
1114
  usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
1076
1115
  });
1077
1116
  this.bloomDownMipViews = [];
@@ -1186,6 +1225,7 @@ export class Engine {
1186
1225
  { binding: 1, resource: compositeBloomView },
1187
1226
  { binding: 2, resource: this.bloomSampler },
1188
1227
  { binding: 3, resource: { buffer: this.compositeUniformBuffer } },
1228
+ { binding: 4, resource: this.maskResolveView },
1189
1229
  ],
1190
1230
  });
1191
1231
  }
@@ -2320,13 +2360,17 @@ export class Engine {
2320
2360
  }
2321
2361
  Engine.instance = null;
2322
2362
  Engine.MULTISAMPLE_COUNT = 4;
2323
- Engine.HDR_FORMAT = "rgba16float";
2324
2363
  /** Stencil value stamped by eye draws so hair can stencil-test against it and
2325
2364
  * alpha-blend a second pass over eye silhouette pixels (see-through-hair effect). */
2326
2365
  Engine.STENCIL_EYE_VALUE = 1;
2327
- /** Single-channel mask written alongside HDR color 1 = model geometry (contributes
2328
- * to bloom), 0 = ground (never blooms). Sampled by the bloom blit pass to gate the
2329
- * prefilter so ground brightness can't halo the scene. */
2330
- Engine.BLOOM_MASK_FORMAT = "r8unorm";
2366
+ /** Aux MRT alongside HDR color. Two channels:
2367
+ * .r bloom mask (1 = model geometry, 0 = ground; sampled by bloom blit to gate prefilter).
2368
+ * .g accumulated alpha (the channel that used to live in hdr.a before the HDR format
2369
+ * switched to rg11b10ufloat, which has no alpha). Sampled by composite/bloom to
2370
+ * un-premultiply color for tonemap and to produce the canvas-drawable alpha used by
2371
+ * the premultiplied alphaMode compositor (so the page background still shows through
2372
+ * cleared / edge-faded regions like before).
2373
+ * rg8unorm at 4× MSAA is 8 bytes/texel — still fits Apple TBDR tile memory comfortably. */
2374
+ Engine.BLOOM_MASK_FORMAT = "rg8unorm";
2331
2375
  Engine.BLOOM_MAX_LEVELS = 7;
2332
2376
  Engine.SHADOW_MAP_SIZE = 2048;