reze-engine 0.41.3 → 0.42.0
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 +42 -3
- package/dist/animation.d.ts +18 -0
- package/dist/animation.d.ts.map +1 -1
- package/dist/animation.js +26 -16
- package/dist/engine.d.ts +45 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +286 -11
- package/dist/model.d.ts +56 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +125 -22
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +8 -5
- package/dist/shaders/passes/composite.d.ts +46 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +243 -13
- package/package.json +1 -1
- package/src/animation.ts +26 -16
- package/src/engine.ts +301 -10
- package/src/model.ts +133 -22
- package/src/pmx-loader.ts +10 -5
- package/src/shaders/passes/composite.ts +221 -14
package/dist/model.js
CHANGED
|
@@ -151,6 +151,8 @@ export class Model {
|
|
|
151
151
|
this.blendGenCounter = 0;
|
|
152
152
|
this.assetReader = null;
|
|
153
153
|
this.assetBasePath = "";
|
|
154
|
+
/** 軸制限 bones and their normalised axes — see applyFixedAxes. */
|
|
155
|
+
this.fixedAxisBones = [];
|
|
154
156
|
// When true, update() skips applyPoseFromClip, so whatever was last written to
|
|
155
157
|
// localRotations / localTranslations persists across frames. Used by gizmo drag
|
|
156
158
|
// and other direct-manipulation flows to prevent the currently-shown clip from
|
|
@@ -201,6 +203,19 @@ export class Model {
|
|
|
201
203
|
localTranslations[i] = Vec3.zeros();
|
|
202
204
|
worldMatrices[i] = Mat4.identity();
|
|
203
205
|
}
|
|
206
|
+
// 軸制限 bones, gathered once and normalised. A standard rig has four —
|
|
207
|
+
// the two arm twists and the two wrist twists — so this is a four-element
|
|
208
|
+
// walk per pose rather than a branch on every bone.
|
|
209
|
+
this.fixedAxisBones = [];
|
|
210
|
+
for (let i = 0; i < boneCount; i++) {
|
|
211
|
+
const a = this.skeleton.bones[i].fixedAxis;
|
|
212
|
+
if (!a)
|
|
213
|
+
continue;
|
|
214
|
+
const len = Math.hypot(a[0], a[1], a[2]);
|
|
215
|
+
if (len < 1e-8)
|
|
216
|
+
continue;
|
|
217
|
+
this.fixedAxisBones.push({ index: i, x: a[0] / len, y: a[1] / len, z: a[2] / len });
|
|
218
|
+
}
|
|
204
219
|
this.runtimeSkeleton = {
|
|
205
220
|
localRotations,
|
|
206
221
|
localTranslations,
|
|
@@ -372,19 +387,42 @@ export class Model {
|
|
|
372
387
|
* Stages are the reason this exists: a door or a lift is rigged as a bone
|
|
373
388
|
* morph and there is no VMD anywhere that drives it.
|
|
374
389
|
*/
|
|
390
|
+
/**
|
|
391
|
+
* Take the last frame's bone-morph offsets back off, before any pose source
|
|
392
|
+
* runs.
|
|
393
|
+
*
|
|
394
|
+
* This cannot live at the top of applyBoneMorphs, which is where it used to
|
|
395
|
+
* be. A pose source writes only the bones its clip keys, and it writes them
|
|
396
|
+
* from the clip — so restoring afterwards overwrote the freshly animated pose
|
|
397
|
+
* with a snapshot taken a frame earlier, and the re-snapshot immediately
|
|
398
|
+
* below then saved that same stale value again. The result was not a drift or
|
|
399
|
+
* a lag: every bone any bone morph touched stayed pinned to the first frame
|
|
400
|
+
* the model was posed at, permanently, and at ANY weight — the restore ran
|
|
401
|
+
* over the touched set before the weight test, so a morph sitting at 0 froze
|
|
402
|
+
* its bones just as hard as one at 1.
|
|
403
|
+
*
|
|
404
|
+
* It reads as "the arms don't animate" because arms are what these morphs
|
|
405
|
+
* touch: character models routinely ship T-Pose / A-Pose / ShouderBlend /
|
|
406
|
+
* ElbowBlend adjusters on 腕 and ひじ, and nothing else in the rig is a
|
|
407
|
+
* comparably popular bone-morph target.
|
|
408
|
+
*/
|
|
409
|
+
undoBoneMorphs() {
|
|
410
|
+
if (!this.boneMorphApplied)
|
|
411
|
+
return;
|
|
412
|
+
for (let i = 0; i < this.boneMorphBones.length; i++) {
|
|
413
|
+
const b = this.boneMorphBones[i];
|
|
414
|
+
this.runtimeSkeleton.localRotations[b].set(this.boneMorphRestoreR[i]);
|
|
415
|
+
this.runtimeSkeleton.localTranslations[b].set(this.boneMorphRestoreT[i]);
|
|
416
|
+
}
|
|
417
|
+
this.boneMorphApplied = false;
|
|
418
|
+
}
|
|
375
419
|
applyBoneMorphs() {
|
|
376
420
|
if (this.boneMorphPlan.length === 0)
|
|
377
421
|
return;
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
for (let i = 0; i < this.boneMorphBones.length; i++) {
|
|
383
|
-
const b = this.boneMorphBones[i];
|
|
384
|
-
this.runtimeSkeleton.localRotations[b].set(this.boneMorphRestoreR[i]);
|
|
385
|
-
this.runtimeSkeleton.localTranslations[b].set(this.boneMorphRestoreT[i]);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
422
|
+
// Snapshot the pose as the sources left it, so undoBoneMorphs can hand these
|
|
423
|
+
// bones back unmorphed at the top of the next frame. The two halves are a
|
|
424
|
+
// pair: without the undo a stage — which has no clip to rewrite its bones —
|
|
425
|
+
// would compound the same offset every frame.
|
|
388
426
|
for (let i = 0; i < this.boneMorphBones.length; i++) {
|
|
389
427
|
const b = this.boneMorphBones[i];
|
|
390
428
|
this.boneMorphRestoreR[i].set(this.runtimeSkeleton.localRotations[b]);
|
|
@@ -598,6 +636,24 @@ export class Model {
|
|
|
598
636
|
return null;
|
|
599
637
|
return this.runtimeSkeleton.worldMatrices[idx].getPosition();
|
|
600
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* A bone's forward axis, normalised — which way a foot points, where a head
|
|
641
|
+
* looks. Model space, like getBoneWorldPosition: the caller composes the model
|
|
642
|
+
* transform on top if it wants world space.
|
|
643
|
+
*
|
|
644
|
+
* Column 2 of the world matrix. Null for a name this rig does not have, which
|
|
645
|
+
* is the ordinary case across rigs that spell bones differently.
|
|
646
|
+
*/
|
|
647
|
+
getBoneWorldForward(boneName) {
|
|
648
|
+
const idx = this.runtimeSkeleton.nameIndex[boneName];
|
|
649
|
+
if (idx === undefined || idx < 0)
|
|
650
|
+
return null;
|
|
651
|
+
const m = this.runtimeSkeleton.worldMatrices[idx].values;
|
|
652
|
+
const len = Math.hypot(m[8], m[9], m[10]);
|
|
653
|
+
if (len < 1e-8)
|
|
654
|
+
return null;
|
|
655
|
+
return new Vec3(m[8] / len, m[9] / len, m[10] / len);
|
|
656
|
+
}
|
|
601
657
|
getSkinning() {
|
|
602
658
|
return this.skinning;
|
|
603
659
|
}
|
|
@@ -1513,6 +1569,38 @@ export class Model {
|
|
|
1513
1569
|
(frameB.frame > frameA.frame ? (clampedFrame - frameA.frame) / (frameB.frame - frameA.frame) : 0)
|
|
1514
1570
|
: frameA.weight;
|
|
1515
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Hold 軸制限 bones to their own axis.
|
|
1574
|
+
*
|
|
1575
|
+
* A twist bone (腕捩 / 手捩) exists to rotate about the length of the limb and
|
|
1576
|
+
* nothing else, and PMX says so by giving it a fixed axis. A VMD still keys it
|
|
1577
|
+
* with a full quaternion, so without the constraint the bone bends as well as
|
|
1578
|
+
* twists — and because the elbow is parented to 腕捩 and 腕捩1..3 inherit a
|
|
1579
|
+
* fraction of it, that error is carried into the whole forearm and multiplied
|
|
1580
|
+
* three ways down the arm. It is the most visible thing on the model.
|
|
1581
|
+
*
|
|
1582
|
+
* Projecting the quaternion's vector part onto the axis and renormalising is
|
|
1583
|
+
* what keeps the twist and drops everything else; the same operation MMD and
|
|
1584
|
+
* every faithful runtime performs.
|
|
1585
|
+
*
|
|
1586
|
+
* Applied to the STORED local rotation rather than inside a matrix path,
|
|
1587
|
+
* because the append children read that array directly: constrain it once and
|
|
1588
|
+
* the bone, its inheritors and a hand-posed gizmo all agree.
|
|
1589
|
+
*/
|
|
1590
|
+
applyFixedAxes() {
|
|
1591
|
+
for (const a of this.fixedAxisBones) {
|
|
1592
|
+
const q = this.runtimeSkeleton.localRotations[a.index];
|
|
1593
|
+
const dot = q.x * a.x + q.y * a.y + q.z * a.z;
|
|
1594
|
+
const x = a.x * dot;
|
|
1595
|
+
const y = a.y * dot;
|
|
1596
|
+
const z = a.z * dot;
|
|
1597
|
+
const len = Math.sqrt(x * x + y * y + z * z + q.w * q.w);
|
|
1598
|
+
if (len > 1e-8) {
|
|
1599
|
+
const inv = 1 / len;
|
|
1600
|
+
q.setXYZW(x * inv, y * inv, z * inv, q.w * inv);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1516
1604
|
applyPoseFromClip(clip, frame) {
|
|
1517
1605
|
if (!clip)
|
|
1518
1606
|
return;
|
|
@@ -1532,6 +1620,7 @@ export class Model {
|
|
|
1532
1620
|
this.runtimeSkeleton.localRotations[boneIdx].set(_animSlerp);
|
|
1533
1621
|
this.runtimeSkeleton.localTranslations[boneIdx].set(localTranslation);
|
|
1534
1622
|
}
|
|
1623
|
+
this.applyFixedAxes();
|
|
1535
1624
|
for (const [morphName, keyFrames] of clip.morphTracks.entries()) {
|
|
1536
1625
|
const weight = this.sampleMorphTrack(morphName, keyFrames, frame, this.morphTrackIndices);
|
|
1537
1626
|
if (Number.isNaN(weight))
|
|
@@ -1680,6 +1769,7 @@ export class Model {
|
|
|
1680
1769
|
this.runtimeSkeleton.localRotations[i].set(_blendQ);
|
|
1681
1770
|
this.runtimeSkeleton.localTranslations[i].set(localTranslation);
|
|
1682
1771
|
}
|
|
1772
|
+
this.applyFixedAxes();
|
|
1683
1773
|
const morphCount = morphAcc.length;
|
|
1684
1774
|
for (let i = 0; i < morphCount; i++) {
|
|
1685
1775
|
if (morphGen[i] !== gen)
|
|
@@ -1835,6 +1925,11 @@ export class Model {
|
|
|
1835
1925
|
const evWatch = this.clipEvents.size > 0 ? this.animationState.getCurrentAnimation() : null;
|
|
1836
1926
|
const evPrevFrame = evWatch !== null ? this.animationState.getCurrentFrame() : 0;
|
|
1837
1927
|
this.animationState.update(deltaTime);
|
|
1928
|
+
// Hand the pose sources their bones unmorphed. A bone morph is an offset on
|
|
1929
|
+
// top of the pose, so last frame's offset has to come off before this
|
|
1930
|
+
// frame's pose goes on — taking it off afterwards is what froze every
|
|
1931
|
+
// morph-touched bone at its first posed frame.
|
|
1932
|
+
this.undoBoneMorphs();
|
|
1838
1933
|
if (!this.clipApplySuspended) {
|
|
1839
1934
|
if (this.oneShot !== null) {
|
|
1840
1935
|
this.applyOneShot(deltaTime);
|
|
@@ -1956,7 +2051,9 @@ export class Model {
|
|
|
1956
2051
|
let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
|
|
1957
2052
|
// Handle append transformations (same logic as computeWorldMatrices)
|
|
1958
2053
|
const appendParentIdx = b.appendParentIndex;
|
|
1959
|
-
|
|
2054
|
+
// `|| appendMove`: a move-only append (no rotation) was skipped altogether,
|
|
2055
|
+
// because the guard demanded appendRotate before either branch could run.
|
|
2056
|
+
const hasAppend = (b.appendRotate || b.appendMove) &&
|
|
1960
2057
|
appendParentIdx !== undefined &&
|
|
1961
2058
|
appendParentIdx >= 0 &&
|
|
1962
2059
|
appendParentIdx < bones.length;
|
|
@@ -1982,12 +2079,15 @@ export class Model {
|
|
|
1982
2079
|
scratchQuat[0].setXYZW(ax, ay, az, aw);
|
|
1983
2080
|
scratchQuat[2].setIdentity();
|
|
1984
2081
|
Quat.slerpInto(scratchQuat[2], scratchQuat[0], absRatio, scratchQuat[1]);
|
|
1985
|
-
//
|
|
2082
|
+
// MMD composes the append on the RIGHT: own animated rotation first, then
|
|
2083
|
+
// the inherited one (saba's `r = r * appendRotate`). We had it on the
|
|
2084
|
+
// left, which is a different rotation whenever a bone carries BOTH its
|
|
2085
|
+
// own key and an append — the twist bones, if a motion keys them.
|
|
1986
2086
|
const sx = scratchQuat[1].x, sy = scratchQuat[1].y, sz = scratchQuat[1].z, sw = scratchQuat[1].w;
|
|
1987
|
-
const nx =
|
|
1988
|
-
const ny =
|
|
1989
|
-
const nz =
|
|
1990
|
-
const nw =
|
|
2087
|
+
const nx = fw * sx + fx * sw + fy * sz - fz * sy;
|
|
2088
|
+
const ny = fw * sy - fx * sz + fy * sw + fz * sx;
|
|
2089
|
+
const nz = fw * sz + fx * sy - fy * sx + fz * sw;
|
|
2090
|
+
const nw = fw * sw - fx * sx - fy * sy - fz * sz;
|
|
1991
2091
|
fx = nx;
|
|
1992
2092
|
fy = ny;
|
|
1993
2093
|
fz = nz;
|
|
@@ -2036,7 +2136,7 @@ export class Model {
|
|
|
2036
2136
|
let fx = boneRot.x, fy = boneRot.y, fz = boneRot.z, fw = boneRot.w;
|
|
2037
2137
|
let addLocalTx = 0, addLocalTy = 0, addLocalTz = 0;
|
|
2038
2138
|
const appendParentIdx = b.appendParentIndex;
|
|
2039
|
-
const hasAppend = b.appendRotate && appendParentIdx !== undefined && appendParentIdx >= 0 && appendParentIdx < boneCount;
|
|
2139
|
+
const hasAppend = (b.appendRotate || b.appendMove) && appendParentIdx !== undefined && appendParentIdx >= 0 && appendParentIdx < boneCount;
|
|
2040
2140
|
if (hasAppend) {
|
|
2041
2141
|
const ratio = b.appendRatio === undefined ? 1 : Math.max(-1, Math.min(1, b.appendRatio));
|
|
2042
2142
|
const hasRatio = Math.abs(ratio) > 1e-6;
|
|
@@ -2054,12 +2154,15 @@ export class Model {
|
|
|
2054
2154
|
scratchQuat[0].setXYZW(ax, ay, az, aw);
|
|
2055
2155
|
scratchQuat[2].setIdentity();
|
|
2056
2156
|
Quat.slerpInto(scratchQuat[2], scratchQuat[0], absRatio, scratchQuat[1]);
|
|
2057
|
-
//
|
|
2157
|
+
// MMD composes the append on the RIGHT: own animated rotation first, then
|
|
2158
|
+
// the inherited one (saba's `r = r * appendRotate`). We had it on the
|
|
2159
|
+
// left, which is a different rotation whenever a bone carries BOTH its
|
|
2160
|
+
// own key and an append — the twist bones, if a motion keys them.
|
|
2058
2161
|
const sx = scratchQuat[1].x, sy = scratchQuat[1].y, sz = scratchQuat[1].z, sw = scratchQuat[1].w;
|
|
2059
|
-
const nx =
|
|
2060
|
-
const ny =
|
|
2061
|
-
const nz =
|
|
2062
|
-
const nw =
|
|
2162
|
+
const nx = fw * sx + fx * sw + fy * sz - fz * sy;
|
|
2163
|
+
const ny = fw * sy - fx * sz + fy * sw + fz * sx;
|
|
2164
|
+
const nz = fw * sz + fx * sy - fy * sx + fz * sw;
|
|
2165
|
+
const nw = fw * sw - fx * sx - fy * sy - fz * sz;
|
|
2063
2166
|
fx = nx;
|
|
2064
2167
|
fy = ny;
|
|
2065
2168
|
fz = nz;
|
package/dist/pmx-loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAcN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAe;IAE/B,OAAO;IAQP,OAAO,CAAC,IAAI;WAMC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IAwDnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAqEtB,OAAO,CAAC,UAAU;
|
|
1
|
+
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAcN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAe;IAE/B,OAAO;IAQP,OAAO,CAAC,IAAI;WAMC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IAwDnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAqEtB,OAAO,CAAC,UAAU;IA2KlB,OAAO,CAAC,WAAW;IAgMnB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IA+FxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkJf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,OAAO;IAwBf,OAAO,CAAC,QAAQ;CAIjB"}
|
package/dist/pmx-loader.js
CHANGED
|
@@ -302,7 +302,7 @@ export class PmxLoader {
|
|
|
302
302
|
const y = this.getFloat32();
|
|
303
303
|
const z = this.getFloat32();
|
|
304
304
|
const parentIndex = this.getNonVertexIndex(this.boneIndexSize);
|
|
305
|
-
this.getInt32(); //
|
|
305
|
+
const deformLayer = this.getInt32(); // 変形階層
|
|
306
306
|
const flags = this.getUint16();
|
|
307
307
|
// Tail: bone index or offset vector3
|
|
308
308
|
if ((flags & FLAG_TAIL_IS_BONE) !== 0) {
|
|
@@ -325,11 +325,10 @@ export class PmxLoader {
|
|
|
325
325
|
appendRotate = (flags & FLAG_APPEND_ROTATE) !== 0;
|
|
326
326
|
appendMove = (flags & FLAG_APPEND_MOVE) !== 0;
|
|
327
327
|
}
|
|
328
|
-
// Axis limit
|
|
328
|
+
// Axis limit (軸制限)
|
|
329
|
+
let fixedAxis = undefined;
|
|
329
330
|
if ((flags & FLAG_AXIS_LIMIT) !== 0) {
|
|
330
|
-
this.getFloat32();
|
|
331
|
-
this.getFloat32();
|
|
332
|
-
this.getFloat32();
|
|
331
|
+
fixedAxis = [this.getFloat32(), this.getFloat32(), this.getFloat32()];
|
|
333
332
|
}
|
|
334
333
|
// Local axis (two vectors x and z)
|
|
335
334
|
if ((flags & FLAG_LOCAL_AXIS) !== 0) {
|
|
@@ -392,6 +391,8 @@ export class PmxLoader {
|
|
|
392
391
|
appendRatio,
|
|
393
392
|
appendRotate,
|
|
394
393
|
appendMove,
|
|
394
|
+
fixedAxis,
|
|
395
|
+
deformLayer,
|
|
395
396
|
ikTargetIndex,
|
|
396
397
|
ikIteration,
|
|
397
398
|
ikLimitAngle,
|
|
@@ -411,6 +412,8 @@ export class PmxLoader {
|
|
|
411
412
|
appendRatio: a.appendRatio,
|
|
412
413
|
appendRotate: a.appendRotate,
|
|
413
414
|
appendMove: a.appendMove,
|
|
415
|
+
fixedAxis: a.fixedAxis,
|
|
416
|
+
deformLayer: a.deformLayer,
|
|
414
417
|
ikTargetIndex: a.ikTargetIndex,
|
|
415
418
|
ikIteration: a.ikIteration,
|
|
416
419
|
ikLimitAngle: a.ikLimitAngle,
|
|
@@ -14,7 +14,15 @@
|
|
|
14
14
|
* Compare a particle's own distance against it and the model
|
|
15
15
|
* occludes it; fog needs no comparison at all, its alpha simply IS
|
|
16
16
|
* a function of distance.
|
|
17
|
-
* - `
|
|
17
|
+
* - `rzResolution()` — canvas size in pixels, for aspect correction.
|
|
18
|
+
* - `rzCameraPos()`, `rzWorldPos(ray, depth)` — the lens, and the place a pixel
|
|
19
|
+
* was drawn.
|
|
20
|
+
* - `rzSubjectCount()`, `rzSubjectHip(i)` — the cast, at HIP height (see the
|
|
21
|
+
* function; it is not the floor, and reading it as the floor is a
|
|
22
|
+
* mistake this API's own comment used to invite).
|
|
23
|
+
* - `rzProject(p)` — a world point as uv + view-axis distance; the cheap way to
|
|
24
|
+
* anchor anything, and `z` compares directly against `depth`.
|
|
25
|
+
* - the bg* spellings of all of the above still resolve, permanently.
|
|
18
26
|
* - declared params arrive as `params.<name>` (f32 or vec3f), shared by both.
|
|
19
27
|
*
|
|
20
28
|
* Return display-space sRGB + alpha, 0..1. Both mounts are alpha-composited
|
|
@@ -23,6 +31,43 @@
|
|
|
23
31
|
* 0 lets it through, which is how a starfield is stars over the user's color;
|
|
24
32
|
* a foreground at alpha 1 covers the frame. No mode flag anywhere — the alpha
|
|
25
33
|
* channel already says it. */
|
|
34
|
+
/**
|
|
35
|
+
* The bones an effect asked for, in declaration order — the slots rzAnchor reads.
|
|
36
|
+
*
|
|
37
|
+
* // @anchor 左手首 trail
|
|
38
|
+
* // @anchor 頭
|
|
39
|
+
*
|
|
40
|
+
* A declaration in the source, like the mounts: what a file names is what gets
|
|
41
|
+
* resolved and uploaded, so naming none costs nothing and nobody pays for a
|
|
42
|
+
* rig's other five hundred bones. Anchored to the start of a line so that
|
|
43
|
+
* writing the word @anchor in ordinary prose does not silently add a slot —
|
|
44
|
+
* which would shift every slot after it.
|
|
45
|
+
*
|
|
46
|
+
* `trail` additionally keeps that bone's recent PATH, for rzTrail. Opt-in
|
|
47
|
+
* because a path is two orders of magnitude more data than a point, and most
|
|
48
|
+
* anchors want a point.
|
|
49
|
+
*
|
|
50
|
+
* Names are passed through verbatim: any bone the rig has works, and one it does
|
|
51
|
+
* not have simply reports invalid.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parseEffectAnchors(wgsl: string, max: number): {
|
|
54
|
+
bone: string;
|
|
55
|
+
trail: boolean;
|
|
56
|
+
}[];
|
|
57
|
+
/**
|
|
58
|
+
* The caps the cast buffer is built to, shared by the shader below and by the
|
|
59
|
+
* engine that fills it. Interpolated into the WGSL rather than written twice:
|
|
60
|
+
* the layout arithmetic on both sides has to agree exactly, and two literals
|
|
61
|
+
* that must match are two literals that eventually will not.
|
|
62
|
+
*
|
|
63
|
+
* All three are MINIMUMS. Raising one breaks nothing, because effects read
|
|
64
|
+
* through accessors and loop to the count functions; lowering one does.
|
|
65
|
+
*/
|
|
66
|
+
export declare const EFFECT_SUBJECTS = 4;
|
|
67
|
+
export declare const EFFECT_ANCHORS = 8;
|
|
68
|
+
export declare const EFFECT_TRAIL_SAMPLES = 128;
|
|
69
|
+
/** vec4 slot where the trails begin — after the subjects and the anchors. */
|
|
70
|
+
export declare const EFFECT_TRAIL_BASE: number;
|
|
26
71
|
export type CompositeEffectSource = {
|
|
27
72
|
/** The user's WGSL verbatim: helpers plus whichever entry points it defines. */
|
|
28
73
|
wgsl: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAeA
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAgC+B;AAC/B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAIhG;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,IAAI,CAAA;AAChC,eAAO,MAAM,cAAc,IAAI,CAAA;AAC/B,eAAO,MAAM,oBAAoB,MAAM,CAAA;AACvC,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,QAA6D,CAAA;AAE3F,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AA2hBD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAclF;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
|
|
@@ -12,6 +12,77 @@
|
|
|
12
12
|
//
|
|
13
13
|
// Both composite in display space, so neither affects lighting, bloom, or
|
|
14
14
|
// tonemapping, and both are captured by offline export like any background.
|
|
15
|
+
/** What user effect WGSL may define, documented once. A file declares its own
|
|
16
|
+
* mounts by which of these it defines — defining both is how one file is one
|
|
17
|
+
* weather system (dark sky behind, rain in front):
|
|
18
|
+
*
|
|
19
|
+
* fn background(ray: vec3f, uv: vec2f, time: f32) -> vec4f
|
|
20
|
+
* fn foreground(ray: vec3f, uv: vec2f, time: f32, depth: f32) -> vec4f
|
|
21
|
+
*
|
|
22
|
+
* - `ray` — normalized world-space view direction of this pixel (left-handed,
|
|
23
|
+
* +Z forward; identical to what the 360 skybox samples by).
|
|
24
|
+
* - `uv` — 0..1 across the canvas, origin bottom-left (shadertoy-style).
|
|
25
|
+
* - `time` — seconds since the effect was applied.
|
|
26
|
+
* - `depth` — FOREGROUND ONLY. Camera-space distance in metres of whatever the
|
|
27
|
+
* scene drew at this pixel, the far plane where it drew nothing.
|
|
28
|
+
* Compare a particle's own distance against it and the model
|
|
29
|
+
* occludes it; fog needs no comparison at all, its alpha simply IS
|
|
30
|
+
* a function of distance.
|
|
31
|
+
* - `rzResolution()` — canvas size in pixels, for aspect correction.
|
|
32
|
+
* - `rzCameraPos()`, `rzWorldPos(ray, depth)` — the lens, and the place a pixel
|
|
33
|
+
* was drawn.
|
|
34
|
+
* - `rzSubjectCount()`, `rzSubjectHip(i)` — the cast, at HIP height (see the
|
|
35
|
+
* function; it is not the floor, and reading it as the floor is a
|
|
36
|
+
* mistake this API's own comment used to invite).
|
|
37
|
+
* - `rzProject(p)` — a world point as uv + view-axis distance; the cheap way to
|
|
38
|
+
* anchor anything, and `z` compares directly against `depth`.
|
|
39
|
+
* - the bg* spellings of all of the above still resolve, permanently.
|
|
40
|
+
* - declared params arrive as `params.<name>` (f32 or vec3f), shared by both.
|
|
41
|
+
*
|
|
42
|
+
* Return display-space sRGB + alpha, 0..1. Both mounts are alpha-composited
|
|
43
|
+
* LAYERS, so alpha is what decides how much they replace: a background effect
|
|
44
|
+
* at alpha 1 covers the base (solid color / 360 equirect / transparent) and at
|
|
45
|
+
* 0 lets it through, which is how a starfield is stars over the user's color;
|
|
46
|
+
* a foreground at alpha 1 covers the frame. No mode flag anywhere — the alpha
|
|
47
|
+
* channel already says it. */
|
|
48
|
+
/**
|
|
49
|
+
* The bones an effect asked for, in declaration order — the slots rzAnchor reads.
|
|
50
|
+
*
|
|
51
|
+
* // @anchor 左手首 trail
|
|
52
|
+
* // @anchor 頭
|
|
53
|
+
*
|
|
54
|
+
* A declaration in the source, like the mounts: what a file names is what gets
|
|
55
|
+
* resolved and uploaded, so naming none costs nothing and nobody pays for a
|
|
56
|
+
* rig's other five hundred bones. Anchored to the start of a line so that
|
|
57
|
+
* writing the word @anchor in ordinary prose does not silently add a slot —
|
|
58
|
+
* which would shift every slot after it.
|
|
59
|
+
*
|
|
60
|
+
* `trail` additionally keeps that bone's recent PATH, for rzTrail. Opt-in
|
|
61
|
+
* because a path is two orders of magnitude more data than a point, and most
|
|
62
|
+
* anchors want a point.
|
|
63
|
+
*
|
|
64
|
+
* Names are passed through verbatim: any bone the rig has works, and one it does
|
|
65
|
+
* not have simply reports invalid.
|
|
66
|
+
*/
|
|
67
|
+
export function parseEffectAnchors(wgsl, max) {
|
|
68
|
+
return [...wgsl.matchAll(/^[ \t]*\/\/[ \t]*@anchor[ \t]+(\S+)([ \t]+trail)?[ \t]*$/gm)]
|
|
69
|
+
.map((m) => ({ bone: m[1], trail: m[2] !== undefined }))
|
|
70
|
+
.slice(0, max);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The caps the cast buffer is built to, shared by the shader below and by the
|
|
74
|
+
* engine that fills it. Interpolated into the WGSL rather than written twice:
|
|
75
|
+
* the layout arithmetic on both sides has to agree exactly, and two literals
|
|
76
|
+
* that must match are two literals that eventually will not.
|
|
77
|
+
*
|
|
78
|
+
* All three are MINIMUMS. Raising one breaks nothing, because effects read
|
|
79
|
+
* through accessors and loop to the count functions; lowering one does.
|
|
80
|
+
*/
|
|
81
|
+
export const EFFECT_SUBJECTS = 4;
|
|
82
|
+
export const EFFECT_ANCHORS = 8;
|
|
83
|
+
export const EFFECT_TRAIL_SAMPLES = 128;
|
|
84
|
+
/** vec4 slot where the trails begin — after the subjects and the anchors. */
|
|
85
|
+
export const EFFECT_TRAIL_BASE = EFFECT_SUBJECTS * 3 + EFFECT_ANCHORS * EFFECT_SUBJECTS * 3;
|
|
15
86
|
const COMPOSITE_HEAD = /* wgsl */ `
|
|
16
87
|
// Pipeline-override constant: the engine creates two composite pipelines, one
|
|
17
88
|
// with APPLY_GAMMA=false (gamma=1 fast path) and one with APPLY_GAMMA=true.
|
|
@@ -70,6 +141,13 @@ override APPLY_GAMMA: bool = true;
|
|
|
70
141
|
// Blender's AgX, as the 57³ lookup it ships as rather than a reconstruction of
|
|
71
142
|
// it. Sampled in the log-encoded E-Gamut space the cube expects — see agxTransform.
|
|
72
143
|
@group(0) @binding(10) var agxLut: texture_3d<f32>;
|
|
144
|
+
// The cast, as data. Read through rzSubject/rzAnchor below — the LAYOUT IS NOT
|
|
145
|
+
// STABLE and never will be, because it depends on what each effect declared.
|
|
146
|
+
// Reading it directly is the one thing that would freeze it forever.
|
|
147
|
+
//
|
|
148
|
+
// vec4 slots: [0 .. 11] four subjects, three each (root+valid, hip, bounds);
|
|
149
|
+
// then MAX_ANCHORS × four subjects, three each (pos+valid, vel, fwd).
|
|
150
|
+
@group(0) @binding(11) var<storage, read> _rzCast: array<vec4f>;
|
|
73
151
|
|
|
74
152
|
// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
|
|
75
153
|
const FILMIC_LUT_W: f32 = 256.0;
|
|
@@ -165,29 +243,179 @@ fn viewTransform(c: vec3f) -> vec3f {
|
|
|
165
243
|
return vec3f(filmic(c.r), filmic(c.g), filmic(c.b));
|
|
166
244
|
}
|
|
167
245
|
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
// ── The effect API ────────────────────────────────────────────────────────────
|
|
247
|
+
//
|
|
248
|
+
// Named rz*, for the engine. The prefix earns its place twice: user code is
|
|
249
|
+
// concatenated into THIS module, so an unprefixed rzAnchor() would collide with
|
|
250
|
+
// exactly the helper an author would write, and the old bg* prefix stopped being
|
|
251
|
+
// true in 0.41.0 when effects gained a mount over the finished frame.
|
|
252
|
+
//
|
|
253
|
+
// The bg* names below are permanent aliases, not a deprecation with an end date.
|
|
254
|
+
// A published link is immutable, so a scene pinned to a bg* effect has to keep
|
|
255
|
+
// compiling forever. They are one-line and inlined; no new function gets one.
|
|
256
|
+
|
|
257
|
+
/** Canvas size in pixels — for aspect correction. */
|
|
258
|
+
fn rzResolution() -> vec2f { return viewU[6].zw; }
|
|
170
259
|
|
|
171
260
|
/** The camera's world position. */
|
|
172
|
-
fn
|
|
261
|
+
fn rzCameraPos() -> vec3f { return viewU[10].xyz; }
|
|
173
262
|
|
|
174
263
|
/** How many characters are in the scene, up to four. */
|
|
175
|
-
fn
|
|
264
|
+
fn rzSubjectCount() -> i32 { return i32(viewU[10].w); }
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A world point as the camera sees it: xy the uv it lands on, z its distance
|
|
268
|
+
* along the VIEW AXIS in metres.
|
|
269
|
+
*
|
|
270
|
+
* The exact inverse of the ray this pass builds per pixel, so it is the cheap way
|
|
271
|
+
* to work with anything anchored in the world. Marching a curve or a trail in 3D
|
|
272
|
+
* costs a distance evaluation per sample per pixel; projecting its points once
|
|
273
|
+
* and measuring in 2D costs a subtraction, which is the difference between a
|
|
274
|
+
* ribbon that runs at 4K and one that does not.
|
|
275
|
+
*
|
|
276
|
+
* z is directly comparable to the depth handed to foreground(), so occlusion is
|
|
277
|
+
* a single test: draw where your z is nearer than the scene's. It is returned
|
|
278
|
+
* SIGNED and unclamped — behind the camera is negative, and worth rejecting
|
|
279
|
+
* before you use the uv, which is meaningless there.
|
|
280
|
+
*/
|
|
281
|
+
fn rzProject(p: vec3f) -> vec3f {
|
|
282
|
+
let d = p - viewU[10].xyz;
|
|
283
|
+
let z = dot(d, viewU[5].xyz);
|
|
284
|
+
// Guard only the divide. z itself is returned as it is, so the caller can see
|
|
285
|
+
// the sign; clamping it here would put points behind the lens on the horizon.
|
|
286
|
+
let inv = 1.0 / select(z, 1e-4, z < 1e-4);
|
|
287
|
+
let ndc = vec2f(dot(d, viewU[3].xyz) * inv / viewU[3].w, dot(d, viewU[4].xyz) * inv / viewU[4].w);
|
|
288
|
+
return vec3f(ndc * 0.5 + 0.5, z);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** A character, as much of one as a shader needs. */
|
|
292
|
+
struct RzSubject {
|
|
293
|
+
/** On the FLOOR, under the body — where a ring or a magic circle belongs. */
|
|
294
|
+
root: vec3f,
|
|
295
|
+
/** At the hips, the middle of the body — where an aura belongs. */
|
|
296
|
+
center: vec3f,
|
|
297
|
+
/** Bounding sphere: xyz centre, w radius. Deliberately generous — cull with it. */
|
|
298
|
+
bounds: vec4f,
|
|
299
|
+
/** False past the end of the cast, and every field is then zero. */
|
|
300
|
+
valid: bool,
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** One bone an effect asked for, by name, at the top of its own source. */
|
|
304
|
+
struct RzAnchor {
|
|
305
|
+
pos: vec3f,
|
|
306
|
+
/** World units per second, from the previous frame. Direction for a trail,
|
|
307
|
+
* magnitude for anything that should react to how hard someone is moving. */
|
|
308
|
+
vel: vec3f,
|
|
309
|
+
/** The bone's forward axis — which way a foot points, where a head looks. */
|
|
310
|
+
fwd: vec3f,
|
|
311
|
+
/** False when this rig has no such bone. Check it: the alternative is drawing
|
|
312
|
+
* a hand effect at the world origin on every model that spells it differently. */
|
|
313
|
+
valid: bool,
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const RZ_MAX_ANCHORS: i32 = ${EFFECT_ANCHORS};
|
|
176
317
|
|
|
177
318
|
/**
|
|
178
|
-
*
|
|
319
|
+
* Character i. Loop to rzSubjectCount(), never to a constant — the caps here are
|
|
320
|
+
* MINIMUMS and are free to grow, which is only true while nobody hardcodes them.
|
|
321
|
+
*/
|
|
322
|
+
fn rzSubject(i: i32) -> RzSubject {
|
|
323
|
+
var s: RzSubject;
|
|
324
|
+
s.valid = i >= 0 && i < rzSubjectCount();
|
|
325
|
+
if (!s.valid) { return s; }
|
|
326
|
+
let b = i * 3;
|
|
327
|
+
s.root = _rzCast[b].xyz;
|
|
328
|
+
s.center = _rzCast[b + 1].xyz;
|
|
329
|
+
s.bounds = _rzCast[b + 2];
|
|
330
|
+
return s;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The slot-th bone this effect declared, on character subject.
|
|
335
|
+
*
|
|
336
|
+
* Slots are the order of the declarations at the top of your source:
|
|
337
|
+
*
|
|
338
|
+
* // @anchor 左手首
|
|
339
|
+
* // @anchor 頭
|
|
340
|
+
*
|
|
341
|
+
* gives you slot 0 and slot 1. Any bone name the model has works; valid is
|
|
342
|
+
* false when it does not have it, which is the normal case across rigs that
|
|
343
|
+
* spell things differently.
|
|
344
|
+
*/
|
|
345
|
+
fn rzAnchor(subject: i32, slot: i32) -> RzAnchor {
|
|
346
|
+
var a: RzAnchor;
|
|
347
|
+
a.valid = false;
|
|
348
|
+
if (subject < 0 || subject >= rzSubjectCount() || slot < 0 || slot >= RZ_MAX_ANCHORS) { return a; }
|
|
349
|
+
let b = ${EFFECT_SUBJECTS * 3} + (slot * ${EFFECT_SUBJECTS} + subject) * 3;
|
|
350
|
+
a.valid = _rzCast[b].w > 0.5;
|
|
351
|
+
a.pos = _rzCast[b].xyz;
|
|
352
|
+
a.vel = _rzCast[b + 1].xyz;
|
|
353
|
+
a.fwd = _rzCast[b + 2].xyz;
|
|
354
|
+
return a;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const RZ_TRAIL_SAMPLES: i32 = ${EFFECT_TRAIL_SAMPLES};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* How many path samples this anchor has. Zero unless it was declared with
|
|
361
|
+
* trail, and it climbs from zero as the trail fills after the effect loads.
|
|
362
|
+
*
|
|
363
|
+
* Loop to THIS, never to RZ_TRAIL_SAMPLES: the cap is a minimum and is free to
|
|
364
|
+
* grow, which stays true only while nobody hardcodes it.
|
|
365
|
+
*/
|
|
366
|
+
fn rzTrailCount(subject: i32, slot: i32) -> i32 {
|
|
367
|
+
if (subject < 0 || subject >= rzSubjectCount() || slot < 0 || slot >= RZ_MAX_ANCHORS) { return 0; }
|
|
368
|
+
return i32(_rzCast[${EFFECT_SUBJECTS * 3} + (slot * ${EFFECT_SUBJECTS} + subject) * 3 + 2].w);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Sample i of an anchor's path: xyz where it was, w how many seconds ago.
|
|
373
|
+
*
|
|
374
|
+
* i = 0 is NOW and they run backwards in time, so a ribbon is drawn by walking i
|
|
375
|
+
* upward and fading on .w. Sampled at a fixed rate on the SCENE clock, not the
|
|
376
|
+
* display's — so the path is identical in the editor, in an export, and in a
|
|
377
|
+
* re-export, and its spacing does not change with framerate.
|
|
378
|
+
*
|
|
379
|
+
* This is what a hand trail wants instead of position and velocity. One position
|
|
380
|
+
* and one velocity is a straight segment that jitters, because a velocity is a
|
|
381
|
+
* difference between two frames; a path is what actually happened.
|
|
382
|
+
*/
|
|
383
|
+
fn rzTrail(subject: i32, slot: i32, i: i32) -> vec4f {
|
|
384
|
+
let n = rzTrailCount(subject, slot);
|
|
385
|
+
if (i < 0 || i >= n) { return vec4f(0.0); }
|
|
386
|
+
let base = ${EFFECT_TRAIL_BASE} + (slot * ${EFFECT_SUBJECTS} + subject) * RZ_TRAIL_SAMPLES;
|
|
387
|
+
return _rzCast[base + i];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fn bgResolution() -> vec2f { return rzResolution(); }
|
|
391
|
+
fn bgCameraPos() -> vec3f { return rzCameraPos(); }
|
|
392
|
+
fn bgSubjectCount() -> i32 { return rzSubjectCount(); }
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Where a character IS, in world space — at the hips, not on the floor.
|
|
396
|
+
*
|
|
397
|
+
* An effect that wants to RESPOND to the cast — a glow that follows someone,
|
|
398
|
+
* dust kicked up where they are — needs to know where they are, and the ray and
|
|
399
|
+
* the depth cannot tell it: they describe the pixel, not the scene.
|
|
179
400
|
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
401
|
+
* The value is model.position + センター + 全ての親. センター sits at hip
|
|
402
|
+
* height on every standard MMD rig, so this is a point in the middle of the
|
|
403
|
+
* body. It is NOT the contact point: a ripple drawn here appears at the waist.
|
|
404
|
+
* Ground effects want the .xz of this and their own floor height, which is what
|
|
405
|
+
* the effects that shipped against it already do.
|
|
406
|
+
*
|
|
407
|
+
* The comment here used to claim it was "between the feet on the floor", which
|
|
408
|
+
* is where that habit came from. Left as it is regardless of the name: a
|
|
409
|
+
* published link is immutable, so every shared scene pinning an effect that
|
|
410
|
+
* reads this depends on it meaning exactly what it has always meant.
|
|
185
411
|
*
|
|
186
412
|
* Clamped rather than bounds-checked: an effect looping past the count reads the
|
|
187
413
|
* last subject instead of sampling whatever follows the array, which is a wrong
|
|
188
414
|
* ripple rather than an undefined one.
|
|
189
415
|
*/
|
|
190
|
-
fn
|
|
416
|
+
fn rzSubjectHip(i: i32) -> vec3f { return viewU[11 + clamp(i, 0, 3)].xyz; }
|
|
417
|
+
|
|
418
|
+
fn bgSubjectPos(i: i32) -> vec3f { return rzSubjectHip(i); }
|
|
191
419
|
|
|
192
420
|
/** Where in the WORLD the scene drew this pixel — the depth handed to
|
|
193
421
|
* foreground() turned into a place. Without it an effect can only think in
|
|
@@ -198,11 +426,13 @@ fn bgSubjectPos(i: i32) -> vec3f { return viewU[11 + clamp(i, 0, 3)].xyz; }
|
|
|
198
426
|
* the ray's projection onto camera-forward before being walked out. At the far
|
|
199
427
|
* plane (nothing drawn) this lands a very long way off, which is what a sky
|
|
200
428
|
* should do to anything reading it. */
|
|
201
|
-
fn
|
|
429
|
+
fn rzWorldPos(ray: vec3f, depth: f32) -> vec3f {
|
|
202
430
|
let axis = max(dot(normalize(ray), viewU[5].xyz), 1e-4);
|
|
203
|
-
return
|
|
431
|
+
return rzCameraPos() + normalize(ray) * (depth / axis);
|
|
204
432
|
}
|
|
205
433
|
|
|
434
|
+
fn bgWorldPos(ray: vec3f, depth: f32) -> vec3f { return rzWorldPos(ray, depth); }
|
|
435
|
+
|
|
206
436
|
/** Color grading, applied to the tonemapped SCENE (not the background — see the
|
|
207
437
|
* call site). The core is ASC CDL, the film-industry interchange standard:
|
|
208
438
|
*
|