reze-engine 0.12.0 → 0.12.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.
- package/README.md +99 -58
- package/dist/bezier-interpolate.d.ts +15 -0
- package/dist/bezier-interpolate.d.ts.map +1 -0
- package/dist/bezier-interpolate.js +40 -0
- package/dist/engine_ts.d.ts +143 -0
- package/dist/engine_ts.d.ts.map +1 -0
- package/dist/engine_ts.js +1575 -0
- package/dist/ik.d.ts +32 -0
- package/dist/ik.d.ts.map +1 -0
- package/dist/ik.js +337 -0
- package/dist/player.d.ts +64 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +220 -0
- package/dist/pool-scene.d.ts +52 -0
- package/dist/pool-scene.d.ts.map +1 -0
- package/dist/pool-scene.js +1122 -0
- package/dist/pool.d.ts +38 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +422 -0
- package/dist/rzm-converter.d.ts +12 -0
- package/dist/rzm-converter.d.ts.map +1 -0
- package/dist/rzm-converter.js +40 -0
- package/dist/rzm-loader.d.ts +24 -0
- package/dist/rzm-loader.d.ts.map +1 -0
- package/dist/rzm-loader.js +488 -0
- package/dist/rzm-writer.d.ts +27 -0
- package/dist/rzm-writer.d.ts.map +1 -0
- package/dist/rzm-writer.js +701 -0
- package/dist/shaders/body.d.ts +1 -1
- package/dist/shaders/body.d.ts.map +1 -1
- package/dist/shaders/body.js +28 -7
- package/dist/shaders/cloth_rough.d.ts +1 -1
- package/dist/shaders/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/cloth_rough.js +16 -4
- package/dist/shaders/cloth_smooth.d.ts +1 -1
- package/dist/shaders/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/cloth_smooth.js +17 -5
- package/dist/shaders/default.d.ts +1 -1
- package/dist/shaders/default.d.ts.map +1 -1
- package/dist/shaders/eye.d.ts +1 -1
- package/dist/shaders/eye.d.ts.map +1 -1
- package/dist/shaders/face.d.ts +1 -1
- package/dist/shaders/face.d.ts.map +1 -1
- package/dist/shaders/face.js +57 -21
- package/dist/shaders/hair.d.ts +1 -1
- package/dist/shaders/hair.d.ts.map +1 -1
- package/dist/shaders/hair.js +27 -7
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/body.js +86 -197
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.js +10 -121
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_smooth.js +59 -172
- package/dist/shaders/materials/common.d.ts +6 -0
- package/dist/shaders/materials/common.d.ts.map +1 -0
- package/dist/shaders/materials/common.js +144 -0
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/default.js +12 -145
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/eye.js +12 -117
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/face.js +85 -197
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/hair.js +78 -183
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/metal.js +15 -121
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +77 -0
- package/dist/shaders/materials/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.js +26 -152
- package/dist/shaders/metal.d.ts +1 -1
- package/dist/shaders/metal.d.ts.map +1 -1
- package/dist/shaders/metal.js +17 -4
- package/dist/shaders/nodes.d.ts +1 -1
- package/dist/shaders/nodes.d.ts.map +1 -1
- package/dist/shaders/nodes.js +9 -0
- package/dist/shaders/stockings.d.ts +1 -1
- package/dist/shaders/stockings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shaders/materials/body.ts +90 -201
- package/src/shaders/materials/cloth_rough.ts +10 -121
- package/src/shaders/materials/cloth_smooth.ts +63 -176
- package/src/shaders/materials/common.ts +155 -0
- package/src/shaders/materials/default.ts +12 -145
- package/src/shaders/materials/eye.ts +12 -117
- package/src/shaders/materials/face.ts +89 -201
- package/src/shaders/materials/hair.ts +82 -187
- package/src/shaders/materials/metal.ts +15 -121
- package/src/shaders/materials/nodes.ts +77 -0
- package/src/shaders/materials/stockings.ts +27 -153
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ npm install reze-engine
|
|
|
17
17
|
- **HDR pipeline** with bloom mip pyramid, Filmic tone mapping, 4× MSAA
|
|
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
|
-
|
|
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. The Blender preset this engine targets is authored with `MixShader(NPR, Principled, fac)` throughout — the 7-stage layout is a direct port of that topology.
|
|
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 one combined DFG + LTC 64×64 RGBA8 LUT baked at `engine.init()`, one `eval_principled(PrincipledIn, N, L, V, sun, amb, shadow)` EEVEE Principled port, and `principled_sheen`.
|
|
257
|
+
- **`common.ts`** — Uniform structs, bind-group layout (same for every material pipeline), 3×3 PCF shadow sampler, skinning vertex shader, shared `FSOut`.
|
|
258
|
+
- **Per-material files** — constants + NPR stack + optional bump + `eval_principled` call + final mix. That's it.
|
|
231
259
|
|
|
232
260
|
### PBR specular core
|
|
233
261
|
|
|
234
|
-
|
|
262
|
+
Inside `eval_principled`:
|
|
263
|
+
|
|
264
|
+
- GGX microfacet specular with Schlick Fresnel, Walter–Smith G1 (reciprocal form collapses the `4·NL·NV` denominator)
|
|
235
265
|
- **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)
|
|
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
|
|
266
|
+
- **Split-sum DFG LUT** (Karis 2013) — drives indirect specular, packed into the `.rg` channels of the baked LUT
|
|
267
|
+
- **LTC direct-spec scale** (Heitz 2016 magnitude LUT) — keeps analytic-light specular in the same energy budget as image-based lighting; packed into `.ba` so a single LUT tap serves both paths
|
|
268
|
+
- **Sheen coarse curve** (`f³·0.077 + f·0.01 + 0.00026`) gated by the `sheen` field on `PrincipledIn` — used by `stockings`
|
|
239
269
|
|
|
240
|
-
###
|
|
270
|
+
### NPR toolbox
|
|
241
271
|
|
|
242
|
-
Every preset is built
|
|
272
|
+
Every preset's stage C is built from these primitives:
|
|
243
273
|
|
|
244
|
-
- **Toon ramps** — quantised NdotL through constant or
|
|
245
|
-
- **HSV remaps** — separate hue/sat/value tints for shadow vs lit zones,
|
|
246
|
-
- **Fresnel rim & layer-weight wrap** —
|
|
247
|
-
- **Procedural micro-detail** —
|
|
248
|
-
- **Selective emission** —
|
|
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
|
-
###
|
|
280
|
+
### Per-material NPR stacks
|
|
251
281
|
|
|
252
|
-
Each PMX material is
|
|
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 |
|
|
255
|
-
| -------------- |
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `cloth_smooth`
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
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
300
|
- Directional shadow map (2048², depth32float, 3×3 PCF unrolled, normal + depth bias)
|
|
269
301
|
- HDR (rgba16f) main pass with 4× MSAA, resolved before tonemap
|
|
270
|
-
- Bloom via threshold + downsample/upsample mip pyramid, gated by an MRT mask channel
|
|
271
|
-
- Filmic tone mapping (LUT
|
|
302
|
+
- Bloom via threshold + downsample/upsample mip pyramid, gated by an MRT mask channel from emissive presets
|
|
303
|
+
- Filmic tone mapping (LUT extracted from Blender 3.6 OCIO "Filmic / Medium High Contrast", exposure baked in)
|
|
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 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. The hash is derived from world-space position, not screen-space, so the dither pattern doesn't 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, and uses `cullMode: "front"` + a small negative `depthBias` so only the back half of the eye mesh renders (the MMD trick that prevents eyes from leaking through the back of the head without a per-model skull occluder).
|
|
315
|
+
- **Main hair pipeline** stencil-tests `not-equal EYE_VALUE` and skips those fragments entirely — depth and color stay as the eye wrote them.
|
|
316
|
+
- **Hair-over-eyes pipeline** re-issues the hair draws through the same shader compiled with `override IS_OVER_EYES = true` (pipeline-override constant, so the dead branch is dropped at compile time). Stencil-tests `equal EYE_VALUE`, `depthWriteEnabled: false`, and alpha blends at 50% — so eye-stamped pixels end up `0.5·hair + 0.5·eye` in linear HDR before tonemap.
|
|
317
|
+
- **Outline pipeline** also stencil-tests `not-equal EYE_VALUE` so the edge color doesn't overwrite the freshly blended eye pixels (otherwise the near-black outline paints a dark almond shape over the see-through area).
|
|
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). One `setStencilReference(EYE_VALUE)` per model covers all four stencil-aware pipelines.
|
|
279
320
|
|
|
280
321
|
## Projects Using This Engine
|
|
281
322
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bezier interpolation for VMD animations
|
|
3
|
+
* Based on the reference implementation from babylon-mmd
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Bezier interpolation function
|
|
7
|
+
* @param x1 First control point X (0-127, normalized to 0-1)
|
|
8
|
+
* @param x2 Second control point X (0-127, normalized to 0-1)
|
|
9
|
+
* @param y1 First control point Y (0-127, normalized to 0-1)
|
|
10
|
+
* @param y2 Second control point Y (0-127, normalized to 0-1)
|
|
11
|
+
* @param t Interpolation parameter (0-1)
|
|
12
|
+
* @returns Interpolated value (0-1)
|
|
13
|
+
*/
|
|
14
|
+
export declare function bezierInterpolate(x1: number, x2: number, y1: number, y2: number, t: number): number;
|
|
15
|
+
//# sourceMappingURL=bezier-interpolate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bezier-interpolate.d.ts","sourceRoot":"","sources":["../src/bezier-interpolate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgCnG"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bezier interpolation for VMD animations
|
|
3
|
+
* Based on the reference implementation from babylon-mmd
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Bezier interpolation function
|
|
7
|
+
* @param x1 First control point X (0-127, normalized to 0-1)
|
|
8
|
+
* @param x2 Second control point X (0-127, normalized to 0-1)
|
|
9
|
+
* @param y1 First control point Y (0-127, normalized to 0-1)
|
|
10
|
+
* @param y2 Second control point Y (0-127, normalized to 0-1)
|
|
11
|
+
* @param t Interpolation parameter (0-1)
|
|
12
|
+
* @returns Interpolated value (0-1)
|
|
13
|
+
*/
|
|
14
|
+
export function bezierInterpolate(x1, x2, y1, y2, t) {
|
|
15
|
+
// Clamp t to [0, 1]
|
|
16
|
+
t = Math.max(0, Math.min(1, t));
|
|
17
|
+
// Binary search for the t value that gives us the desired x
|
|
18
|
+
// We're solving for t in the Bezier curve: x(t) = 3*(1-t)^2*t*x1 + 3*(1-t)*t^2*x2 + t^3
|
|
19
|
+
let start = 0;
|
|
20
|
+
let end = 1;
|
|
21
|
+
let mid = 0.5;
|
|
22
|
+
// Iterate until we find the t value that gives us the desired x
|
|
23
|
+
for (let i = 0; i < 15; i++) {
|
|
24
|
+
// Evaluate Bezier curve at mid point
|
|
25
|
+
const x = 3 * (1 - mid) * (1 - mid) * mid * x1 + 3 * (1 - mid) * mid * mid * x2 + mid * mid * mid;
|
|
26
|
+
if (Math.abs(x - t) < 0.0001) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
if (x < t) {
|
|
30
|
+
start = mid;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
end = mid;
|
|
34
|
+
}
|
|
35
|
+
mid = (start + end) / 2;
|
|
36
|
+
}
|
|
37
|
+
// Now evaluate the y value at this t
|
|
38
|
+
const y = 3 * (1 - mid) * (1 - mid) * mid * y1 + 3 * (1 - mid) * mid * mid * y2 + mid * mid * mid;
|
|
39
|
+
return y;
|
|
40
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Quat, Vec3 } from "reze-mmd";
|
|
2
|
+
export type EngineOptions = {
|
|
3
|
+
ambientColor?: Vec3;
|
|
4
|
+
bloomIntensity?: number;
|
|
5
|
+
rimLightIntensity?: number;
|
|
6
|
+
cameraDistance?: number;
|
|
7
|
+
cameraTarget?: Vec3;
|
|
8
|
+
};
|
|
9
|
+
export interface EngineStats {
|
|
10
|
+
fps: number;
|
|
11
|
+
frameTime: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class Engine {
|
|
14
|
+
private canvas;
|
|
15
|
+
private device;
|
|
16
|
+
private context;
|
|
17
|
+
private presentationFormat;
|
|
18
|
+
private camera;
|
|
19
|
+
private cameraUniformBuffer;
|
|
20
|
+
private cameraMatrixData;
|
|
21
|
+
private cameraDistance;
|
|
22
|
+
private cameraTarget;
|
|
23
|
+
private lightUniformBuffer;
|
|
24
|
+
private lightData;
|
|
25
|
+
private vertexBuffer;
|
|
26
|
+
private indexBuffer?;
|
|
27
|
+
private resizeObserver;
|
|
28
|
+
private depthTexture;
|
|
29
|
+
private modelPipeline;
|
|
30
|
+
private eyePipeline;
|
|
31
|
+
private hairPipelineOverEyes;
|
|
32
|
+
private hairPipelineOverNonEyes;
|
|
33
|
+
private hairDepthPipeline;
|
|
34
|
+
private outlinePipeline;
|
|
35
|
+
private hairOutlinePipeline;
|
|
36
|
+
private mainBindGroupLayout;
|
|
37
|
+
private outlineBindGroupLayout;
|
|
38
|
+
private jointsBuffer;
|
|
39
|
+
private weightsBuffer;
|
|
40
|
+
private skinMatrixBuffer?;
|
|
41
|
+
private multisampleTexture;
|
|
42
|
+
private readonly sampleCount;
|
|
43
|
+
private renderPassDescriptor;
|
|
44
|
+
private readonly STENCIL_EYE_VALUE;
|
|
45
|
+
private readonly BLOOM_DOWNSCALE_FACTOR;
|
|
46
|
+
private static readonly DEFAULT_BLOOM_THRESHOLD;
|
|
47
|
+
private static readonly DEFAULT_BLOOM_INTENSITY;
|
|
48
|
+
private static readonly DEFAULT_RIM_LIGHT_INTENSITY;
|
|
49
|
+
private static readonly DEFAULT_CAMERA_DISTANCE;
|
|
50
|
+
private static readonly DEFAULT_CAMERA_TARGET;
|
|
51
|
+
private static readonly TRANSPARENCY_EPSILON;
|
|
52
|
+
private static readonly STATS_FPS_UPDATE_INTERVAL_MS;
|
|
53
|
+
private static readonly STATS_FRAME_TIME_ROUNDING;
|
|
54
|
+
private ambientColor;
|
|
55
|
+
private sceneRenderTexture;
|
|
56
|
+
private sceneRenderTextureView;
|
|
57
|
+
private bloomExtractTexture;
|
|
58
|
+
private bloomBlurTexture1;
|
|
59
|
+
private bloomBlurTexture2;
|
|
60
|
+
private bloomExtractPipeline;
|
|
61
|
+
private bloomBlurPipeline;
|
|
62
|
+
private bloomComposePipeline;
|
|
63
|
+
private blurDirectionBuffer;
|
|
64
|
+
private bloomIntensityBuffer;
|
|
65
|
+
private bloomThresholdBuffer;
|
|
66
|
+
private linearSampler;
|
|
67
|
+
private bloomExtractBindGroup?;
|
|
68
|
+
private bloomBlurHBindGroup?;
|
|
69
|
+
private bloomBlurVBindGroup?;
|
|
70
|
+
private bloomComposeBindGroup?;
|
|
71
|
+
private bloomThreshold;
|
|
72
|
+
private bloomIntensity;
|
|
73
|
+
private rimLightIntensity;
|
|
74
|
+
private currentModel;
|
|
75
|
+
private modelDir;
|
|
76
|
+
private physics;
|
|
77
|
+
private materialSampler;
|
|
78
|
+
private textureCache;
|
|
79
|
+
private vertexBufferNeedsUpdate;
|
|
80
|
+
private drawCalls;
|
|
81
|
+
private lastFpsUpdate;
|
|
82
|
+
private framesSinceLastUpdate;
|
|
83
|
+
private lastFrameTime;
|
|
84
|
+
private frameTimeSum;
|
|
85
|
+
private frameTimeCount;
|
|
86
|
+
private stats;
|
|
87
|
+
private animationFrameId;
|
|
88
|
+
private renderLoopCallback;
|
|
89
|
+
private player;
|
|
90
|
+
private hasAnimation;
|
|
91
|
+
constructor(canvas: HTMLCanvasElement, options?: EngineOptions);
|
|
92
|
+
init(): Promise<void>;
|
|
93
|
+
private createRenderPipeline;
|
|
94
|
+
private createPipelines;
|
|
95
|
+
private createBloomPipelines;
|
|
96
|
+
private setupBloom;
|
|
97
|
+
private setupResize;
|
|
98
|
+
private handleResize;
|
|
99
|
+
private setupCamera;
|
|
100
|
+
private setupLighting;
|
|
101
|
+
private setAmbientColor;
|
|
102
|
+
loadAnimation(url: string): Promise<void>;
|
|
103
|
+
playAnimation(): void;
|
|
104
|
+
stopAnimation(): void;
|
|
105
|
+
pauseAnimation(): void;
|
|
106
|
+
seekAnimation(time: number): void;
|
|
107
|
+
getAnimationProgress(): import("./player").AnimationProgress;
|
|
108
|
+
/**
|
|
109
|
+
* Apply animation pose to model
|
|
110
|
+
*/
|
|
111
|
+
private applyPose;
|
|
112
|
+
/**
|
|
113
|
+
* Reset bones and physics to match a given pose
|
|
114
|
+
* @param pose The pose to apply
|
|
115
|
+
* @param resetBonesWithoutKeyframes If true, reset bones that don't have keyframes in the pose to identity
|
|
116
|
+
*/
|
|
117
|
+
private resetBonesAndPhysics;
|
|
118
|
+
getStats(): EngineStats;
|
|
119
|
+
runRenderLoop(callback?: () => void): void;
|
|
120
|
+
stopRenderLoop(): void;
|
|
121
|
+
dispose(): void;
|
|
122
|
+
loadModel(path: string): Promise<void>;
|
|
123
|
+
rotateBones(bones: string[], rotations: Quat[], durationMs?: number): void;
|
|
124
|
+
moveBones(bones: string[], relativeTranslations: Vec3[], durationMs?: number): void;
|
|
125
|
+
setMorphWeight(name: string, weight: number, durationMs?: number): void;
|
|
126
|
+
private updateVertexBuffer;
|
|
127
|
+
private setupModelBuffers;
|
|
128
|
+
private setupMaterials;
|
|
129
|
+
private createMaterialUniformBuffer;
|
|
130
|
+
private createUniformBuffer;
|
|
131
|
+
private createTextureFromPath;
|
|
132
|
+
private renderEyes;
|
|
133
|
+
private renderHair;
|
|
134
|
+
render(): void;
|
|
135
|
+
private applyBloom;
|
|
136
|
+
private updateCameraUniforms;
|
|
137
|
+
private updateRenderTarget;
|
|
138
|
+
private updateModelPose;
|
|
139
|
+
private computeSkinMatrices;
|
|
140
|
+
private drawOutlines;
|
|
141
|
+
private updateStats;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=engine_ts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine_ts.d.ts","sourceRoot":"","sources":["../src/engine_ts.ts"],"names":[],"mappings":"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"}
|