three-vat 1.0.1 → 2.1.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 +72 -4
- package/dist/carrier-BXaLAPRO.d.ts +515 -0
- package/dist/chunk-3PWAY6MD.js +211 -0
- package/dist/chunk-I4STYOD5.js +53 -0
- package/dist/index.d.ts +113 -14
- package/dist/index.js +622 -68
- package/dist/tsl.d.ts +82 -47
- package/dist/tsl.js +176 -40
- package/dist/webgl.d.ts +31 -26
- package/dist/webgl.js +295 -35
- package/package.json +20 -14
- package/dist/chunk-SXTVASKG.js +0 -40
- package/dist/instance-playback-BrGBIKLe.d.ts +0 -109
package/dist/tsl.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { BufferGeometry, InstancedMesh } from 'three';
|
|
2
1
|
import { Node } from 'three/webgpu';
|
|
3
|
-
import {
|
|
2
|
+
import { e as VATClock, j as VATPlaybackTexture, b as VATCarrier, V as VAT, i as VATInstance, f as VATCrowd } from './carrier-BXaLAPRO.js';
|
|
3
|
+
import 'three';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The real maximum texture dimension this renderer accepts, for
|
|
@@ -31,40 +31,52 @@ interface VATNodeOptions {
|
|
|
31
31
|
*/
|
|
32
32
|
time?: VATTimeUniform;
|
|
33
33
|
/**
|
|
34
|
-
* The
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* `desync`.
|
|
34
|
+
* The crowd's playback texture — build it with `createVATPlaybackTexture`
|
|
35
|
+
* from `three-vat` *before* calling this, or let `createVATMesh` do it — and
|
|
36
|
+
* each instance plays its own clip, at its own phase and rate, read from its
|
|
37
|
+
* own row — which row that is, is the carrier's answer (see `carrier`). Without
|
|
38
|
+
* it, every instance plays `clipIndex`, phase-desynced by `desync`.
|
|
39
39
|
*
|
|
40
|
-
* Which decode the graph compiles is decided here, at build time:
|
|
41
|
-
*
|
|
42
|
-
* fallback cannot be a shader-side branch.
|
|
40
|
+
* Which decode the graph compiles is decided here, at build time: the
|
|
41
|
+
* fallback is a different graph, not a shader-side branch.
|
|
43
42
|
*/
|
|
44
|
-
|
|
43
|
+
playback?: VATPlaybackTexture;
|
|
45
44
|
/**
|
|
46
|
-
* The
|
|
45
|
+
* The crowd's **carrier** — the mesh these nodes will render on, an
|
|
46
|
+
* `InstancedMesh` or a `BatchedMesh`. Named as the WebGL path's
|
|
47
|
+
* `patchVATMaterial` names it, because it is the one concept (CONTEXT.md).
|
|
47
48
|
*
|
|
48
|
-
* Required for a crowd, and for
|
|
49
|
-
* to `positionLocal` *before* it reads `positionNode`, so
|
|
50
|
-
*
|
|
51
|
-
* itself. Without this the
|
|
52
|
-
* unscaled — and every instance deforms
|
|
49
|
+
* Required for a crowd, and for two reasons. First: three applies the
|
|
50
|
+
* carrier's transform to `positionLocal` *before* it reads `positionNode`, so
|
|
51
|
+
* the decode has to pose in the geometry's own space and then re-apply that
|
|
52
|
+
* transform itself. Without this the vertex encoding adds its delta in
|
|
53
|
+
* instance space — unrotated and unscaled — and every instance deforms
|
|
54
|
+
* according to its own matrix; the rig encoding, which skins the rest pose
|
|
55
|
+
* outright, never applies the instance matrix at all and draws the whole
|
|
56
|
+
* crowd at the origin. Second: the carrier decides how this instance's
|
|
57
|
+
* *logical* index is spelled, which is the row of the playback texture the
|
|
58
|
+
* pack is read from — `instanceIndex` on an `InstancedMesh`,
|
|
59
|
+
* `batchIndirectIndex` on a `BatchedMesh`, whose drawn slot is a permutation
|
|
60
|
+
* that changes every frame (ADR-0016).
|
|
61
|
+
*
|
|
62
|
+
* One option rather than one per carrier, because those two answers have to
|
|
63
|
+
* come from the same object: a decode that re-applied one mesh's transform
|
|
64
|
+
* while reading another's index would render a crowd nothing could explain.
|
|
53
65
|
*
|
|
54
66
|
* Omit it for a single, non-instanced mesh, where `positionLocal` is the
|
|
55
67
|
* geometry position and there is nothing to re-apply.
|
|
56
68
|
*/
|
|
57
|
-
|
|
69
|
+
carrier?: VATCarrier;
|
|
58
70
|
/**
|
|
59
71
|
* Which clip to play (index into `vat.clips`). Ignored — along with
|
|
60
|
-
* `desync` — when `
|
|
61
|
-
*
|
|
72
|
+
* `desync` — when a `playback` texture is given, which says all of this per
|
|
73
|
+
* instance. Default `0`.
|
|
62
74
|
*/
|
|
63
75
|
clipIndex?: number;
|
|
64
76
|
/**
|
|
65
|
-
* Max random per-instance time offset in seconds, hashed from
|
|
66
|
-
* `0` (default) plays every instance in lockstep. Ignored when
|
|
67
|
-
*
|
|
77
|
+
* Max random per-instance time offset in seconds, hashed from the instance's
|
|
78
|
+
* logical index. `0` (default) plays every instance in lockstep. Ignored when
|
|
79
|
+
* a `playback` texture is given.
|
|
68
80
|
*/
|
|
69
81
|
desync?: number;
|
|
70
82
|
}
|
|
@@ -72,17 +84,19 @@ interface VATNodeOptions {
|
|
|
72
84
|
interface VATNodes {
|
|
73
85
|
/**
|
|
74
86
|
* Assign to `material.positionNode`. It carries the whole decode — the normal
|
|
75
|
-
* with it.
|
|
87
|
+
* with it, and the tangent under the rig encoding.
|
|
76
88
|
*
|
|
77
|
-
* There is deliberately no `normalNode
|
|
78
|
-
* the *fragment* stage (three reaches it from
|
|
79
|
-
* `builder.context.setupNormal()`) and is expected in
|
|
80
|
-
* VAT's
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
89
|
+
* There is deliberately no `normalNode`, under either encoding. A material's
|
|
90
|
+
* `normalNode` is built in the *fragment* stage (three reaches it from
|
|
91
|
+
* `normalView` through `builder.context.setupNormal()`) and is expected in
|
|
92
|
+
* **view** space, whereas a VAT's normals are per-vertex and in the geometry's
|
|
93
|
+
* own space — read from the normal texture, or skinned from the rig one.
|
|
94
|
+
* Handing an object-space normal to a fragment-stage node skipped both the
|
|
95
|
+
* instance matrix and the normal matrix, and took `vertexIndex` into the
|
|
96
|
+
* fragment stage with it — where `IndexNode` does not give you the vertex
|
|
97
|
+
* index at all, but quietly turns itself into a varying, so every fragment
|
|
98
|
+
* read a linearly *interpolated* index that addresses neither of the vertices
|
|
99
|
+
* it lies between.
|
|
86
100
|
*
|
|
87
101
|
* Writing `normalLocal` inside the vertex-stage decode instead is what the
|
|
88
102
|
* GLSL path does when it sets `objectNormal` in `beginnormal_vertex`: three
|
|
@@ -97,19 +111,42 @@ interface VATNodes {
|
|
|
97
111
|
* Build TSL decode nodes for a baked VAT, for the WebGPU/TSL renderer path.
|
|
98
112
|
* Shadows work automatically because `positionNode` also feeds the depth pass.
|
|
99
113
|
*
|
|
100
|
-
* Pass the `
|
|
101
|
-
*
|
|
114
|
+
* Pass the crowd's `playback` texture and each instance plays the clip, phase
|
|
115
|
+
* and rate written into its row by `createVATPlaybackTexture` — the same
|
|
102
116
|
* instance-playback contract the WebGL path reads (ADR-0009), so a mixed-clip
|
|
103
|
-
* crowd renders identically on either renderer. Without
|
|
104
|
-
*
|
|
117
|
+
* crowd renders identically on either renderer. Without it every instance plays
|
|
118
|
+
* `clipIndex`, desynced by a phase hashed from the instance index.
|
|
105
119
|
*
|
|
106
120
|
* Coverage note: the node graph is tested structurally in CI (no GPU); that the
|
|
107
121
|
* two paths decode *identically* is a pixel-diff release gate.
|
|
108
122
|
*/
|
|
109
123
|
declare function vatNodes(vat: VAT, options?: VATNodeOptions): VATNodes;
|
|
110
124
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
125
|
+
* What a decode hands the vertex stage, discriminated on the encoding it read
|
|
126
|
+
* (ADR-0018) — because the two encodings answer a different question. The
|
|
127
|
+
* vertex encoding reads where this vertex *moved to*: `position` is the delta
|
|
128
|
+
* to add to the rest position, `normal` the baked normal or `null` when the
|
|
129
|
+
* bake skipped it. The rig encoding *skins* the rest pose: `position` is the
|
|
130
|
+
* posed position itself, `normal` always exists because it comes out of the
|
|
131
|
+
* skin matrix, and so does `tangent` when the geometry carries one.
|
|
132
|
+
*
|
|
133
|
+
* @internal The return type of {@link vatDecode}, exported for the same
|
|
134
|
+
* structural tests and for nothing else.
|
|
135
|
+
*/
|
|
136
|
+
type VATDecoded = {
|
|
137
|
+
encoding: 'delta';
|
|
138
|
+
position: Vec3Node;
|
|
139
|
+
normal: Vec3Node | null;
|
|
140
|
+
} | {
|
|
141
|
+
encoding: 'rig';
|
|
142
|
+
position: Vec3Node;
|
|
143
|
+
normal: Vec3Node;
|
|
144
|
+
tangent: Vec3Node | null;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* The decode's arithmetic: what this instance reads at this moment, as nodes —
|
|
148
|
+
* before the vertex-stage writes that place it. See {@link VATDecoded} for what
|
|
149
|
+
* `position` means under each encoding.
|
|
113
150
|
*
|
|
114
151
|
* @internal Split out and exported for the structural tests. A `Fn` body is
|
|
115
152
|
* opaque to graph traversal (its statements are not built until the shader is),
|
|
@@ -117,10 +154,7 @@ declare function vatNodes(vat: VAT, options?: VATNodeOptions): VATNodes;
|
|
|
117
154
|
* so the arithmetic that matters stays reachable as a graph. Not re-exported
|
|
118
155
|
* from `three-vat`; nothing outside this package should build against it.
|
|
119
156
|
*/
|
|
120
|
-
declare function vatDecode(vat: VAT, options?: VATNodeOptions):
|
|
121
|
-
position: Vec3Node;
|
|
122
|
-
normal: Vec3Node;
|
|
123
|
-
};
|
|
157
|
+
declare function vatDecode(vat: VAT, options?: VATNodeOptions): VATDecoded;
|
|
124
158
|
/** Options for {@link createVATMesh}. */
|
|
125
159
|
interface CreateVATMeshOptions {
|
|
126
160
|
/**
|
|
@@ -137,11 +171,12 @@ interface CreateVATMeshOptions {
|
|
|
137
171
|
}
|
|
138
172
|
/**
|
|
139
173
|
* Turn a baked VAT and a list of instances into a crowd ready to render: an
|
|
140
|
-
* `InstancedMesh`
|
|
141
|
-
*
|
|
174
|
+
* `InstancedMesh` rendering the bake's geometry, a playback texture carrying
|
|
175
|
+
* the instance-playback contract, and materials that decode the VAT on the
|
|
176
|
+
* vertex stage.
|
|
142
177
|
*
|
|
143
178
|
* ```ts
|
|
144
|
-
* const { mesh, time } = createVATMesh(vat, instances)
|
|
179
|
+
* const { mesh, time, playback } = createVATMesh(vat, instances)
|
|
145
180
|
* mesh.castShadow = mesh.receiveShadow = true
|
|
146
181
|
* scene.add(mesh)
|
|
147
182
|
* // per frame:
|
|
@@ -161,4 +196,4 @@ interface CreateVATMeshOptions {
|
|
|
161
196
|
*/
|
|
162
197
|
declare function createVATMesh(vat: VAT, instances: VATInstance[], options?: CreateVATMeshOptions): VATCrowd;
|
|
163
198
|
|
|
164
|
-
export { type CreateVATMeshOptions, type VATNodeOptions, type VATNodes, type VATTimeUniform, createVATMesh, getMaxTextureSize, vatDecode, vatNodes };
|
|
199
|
+
export { type CreateVATMeshOptions, type VATDecoded, type VATNodeOptions, type VATNodes, type VATTimeUniform, createVATMesh, getMaxTextureSize, vatDecode, vatNodes };
|
package/dist/tsl.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isBatchedCarrier, assertVATCarrier, assertBakedNormal } from './chunk-I4STYOD5.js';
|
|
2
|
+
import { INFINITE_REPETITIONS, LoopMode, EndMode, createVATPlaybackTexture, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT } from './chunk-3PWAY6MD.js';
|
|
2
3
|
import { InstancedMesh } from 'three';
|
|
3
|
-
import { uniform, Fn, positionLocal, positionGeometry,
|
|
4
|
+
import { uniform, Fn, positionLocal, normalLocal, tangentLocal, positionGeometry, batch, instancedMesh, float, bool, int, vertexIndex, attribute, mat3, tangentGeometry, normalGeometry, vec4, batchIndirectIndex, instanceIndex, hash, mix, textureLoad, ivec2, dot, mat4 } from 'three/tsl';
|
|
4
5
|
|
|
5
6
|
function getMaxTextureSize(renderer) {
|
|
6
7
|
const backend = renderer.backend;
|
|
@@ -10,17 +11,30 @@ function getMaxTextureSize(renderer) {
|
|
|
10
11
|
if (gl) return gl.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
11
12
|
throw new Error("three-vat: renderer has no initialized backend \u2014 call `await renderer.init()` first");
|
|
12
13
|
}
|
|
13
|
-
var
|
|
14
|
-
function
|
|
15
|
-
const
|
|
14
|
+
var packTexel = (texture, field, instance) => textureLoad(texture, ivec2(int(field), instance));
|
|
15
|
+
function texturePlayback(texture, instance) {
|
|
16
|
+
const clip = packTexel(texture, PACK_TEXELS.clip, instance);
|
|
17
|
+
const playback = packTexel(texture, PACK_TEXELS.playback, instance);
|
|
18
|
+
const fade = packTexel(texture, PACK_TEXELS.fade, instance);
|
|
19
|
+
const frames = clip.y;
|
|
16
20
|
return {
|
|
17
|
-
startFrame: int(
|
|
21
|
+
startFrame: int(clip.x),
|
|
18
22
|
frames,
|
|
19
|
-
duration: frames.div(
|
|
20
|
-
|
|
21
|
-
speed:
|
|
23
|
+
duration: frames.div(clip.z),
|
|
24
|
+
startTime: playback.x,
|
|
25
|
+
speed: clip.w,
|
|
26
|
+
loopMode: playback.y,
|
|
27
|
+
repetitions: playback.z,
|
|
28
|
+
endMode: playback.w,
|
|
29
|
+
fade: {
|
|
30
|
+
startFrame: fade.x,
|
|
31
|
+
frames: fade.y,
|
|
32
|
+
phase: fade.z,
|
|
33
|
+
duration: fade.w
|
|
34
|
+
}
|
|
22
35
|
};
|
|
23
36
|
}
|
|
37
|
+
var instanceIdOf = (carrier) => isBatchedCarrier(carrier) ? int(batchIndirectIndex) : int(instanceIndex);
|
|
24
38
|
function clipAt(vat, clipIndex) {
|
|
25
39
|
const clip = vat.clips[clipIndex];
|
|
26
40
|
if (!clip) {
|
|
@@ -28,60 +42,182 @@ function clipAt(vat, clipIndex) {
|
|
|
28
42
|
}
|
|
29
43
|
return clip;
|
|
30
44
|
}
|
|
31
|
-
function hashedPlayback(clip, desync) {
|
|
45
|
+
function hashedPlayback(clip, desync, instance) {
|
|
32
46
|
return {
|
|
33
47
|
startFrame: int(clip.startFrame),
|
|
34
48
|
frames: float(clip.frames),
|
|
35
49
|
duration: float(clip.frames / clip.fps),
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
// Negated, because desync is now a start time in the *past*: an instance
|
|
51
|
+
// that began `desync` seconds ago is that far into its clip already.
|
|
52
|
+
startTime: hash(instance).mul(-desync),
|
|
53
|
+
// Everything but the phase comes from the clip's own baked defaults, so a
|
|
54
|
+
// clip baked "once, clamped, at 2x" plays that way here too. A second set
|
|
55
|
+
// of defaults living in this path would be a crowd that animates
|
|
56
|
+
// differently depending on whether anyone wrote the attributes.
|
|
57
|
+
speed: float(clip.speed),
|
|
58
|
+
loopMode: float(clip.loopMode),
|
|
59
|
+
repetitions: float(clip.repetitions),
|
|
60
|
+
endMode: float(clip.endMode),
|
|
61
|
+
// Nothing to fade out of: this path is the zero-config default, where an
|
|
62
|
+
// instance has never been written and so has no animation it left behind.
|
|
63
|
+
fade: { startFrame: float(0), frames: float(0), phase: float(0), duration: float(0) }
|
|
38
64
|
};
|
|
39
65
|
}
|
|
40
|
-
function checkPlaybackAttributes(geometry) {
|
|
41
|
-
const names = Object.values(PLAYBACK_ATTRIBUTES);
|
|
42
|
-
const missing = names.filter((name) => geometry.getAttribute(name) === void 0);
|
|
43
|
-
if (missing.length === 0) return true;
|
|
44
|
-
if (missing.length === names.length) return false;
|
|
45
|
-
throw new Error(
|
|
46
|
-
`three-vat: geometry carries only part of the instance-playback contract (missing ${missing.join(", ")}) \u2014 write all of it with \`addVATInstanceAttributes\` from \`three-vat\`, or pass no geometry for the hashed default`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
66
|
function vatNodes(vat, options = {}) {
|
|
50
|
-
const { time = uniform(0),
|
|
51
|
-
const
|
|
67
|
+
const { time = uniform(0), carrier } = options;
|
|
68
|
+
const decoded = vatDecode(vat, options);
|
|
52
69
|
const decode = Fn(() => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
if (decoded.encoding === "rig") {
|
|
71
|
+
positionLocal.assign(decoded.position);
|
|
72
|
+
normalLocal.assign(decoded.normal);
|
|
73
|
+
if (decoded.tangent) tangentLocal.assign(decoded.tangent);
|
|
74
|
+
} else {
|
|
75
|
+
positionLocal.assign((carrier ? positionGeometry : positionLocal).add(decoded.position));
|
|
76
|
+
if (decoded.normal) normalLocal.assign(decoded.normal);
|
|
77
|
+
}
|
|
78
|
+
if (carrier) {
|
|
79
|
+
if (isBatchedCarrier(carrier)) batch(carrier);
|
|
80
|
+
else instancedMesh(carrier);
|
|
81
|
+
}
|
|
56
82
|
return positionLocal;
|
|
57
83
|
}, "vec3");
|
|
58
84
|
return { positionNode: decode(), time };
|
|
59
85
|
}
|
|
60
86
|
function vatDecode(vat, options = {}) {
|
|
61
|
-
const { time = uniform(0),
|
|
62
|
-
|
|
87
|
+
const { time = uniform(0), playback: playbackTexture, carrier, clipIndex = 0, desync = 0 } = options;
|
|
88
|
+
if (carrier) assertVATCarrier(carrier, vat);
|
|
89
|
+
const instance = instanceIdOf(carrier);
|
|
90
|
+
const playback = playbackTexture ? texturePlayback(playbackTexture.texture, instance) : hashedPlayback(clipAt(vat, clipIndex), desync, instance);
|
|
91
|
+
const frames = playback.frames;
|
|
92
|
+
const last = frames.sub(1);
|
|
93
|
+
const elapsed = time.sub(playback.startTime);
|
|
94
|
+
const local = elapsed.mul(playback.speed);
|
|
95
|
+
const loops = local.div(playback.duration);
|
|
96
|
+
const started = local.greaterThanEqual(0);
|
|
97
|
+
const finished = started.and(playback.repetitions.notEqual(INFINITE_REPETITIONS)).and(loops.greaterThanEqual(playback.repetitions));
|
|
98
|
+
const isPingPong = playback.loopMode.equal(LoopMode.PingPong);
|
|
99
|
+
const bounce = loops.mod(2);
|
|
100
|
+
const pingPongPhase = bounce.lessThan(1).select(bounce, float(2).sub(bounce));
|
|
101
|
+
const endPhase = playback.endMode.equal(EndMode.Clamp).select(float(1), float(0));
|
|
102
|
+
const phase = started.select(
|
|
103
|
+
finished.select(endPhase, isPingPong.select(pingPongPhase, loops.fract())),
|
|
104
|
+
float(0)
|
|
105
|
+
);
|
|
106
|
+
const wraps = started.select(
|
|
107
|
+
finished.select(bool(false), isPingPong.select(bool(false), bool(true))),
|
|
108
|
+
bool(false)
|
|
109
|
+
);
|
|
110
|
+
const f = phase.mul(wraps.select(frames, last));
|
|
111
|
+
const f0 = f.floor().min(last);
|
|
112
|
+
const f1 = wraps.select(f0.add(1).mod(frames), f0.add(1).min(last));
|
|
113
|
+
const blend = f.sub(f0);
|
|
114
|
+
const bandRow = (offset) => int(offset).add(playback.startFrame);
|
|
115
|
+
const row0 = bandRow(f0);
|
|
116
|
+
const row1 = bandRow(f1);
|
|
117
|
+
const fade = playback.fade;
|
|
118
|
+
const fadeWeight = fade.duration.greaterThan(0).select(
|
|
119
|
+
float(1).sub(elapsed.div(fade.duration).clamp(0, 1)),
|
|
120
|
+
float(0)
|
|
121
|
+
);
|
|
122
|
+
const fadeRow = int(fade.phase.mul(fade.frames).floor().min(fade.frames.sub(1)).max(0)).add(
|
|
123
|
+
int(fade.startFrame)
|
|
124
|
+
);
|
|
125
|
+
const rows = { row0, row1, blend, fadeRow, fadeWeight };
|
|
126
|
+
switch (vat.encoding) {
|
|
127
|
+
case "delta":
|
|
128
|
+
return vertexDecode(vat, rows);
|
|
129
|
+
case "rig":
|
|
130
|
+
return rigDecode(vat, rows);
|
|
131
|
+
default: {
|
|
132
|
+
const unhandled = vat;
|
|
133
|
+
throw new Error(
|
|
134
|
+
`three-vat: vatDecode has no decode for encoding "${String(unhandled.encoding)}"`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function vertexDecode({ positionTexture, normalTexture }, rows) {
|
|
63
140
|
const vertexRow = int(vertexIndex);
|
|
64
141
|
const sample = (tex) => {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
142
|
+
const s0 = textureLoad(tex, ivec2(vertexRow, rows.row0)).xyz;
|
|
143
|
+
const s1 = textureLoad(tex, ivec2(vertexRow, rows.row1)).xyz;
|
|
144
|
+
const frozen = textureLoad(tex, ivec2(vertexRow, rows.fadeRow)).xyz;
|
|
145
|
+
return mix(mix(s0, s1, rows.blend), frozen, rows.fadeWeight);
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
encoding: "delta",
|
|
149
|
+
position: sample(positionTexture),
|
|
150
|
+
normal: normalTexture ? sample(normalTexture).normalize() : null
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function compose(q, ts) {
|
|
154
|
+
const x = q.x;
|
|
155
|
+
const y = q.y;
|
|
156
|
+
const z = q.z;
|
|
157
|
+
const w = q.w;
|
|
158
|
+
const x2 = x.add(x);
|
|
159
|
+
const y2 = y.add(y);
|
|
160
|
+
const z2 = z.add(z);
|
|
161
|
+
const xx = x.mul(x2);
|
|
162
|
+
const xy = x.mul(y2);
|
|
163
|
+
const xz = x.mul(z2);
|
|
164
|
+
const yy = y.mul(y2);
|
|
165
|
+
const yz = y.mul(z2);
|
|
166
|
+
const zz = z.mul(z2);
|
|
167
|
+
const wx = w.mul(x2);
|
|
168
|
+
const wy = w.mul(y2);
|
|
169
|
+
const wz = w.mul(z2);
|
|
170
|
+
const s = ts.w;
|
|
171
|
+
const one = float(1);
|
|
172
|
+
return mat4(
|
|
173
|
+
vec4(one.sub(yy.add(zz)).mul(s), xy.add(wz).mul(s), xz.sub(wy).mul(s), 0),
|
|
174
|
+
vec4(xy.sub(wz).mul(s), one.sub(xx.add(zz)).mul(s), yz.add(wx).mul(s), 0),
|
|
175
|
+
vec4(xz.add(wy).mul(s), yz.sub(wx).mul(s), one.sub(xx.add(yy)).mul(s), 0),
|
|
176
|
+
vec4(ts.xyz, 1)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
var hemisphereOf = (reference, q) => dot(reference, q).lessThan(0).select(q.negate(), q);
|
|
180
|
+
function rigDecode({ rigTexture, geometry }, rows) {
|
|
181
|
+
const skinIndex = attribute("skinIndex", "uvec4");
|
|
182
|
+
const skinWeight = attribute("skinWeight", "vec4");
|
|
183
|
+
const fetch = (column, row) => textureLoad(rigTexture, ivec2(column, row));
|
|
184
|
+
const slot = (index, weight) => {
|
|
185
|
+
const column = (texel) => int(index).mul(RIG_TEXELS_PER_SLOT).add(texel);
|
|
186
|
+
const rotation = column(RIG_TEXELS.rotation);
|
|
187
|
+
const placement = column(RIG_TEXELS.placement);
|
|
188
|
+
const q0 = fetch(rotation, rows.row0);
|
|
189
|
+
const ts0 = fetch(placement, rows.row0);
|
|
190
|
+
const q1 = hemisphereOf(q0, fetch(rotation, rows.row1));
|
|
191
|
+
const ts1 = fetch(placement, rows.row1);
|
|
192
|
+
const q = mix(q0, q1, rows.blend).normalize();
|
|
193
|
+
const ts = mix(ts0, ts1, rows.blend);
|
|
194
|
+
const qf = hemisphereOf(q, fetch(rotation, rows.fadeRow));
|
|
195
|
+
const tsf = fetch(placement, rows.fadeRow);
|
|
196
|
+
const qFaded = mix(q, qf, rows.fadeWeight).normalize();
|
|
197
|
+
const tsFaded = mix(ts, tsf, rows.fadeWeight);
|
|
198
|
+
return compose(qFaded, tsFaded).mul(weight);
|
|
71
199
|
};
|
|
200
|
+
const skin = slot(skinIndex.x, skinWeight.x).add(slot(skinIndex.y, skinWeight.y)).add(slot(skinIndex.z, skinWeight.z)).add(slot(skinIndex.w, skinWeight.w));
|
|
201
|
+
const skin3 = mat3(skin);
|
|
72
202
|
return {
|
|
73
|
-
|
|
74
|
-
|
|
203
|
+
encoding: "rig",
|
|
204
|
+
position: skin.mul(vec4(positionGeometry, 1)).xyz,
|
|
205
|
+
normal: skin3.mul(normalGeometry).normalize(),
|
|
206
|
+
// Gated on the geometry rather than read and ignored: `tangentGeometry`
|
|
207
|
+
// is an attribute read, and three computes tangents onto a geometry that
|
|
208
|
+
// has none the moment a graph asks for them.
|
|
209
|
+
tangent: geometry.hasAttribute("tangent") ? skin3.mul(tangentGeometry.xyz).normalize() : null
|
|
75
210
|
};
|
|
76
211
|
}
|
|
77
212
|
function createVATMesh(vat, instances, options = {}) {
|
|
78
213
|
const time = options.time ?? uniform(0);
|
|
79
|
-
const
|
|
214
|
+
const playback = createVATPlaybackTexture(instances);
|
|
215
|
+
for (const source of vat.materials) assertBakedNormal(vat, source);
|
|
80
216
|
const materials = vat.materials.map((source) => source.clone());
|
|
81
|
-
const mesh = new InstancedMesh(geometry, materials, instances.length);
|
|
82
|
-
const { positionNode } = vatNodes(vat, { time,
|
|
217
|
+
const mesh = new InstancedMesh(vat.geometry, materials, instances.length);
|
|
218
|
+
const { positionNode } = vatNodes(vat, { time, playback, carrier: mesh });
|
|
83
219
|
for (const material of materials) material.positionNode = positionNode;
|
|
84
|
-
return { mesh, time };
|
|
220
|
+
return { mesh, time, playback };
|
|
85
221
|
}
|
|
86
222
|
|
|
87
223
|
export { createVATMesh, getMaxTextureSize, vatDecode, vatNodes };
|
package/dist/webgl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IUniform, MeshDepthMaterial, WebGLRenderer, Material } from 'three';
|
|
2
|
-
import {
|
|
2
|
+
import { V as VAT, j as VATPlaybackTexture, b as VATCarrier, i as VATInstance, f as VATCrowd } from './carrier-BXaLAPRO.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The real maximum texture dimension this GPU accepts, for
|
|
@@ -16,34 +16,36 @@ interface VATUniforms {
|
|
|
16
16
|
}
|
|
17
17
|
/** Create the shared time uniform. Update `uVatTime.value` once per frame. */
|
|
18
18
|
declare function createVATUniforms(time?: number): VATUniforms;
|
|
19
|
-
/**
|
|
20
|
-
* The instance-playback contract now lives in the core entry point, so both
|
|
21
|
-
* decode paths can read it (ADR-0009).
|
|
22
|
-
*
|
|
23
|
-
* @deprecated Renamed to `addVATInstanceAttributes` and moved to `three-vat`.
|
|
24
|
-
* Removed from `three-vat/webgl` in the next minor version — import it from
|
|
25
|
-
* `three-vat` instead.
|
|
26
|
-
*/
|
|
27
|
-
declare const addInstancedVATAttributes: typeof addVATInstanceAttributes;
|
|
28
|
-
/**
|
|
29
|
-
* @deprecated Moved to `three-vat`. Removed from `three-vat/webgl` in the next
|
|
30
|
-
* minor version — import `VATInstance` from `three-vat` instead.
|
|
31
|
-
*/
|
|
32
|
-
type VATInstance = VATInstance$1;
|
|
33
19
|
/**
|
|
34
20
|
* Patch any built-in material so its vertex stage samples the VAT instead of
|
|
35
21
|
* skinning. Works on the render material and on `MeshDepthMaterial` (needed for
|
|
36
22
|
* instanced shadows — see {@link createVATDepthMaterial}). Mutates and returns
|
|
37
23
|
* the material.
|
|
24
|
+
*
|
|
25
|
+
* `playback` is the crowd's playback texture — the decode reads the pack out of
|
|
26
|
+
* it by this instance's logical index, so a material patched for one crowd
|
|
27
|
+
* renders that crowd's playback and no other's.
|
|
28
|
+
*
|
|
29
|
+
* `carrier` is the mesh this material will draw on, and it is needed for one
|
|
30
|
+
* reason: how the shader names that logical index. Omit it for an
|
|
31
|
+
* `InstancedMesh`, where the index is `gl_InstanceID`. Pass a `BatchedMesh` and
|
|
32
|
+
* the decode resolves the index through `getIndirectIndex( gl_DrawID )`
|
|
33
|
+
* instead, because that carrier culls and sorts per instance and its drawn slot
|
|
34
|
+
* is a permutation that changes every frame (ADR-0016). A batch a VAT cannot be
|
|
35
|
+
* decoded on is refused here rather than rendered wrong.
|
|
38
36
|
*/
|
|
39
|
-
declare function patchVATMaterial<T extends Material>(material: T, vat: VAT, uniforms: VATUniforms): T;
|
|
37
|
+
declare function patchVATMaterial<T extends Material>(material: T, vat: VAT, uniforms: VATUniforms, playback: VATPlaybackTexture, carrier?: VATCarrier): T;
|
|
40
38
|
/**
|
|
41
|
-
* Build the `customDepthMaterial`
|
|
39
|
+
* Build the `customDepthMaterial` a VAT crowd needs so it casts
|
|
42
40
|
* correctly-deformed shadows instead of bind-pose shadows. Assign the result to
|
|
43
41
|
* `mesh.customDepthMaterial` (and, for point lights, mirror with a patched
|
|
44
42
|
* `MeshDistanceMaterial`).
|
|
43
|
+
*
|
|
44
|
+
* `carrier` means what it means in {@link patchVATMaterial}: omit it for an
|
|
45
|
+
* `InstancedMesh`, pass the `BatchedMesh` for a batched crowd, so the shadow
|
|
46
|
+
* pass resolves the same instance index the render pass does.
|
|
45
47
|
*/
|
|
46
|
-
declare function createVATDepthMaterial(vat: VAT, uniforms: VATUniforms): MeshDepthMaterial;
|
|
48
|
+
declare function createVATDepthMaterial(vat: VAT, uniforms: VATUniforms, playback: VATPlaybackTexture, carrier?: VATCarrier): MeshDepthMaterial;
|
|
47
49
|
/** Options for {@link createVATMesh}. */
|
|
48
50
|
interface CreateVATMeshOptions {
|
|
49
51
|
/**
|
|
@@ -56,12 +58,12 @@ interface CreateVATMeshOptions {
|
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* Turn a baked VAT and a list of instances into a crowd ready to render: an
|
|
59
|
-
* `InstancedMesh`
|
|
60
|
-
* materials decode the VAT, and
|
|
61
|
-
* in the bind pose.
|
|
61
|
+
* `InstancedMesh` rendering the bake's geometry, a playback texture carrying
|
|
62
|
+
* the instance-playback contract, materials that decode the VAT, and shadows
|
|
63
|
+
* that are deformed rather than frozen in the bind pose.
|
|
62
64
|
*
|
|
63
65
|
* ```ts
|
|
64
|
-
* const { mesh, time } = createVATMesh(vat, instances)
|
|
66
|
+
* const { mesh, time, playback } = createVATMesh(vat, instances)
|
|
65
67
|
* mesh.castShadow = mesh.receiveShadow = true
|
|
66
68
|
* scene.add(mesh)
|
|
67
69
|
* // per frame:
|
|
@@ -78,11 +80,14 @@ interface CreateVATMeshOptions {
|
|
|
78
80
|
* - **`castShadow` / `receiveShadow`**, which are scene decisions. The depth and
|
|
79
81
|
* distance materials the shadow passes need are already attached either way.
|
|
80
82
|
*
|
|
81
|
-
* Everything here is the exported primitives —
|
|
83
|
+
* Everything here is the exported primitives — `createVATPlaybackTexture`,
|
|
82
84
|
* {@link patchVATMaterial}, {@link createVATDepthMaterial} — composed in the one
|
|
83
85
|
* order that is correct. Reach for them directly only when rendering onto
|
|
84
|
-
* something other than a plain `InstancedMesh
|
|
86
|
+
* something other than a plain `InstancedMesh` — a `BatchedMesh`, for three's
|
|
87
|
+
* own per-instance culling and sorting, being the other carrier this library
|
|
88
|
+
* supports (docs/usage.md); the returned `playback` is what
|
|
89
|
+
* {@link setVATInstance} writes into either way.
|
|
85
90
|
*/
|
|
86
|
-
declare function createVATMesh(vat: VAT, instances: VATInstance
|
|
91
|
+
declare function createVATMesh(vat: VAT, instances: VATInstance[], options?: CreateVATMeshOptions): VATCrowd;
|
|
87
92
|
|
|
88
|
-
export { type CreateVATMeshOptions, type
|
|
93
|
+
export { type CreateVATMeshOptions, type VATUniforms, createVATDepthMaterial, createVATMesh, createVATUniforms, getMaxTextureSize, patchVATMaterial };
|