three-vat 2.0.0 → 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 +5 -0
- package/dist/{carrier-BFCPmcQK.d.ts → carrier-BXaLAPRO.d.ts} +61 -18
- package/dist/{chunk-2PGZ44TP.js → chunk-3PWAY6MD.js} +10 -1
- package/dist/{chunk-W2ZAFMPB.js → chunk-I4STYOD5.js} +1 -1
- package/dist/index.d.ts +39 -4
- package/dist/index.js +499 -35
- package/dist/tsl.d.ts +50 -27
- package/dist/tsl.js +95 -15
- package/dist/webgl.d.ts +1 -1
- package/dist/webgl.js +179 -21
- package/package.json +1 -1
package/dist/tsl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Node } from 'three/webgpu';
|
|
2
|
-
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
3
|
import 'three';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -48,14 +48,16 @@ interface VATNodeOptions {
|
|
|
48
48
|
*
|
|
49
49
|
* Required for a crowd, and for two reasons. First: three applies the
|
|
50
50
|
* carrier's transform to `positionLocal` *before* it reads `positionNode`, so
|
|
51
|
-
* the decode has to
|
|
52
|
-
*
|
|
53
|
-
* unrotated and unscaled — and every instance deforms
|
|
54
|
-
* matrix
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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).
|
|
59
61
|
*
|
|
60
62
|
* One option rather than one per carrier, because those two answers have to
|
|
61
63
|
* come from the same object: a decode that re-applied one mesh's transform
|
|
@@ -82,17 +84,19 @@ interface VATNodeOptions {
|
|
|
82
84
|
interface VATNodes {
|
|
83
85
|
/**
|
|
84
86
|
* Assign to `material.positionNode`. It carries the whole decode — the normal
|
|
85
|
-
* with it.
|
|
87
|
+
* with it, and the tangent under the rig encoding.
|
|
86
88
|
*
|
|
87
|
-
* There is deliberately no `normalNode
|
|
88
|
-
* the *fragment* stage (three reaches it from
|
|
89
|
-
* `builder.context.setupNormal()`) and is expected in
|
|
90
|
-
* VAT's
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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.
|
|
96
100
|
*
|
|
97
101
|
* Writing `normalLocal` inside the vertex-stage decode instead is what the
|
|
98
102
|
* GLSL path does when it sets `objectNormal` in `beginnormal_vertex`: three
|
|
@@ -118,9 +122,31 @@ interface VATNodes {
|
|
|
118
122
|
*/
|
|
119
123
|
declare function vatNodes(vat: VAT, options?: VATNodeOptions): VATNodes;
|
|
120
124
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
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.
|
|
124
150
|
*
|
|
125
151
|
* @internal Split out and exported for the structural tests. A `Fn` body is
|
|
126
152
|
* opaque to graph traversal (its statements are not built until the shader is),
|
|
@@ -128,10 +154,7 @@ declare function vatNodes(vat: VAT, options?: VATNodeOptions): VATNodes;
|
|
|
128
154
|
* so the arithmetic that matters stays reachable as a graph. Not re-exported
|
|
129
155
|
* from `three-vat`; nothing outside this package should build against it.
|
|
130
156
|
*/
|
|
131
|
-
declare function vatDecode(vat: VAT, options?: VATNodeOptions):
|
|
132
|
-
position: Vec3Node;
|
|
133
|
-
normal: Vec3Node | null;
|
|
134
|
-
};
|
|
157
|
+
declare function vatDecode(vat: VAT, options?: VATNodeOptions): VATDecoded;
|
|
135
158
|
/** Options for {@link createVATMesh}. */
|
|
136
159
|
interface CreateVATMeshOptions {
|
|
137
160
|
/**
|
|
@@ -173,4 +196,4 @@ interface CreateVATMeshOptions {
|
|
|
173
196
|
*/
|
|
174
197
|
declare function createVATMesh(vat: VAT, instances: VATInstance[], options?: CreateVATMeshOptions): VATCrowd;
|
|
175
198
|
|
|
176
|
-
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,7 +1,7 @@
|
|
|
1
|
-
import { isBatchedCarrier, assertVATCarrier, assertBakedNormal } from './chunk-
|
|
2
|
-
import { INFINITE_REPETITIONS, LoopMode, EndMode, createVATPlaybackTexture, PACK_TEXELS } from './chunk-
|
|
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';
|
|
3
3
|
import { InstancedMesh } from 'three';
|
|
4
|
-
import { uniform, Fn, positionLocal,
|
|
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';
|
|
5
5
|
|
|
6
6
|
function getMaxTextureSize(renderer) {
|
|
7
7
|
const backend = renderer.backend;
|
|
@@ -65,10 +65,16 @@ function hashedPlayback(clip, desync, instance) {
|
|
|
65
65
|
}
|
|
66
66
|
function vatNodes(vat, options = {}) {
|
|
67
67
|
const { time = uniform(0), carrier } = options;
|
|
68
|
-
const
|
|
68
|
+
const decoded = vatDecode(vat, options);
|
|
69
69
|
const decode = Fn(() => {
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
78
|
if (carrier) {
|
|
73
79
|
if (isBatchedCarrier(carrier)) batch(carrier);
|
|
74
80
|
else instancedMesh(carrier);
|
|
@@ -82,7 +88,6 @@ function vatDecode(vat, options = {}) {
|
|
|
82
88
|
if (carrier) assertVATCarrier(carrier, vat);
|
|
83
89
|
const instance = instanceIdOf(carrier);
|
|
84
90
|
const playback = playbackTexture ? texturePlayback(playbackTexture.texture, instance) : hashedPlayback(clipAt(vat, clipIndex), desync, instance);
|
|
85
|
-
const vertexRow = int(vertexIndex);
|
|
86
91
|
const frames = playback.frames;
|
|
87
92
|
const last = frames.sub(1);
|
|
88
93
|
const elapsed = time.sub(playback.startTime);
|
|
@@ -105,7 +110,7 @@ function vatDecode(vat, options = {}) {
|
|
|
105
110
|
const f = phase.mul(wraps.select(frames, last));
|
|
106
111
|
const f0 = f.floor().min(last);
|
|
107
112
|
const f1 = wraps.select(f0.add(1).mod(frames), f0.add(1).min(last));
|
|
108
|
-
const
|
|
113
|
+
const blend = f.sub(f0);
|
|
109
114
|
const bandRow = (offset) => int(offset).add(playback.startFrame);
|
|
110
115
|
const row0 = bandRow(f0);
|
|
111
116
|
const row1 = bandRow(f1);
|
|
@@ -114,21 +119,96 @@ function vatDecode(vat, options = {}) {
|
|
|
114
119
|
float(1).sub(elapsed.div(fade.duration).clamp(0, 1)),
|
|
115
120
|
float(0)
|
|
116
121
|
);
|
|
117
|
-
const
|
|
122
|
+
const fadeRow = int(fade.phase.mul(fade.frames).floor().min(fade.frames.sub(1)).max(0)).add(
|
|
118
123
|
int(fade.startFrame)
|
|
119
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) {
|
|
140
|
+
const vertexRow = int(vertexIndex);
|
|
120
141
|
const sample = (tex) => {
|
|
121
|
-
const s0 = textureLoad(tex, ivec2(vertexRow, row0)).xyz;
|
|
122
|
-
const s1 = textureLoad(tex, ivec2(vertexRow, row1)).xyz;
|
|
123
|
-
const frozen = textureLoad(tex, ivec2(vertexRow,
|
|
124
|
-
return mix(mix(s0, s1,
|
|
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);
|
|
125
146
|
};
|
|
126
|
-
const normalTexture = vat.normalTexture;
|
|
127
147
|
return {
|
|
128
|
-
|
|
148
|
+
encoding: "delta",
|
|
149
|
+
position: sample(positionTexture),
|
|
129
150
|
normal: normalTexture ? sample(normalTexture).normalize() : null
|
|
130
151
|
};
|
|
131
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);
|
|
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);
|
|
202
|
+
return {
|
|
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
|
|
210
|
+
};
|
|
211
|
+
}
|
|
132
212
|
function createVATMesh(vat, instances, options = {}) {
|
|
133
213
|
const time = options.time ?? uniform(0);
|
|
134
214
|
const playback = createVATPlaybackTexture(instances);
|
package/dist/webgl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IUniform, MeshDepthMaterial, WebGLRenderer, Material } from 'three';
|
|
2
|
-
import { V as VAT,
|
|
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
|
package/dist/webgl.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertBakedNormal, assertVATCarrier, isBatchedCarrier } from './chunk-
|
|
2
|
-
import { PACK_TEXELS, EndMode, LoopMode, INFINITE_REPETITIONS, createVATPlaybackTexture } from './chunk-
|
|
1
|
+
import { assertBakedNormal, assertVATCarrier, isBatchedCarrier } from './chunk-I4STYOD5.js';
|
|
2
|
+
import { PACK_TEXELS, EndMode, LoopMode, RIG_TEXELS_PER_SLOT, RIG_TEXELS, INFINITE_REPETITIONS, createVATPlaybackTexture } from './chunk-3PWAY6MD.js';
|
|
3
3
|
import { MeshDepthMaterial, RGBADepthPacking, InstancedMesh, MeshDistanceMaterial } from 'three';
|
|
4
4
|
|
|
5
5
|
function getMaxTextureSize(renderer) {
|
|
@@ -9,13 +9,24 @@ function createVATUniforms(time = 0) {
|
|
|
9
9
|
return { uVatTime: { value: time } };
|
|
10
10
|
}
|
|
11
11
|
var glslFloat = (n) => n.toFixed(1);
|
|
12
|
-
var
|
|
12
|
+
var ROW_PRELUDE = (
|
|
13
13
|
/* glsl */
|
|
14
14
|
`
|
|
15
|
-
uniform highp sampler2D uVatPosTex;
|
|
16
15
|
uniform highp sampler2D uVatPlaybackTex;
|
|
17
16
|
uniform float uVatTime;
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
// Where an instance is reading: the two rows of its band it sits between and
|
|
19
|
+
// the blend toward the second; and the pose-freeze fade's frozen row and
|
|
20
|
+
// weight, a weight of zero being "not fading".
|
|
21
|
+
struct VatRows {
|
|
22
|
+
int row0;
|
|
23
|
+
int row1;
|
|
24
|
+
float blend;
|
|
25
|
+
int fadeRow;
|
|
26
|
+
float fadeWeight;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
VatRows vatRows( const in int vatInstance ) {
|
|
19
30
|
// The pack, fetched by this instance's *logical* index rather than read
|
|
20
31
|
// off an attribute indexed by the drawn slot (ADR-0016). Three texels of
|
|
21
32
|
// one row, in the order src/instance-playback.ts lays them out; the
|
|
@@ -72,19 +83,42 @@ var DECODE_PRELUDE = (
|
|
|
72
83
|
float f = phase * ( wraps ? frames : last );
|
|
73
84
|
float f0 = min( floor( f ), last );
|
|
74
85
|
float f1 = wraps ? mod( f0 + 1.0, frames ) : min( f0 + 1.0, last );
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
|
|
87
|
+
VatRows rows;
|
|
88
|
+
rows.row0 = int( vatClip.x + f0 );
|
|
89
|
+
rows.row1 = int( vatClip.x + f1 );
|
|
90
|
+
rows.blend = f - f0;
|
|
78
91
|
|
|
79
92
|
// The pose-freeze fade, transcribed from the same resolver: one frozen row
|
|
80
93
|
// of the clip this instance was playing when it changed, blended away over
|
|
81
94
|
// vatFade.w. Wall clock, not clip time \u2014 the incoming clip's speed does
|
|
82
95
|
// not stretch a fade. A duration of zero is what "not fading" is, and the
|
|
83
96
|
// pack never writes one without a band to go with it.
|
|
97
|
+
rows.fadeRow = 0;
|
|
98
|
+
rows.fadeWeight = 0.0;
|
|
84
99
|
if ( vatFade.w > 0.0 ) {
|
|
85
100
|
float weight = 1.0 - clamp( ( uVatTime - vatPlayback.x ) / vatFade.w, 0.0, 1.0 );
|
|
86
101
|
float fromRow = max( min( floor( vatFade.z * vatFade.y ), vatFade.y - 1.0 ), 0.0 );
|
|
87
|
-
|
|
102
|
+
rows.fadeRow = int( vatFade.x + fromRow );
|
|
103
|
+
rows.fadeWeight = weight;
|
|
104
|
+
}
|
|
105
|
+
return rows;
|
|
106
|
+
}
|
|
107
|
+
`
|
|
108
|
+
);
|
|
109
|
+
var VERTEX_PRELUDE = (
|
|
110
|
+
/* glsl */
|
|
111
|
+
`
|
|
112
|
+
uniform highp sampler2D uVatPosTex;
|
|
113
|
+
vec3 vatSample( const in sampler2D tex, const in int vatInstance ) {
|
|
114
|
+
VatRows rows = vatRows( vatInstance );
|
|
115
|
+
vec3 s0 = texelFetch( tex, ivec2( gl_VertexID, rows.row0 ), 0 ).xyz;
|
|
116
|
+
vec3 s1 = texelFetch( tex, ivec2( gl_VertexID, rows.row1 ), 0 ).xyz;
|
|
117
|
+
vec3 sampled = mix( s0, s1, rows.blend );
|
|
118
|
+
// The frozen row, blended in by the weight the rows resolved.
|
|
119
|
+
float weight = rows.fadeWeight;
|
|
120
|
+
if ( weight > 0.0 ) {
|
|
121
|
+
vec3 frozen = texelFetch( tex, ivec2( gl_VertexID, rows.fadeRow ), 0 ).xyz;
|
|
88
122
|
sampled = mix( sampled, frozen, weight );
|
|
89
123
|
}
|
|
90
124
|
return sampled;
|
|
@@ -97,17 +131,88 @@ var NORMAL_PRELUDE = (
|
|
|
97
131
|
uniform highp sampler2D uVatNrmTex;
|
|
98
132
|
`
|
|
99
133
|
);
|
|
134
|
+
var RIG_PRELUDE = (
|
|
135
|
+
/* glsl */
|
|
136
|
+
`
|
|
137
|
+
uniform highp sampler2D uVatRigTex;
|
|
138
|
+
attribute vec4 skinIndex;
|
|
139
|
+
attribute vec4 skinWeight;
|
|
140
|
+
|
|
141
|
+
// Matrix4.compose, component for component: a rotation, a translation and
|
|
142
|
+
// one scale back to the matrix the skinning wants \u2014 so both encodings feed
|
|
143
|
+
// the same linear blend, and a rig crowd deforms as its vertex bake does.
|
|
144
|
+
mat4 vatCompose( const in vec4 q, const in vec4 ts ) {
|
|
145
|
+
float x2 = q.x + q.x, y2 = q.y + q.y, z2 = q.z + q.z;
|
|
146
|
+
float xx = q.x * x2, xy = q.x * y2, xz = q.x * z2;
|
|
147
|
+
float yy = q.y * y2, yz = q.y * z2, zz = q.z * z2;
|
|
148
|
+
float wx = q.w * x2, wy = q.w * y2, wz = q.w * z2;
|
|
149
|
+
float s = ts.w;
|
|
150
|
+
return mat4(
|
|
151
|
+
vec4( ( 1.0 - ( yy + zz ) ) * s, ( xy + wz ) * s, ( xz - wy ) * s, 0.0 ),
|
|
152
|
+
vec4( ( xy - wz ) * s, ( 1.0 - ( xx + zz ) ) * s, ( yz + wx ) * s, 0.0 ),
|
|
153
|
+
vec4( ( xz + wy ) * s, ( yz - wx ) * s, ( 1.0 - ( xx + yy ) ) * s, 0.0 ),
|
|
154
|
+
vec4( ts.xyz, 1.0 )
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// One slot's matrix between the two rows the instance sits between \u2014 and
|
|
159
|
+
// through the fade, blended before it is composed.
|
|
160
|
+
mat4 vatSlot( const in int slot, const in VatRows rows ) {
|
|
161
|
+
int rotation = slot * ${RIG_TEXELS_PER_SLOT} + ${RIG_TEXELS.rotation};
|
|
162
|
+
int placement = slot * ${RIG_TEXELS_PER_SLOT} + ${RIG_TEXELS.placement};
|
|
163
|
+
vec4 q0 = texelFetch( uVatRigTex, ivec2( rotation, rows.row0 ), 0 );
|
|
164
|
+
vec4 ts0 = texelFetch( uVatRigTex, ivec2( placement, rows.row0 ), 0 );
|
|
165
|
+
vec4 q1 = texelFetch( uVatRigTex, ivec2( rotation, rows.row1 ), 0 );
|
|
166
|
+
vec4 ts1 = texelFetch( uVatRigTex, ivec2( placement, rows.row1 ), 0 );
|
|
167
|
+
// The bake keeps consecutive rows on one hemisphere, but a looping clip
|
|
168
|
+
// blends its band's last row into its first, and a bone that turned a full
|
|
169
|
+
// circle over the clip arrives there on the far side: one dot product per
|
|
170
|
+
// slot, or the blend passes through zero on the wrap frame.
|
|
171
|
+
if ( dot( q0, q1 ) < 0.0 ) q1 = -q1;
|
|
172
|
+
// A normalised lerp, not a slerp: at a bake's frame step the angular error
|
|
173
|
+
// against a true slerp is far below anything visible. It is still a
|
|
174
|
+
// *rotation* at every blend, which is what a componentwise matrix lerp is
|
|
175
|
+
// not \u2014 that one shortens a limb as it turns (ADR-0018).
|
|
176
|
+
vec4 q = normalize( mix( q0, q1, rows.blend ) );
|
|
177
|
+
vec4 ts = mix( ts0, ts1, rows.blend );
|
|
178
|
+
if ( rows.fadeWeight > 0.0 ) {
|
|
179
|
+
vec4 qf = texelFetch( uVatRigTex, ivec2( rotation, rows.fadeRow ), 0 );
|
|
180
|
+
vec4 tsf = texelFetch( uVatRigTex, ivec2( placement, rows.fadeRow ), 0 );
|
|
181
|
+
// The frozen row is any row of the bake, not this row's neighbour, so
|
|
182
|
+
// the same check.
|
|
183
|
+
if ( dot( q, qf ) < 0.0 ) qf = -qf;
|
|
184
|
+
q = normalize( mix( q, qf, rows.fadeWeight ) );
|
|
185
|
+
ts = mix( ts, tsf, rows.fadeWeight );
|
|
186
|
+
}
|
|
187
|
+
return vatCompose( q, ts );
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Linear blend skinning: the weighted sum of slot matrices, which is the
|
|
191
|
+
// blend the bake did on the CPU for the bounds and three's own
|
|
192
|
+
// skinning_vertex does on the GPU. A zero weight skips its four fetches.
|
|
193
|
+
mat4 vatSkinMatrix( const in int vatInstance ) {
|
|
194
|
+
VatRows rows = vatRows( vatInstance );
|
|
195
|
+
mat4 skin = mat4( 0.0 );
|
|
196
|
+
for ( int i = 0; i < 4; i ++ ) {
|
|
197
|
+
float w = skinWeight[ i ];
|
|
198
|
+
if ( w == 0.0 ) continue;
|
|
199
|
+
skin += w * vatSlot( int( skinIndex[ i ] ), rows );
|
|
200
|
+
}
|
|
201
|
+
return skin;
|
|
202
|
+
}
|
|
203
|
+
`
|
|
204
|
+
);
|
|
100
205
|
var INSTANCE_ID = {
|
|
101
206
|
instance: "gl_InstanceID",
|
|
102
207
|
batch: "int( getIndirectIndex( gl_DrawID ) )"
|
|
103
208
|
};
|
|
104
|
-
var
|
|
209
|
+
var vertexPosition = (id) => (
|
|
105
210
|
/* glsl */
|
|
106
211
|
`
|
|
107
212
|
vec3 transformed = position + vatSample( uVatPosTex, ${INSTANCE_ID[id]} );
|
|
108
213
|
`
|
|
109
214
|
);
|
|
110
|
-
var
|
|
215
|
+
var vertexNormal = (id) => (
|
|
111
216
|
/* glsl */
|
|
112
217
|
`
|
|
113
218
|
vec3 objectNormal = normalize( vatSample( uVatNrmTex, ${INSTANCE_ID[id]} ) );
|
|
@@ -116,24 +221,77 @@ var decodeNormal = (id) => (
|
|
|
116
221
|
#endif
|
|
117
222
|
`
|
|
118
223
|
);
|
|
224
|
+
var rigPosition = (id) => (
|
|
225
|
+
/* glsl */
|
|
226
|
+
`
|
|
227
|
+
mat4 vatSkin = vatSkinMatrix( ${INSTANCE_ID[id]} );
|
|
228
|
+
vec3 transformed = ( vatSkin * vec4( position, 1.0 ) ).xyz;
|
|
229
|
+
`
|
|
230
|
+
);
|
|
231
|
+
var rigNormal = (id) => (
|
|
232
|
+
/* glsl */
|
|
233
|
+
`
|
|
234
|
+
mat4 vatSkinN = vatSkinMatrix( ${INSTANCE_ID[id]} );
|
|
235
|
+
vec3 objectNormal = normalize( mat3( vatSkinN ) * normal );
|
|
236
|
+
#ifdef USE_TANGENT
|
|
237
|
+
vec3 objectTangent = normalize( mat3( vatSkinN ) * tangent.xyz );
|
|
238
|
+
#endif
|
|
239
|
+
`
|
|
240
|
+
);
|
|
241
|
+
function vertexDecode({ positionTexture, normalTexture }, id) {
|
|
242
|
+
return {
|
|
243
|
+
prelude: (normalTexture ? NORMAL_PRELUDE : "") + VERTEX_PRELUDE,
|
|
244
|
+
bind(uniforms) {
|
|
245
|
+
uniforms.uVatPosTex = { value: positionTexture };
|
|
246
|
+
if (normalTexture) uniforms.uVatNrmTex = { value: normalTexture };
|
|
247
|
+
},
|
|
248
|
+
inject(vertexShader) {
|
|
249
|
+
const positioned = vertexShader.replace("#include <begin_vertex>", vertexPosition(id));
|
|
250
|
+
return normalTexture ? positioned.replace("#include <beginnormal_vertex>", vertexNormal(id)) : positioned;
|
|
251
|
+
},
|
|
252
|
+
// A normal-less VAT injects a different vertex shader off the same material
|
|
253
|
+
// parameters, so the variant is in the key (see `patchVATMaterial`).
|
|
254
|
+
key: `three-vat:${id}${normalTexture ? "" : ":no-normal"}`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function rigDecode({ rigTexture }, id) {
|
|
258
|
+
return {
|
|
259
|
+
prelude: RIG_PRELUDE,
|
|
260
|
+
bind(uniforms) {
|
|
261
|
+
uniforms.uVatRigTex = { value: rigTexture };
|
|
262
|
+
},
|
|
263
|
+
inject(vertexShader) {
|
|
264
|
+
return vertexShader.replace("#include <begin_vertex>", rigPosition(id)).replace("#include <beginnormal_vertex>", rigNormal(id));
|
|
265
|
+
},
|
|
266
|
+
key: `three-vat:rig:${id}`
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function decodeFor(vat, id) {
|
|
270
|
+
switch (vat.encoding) {
|
|
271
|
+
case "delta":
|
|
272
|
+
return vertexDecode(vat, id);
|
|
273
|
+
case "rig":
|
|
274
|
+
return rigDecode(vat, id);
|
|
275
|
+
default: {
|
|
276
|
+
const unhandled = vat;
|
|
277
|
+
throw new Error(
|
|
278
|
+
`three-vat: patchVATMaterial has no decode for encoding "${String(unhandled.encoding)}"`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
119
283
|
function patchVATMaterial(material, vat, uniforms, playback, carrier) {
|
|
120
284
|
assertBakedNormal(vat, material);
|
|
121
285
|
if (carrier) assertVATCarrier(carrier, vat);
|
|
122
|
-
const normalTexture = vat.normalTexture;
|
|
123
286
|
const id = isBatchedCarrier(carrier) ? "batch" : "instance";
|
|
287
|
+
const decode = decodeFor(vat, id);
|
|
124
288
|
material.onBeforeCompile = (shader) => {
|
|
125
|
-
shader.uniforms.uVatPosTex = { value: vat.positionTexture };
|
|
126
289
|
shader.uniforms.uVatPlaybackTex = { value: playback.texture };
|
|
127
290
|
shader.uniforms.uVatTime = uniforms.uVatTime;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
shader.uniforms.uVatNrmTex = { value: normalTexture };
|
|
131
|
-
vertexShader = vertexShader.replace("#include <beginnormal_vertex>", decodeNormal(id));
|
|
132
|
-
}
|
|
133
|
-
shader.vertexShader = (normalTexture ? NORMAL_PRELUDE : "") + DECODE_PRELUDE + vertexShader;
|
|
291
|
+
decode.bind(shader.uniforms);
|
|
292
|
+
shader.vertexShader = ROW_PRELUDE + decode.prelude + decode.inject(shader.vertexShader);
|
|
134
293
|
};
|
|
135
|
-
|
|
136
|
-
material.customProgramCacheKey = () => key;
|
|
294
|
+
material.customProgramCacheKey = () => decode.key;
|
|
137
295
|
guardCarrierMismatch(material, id);
|
|
138
296
|
return material;
|
|
139
297
|
}
|