reze-engine 0.43.0 → 0.50.1
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 +53 -429
- package/dist/animation.d.ts +26 -0
- package/dist/animation.d.ts.map +1 -1
- package/dist/animation.js +42 -0
- package/dist/camera.d.ts +3 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +33 -8
- package/dist/engine.d.ts +841 -53
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3498 -451
- package/dist/graph/registry.d.ts +2 -2
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +1 -1
- package/dist/graph/slots.d.ts +0 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +37 -9
- package/dist/hdr.d.ts +18 -0
- package/dist/hdr.d.ts.map +1 -0
- package/dist/hdr.js +162 -0
- package/dist/ibl.d.ts +19 -0
- package/dist/ibl.d.ts.map +1 -0
- package/dist/ibl.js +113 -0
- package/dist/ik-solver.d.ts +2 -1
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/math.d.ts +20 -1
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +23 -16
- package/dist/midi-loader.d.ts +10 -0
- package/dist/midi-loader.d.ts.map +1 -0
- package/dist/midi-loader.js +247 -0
- package/dist/model.d.ts +19 -14
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +31 -2
- package/dist/param-track.d.ts +48 -0
- package/dist/param-track.d.ts.map +1 -0
- package/dist/param-track.js +80 -0
- package/dist/physics/types.d.ts.map +1 -1
- package/dist/physics/types.js +3 -0
- package/dist/reflection.d.ts +27 -0
- package/dist/reflection.d.ts.map +1 -0
- package/dist/reflection.js +93 -0
- package/dist/shaders/anchor-table.d.ts +56 -0
- package/dist/shaders/anchor-table.d.ts.map +1 -0
- package/dist/shaders/anchor-table.js +128 -0
- package/dist/shaders/cast-api.d.ts +2 -0
- package/dist/shaders/cast-api.d.ts.map +1 -0
- package/dist/shaders/cast-api.js +121 -0
- package/dist/shaders/cast-layout.d.ts +21 -0
- package/dist/shaders/cast-layout.d.ts.map +1 -0
- package/dist/shaders/cast-layout.js +20 -0
- package/dist/shaders/lights.d.ts +79 -0
- package/dist/shaders/lights.d.ts.map +1 -0
- package/dist/shaders/lights.js +269 -0
- package/dist/shaders/lyrics-api.d.ts +39 -0
- package/dist/shaders/lyrics-api.d.ts.map +1 -0
- package/dist/shaders/lyrics-api.js +187 -0
- package/dist/shaders/materials/common.d.ts +2 -4
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +87 -35
- package/dist/shaders/midi-api.d.ts +10 -0
- package/dist/shaders/midi-api.d.ts.map +1 -0
- package/dist/shaders/midi-api.js +114 -0
- package/dist/shaders/passes/composite.d.ts +28 -22
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +165 -138
- package/dist/shaders/passes/cull.d.ts +2 -0
- package/dist/shaders/passes/cull.d.ts.map +1 -0
- package/dist/shaders/passes/cull.js +138 -0
- package/dist/shaders/passes/field-blit.d.ts +26 -0
- package/dist/shaders/passes/field-blit.d.ts.map +1 -0
- package/dist/shaders/passes/field-blit.js +65 -0
- package/dist/shaders/passes/grid.d.ts +31 -0
- package/dist/shaders/passes/grid.d.ts.map +1 -0
- package/dist/shaders/passes/grid.js +169 -0
- package/dist/shaders/passes/ground.d.ts +13 -1
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +170 -25
- package/dist/shaders/passes/hosted-api.d.ts +57 -0
- package/dist/shaders/passes/hosted-api.d.ts.map +1 -0
- package/dist/shaders/passes/hosted-api.js +166 -0
- package/dist/shaders/passes/id-debug.d.ts +28 -0
- package/dist/shaders/passes/id-debug.d.ts.map +1 -0
- package/dist/shaders/passes/id-debug.js +74 -0
- package/dist/shaders/passes/particles.d.ts +10 -2
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +37 -109
- package/dist/shaders/passes/scene-contract.d.ts +128 -0
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -0
- package/dist/shaders/passes/scene-contract.js +207 -0
- package/dist/shaders/passes/sim.d.ts +34 -0
- package/dist/shaders/passes/sim.d.ts.map +1 -0
- package/dist/shaders/passes/sim.js +169 -0
- package/dist/shaders/passes/trails.d.ts +14 -2
- package/dist/shaders/passes/trails.d.ts.map +1 -1
- package/dist/shaders/passes/trails.js +55 -90
- package/dist/shaders/score-api.d.ts +10 -0
- package/dist/shaders/score-api.d.ts.map +1 -0
- package/dist/shaders/score-api.js +114 -0
- package/dist/shadow-cascades.d.ts +45 -0
- package/dist/shadow-cascades.d.ts.map +1 -0
- package/dist/shadow-cascades.js +70 -0
- package/dist/vmd-loader.d.ts +3 -2
- package/dist/vmd-loader.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/animation.ts +41 -0
- package/src/camera.ts +31 -8
- package/src/engine.ts +4015 -556
- package/src/graph/registry.ts +2 -2
- package/src/graph/slots.ts +37 -9
- package/src/hdr.ts +156 -0
- package/src/ibl.ts +115 -0
- package/src/ik-solver.ts +1 -1
- package/src/index.ts +12 -0
- package/src/math.ts +23 -17
- package/src/midi-loader.ts +246 -0
- package/src/model.ts +34 -4
- package/src/param-track.ts +83 -0
- package/src/physics/types.ts +4 -1
- package/src/reflection.ts +94 -0
- package/src/shaders/anchor-table.ts +147 -0
- package/src/shaders/cast-api.ts +123 -0
- package/src/shaders/cast-layout.ts +20 -0
- package/src/shaders/lights.ts +280 -0
- package/src/shaders/lyrics-api.ts +202 -0
- package/src/shaders/materials/common.ts +89 -35
- package/src/shaders/midi-api.ts +116 -0
- package/src/shaders/passes/composite.ts +182 -139
- package/src/shaders/passes/cull.ts +139 -0
- package/src/shaders/passes/grid.ts +178 -0
- package/src/shaders/passes/ground.ts +172 -25
- package/src/shaders/passes/hosted-api.ts +171 -0
- package/src/shaders/passes/id-debug.ts +75 -0
- package/src/shaders/passes/particles.ts +54 -112
- package/src/shaders/passes/scene-contract.ts +266 -0
- package/src/shaders/passes/trails.ts +77 -93
- package/src/shadow-cascades.ts +97 -0
- package/src/vmd-loader.ts +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/reze-engine)
|
|
4
4
|
|
|
5
|
-
**Zero-runtime-dependency** WebGPU engine for real-time MMD/PMX rendering — renderer, animation, IK, and
|
|
5
|
+
**Zero-runtime-dependency** WebGPU engine for real-time MMD/PMX rendering — renderer, animation, IK, physics, and a multi-effect VFX system, all in TypeScript.
|
|
6
|
+
|
|
7
|
+
One piece of the **Reze MMD family**, covering the whole MMD workflow on the web:
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
11
|
+
| **reze-engine** | This repo — the WebGPU foundation, anime-character rendering and physics |
|
|
12
|
+
| [reze-design](https://github.com/AmyangXYZ/reze-design) | Scene design, rendering and sharing |
|
|
13
|
+
| [reze-studio](https://github.com/AmyangXYZ/reze-studio) | Animation editing on a professional timeline and curve editor |
|
|
14
|
+
| [MiKaPo](https://github.com/AmyangXYZ/MiKaPo) | Real-time motion capture in the browser, exporting straight to VMD |
|
|
15
|
+
| [reze-rig](https://github.com/AmyangXYZ/reze-rig) | Retarget FBX animations to MMD VMD format, Mixamo and Unity tested |
|
|
6
16
|
|
|
7
17
|

|
|
8
18
|
|
|
@@ -12,452 +22,66 @@ npm install reze-engine
|
|
|
12
22
|
|
|
13
23
|
## Features
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
- **Shader-graph materials** — every look is a Blender-style node graph compiled to WGSL; style groups bind any materials to any graph, fully customizable, and PMX sphere maps (`.sph`/`.spa`) are a node, so a model's own highlights are available to any graph that wants them
|
|
17
|
-
- **HDR pipeline** — bloom, Filmic tone mapping, ASC CDL colour grading, 4× MSAA, Apple-TBDR-friendly targets
|
|
18
|
-
- **Depth of field** — a bladed-bokeh gather in the composite pass, focusing automatically on the character's own depth span. It reads the scene pass's existing depth buffer rather than a G-buffer, and while it is off that buffer is never even stored — the feature costs nothing until you turn it on
|
|
19
|
-
- **In-house TS physics** — sequential-impulse rigid bodies for PMX rigs with rest-stable implicit spring-dampers, zero dependencies; structure-of-arrays solver, scene gravity and wind, a world floor so hair and hems rest on the ground instead of clipping through it, and per-frame load shedding that holds framerate on weak devices
|
|
20
|
-
- **Math library** — the shared Vec3/Quat/Mat4 layer of the Reze family: euler orders, swing-twist, shortest-arc, look-rotation, zero-alloc `*Into` variants
|
|
21
|
-
- **VMD animation** — per-channel bezier curves read the way MMD packs them, IK with per-chain enable read from the motion, morphs on a GPU compute path, and VMD export
|
|
22
|
-
- **MMD rig semantics** — 付与親 append inheritance for rotation and movement, composed in MMD's order, and 軸制限 fixed-axis bones, so a 腕捩 twist bone turns along the arm instead of bending it
|
|
23
|
-
- **PMX morphs** — vertex, group, bone and material morphs, the last carrying MMD's multiply/add blend so an author's colour and on/off switches work; `getSupportedMorphIndices()` reports what the renderer can actually drive, so a UI never offers a dead control
|
|
24
|
-
- **Stage models** — a PMX added as environment rather than cast: no physics, no IK, no outline hulls, and the pose pass skipped while it is idle. Same materials and style groups as any model, and it suppresses the built-in ground plane it would otherwise z-fight with
|
|
25
|
-
- **Clip blending & locomotion** — weighted multi-clip pose blending, crossfades, and a game-style character controller (idle/run/sprint speed blend, authored stop skids, code-driven root motion) — the [demo](https://reze.one) is WASD-playable
|
|
26
|
-
- **State machine & clip events** — declarative animation states (clips or delegates like the LocomotionController) with guarded crossfade transitions; time-triggered clip callbacks on any playback path
|
|
27
|
-
- **Interactive editing** — GPU picking, transform gizmo, bone/material selection
|
|
28
|
-
- **Camera** — orbit, bone-follow, or a driven MMD camera VMD; ground + PCF shadows, multi-model scenes
|
|
29
|
-
- **Offline rendering** — frame-accurate stepping (`renderFrame`) at any resolution (`setRenderSize`) for video export; background color, 360° equirect backdrop, ground shadow-catcher
|
|
30
|
-
- **WGSL scene effects** — a user shader (shadertoy-style) with FIVE mounts, declared by which entry points the code defines: `background` composites between the background and the scene, `foreground` over the finished frame and is handed the scene's depth, so rain and petals are occluded by the character they pass behind and fog thickens with distance. Compile diagnostics and live-tweakable uniform params on both
|
|
31
|
-
- **GPU particles in an effect** — declare `// @particles 4096` and write `particleInit`/`particleStep`/`particleShade`, and the pool lives entirely on the GPU: one compute dispatch steps every particle, one instanced draw puts them on screen, positions never touching the CPU. Drawn inside the scene pass, so they are depth-tested against the cast for free — a spark behind a shoulder is simply hidden. `// @blend additive` and `// @bloom` per effect; `stretch` on the particle struct is aspect along the direction of travel, which is what makes a raindrop a streak and a snowflake a square
|
|
32
|
-
- **Ribbon trails in an effect** — `trailWidth`/`trailShade` over the recorded path of any bone declared with `trail`. Catmull-Rom through the samples, extruded in SCREEN space so a ribbon holds its width at any camera distance, with mitre joints, composited in its own layer with MAX blending after tone mapping — which is what stops a bright ribbon crossing itself from stacking into a white blob
|
|
33
|
-
- **Audio-reactive effects** — `rzAudioLevel()`, `rzAudioOnset()` (a precomputed kick detector) and a 32-band log-spaced spectrum, plus `…At(offset)` forms that read SECONDS into the past or the future, so a bar can lean into a beat before it lands. The track is analysed once ahead of time rather than sampled live, which is what makes an export identical to the editor: offline rendering steps frame by frame, and a live analyser would hear silence
|
|
34
|
-
- **Half-resolution field pass** — `background`/`foreground` run at half resolution and upsample, which is free for anything soft; an effect drawing sub-pixel detail declares `// @fullres` and pays double
|
|
35
|
-
- **Effects that read the cast** — an effect declares the BONES it wants (`// @anchor 左手首 trail`) and gets their world position, velocity and facing, plus each character's floor point, hip and bounding sphere. `trail` additionally keeps that bone's recent PATH, sampled on the scene clock so a ribbon is identical in the editor, in an export and in a re-export. This is what a hand trail, a halo parented to a head, or a mark under a planted foot is made of
|
|
36
|
-
|
|
37
|
-
See [Physics](#physics) and [Rendering](#rendering) for the internals.
|
|
38
|
-
|
|
39
|
-
## Used by
|
|
40
|
-
|
|
41
|
-
- [Reze Design](https://reze.design) (web-native scene composer & shader-graph styling)
|
|
42
|
-
- [Reze Studio](https://reze.studio) (MMD animation editor)
|
|
43
|
-
- [MiKaPo](https://mikapo.reze.one) (motion capture)
|
|
44
|
-
- [Reze Rig](https://rig.reze.one) (FBX→VMD retarget)
|
|
45
|
-
- [Popo](https://popo.love) (LLM-generated poses)
|
|
46
|
-
- [MPL](https://mmd-mpl.vercel.app) (motion language)
|
|
47
|
-
|
|
48
|
-
## Quick start
|
|
49
|
-
|
|
50
|
-
```javascript
|
|
51
|
-
import { Engine } from "reze-engine";
|
|
52
|
-
|
|
53
|
-
const engine = new Engine(canvas);
|
|
54
|
-
await engine.init();
|
|
55
|
-
|
|
56
|
-
const model = await engine.loadModel("reze", "/models/reze/reze.pmx");
|
|
57
|
-
await engine.autoStyleGroups("reze");
|
|
58
|
-
|
|
59
|
-
await model.loadVmd("idle", "/animations/idle.vmd");
|
|
60
|
-
model.show("idle");
|
|
61
|
-
model.play();
|
|
62
|
-
|
|
63
|
-
engine.addGround();
|
|
64
|
-
engine.runRenderLoop();
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Codebase map
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
engine/src/
|
|
71
|
-
engine.ts Engine: device/context, render loop, all passes & pipelines,
|
|
72
|
-
per-model GPU resources, picking, gizmo (entry point)
|
|
73
|
-
model.ts Model: skeleton, 4-bone skinning, morphs (CPU + GPU compute),
|
|
74
|
-
animation state, drives IK; per-frame update()
|
|
75
|
-
animation.ts AnimationClip, VMD bezier interpolation, playback/priority
|
|
76
|
-
locomotion.ts LocomotionController — idle/run/sprint blended over setBlendPose,
|
|
77
|
-
pivot-gated turns, root motion returned for setModelTransform
|
|
78
|
-
state-machine.ts AnimationStateMachine — clip/delegate states, guarded crossfade
|
|
79
|
-
transitions, exit-time returns
|
|
80
|
-
ik-solver.ts MMD-style CCD IK (angle limits, solve-axis specialization)
|
|
81
|
-
camera.ts Orbit camera (alpha/beta/radius), bone-follow, mouse/touch
|
|
82
|
-
math.ts Vec3 / Quat / Mat4 — euler orders, swing-twist, shortest-arc,
|
|
83
|
-
look-rotation (zero-alloc *Into variants for hot paths)
|
|
84
|
-
pmx-loader.ts PMX parser: mesh, bones, morphs, rigid bodies, joints
|
|
85
|
-
vmd-loader.ts VMD motion parser · vmd-writer.ts VMD export (Shift-JIS)
|
|
86
|
-
asset-reader.ts URL + local-folder asset resolution · folder-upload.ts
|
|
87
|
-
tga-loader.ts TGA decoder · dds-loader.ts DDS/BC1-3 · psd-loader.ts PSD
|
|
88
|
-
composite — the texture formats createImageBitmap can't read
|
|
89
|
-
index.ts public exports
|
|
90
|
-
|
|
91
|
-
graph/ shader-graph → WGSL compiler — materials as data
|
|
92
|
-
schema.ts ShaderGraph / StyleGroup / param types + validation
|
|
93
|
-
registry.ts node registry (Blender node → WGSL) + socket conversions
|
|
94
|
-
compile.ts validate → prune → toposort → peephole → emit
|
|
95
|
-
render-class.ts RenderClass / AlphaMode + the RENDER_CLASSES manifest
|
|
96
|
-
slots.ts per-render-class fs() shell (stencil/alpha) around the graph body
|
|
97
|
-
presets/ the 9 built-in shader graphs (hair, face, eye, cloth, …)
|
|
98
|
-
|
|
99
|
-
physics/ in-house rigid-body solver (~4.2k lines)
|
|
100
|
-
physics.ts RezePhysics: bone↔body sync, fixed-step accumulator + interpolation
|
|
101
|
-
solver.ts sequential-impulse PGS (joint + contact rows)
|
|
102
|
-
contact.ts narrowphase (analytical sphere/box/capsule pairs, incl. box-box) + contact pool
|
|
103
|
-
constraint.ts 6DOF spring joints · world.ts step, gravity + wind
|
|
104
|
-
body.ts SoA rigid-body store · types.ts
|
|
105
|
-
|
|
106
|
-
shaders/
|
|
107
|
-
materials/ nodes.ts (Blender-node WGSL library the graph compiler emits into) +
|
|
108
|
-
common.ts (bindings, skinning VS, fs() shell)
|
|
109
|
-
passes/ shadow, morph (GPU vertex-morph compute), bloom, composite (Filmic),
|
|
110
|
-
outline, selection, gizmo, pick, ground, mipmap
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## API
|
|
114
|
-
|
|
115
|
-
One WebGPU **Engine** per page (singleton after `init()`). Models load by URL **or** from a user-selected folder ([below](#local-folder-uploads-browser)).
|
|
116
|
-
|
|
117
|
-
### Engine
|
|
118
|
-
|
|
119
|
-
```javascript
|
|
120
|
-
engine.init()
|
|
121
|
-
engine.loadModel(name, path) // or ({ files, pmxFile? }) for folder upload
|
|
122
|
-
engine.getModel(name) / getModelNames() / removeModel(name)
|
|
123
|
-
engine.setModelTransform(name, { position?, rotation?, scale?, visible? }) / getModelTransform(name) // place, scale or hide a model (scale is uniform)
|
|
124
|
-
|
|
125
|
-
engine.loadStage(name, { files, pmxFile?, transform? }) // a PMX as ENVIRONMENT, not cast: same geometry and materials (style groups work on it unchanged — that is what makes pure-PMX stages worth supporting), but no physics, no IK, no outline hulls, and the pose pass is skipped while it is idle. A stage is usually the heaviest mesh in the scene and the one that never moves
|
|
126
|
-
engine.addStage(model, pmxPath, { name?, transform?, assetReader? }) // the same, from an already-loaded Model
|
|
127
|
-
engine.groundIsSuppressed() // true while a stage is present — its own floor and addGround's plane both sit at y=0, so the engine refuses to draw the plane rather than z-fight. addGround's settings are kept and come back when the stage is removed
|
|
128
|
-
|
|
129
|
-
engine.autoStyleGroups(name, overrides?) // default style groups by material name
|
|
130
|
-
engine.applyStyleGroups(name, groups) / upsertStyleGroup / removeStyleGroup / getStyleGroups
|
|
131
|
-
engine.setMaterialVisible(name, material, visible) / toggleMaterialVisible / isMaterialVisible
|
|
132
|
-
|
|
133
|
-
engine.setIKEnabled(enabled) // engine-wide OFF for hosts that pose bones themselves; ON hands per-chain state to the clip
|
|
134
|
-
engine.setPhysicsEnabled(enabled)
|
|
135
|
-
engine.resetPhysics() // re-pose bodies from animation + zero velocities (call if physics explodes)
|
|
136
|
-
engine.setGravity(vec3) / getGravity() // scene-wide; default (0, -98, 0) — MMD scale, where a character is ~18 units tall
|
|
137
|
-
engine.setWind({ direction, strength, turbulence?, frequency? } | null) / getWind() // scene-wide air; strength is in gravity's units, so 10-30 reads as a breeze through hair and skirt
|
|
138
|
-
|
|
139
|
-
engine.setCameraFollow(model, bone?, offset?) / setCameraFollow(null)
|
|
140
|
-
engine.setCameraTarget(vec3) / setCameraDistance(d) / setCameraAlpha(a) / setCameraBeta(b)
|
|
141
|
-
|
|
142
|
-
engine.loadCameraVmd(url) / loadCameraVmdFromBuffer(buffer) // MMD camera track (dedicated file or a VMD's camera block) drives target/rotation/distance/fov — default-on once loaded
|
|
143
|
-
engine.setCameraVmdEnabled(on) / isCameraVmdEnabled() / hasCameraVmd() / clearCameraVmd() // toggle the shot; while it drives, orbit/pan/zoom is inert — toggle off to hand control back
|
|
144
|
-
engine.getCameraVmdDuration() // seconds the camera track runs for (its last keyframe), 0 with none loaded — its length is its own and need not match any model's clip
|
|
145
|
-
|
|
146
|
-
engine.setWorld({ color?, strength? }) / setSun({ color?, strength?, direction? }) // runtime lighting
|
|
147
|
-
engine.setBackgroundColor(color | null) // canvas background (display-space sRGB 0–1, composited post-tonemap so it matches a CSS color of the same value exactly); null = transparent canvas (DOM shows through)
|
|
148
|
-
engine.setBackdropEquirect(source | null) // 360° backdrop from an equirect (2:1) image — PhotoDome-style dome at infinity, follows the camera, display-only (no lighting/bloom influence); oversized panoramas auto-downscale to the device texture limit
|
|
149
|
-
engine.setEffect(wgsl | null, params?) // the scene's WGSL effect, mounted by which entry points the code defines — either, or both in one file. fn background(ray: vec3f, uv: vec2f, time: f32) -> vec4f is a LAYER between the base background (color/equirect/transparent) and the scene; fn foreground(ray: vec3f, uv: vec2f, time: f32, depth: f32) -> vec4f composites over the finished frame, which is where rain, snow, petals and fog live. ray = the pixel's world-space view direction (pans with the orbit, same mapping the skybox samples by), uv = 0..1 bottom-left origin, alpha = how much the layer replaces what is behind it. depth (foreground only) = camera-space metres of whatever the scene drew at that pixel, far plane where it drew nothing — compare a particle's own distance against it and the model occludes it; fog reads it directly, since fog's alpha IS a function of distance. Helpers: bgResolution() = canvas size in px, bgCameraPos() = the camera in world space, bgWorldPos(ray, depth) = the world point the scene drew at this pixel — the depth turned into a PLACE, which is what fog lying on the ground needs (it has to know where the ground is) and what keeps a pattern keyed to world position from swimming as the camera orbits. Compiles async off the hot path; on failure the previous effect is KEPT and {ok, diagnostics, mounts} returns line:col errors rebased to the user's code. Declared params ({name: number | {x,y,z}}) become a params.<name> uniform struct shared by both mounts. A foreground makes the scene pass STORE its depth buffer while installed (it is otherwise discarded into tile memory)
|
|
150
|
-
engine.setEffectParam(name, value) // write one declared param — a uniform write, no recompile (live slider tier)
|
|
151
|
-
|
|
152
|
-
// ── What an effect can ask about the scene (WGSL, in scope at both mounts) ──
|
|
153
|
-
// Named rz*; the older bg* spellings all still resolve and always will, because a
|
|
154
|
-
// published scene pins the effect that calls them.
|
|
155
|
-
rzResolution() -> vec2f // canvas size in px, for aspect correction
|
|
156
|
-
rzCameraPos() -> vec3f // the lens, in world space
|
|
157
|
-
rzWorldPos(ray, depth) -> vec3f // the depth turned into a PLACE — what fog lying on the ground needs
|
|
158
|
-
rzProject(p) -> vec3f // a world point as the camera sees it: xy the uv it lands on, z its distance along the VIEW AXIS. The exact inverse of the per-pixel ray, so anchored work can be measured in 2D instead of marched in 3D, and z compares directly against depth for occlusion. Negative behind the lens
|
|
159
|
-
rzSubjectCount() -> i32 // characters in the scene, up to 4 (stages excluded)
|
|
160
|
-
rzSubjectHip(i) -> vec3f // where a character IS — at the HIPS, not on the floor. model.position + センター
|
|
161
|
-
rzSubject(i) -> RzSubject // { root, center, bounds, valid } — root is the FLOOR under them (the model's origin, which PMX puts between the feet and which moves with a character placed on a stage), center is the hip, bounds is a generous cull sphere
|
|
162
|
-
rzAnchor(subject, slot) -> RzAnchor // { pos, vel, fwd, valid } for the slot-th bone this effect DECLARED. Slots are declaration order; valid is false on a rig that spells the bone differently, which is the normal case
|
|
163
|
-
rzTrailCount(subject, slot) -> i32 // path samples available — loop to THIS, never to a constant
|
|
164
|
-
rzTrail(subject, slot, i) -> vec4f // sample i of that bone's path: xyz where it was, w how many seconds ago. i = 0 is now, running backwards in time
|
|
165
|
-
rzCameraRight() / rzCameraUp() / rzCameraForward() -> vec3f // the camera's axes. Screen up is only world up when the camera is level — an effect with a direction of its own (flames climbing, a column standing) should project a world-up vector and use ITS screen direction, or it tilts with the camera
|
|
166
|
-
rzAudioLevel() -> f32 // loudness now, 0..1. Zero when the scene has no music
|
|
167
|
-
rzAudioOnset() -> f32 // how hard the bass is RISING — the kick, computed once for the whole song rather than once per pixel in every effect that wants a beat
|
|
168
|
-
rzAudioBandCount() -> i32 // bands in the spectrum (32), log-spaced between 40Hz and 14kHz
|
|
169
|
-
rzAudioBand(i) -> f32 // band i now, 0..1, normalised per band against its own p95 so the treble is not a flat zero beside the bass
|
|
170
|
-
rzAudioLevelAt(o) / rzAudioOnsetAt(o) / rzAudioBandAt(i, o) -> f32 // the same, o SECONDS away: negative is the past, positive the FUTURE. The whole track is already analysed, so an effect can anticipate a beat — which no live analyser can do
|
|
171
|
-
rzAudioTime() -> f32 / rzAudioPlaying() -> f32 // where the song is, and whether it is running
|
|
172
|
-
|
|
173
|
-
// ── The particle mounts (WGSL) ──
|
|
174
|
-
// struct Particle { pos: vec3f, age: f32, vel: vec3f, life: f32, size: f32, rot: f32, seed: f32, stretch: f32 }
|
|
175
|
-
particleInit(i: u32, seed: f32) -> Particle // a fresh particle; called again whenever one retires
|
|
176
|
-
particleStep(p: Particle, dt: f32) -> Particle // one frame of motion. Set life to 0 to retire it
|
|
177
|
-
particleShade(p: Particle, uv: vec2f) -> vec4f // its billboard, uv 0..1 across the quad
|
|
178
|
-
|
|
179
|
-
// ── The trail mounts (WGSL) ──
|
|
180
|
-
trailWidth(u: f32, age: f32) -> f32 // ribbon half-width in PIXELS, along the path
|
|
181
|
-
trailShade(u: f32, v: f32, age: f32, weight: f32, slot: i32) -> vec4f // u along the ribbon, v across it
|
|
182
|
-
|
|
183
|
-
// Declared at the top of the effect's own source, like the mounts:
|
|
184
|
-
// // @anchor 頭 → slot 0, position only
|
|
185
|
-
// // @anchor 左手首 trail → slot 1, position AND its recent path
|
|
186
|
-
// // @particles 4096 → pool size, with the particle mounts
|
|
187
|
-
// // @blend additive → particles add light instead of covering it
|
|
188
|
-
// // @bloom → particles reach the bloom pyramid
|
|
189
|
-
// // @fullres → field mounts run at full resolution
|
|
190
|
-
// A file declares FIELD mounts (background/foreground) or PARTICLES, not both.
|
|
191
|
-
// Only what a file names is resolved and uploaded, so naming none costs nothing
|
|
192
|
-
// and nobody pays for a rig's other five hundred bones. Up to 8 anchors, 4
|
|
193
|
-
// subjects, 128 samples (~2.1s at 60Hz) — all MINIMUMS: they can grow without
|
|
194
|
-
// breaking a published effect, because effects read through these accessors and
|
|
195
|
-
// loop to the count functions rather than indexing the buffer.
|
|
196
|
-
engine.setAudioData(analysis | null) // the precomputed per-frame analysis the rzAudio* functions read: level, onset and the band magnitudes for the whole track
|
|
197
|
-
engine.setAudioTime(seconds, playing) // where the song is. The export loop drives this per FRAME rather than letting an element play, which is what makes an exported video's reactivity identical to the editor's
|
|
198
|
-
engine.getEffectMounts() // { background, foreground } — what the installed effect declared; both false with none set
|
|
199
|
-
engine.setColorGrading({ shadows?, midtones?, highlights?, contrast?, saturation? }) // ASC CDL grade on the TONEMAPPED scene: the three tonal colours are display-space sRGB with mid-grey (0.5,0.5,0.5) neutral — direction from neutral is the hue that range is pushed toward, distance is the amount, and going darker/lighter than 0.5 crushes or lifts it (shadows→CDL offset, midtones→power, highlights→slope). contrast pivots on 0.5; saturation is Rec.709. Uniforms-only, no pipeline rebuild — safe to call per frame from a slider. A neutral grade is flagged off and costs nothing per pixel
|
|
200
|
-
engine.getColorGrading() // current grade, for serialising into a scene descriptor
|
|
201
|
-
engine.addGround(options?) // options include opacity (0–1): fades the SURFACE while the received shadow persists (shadow catcher — models stay grounded on photo backdrops); shadowStrength 0 disables the shadow
|
|
202
|
-
engine.runRenderLoop(callback?) / stopRenderLoop()
|
|
203
|
-
engine.renderFrame(deltaSeconds) // offline rendering: render one frame advancing EVERY clock (animation, physics, camera VMD) by exactly dt — wall-clock independent; call N times with 1/fps for deterministic video export
|
|
204
|
-
engine.setRenderSize(w, h) / setRenderSize(null) // pin render resolution (all targets) independent of the canvas CSS size, e.g. 3840×2160 for export; null returns to CSS-size × devicePixelRatio tracking
|
|
205
|
-
engine.getStats() // fps + smoothness metrics (frameTimeMax, fps1PercentLow, jitter)
|
|
206
|
-
engine.dispose()
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**Options** — Blender-style scene config: `world` = environment lighting, `sun` = directional lamp (`direction` points from sun into the scene), `camera` = framing (`fov` in radians), `background` = canvas background (display-space sRGB, same semantics as `setBackgroundColor`). Callbacks: `onRaycast`, `onGizmoDrag`. The shadow map is cast from `sun.direction` — the same vector the shader lights with — so shading and cast shadows stay coupled.
|
|
210
|
-
|
|
211
|
-
### Model
|
|
212
|
-
|
|
213
|
-
```javascript
|
|
214
|
-
await model.loadVmd(name, url) / model.loadClip(name, clip)
|
|
215
|
-
model.show(name)
|
|
216
|
-
model.play(name, { priority?, loop? }) // priority: higher wins when clips compete (0 = default)
|
|
217
|
-
model.pause() / stop() / seek(time)
|
|
218
|
-
model.clearAnimation() // stop + DEACTIVATE the clip (stop() keeps it current for re-play; clear() forgets it, so resetAllBones/Morphs actually shows the bind pose)
|
|
219
|
-
model.getAnimationProgress() // { current, duration (s), playing, paused, looping, … }
|
|
220
|
-
model.exportVmd(name) // → ArrayBuffer (Shift-JIS bone/morph names)
|
|
221
|
-
|
|
222
|
-
model.rotateBones({ 首: quat }, ms?) / moveBones({ センター: vec3 }, ms?)
|
|
223
|
-
model.setMorphWeight(name, weight, ms?) // drives vertex, group, bone and material morphs alike
|
|
224
|
-
model.getMorphing() // { morphs: [{ name, type, … }] } — PMX type: 0 group, 1 vertex, 2 bone, 3–7 UV, 8 material, 9 flip, 10 impulse
|
|
225
|
-
model.getSupportedMorphIndices() // indices this renderer can actually move: vertex, bone, material, and group morphs resolving to one of those. UV morphs are parsed but not yet applied; flip and impulse are PMX 2.1 and unsupported. Filter a morph UI by this so it never shows a control that does nothing
|
|
226
|
-
model.resetAllBones() / resetAllMorphs()
|
|
227
|
-
model.getBoneWorldPosition(name)
|
|
228
|
-
|
|
229
|
-
model.setBlendPose(entries) // pose from N weighted clips {name, time, weight} — the caller owns every clock; rest pose fills a weight sum below 1
|
|
230
|
-
model.crossfadeTo(name, seconds, { loop? }) // fade the current clip (or the rest pose) into a target that owns the progress clock
|
|
231
|
-
model.setBoneRotationOffset(name, quat) // constant local offset composed after every pose source (the classic heel correction); null clears
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Locomotion
|
|
235
|
-
|
|
236
|
-
```javascript
|
|
237
|
-
const walk = new LocomotionController(model, { idle, run, sprint }, { runSpeed, sprintSpeed })
|
|
238
|
-
walk.setMove(x, y, sprint?) // world-vector input: turns toward it (pivots in place past 45°), then runs
|
|
239
|
-
walk.setDrive(forward, steer, sprint?) // tank-style alternative: steer rotates the facing, forward runs along it
|
|
240
|
-
const pose = walk.update(dt) // per frame: blends the pose, integrates root motion
|
|
241
|
-
engine.setModelTransform(name, { position: pose.position, rotation: pose.rotation })
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Clips are in-place; run and sprint share one gait phase so blends stay on the same feet. Match `runSpeed`/`sprintSpeed` to the clips' authored root motion or the feet slide.
|
|
245
|
-
|
|
246
|
-
`AnimationClip` holds keyframes only (bone/morph tracks keyed by `frame`, plus `frameCount`); time advances at fixed `FPS` (exported, default 30).
|
|
25
|
+
**MMD fidelity**
|
|
247
26
|
|
|
248
|
-
|
|
27
|
+
- PMX models, VMD motion with MMD's own bezier packing, IK with per-chain enable, append-inherit bones and fixed-axis twist bones, VMD export
|
|
28
|
+
- Vertex / group / bone / material morphs (multiply and add), vertex morphs on a GPU compute path
|
|
29
|
+
- MMD draw disciplines reproduced: author-order transparency with depth write, per-mesh interleaved outline hulls, the eyes-through-bangs stencil pass, sphere maps as graph nodes
|
|
30
|
+
- In-house sequential-impulse physics for PMX rigs — rigid bodies, joints, deterministic wind, a world floor
|
|
31
|
+
- Stages as environments, not characters: a stage PMX takes the same materials, graphs and style groups a character does, and keeps the bone and material morphs its author rigged for doors, lifts and colour switches — while skipping physics, IK and idle pose work, and owning the floor so the built-in ground steps aside
|
|
249
32
|
|
|
250
|
-
|
|
251
|
-
const loco = new LocomotionController(model, clips, { autoApply: false }) // computes but does not apply
|
|
252
|
-
const sm = new AnimationStateMachine(model, {
|
|
253
|
-
loco: { entries: (dt) => { loco.update(dt); return loco.getBlendEntries() } },
|
|
254
|
-
skill: { clip: "Skill_A", loop: false },
|
|
255
|
-
}, [
|
|
256
|
-
{ from: "loco", to: "skill", when: () => wantSkill },
|
|
257
|
-
{ from: "skill", to: "loco" }, // unconditional: fires as the clip ends
|
|
258
|
-
], { initial: "loco" })
|
|
259
|
-
sm.update(dt) // per frame; crossfades keep the outgoing state advancing
|
|
260
|
-
|
|
261
|
-
model.addClipEvent("Skill_A", 0.42, (e) => sfx()) // fires when playback crosses 0.42s on any path; returns unsubscribe
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Math
|
|
265
|
-
|
|
266
|
-
Every operation has an allocating form and a zero-alloc `*Into` form (out parameter last, returned).
|
|
267
|
-
|
|
268
|
-
```javascript
|
|
269
|
-
Quat.fromEulerOrder(x, y, z, order) // intrinsic order string "YXZ" | "ZYX" | … ; toEulerOrder(q, order) inverts
|
|
270
|
-
Quat.fromUnitVectors(from, to) // shortest arc
|
|
271
|
-
Quat.twistAroundAxis(q, axis) // swing-twist split: q = swing · twist
|
|
272
|
-
Quat.lookRotation(forward, up) // +Z forward
|
|
273
|
-
Quat.rotateVec(q, v) / rotateVecInv(q, v)
|
|
274
|
-
Quat.fromBasis(x, y, z) // rotation taking the standard basis onto x/y/z
|
|
275
|
-
Quat.slerp(a, b, t) / nlerp / dot / angleTo / mirrorZ // mirrorZ = RH ↔ LH (with Vec3.mirrorZ)
|
|
276
|
-
bezierInterpolate(x1, x2, y1, y2, t) / interpolateControlPoints(cp, t) // VMD 127-space curves
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Local folder uploads (browser)
|
|
280
|
-
|
|
281
|
-
Feed a `<input type="file" webkitdirectory>` `FileList` (or drag/drop) into the engine; textures resolve relative to the chosen PMX inside that tree.
|
|
282
|
-
|
|
283
|
-
> **Gotcha:** copy `input.files` into an array **before** `input.value = ""` — the `FileList` is live and clearing the input empties it.
|
|
284
|
-
|
|
285
|
-
`parsePmxFolderInput(fileList)` returns a tagged result; for `single` you get `{ files, pmxFile }` directly, for `multiple` show a picker over `pmxRelativePaths` and resolve with `pmxFileAtRelativePath(files, path)`. Then:
|
|
286
|
-
|
|
287
|
-
```javascript
|
|
288
|
-
const picked = parsePmxFolderInput(e.target.files);
|
|
289
|
-
e.target.value = "";
|
|
290
|
-
if (picked.status === "single")
|
|
291
|
-
await engine.loadModel("m", { files: picked.files, pmxFile: picked.pmxFile });
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
VMD and other assets still load by URL when the path starts with `/` or `http(s):`; relative paths resolve against the PMX directory.
|
|
295
|
-
|
|
296
|
-
**Any `File[]` works, not just folder picks** — files from plain multi-select or drag & drop carry `webkitRelativePath === ""` and key by **filename** instead (which may itself contain a path: hosts that extract a model `.zip` in-app can synthesize `new File(data, "model/tex/body.png")` and paths resolve exactly like a folder pick). Texture paths additionally fall back to **basename matching** — a PMX referencing `tex/body.png` finds a flat `body.png`; the same fallback rescues wrongly-cased directory names.
|
|
297
|
-
|
|
298
|
-
### Texture formats
|
|
299
|
-
|
|
300
|
-
PNG, JPEG, WebP and the rest of what the browser decodes, plus three it doesn't:
|
|
301
|
-
|
|
302
|
-
| | |
|
|
303
|
-
| --- | --- |
|
|
304
|
-
| **TGA** | true-colour (16/24/32), grayscale and colour-mapped, raw or RLE — common in PMX packs, especially sphere maps |
|
|
305
|
-
| **DDS** | BC1/BC2/BC3 (DXT1/3/5) and uncompressed BGRA/RGBA/24-bit, including `DX10` headers — what a stage converted out of a game usually carries |
|
|
306
|
-
| **PSD** | the flattened composite of a PSD or PSB: RGB, grayscale, indexed and duotone, 8 or 16-bit, raw or RLE — texture packs are often shipped as the artist's working files |
|
|
307
|
-
|
|
308
|
-
DDS and PSD are recognised by their **magic bytes rather than their extension**, because in these packs the extension is frequently wrong — a `.tga` that is really a DDS, a `.png` that never stopped being a Photoshop file. All three decode on the CPU to RGBA8, so DDS needs no `texture-compression-bc` device feature and works where that is unavailable.
|
|
309
|
-
|
|
310
|
-
A texture that cannot be decoded is logged and skipped, and its material falls back to white — a model never fails to load over one bad file. Two known limits: a PSD saved with *Maximize Compatibility* off carries no usable composite, and CMYK/Lab PSDs are refused rather than converted without a profile.
|
|
311
|
-
|
|
312
|
-
### Interactive pose editing
|
|
313
|
-
|
|
314
|
-
Double-click picks a bone or material (per-triangle dominant-joint from the GPU pick, so one handler serves both modes); a local-axis transform gizmo drags it. **The engine only reports — it never writes the skeleton itself**, so the host chooses the write policy.
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
engine.setSelectedBone(modelName | null, boneName | null) // shows the gizmo
|
|
318
|
-
engine.setSelectedMaterial(modelName | null, materialName | null) // selection outline
|
|
33
|
+
**Rendering**
|
|
319
34
|
|
|
320
|
-
|
|
35
|
+
- Shader-graph materials: every look is a Blender-style node graph compiled to WGSL; style groups bind any materials to any graph; ungrouped materials render a Principled BSDF default
|
|
36
|
+
- HDR pipeline end to end — bloom, 4× MSAA, three view transforms (Filmic, Standard, AgX from Blender's own LUT), bladed-bokeh depth of field
|
|
37
|
+
- Colour grading over the tonemapped image: ASC CDL slope / offset / power with contrast and saturation, uniforms-only so dragging a slider rebuilds no pipeline — and the background layer stays ungraded, so a grade shapes the scene without staining the sky behind it
|
|
38
|
+
- Two concentric shadow cascades: crisp contact shadows on the cast, coverage for a full stage
|
|
39
|
+
- Floor mirror: a planar reflection pass reusing the scene pipelines, with depth-proportional blur and the reflection composited as its own ground layer
|
|
40
|
+
- HDRI worlds (`.hdr`): the sky renders through the same view transform as the scene and lights the cast via spherical-harmonic irradiance — the sun keeps the toon ramp
|
|
41
|
+
- Positional lights, placed in the scene or emitted by effect shaders — stage rigs, firework bursts, a hand ribbon lighting the dancer as it passes
|
|
321
42
|
|
|
322
|
-
|
|
323
|
-
boneName: string; boneIndex: number; kind: "rotate" | "translate"
|
|
324
|
-
localRotation: Quat; localTranslation: Vec3 // target absolute local transform
|
|
325
|
-
phase?: "start" | "end" // undefined during drag moves
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
The gizmo consumes mouse input inside its bounding sphere so drags never fight camera orbit. Apply the reported transform — runtime override (below) or keyframe edit into a clip you re-`loadClip`:
|
|
330
|
-
|
|
331
|
-
```javascript
|
|
332
|
-
onGizmoDrag: (e) => {
|
|
333
|
-
const model = engine.getModel(e.modelName);
|
|
334
|
-
if (!model) return;
|
|
335
|
-
if (e.phase === "start") {
|
|
336
|
-
model.pause();
|
|
337
|
-
model.setClipApplySuspended(true);
|
|
338
|
-
return;
|
|
339
|
-
} // stop re-sampling wiping the edit
|
|
340
|
-
if (e.phase === "end") return;
|
|
341
|
-
if (e.kind === "rotate")
|
|
342
|
-
model.rotateBones({ [e.boneName]: e.localRotation }, 0); // 0 = instant write
|
|
343
|
-
else model.setBoneLocalTranslation(e.boneIndex, e.localTranslation);
|
|
344
|
-
};
|
|
345
|
-
// play()/seek() auto-clear the suspend flag (edit is lost — runtime-override semantic).
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Note the asymmetry: rotation goes through `rotateBones(…, 0)`, but translation uses `setBoneLocalTranslation(idx, v)` — `moveBones` converts VMD-relative→local, and the gizmo output is already local.
|
|
349
|
-
|
|
350
|
-
## Shader graphs & style groups
|
|
43
|
+
**Effects**
|
|
351
44
|
|
|
352
|
-
|
|
45
|
+
- N simultaneous scene effects, each one WGSL file declaring its mounts: fullscreen background/foreground, GPU particles, bone-trail ribbons, a persistent simulation grid, and lights
|
|
46
|
+
- Effects read the scene through data interfaces — the cast (bones, velocities, trail history), the audio analysis, MIDI notes, `.lrc` lyrics with their words rasterised for the shader to draw, and per-pixel object/material ids
|
|
47
|
+
- Particles and ribbons are scene geometry: depth-tested, bloomed, and reflected in the mirror
|
|
48
|
+
- Install-time compile diagnostics per effect; a broken effect fails alone
|
|
353
49
|
|
|
354
|
-
|
|
50
|
+
**Pipeline**
|
|
355
51
|
|
|
356
|
-
|
|
52
|
+
- GPU frustum culling writing indirect draw arguments, replayed through render bundles — the CPU re-encodes only when scene structure changes
|
|
53
|
+
- Deterministic by construction: everything runs on the scene clock, so offline export (`renderFrame` at any resolution) reproduces the live scene exactly
|
|
54
|
+
- Camera: orbit, bone-follow, or a driven MMD camera VMD; GPU picking, gizmos, morph/material editing surfaces
|
|
55
|
+
- Every emitted shader is compiled on a real GPU device by the repo's validation tool (`engine/tools/validate-wgsl.mjs`) before release
|
|
357
56
|
|
|
358
|
-
|
|
57
|
+
## Quick start
|
|
359
58
|
|
|
360
59
|
```javascript
|
|
361
|
-
import {
|
|
362
|
-
|
|
363
|
-
// 1. autoStyleGroups — one default group per matched category (its shipped graph). Easy path.
|
|
364
|
-
await engine.autoStyleGroups("reze");
|
|
365
|
-
|
|
366
|
-
// 2. applyStyleGroups — arbitrary groups: any id, any materials, any graph.
|
|
367
|
-
await engine.applyStyleGroups("reze", [
|
|
368
|
-
{
|
|
369
|
-
id: "hair",
|
|
370
|
-
materials: ["髪", "前髪"],
|
|
371
|
-
graph: HAIR_GRAPH,
|
|
372
|
-
renderClass: "hair",
|
|
373
|
-
},
|
|
374
|
-
{ id: "visor", materials: ["visor", "hud"], graph: myCustomGraph }, // your own graph
|
|
375
|
-
]);
|
|
376
|
-
engine.setStyleParam("reze", "hair", "rim", 0.8); // exposed param → instant uniform write
|
|
377
|
-
engine.removeStyleGroup("reze", "hair"); // its materials drop to the neutral default
|
|
378
|
-
|
|
379
|
-
// Headless (no GPU needed):
|
|
380
|
-
const { ok, wgsl, diagnostics } = compileGraph(HAIR_GRAPH, {
|
|
381
|
-
renderClass: "hair",
|
|
382
|
-
});
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
**How `autoStyleGroups(model, overrides?)` resolves** — it assigns each material a _style category_, then buckets materials by category into one group each:
|
|
60
|
+
import { Engine } from "reze-engine"
|
|
386
61
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
3. **No match → ungrouped** — the material renders the neutral default. "Unmatched" is a real, intended outcome, not a catch-all bucket.
|
|
62
|
+
const engine = new Engine(canvas)
|
|
63
|
+
await engine.init()
|
|
390
64
|
|
|
391
|
-
|
|
65
|
+
const model = await engine.loadModel("reze", "/models/reze/reze.pmx")
|
|
66
|
+
await engine.autoStyleGroups("reze")
|
|
392
67
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
| `eye` | `EYE_GRAPH` · `eye` | 白目 · 目影 · 二重 · 睫 · まつげ · まゆ · 眉 · 目 · 瞳 · 眼 · eye · iris · pupil · lash · brow |
|
|
397
|
-
| `face` | `FACE_GRAPH` | 顔 · 颜 · 脸 · かお · face · 舌 · tongue · 牙 · 牙齿 · 歯 · teeth · tooth · 口腔 · 口内 · mouth · 嘴 · 歯茎 · gums |
|
|
398
|
-
| `hair` | `HAIR_GRAPH` · `hair` | 前髪 · 後髪 · 髪 · 髮 · 头发 · 頭髪 · もみあげ · アホ毛 · ヘア · hair · ahoge · bang |
|
|
399
|
-
| `body` | `BODY_GRAPH` | 肌 · 皮肤 · skin |
|
|
400
|
-
| `metal` | `METAL_GRAPH` | 金属 · メタル · metal · earring · 耳环 · 耳環 |
|
|
401
|
-
| `cloth_smooth` | `CLOTH_SMOOTH_GRAPH` | 服 · 衣 · 裙 · 裤 · スカート · ワンピ · リボン · 袖 · 靴 · 鞋 · 帽 · 体 · 飾 · 饰 · 尾 · 套 · 腿 · skirt · dress · ribbon · sleeve · shoes · shirt · short · boot · hat · cloth · accessor · trigger |
|
|
68
|
+
await model.loadVmd("dance", "/animations/dance.vmd")
|
|
69
|
+
model.show("dance")
|
|
70
|
+
model.play()
|
|
402
71
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
Validation catches material conflicts, type mismatches, cycles, and bad links with node-level diagnostics; a failed compile keeps the previous pipeline rendering (fallback-on-error).
|
|
406
|
-
|
|
407
|
-
## Physics
|
|
408
|
-
|
|
409
|
-
In-house sequential-impulse rigid-body solver for PMX rigs (sphere / box / capsule colliders, 6DOF spring joints), ~4.2k lines of TypeScript, no external dependency, at quality comparable to Bullet's defaults. A fixed-timestep accumulator runs at a constant **60 Hz** (≤6 substeps/frame) so spring impulse, damping, and integration stay deterministic; dynamic bodies are **render-interpolated** between substeps to stay smooth when the display rate ≠ 60 Hz.
|
|
410
|
-
|
|
411
|
-
**Per substep:** `predict velocities → broad + narrowphase → solve constraints (10 iters) → split-impulse position correction → integrate`.
|
|
412
|
-
|
|
413
|
-
- **Solver** — projected Gauss-Seidel, joint rows + contact rows in one loop. Joints are 6DOF spring constraints (3 linear + 3 angular) with stop-ERP limit correction. Linear rows pivot on each body's own joint-frame origin (Spring2-style, not Bullet 2.7x's shared mid-anchor), so a violated joint pulls itself back together instead of degenerating into torque and "breaking".
|
|
414
|
-
- **Implicit spring-dampers** — each sprung axis solves the backward-Euler soft constraint `relVel⁺ + (k/γ)·err + s·λ = 0` (`γ = c + h·k`, `s = 1/(h·γ)`), unconditionally stable for any authored stiffness, with damping `c = 2ζ√(k·m_eff)` intrinsic to the row. Resting cloth genuinely settles: the previous clamped velocity-drive could inject energy far from equilibrium but not absorb it near it, so static dresses slowly "boiled" — measured 15× lower resting velocity after the change, with authored stiffness now delivered in full (no deadbeat clamp).
|
|
415
|
-
- **Angular limits** — hybrid: small violations (< 0.5 rad, the resting-cloth regime) use per-axis Euler stop rows, which converge cleanly and keep resting cloth still; larger violations switch to a single geodesic row toward the Euler-clamped target rotation, because per-axis Euler rows chase phantom errors near the ±90° singularity and pump energy. Ranged stops are unilateral (accumulated impulse clamped to the corrective sign) so a limit pushes back into range but never brakes natural recovery; locked axes stay bilateral equality joints. Spring rows stay per-axis, with stiffness clamped to the `k·dt² ≤ ¼` stability bound.
|
|
416
|
-
- **Narrowphase** — analytical sphere-sphere / -capsule / -box, capsule-capsule / -box, and box-box (SAT + face clipping, up to 4 points per manifold). MMD dress rigs are built from box panels, and box-box is the majority of collidable pairs on those models — without it cloth passes through cloth. Capsule-capsule emits multiple contacts along near-parallel axes so cloth can't pivot around a single closest point.
|
|
417
|
-
- **Penetration and separation are one term**, ported from Bullet 2.75 (`m_erp` 0.2), the build MMD's own physics runs: the contact row's target carries `depth · erp / dt`. Penetrating, it pushes apart; separated — a speculative row inside the `0.04` margin — it *allows* the approach that closes the gap, which is what keeps those rows inert until the body would really arrive. A push-only clamp does not achieve that on its own: it forbids a pulling impulse, not a large pushing one on a body still in mid-air.
|
|
418
|
-
- **Warm starting** (`0.85`) over persistent per-pair manifolds, up to 4 points matched in each body's own local frame, so a resting stack starts each substep already holding roughly the load it needs. When a 5th point arrives the one dropped is whichever leaves the largest quadrilateral — area is what stops a resting box pivoting.
|
|
419
|
-
- **Contact ordering is reshuffled between iterations.** Projected Gauss-Seidel is order-biased, rows solved first win, and on a cross-linked skirt lattice that reads as chatter. The shuffle is re-seeded per substep, so a scene still replays identically.
|
|
420
|
-
- **Split impulse** resolves deep overlap on a pseudo-velocity channel with its own full iteration pass, integrated straight into the transform. Recovering penetration therefore adds no real momentum for the joint springs to hand back — the property MMD depends on, given how constantly its hair and layered skirts clip.
|
|
421
|
-
- **Kinematic advancement** — bone-driven bodies move toward the frame's bone pose incrementally per substep, with velocities derived over the fixed step, so the solver never sees more than one 60 Hz step of anchor motion regardless of render dt.
|
|
422
|
-
- **Discontinuity guards** — a bone-pose jump beyond continuous motion (timeline scrub, long stall) rigidly carries each dynamic body along with its kinematic root's transform delta and zeroes momentum instead of dragging cloth across the gap; correction velocities are clamped (120 u/s linear, 30 rad/s angular), per-step travel is capped, and any body that still goes non-finite is restored to its previous substep pose.
|
|
423
|
-
- Sleeping is off (cloth must always react); resting bodies bleed micro-velocity via per-PMX damping.
|
|
424
|
-
|
|
425
|
-
- **Gravity and wind** are scene-wide and summed once per substep, so wind costs the per-body loop nothing. Wind is an acceleration rather than a drag model: PMX authors already tune per-body damping to get the hang they want, and a second hidden drag term would fight it. Gusting rides a pair of incommensurate sines — one alone is a metronome — and advances on *simulated* time, so an exported take gusts frame-for-frame as its preview did.
|
|
426
|
-
|
|
427
|
-
Engine surface is `setPhysicsEnabled` / `resetPhysics` / `setGravity` / `setWind` — everything else (mass, damping, friction, restitution, joint stiffness/limits, collision groups) lives on the PMX rig.
|
|
428
|
-
|
|
429
|
-
## Rendering
|
|
430
|
-
|
|
431
|
-
Each built-in shader graph mixes an NPR stack with a Principled-style BSDF, so characters keep a flat illustrated look while highlights and reflections stay grounded. A graph compiles to a fragment shader following one 7-stage layout (node primitives from `nodes.ts`, the fs() shell from `common.ts`):
|
|
432
|
-
|
|
433
|
-
```
|
|
434
|
-
(A) setup → (B) texture + alpha → (C) NPR stack → (D) optional bump
|
|
435
|
-
→ (E) Principled BSDF → (F) NPR↔PBR mix → (G) FSOut
|
|
72
|
+
engine.runRenderLoop()
|
|
436
73
|
```
|
|
437
74
|
|
|
438
|
-
`
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
- **NPR toolbox** — toon ramps (constant / fwidth-AA'd), HSV warm-shadow / cool-light remaps, fresnel + layer-weight rims, value-noise bump, Voronoi metallic sparkle, BT.601-gated emission.
|
|
75
|
+
`autoStyleGroups` reads the model's own material names and gives each group a
|
|
76
|
+
shader graph, so hair, eyes and skin come out looking like MMD with no shading
|
|
77
|
+
code of your own.
|
|
442
78
|
|
|
443
|
-
|
|
444
|
-
| -------------- | ----------------------------------------------------------------------- |
|
|
445
|
-
| `default` | Plain Principled, metallic 0, rough 0.5 |
|
|
446
|
-
| `eye` | Plain + post-eval emission ×1.5 |
|
|
447
|
-
| `face` | Toon + warm rim + dual-fresnel rim + bright-tex gate, noise bump |
|
|
448
|
-
| `body` | Toon + warm rim + fresnel + facing rim, noise bump |
|
|
449
|
-
| `hair` | Toon + fresnel + bevel + bright-tex gate, 20% PBR |
|
|
450
|
-
| `cloth_smooth` | Toon + bevel + emission overlay (×18) |
|
|
451
|
-
| `cloth_rough` | Same NPR, live noise bump, rough 0.82 |
|
|
452
|
-
| `metal` | Toon + emission overlay (×8), Voronoi base, metallic 1 |
|
|
453
|
-
| `stockings` | Gradient × facing mask + HSV emission (×5), sheen 0.7, **alpha-hashed** |
|
|
79
|
+
## Architecture
|
|
454
80
|
|
|
455
|
-
|
|
81
|
+
One frame, top to bottom. The CPU describes the scene once and the GPU runs it: culling and effect simulation in compute, shadows and the floor mirror feeding a scene pass that draws MMD the way MMD artists expect — author order, outlines, the eye/hair stencil — then bloom, film tone mapping, and depth of field finish the image.
|
|
456
82
|
|
|
457
|
-
|
|
458
|
-
- **Sheer-material detection** — PMX has no "translucent" flag (a see-through veil usually ships diffuse alpha 1.0 with the transparency in its texture), so at load each material samples its texture's alpha at its own triangle centroids; genuinely sheer materials route to the transparent bucket — drawn after the opaque + hair passes so a veil composites over the hair behind it, and excluded from the shadow map so sheer cloth doesn't cast the solid shadow of an opaque sheet. Centroids, not vertices: hair-card corners sit in transparent texture margins, and hair must stay opaque-bucket for stencil interplay and shadows.
|
|
459
|
-
- **See-through hair over eyes** — stencil-gated extra pass: the eye stamps `EYE_VALUE`, main hair skips it, an extra pass matches it and blends hair at 25% in linear HDR so eyes stay readable.
|
|
83
|
+

|
|
460
84
|
|
|
461
|
-
##
|
|
85
|
+
## License
|
|
462
86
|
|
|
463
|
-
|
|
87
|
+
MIT
|
package/dist/animation.d.ts
CHANGED
|
@@ -61,6 +61,16 @@ export interface BlendEntry {
|
|
|
61
61
|
* NOT renormalized — the remainder blends toward the rest pose. */
|
|
62
62
|
weight: number;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Morphs the OLD tracks drove that the new ones do not name.
|
|
66
|
+
*
|
|
67
|
+
* Nothing in the pose pass resets a morph weight — it writes only the morphs
|
|
68
|
+
* its clip names — so a morph left behind by a replaced track keeps its last
|
|
69
|
+
* value for the rest of the session. These are the ones a caller has to zero,
|
|
70
|
+
* or the old face stays frozen underneath the new one, which reads exactly
|
|
71
|
+
* like the new file having had no effect at all.
|
|
72
|
+
*/
|
|
73
|
+
export declare function retiredMorphs(prev: AnimationClip | null, next: Map<string, MorphKeyframe[]>): string[];
|
|
64
74
|
export declare class AnimationState {
|
|
65
75
|
private animations;
|
|
66
76
|
private currentAnimationName;
|
|
@@ -72,6 +82,22 @@ export declare class AnimationState {
|
|
|
72
82
|
private nextAnimation;
|
|
73
83
|
private onEnd;
|
|
74
84
|
loadAnimation(name: string, clip: AnimationClip): void;
|
|
85
|
+
/**
|
|
86
|
+
* Replace a clip's morph tracks wholesale, leaving its bones alone.
|
|
87
|
+
*
|
|
88
|
+
* MMD authors ship expressions as their own file (表情モーション) beside the
|
|
89
|
+
* body motion, and when they do it is AUTHORITATIVE: whatever morphs the body
|
|
90
|
+
* motion carried are the ones the morph pass was made to replace. So
|
|
91
|
+
* this overwrites rather than merges per-morph — a half-overridden face is
|
|
92
|
+
* nobody's intent. With no expression file loaded, the motion's own morphs
|
|
93
|
+
* play untouched, because nothing calls this.
|
|
94
|
+
*
|
|
95
|
+
* The clip is created if it does not exist yet, so the two files may arrive
|
|
96
|
+
* in either order, and frameCount grows to cover the longer of the two — an
|
|
97
|
+
* morph track running past the body motion is common and must not be
|
|
98
|
+
* truncated to it.
|
|
99
|
+
*/
|
|
100
|
+
setMorphTracks(name: string, morphTracks: Map<string, MorphKeyframe[]>, frameCount: number): void;
|
|
75
101
|
removeAnimation(name: string): void;
|
|
76
102
|
play(name: string, options?: AnimationPlayOptions): boolean;
|
|
77
103
|
play(): void;
|
package/dist/animation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../src/animation.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../src/animation.ts"],"names":[],"mappings":"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"}
|