reze-engine 0.55.1 → 0.55.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.
- package/dist/engine.d.ts +135 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +261 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/model.d.ts +27 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +70 -8
- package/dist/shaders/passes/ground.d.ts +5 -1
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +47 -28
- package/package.json +1 -1
- package/src/engine.ts +313 -20
- package/src/index.ts +1 -0
- package/src/model.ts +71 -8
- package/src/shaders/passes/ground.ts +48 -27
package/dist/model.js
CHANGED
|
@@ -70,6 +70,35 @@ export class Model {
|
|
|
70
70
|
setVisible(visible) {
|
|
71
71
|
this._visible = visible;
|
|
72
72
|
}
|
|
73
|
+
/** Hang the rig's root bones from `matrix` (model space, column-major 16
|
|
74
|
+
* floats), or from nothing. The engine drives this every frame for an
|
|
75
|
+
* attached model; the matrix is read at the next world pass, not copied. */
|
|
76
|
+
setRootParent(matrix) {
|
|
77
|
+
this.rootParent = matrix;
|
|
78
|
+
if (matrix) {
|
|
79
|
+
const root = this.skeleton.bones.find((b) => b.parentIndex < 0);
|
|
80
|
+
this.primaryRootBind = root ? [root.bindTranslation[0], root.bindTranslation[1], root.bindTranslation[2]] : [0, 0, 0];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
getRootParent() {
|
|
84
|
+
return this.rootParent;
|
|
85
|
+
}
|
|
86
|
+
/** The placement matrix (position · rotation · scale) the skin bake composes
|
|
87
|
+
* onto every bone. Rebuilt lazily, the way getSkinMatrices does it. */
|
|
88
|
+
getRootMatrix() {
|
|
89
|
+
this.refreshRootMatrix();
|
|
90
|
+
return this.rootMatrixValues;
|
|
91
|
+
}
|
|
92
|
+
refreshRootMatrix() {
|
|
93
|
+
if (!this.rootMatrixDirty)
|
|
94
|
+
return;
|
|
95
|
+
const p = this._position, r = this._rotation, s = this._scale;
|
|
96
|
+
Mat4.fromPositionRotationScaleInto(p.x, p.y, p.z, r.x, r.y, r.z, r.w, s, this.rootMatrixValues);
|
|
97
|
+
this.rootIsIdentity =
|
|
98
|
+
p.x === 0 && p.y === 0 && p.z === 0 &&
|
|
99
|
+
r.x === 0 && r.y === 0 && r.z === 0 && r.w === 1 && s === 1;
|
|
100
|
+
this.rootMatrixDirty = false;
|
|
101
|
+
}
|
|
73
102
|
/** Called by Engine when registering the model; enables loadVmd to resolve relative paths for folder uploads. */
|
|
74
103
|
setAssetContext(reader, basePath) {
|
|
75
104
|
this.assetReader = reader;
|
|
@@ -122,6 +151,21 @@ export class Model {
|
|
|
122
151
|
this.rootMatrixValues = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
123
152
|
this.rootMatrixDirty = false;
|
|
124
153
|
this.rootIsIdentity = true;
|
|
154
|
+
/** What every parentless bone hangs from — MMD's 外部親 (outside parent).
|
|
155
|
+
* Model space, so the pose pipeline, IK and physics all see it: a prop bound
|
|
156
|
+
* to a hand is moved by its BONES, and gravity keeps pointing down while the
|
|
157
|
+
* hand tilts. Null is the ordinary rig, rooted at the model's own origin.
|
|
158
|
+
* Written per frame by the engine from the parent's posed bone; see
|
|
159
|
+
* Engine.setModelParent. */
|
|
160
|
+
this.rootParent = null;
|
|
161
|
+
/** The bind position of the PRIMARY root — the first parentless bone, 全ての親
|
|
162
|
+
* by convention. Under a root parent that bone sits exactly ON the parent
|
|
163
|
+
* bone, as MMD's 外部親 does, so its bind position is taken off every root's
|
|
164
|
+
* local matrix: the primary lands at the parent, the other roots keep their
|
|
165
|
+
* layout relative to it. Without this the MODEL ORIGIN went to the parent
|
|
166
|
+
* bone, and a prop rigged with its one bone at the mesh's centre hung that
|
|
167
|
+
* far away from the hand. */
|
|
168
|
+
this.primaryRootBind = [0, 0, 0];
|
|
125
169
|
/** 1 where the simulation overwrites the bone's world matrix. See setPhysicsDrivenBones. */
|
|
126
170
|
this.physicsDriven = null;
|
|
127
171
|
/** Bones to recompute after a step, in deform order — null when no rig needs it. */
|
|
@@ -652,6 +696,14 @@ export class Model {
|
|
|
652
696
|
isClipApplySuspended() {
|
|
653
697
|
return this.clipApplySuspended;
|
|
654
698
|
}
|
|
699
|
+
/** A bone's posed matrix — model space, column-major, the live array rather
|
|
700
|
+
* than a copy. Null for a name this rig does not have. */
|
|
701
|
+
getBoneWorldMatrix(boneName) {
|
|
702
|
+
const idx = this.runtimeSkeleton.nameIndex[boneName];
|
|
703
|
+
if (idx === undefined || idx < 0)
|
|
704
|
+
return null;
|
|
705
|
+
return this.runtimeSkeleton.worldMatrices[idx].values;
|
|
706
|
+
}
|
|
655
707
|
// World bone origin (world matrix col3); unknown name → null
|
|
656
708
|
getBoneWorldPosition(boneName) {
|
|
657
709
|
const idx = this.runtimeSkeleton.nameIndex[boneName];
|
|
@@ -879,14 +931,7 @@ export class Model {
|
|
|
879
931
|
}
|
|
880
932
|
const skinMatrices = this.skinMatricesArray;
|
|
881
933
|
// Rebuild root matrix + cache identity-shortcut flag only when pos/rot changed.
|
|
882
|
-
|
|
883
|
-
const p = this._position, r = this._rotation, s = this._scale;
|
|
884
|
-
Mat4.fromPositionRotationScaleInto(p.x, p.y, p.z, r.x, r.y, r.z, r.w, s, this.rootMatrixValues);
|
|
885
|
-
this.rootIsIdentity =
|
|
886
|
-
p.x === 0 && p.y === 0 && p.z === 0 &&
|
|
887
|
-
r.x === 0 && r.y === 0 && r.z === 0 && r.w === 1 && s === 1;
|
|
888
|
-
this.rootMatrixDirty = false;
|
|
889
|
-
}
|
|
934
|
+
this.refreshRootMatrix();
|
|
890
935
|
if (this.rootIsIdentity) {
|
|
891
936
|
// skinMatrix = worldMatrix × inverseBindMatrix
|
|
892
937
|
for (let i = 0; i < boneCount; i++) {
|
|
@@ -2209,6 +2254,13 @@ export class Model {
|
|
|
2209
2254
|
const parentMat = worldMats[b.parentIndex];
|
|
2210
2255
|
Mat4.multiplyArrays(parentMat.values, 0, localMVals, 0, worldMat.values, 0);
|
|
2211
2256
|
}
|
|
2257
|
+
else if (this.rootParent) {
|
|
2258
|
+
const pr = this.primaryRootBind;
|
|
2259
|
+
localMVals[12] -= pr[0];
|
|
2260
|
+
localMVals[13] -= pr[1];
|
|
2261
|
+
localMVals[14] -= pr[2];
|
|
2262
|
+
Mat4.multiplyArrays(this.rootParent, 0, localMVals, 0, worldMat.values, 0);
|
|
2263
|
+
}
|
|
2212
2264
|
else {
|
|
2213
2265
|
worldMat.values.set(localMVals);
|
|
2214
2266
|
}
|
|
@@ -2362,6 +2414,8 @@ export class Model {
|
|
|
2362
2414
|
// leaving every other bone — the simulated ones above all — untouched.
|
|
2363
2415
|
const order = subset ?? this.deformOrder;
|
|
2364
2416
|
const count = subset ? subset.length : boneCount;
|
|
2417
|
+
const rootParent = this.rootParent;
|
|
2418
|
+
const primaryRootBind = this.primaryRootBind;
|
|
2365
2419
|
const override = this.appendRotOverride;
|
|
2366
2420
|
const overrideSet = this.appendRotOverrideSet;
|
|
2367
2421
|
for (let k = 0; k < count; k++) {
|
|
@@ -2427,6 +2481,14 @@ export class Model {
|
|
|
2427
2481
|
const parentMat = worldMats[b.parentIndex];
|
|
2428
2482
|
Mat4.multiplyArrays(parentMat.values, 0, localMVals, 0, worldMat.values, 0);
|
|
2429
2483
|
}
|
|
2484
|
+
else if (rootParent) {
|
|
2485
|
+
// The primary root's bind position comes off every root, so the primary
|
|
2486
|
+
// sits ON the parent bone. See primaryRootBind.
|
|
2487
|
+
localMVals[12] -= primaryRootBind[0];
|
|
2488
|
+
localMVals[13] -= primaryRootBind[1];
|
|
2489
|
+
localMVals[14] -= primaryRootBind[2];
|
|
2490
|
+
Mat4.multiplyArrays(rootParent, 0, localMVals, 0, worldMat.values, 0);
|
|
2491
|
+
}
|
|
2430
2492
|
else {
|
|
2431
2493
|
worldMat.values.set(localMVals);
|
|
2432
2494
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The ground's shader. `soft` selects the shadow-edge variant — see pcfWgsl for
|
|
3
|
+
* why this is a compiled flag and not a uniform the shader branches on.
|
|
4
|
+
*/
|
|
5
|
+
export declare function groundShaderWgsl(soft?: boolean): string;
|
|
2
6
|
/**
|
|
3
7
|
* The ground belongs to no model instance, so it takes ids of its own — at the
|
|
4
8
|
* TOP of the u16 range, not at the bottom.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ground.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/ground.ts"],"names":[],"mappings":"AAgFA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,UAAQ,GAAG,MAAM,CA+TrD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAS,CAAA;AACxC,eAAO,MAAM,gBAAgB,QAAS,CAAA;AAEtC;;;oEAGoE;AACpE,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAErC;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,+oCAiClC,CAAA"}
|
|
@@ -32,36 +32,53 @@ const SOFT_MAX_SPREAD = 14;
|
|
|
32
32
|
* `acc` must already be declared and zeroed; the caller reads it back
|
|
33
33
|
* normalised, so there is no `/ 9.0` left at the call site.
|
|
34
34
|
*/
|
|
35
|
-
function pcfWgsl(map, uv, texel, z, acc, pad) {
|
|
35
|
+
function pcfWgsl(map, uv, texel, z, acc, pad, soft) {
|
|
36
36
|
const golden = 2.39996323;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// ONE BRANCH IS COMPILED, NOT BOTH.
|
|
38
|
+
//
|
|
39
|
+
// A runtime `if` on the softness uniform was the first cut, and it costs a
|
|
40
|
+
// scene that never softens a shadow. Both bodies land in the module, the wide
|
|
41
|
+
// one needs more live registers than the narrow one, and occupancy on this
|
|
42
|
+
// draw is set by the worst of them — so the frame gets slower whether or not
|
|
43
|
+
// the branch is ever taken. That is expensive HERE in particular: this file
|
|
44
|
+
// already records that the ground is a full-coverage draw costing tens of
|
|
45
|
+
// millions of shadow fetches, and that bisection on a slow device pinned the
|
|
46
|
+
// whole cost to it.
|
|
47
|
+
//
|
|
48
|
+
// So the flag is the COMPILED VARIANT, which is the idiom the composite pass
|
|
49
|
+
// already uses for its effects. A scene at softness 0 gets byte-for-byte the
|
|
50
|
+
// shader it had before any of this existed, and cannot pay for a feature it
|
|
51
|
+
// is not using.
|
|
52
|
+
const sharp = [
|
|
53
|
+
`let st = ${texel} * 2.0;`,
|
|
54
|
+
`for (var y = -1; y <= 1; y++) {`,
|
|
55
|
+
` for (var x = -1; x <= 1; x++) {`,
|
|
42
56
|
// ...Level, not the implicit-derivative form: identical on a single-mip
|
|
43
57
|
// shadow map, and legal inside a branch.
|
|
44
|
-
`
|
|
45
|
-
` }`,
|
|
58
|
+
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, ${uv} + vec2f(f32(x), f32(y)) * st, ${z});`,
|
|
46
59
|
` }`,
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`
|
|
60
|
+
`}`,
|
|
61
|
+
`${acc} *= ${1 / 9};`,
|
|
62
|
+
];
|
|
63
|
+
const wide = [
|
|
64
|
+
`let radius = ${texel} * 2.0 * (1.0 + material.shadowSoftness * ${SOFT_MAX_SPREAD}.0);`,
|
|
65
|
+
`for (var s = 0; s < ${SOFT_TAPS}; s++) {`,
|
|
66
|
+
` let fs = f32(s);`,
|
|
52
67
|
// sqrt of the index spaces the ring radii evenly by AREA; the golden angle
|
|
53
68
|
// keeps successive taps from lining up into spokes.
|
|
54
|
-
`
|
|
55
|
-
`
|
|
56
|
-
`
|
|
57
|
-
` }`,
|
|
58
|
-
` ${acc} *= ${1 / SOFT_TAPS};`,
|
|
69
|
+
` let r = sqrt((fs + 0.5) * ${1 / SOFT_TAPS});`,
|
|
70
|
+
` let a = fs * ${golden} + rot;`,
|
|
71
|
+
` ${acc} += textureSampleCompareLevel(${map}, shadowSampler, ${uv} + vec2f(cos(a), sin(a)) * (r * radius), ${z});`,
|
|
59
72
|
`}`,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
`${acc} *= ${1 / SOFT_TAPS};`,
|
|
74
|
+
];
|
|
75
|
+
return (soft ? wide : sharp).map((l) => pad + l).join("\n");
|
|
63
76
|
}
|
|
64
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The ground's shader. `soft` selects the shadow-edge variant — see pcfWgsl for
|
|
79
|
+
* why this is a compiled flag and not a uniform the shader branches on.
|
|
80
|
+
*/
|
|
81
|
+
export function groundShaderWgsl(soft = false) {
|
|
65
82
|
return /* wgsl */ `
|
|
66
83
|
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
67
84
|
struct Light { direction: vec4f, color: vec4f, };
|
|
@@ -203,11 +220,13 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
203
220
|
// The same reasoning the noise tint below already got, applied to the term
|
|
204
221
|
// that costs a hundred times more.
|
|
205
222
|
if (material.shadowStrength > 0.0 && shadowPossible) {
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
//
|
|
223
|
+
${soft
|
|
224
|
+
? ` // Per-pixel rotation for the soft disk, so its rings break up into fine
|
|
225
|
+
// noise rather than banding. Interleaved gradient noise: a function of the
|
|
226
|
+
// pixel alone, so a still camera gives a still shadow.
|
|
209
227
|
let ign = fract(52.9829189 * fract(dot(i.position.xy, vec2f(0.06711056, 0.00583715))));
|
|
210
|
-
let rot = ign * 6.28318530718
|
|
228
|
+
let rot = ign * 6.28318530718;`
|
|
229
|
+
: ""}
|
|
211
230
|
// The far cascade's taps, skipped entirely when nothing ever drew into it.
|
|
212
231
|
//
|
|
213
232
|
// This branch is the expensive one on a wide floor: it runs wherever the NEAR
|
|
@@ -223,7 +242,7 @@ ${sceneFsOutWgsl()}@fragment fn fs(i: VO) -> FSOut {
|
|
|
223
242
|
let suv1_c = clamp(suv1, vec2f(0.02), vec2f(0.98));
|
|
224
243
|
let compareZ1 = ndc1.z - 0.0035;
|
|
225
244
|
var acc1 = 0.0;
|
|
226
|
-
${pcfWgsl("shadowMapFar", "suv1_c", `${1 / SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize}`, "compareZ1", "acc1", " ")}
|
|
245
|
+
${pcfWgsl("shadowMapFar", "suv1_c", `${1 / SHADOW_CASCADES[SHADOW_CASCADES.length - 1].mapSize}`, "compareZ1", "acc1", " ", soft)}
|
|
227
246
|
vis = mix(1.0, acc1, frustum1);
|
|
228
247
|
}
|
|
229
248
|
if (frustum > 0.0) {
|
|
@@ -231,7 +250,7 @@ ${pcfWgsl("shadowMapFar", "suv1_c", `${1 / SHADOW_CASCADES[SHADOW_CASCADES.lengt
|
|
|
231
250
|
let suv_c = clamp(suv, vec2f(0.02), vec2f(0.98));
|
|
232
251
|
let compareZ = ndc.z - 0.0035;
|
|
233
252
|
var acc = 0.0;
|
|
234
|
-
${pcfWgsl("shadowMap", "suv_c", "material.pcfTexel", "compareZ", "acc", " ")}
|
|
253
|
+
${pcfWgsl("shadowMap", "suv_c", "material.pcfTexel", "compareZ", "acc", " ", soft)}
|
|
235
254
|
// The base is whatever the far cascade decided, so the near border blends
|
|
236
255
|
// cascade to cascade rather than snapping to lit mid-floor.
|
|
237
256
|
vis = mix(vis, acc, frustum);
|