three-vat 3.0.0 → 4.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 +20 -16
- package/dist/{carrier-BxRuW47G.d.ts → carrier-VLSJLgjX.d.ts} +70 -18
- package/dist/{chunk-J5IEUGSB.js → chunk-OEKKEHRQ.js} +51 -14
- package/dist/index.d.ts +199 -33
- package/dist/index.js +876 -303
- package/dist/tsl.d.ts +7 -1
- package/dist/tsl.js +46 -11
- package/dist/webgl.d.ts +7 -1
- package/dist/webgl.js +69 -24
- package/package.json +1 -1
package/dist/tsl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Node } from 'three/webgpu';
|
|
2
|
-
import { e as VATClock, k as VATPlaybackTexture, b as VATCarrier, V as VAT, h as VATInstance, f as VATCrowd } from './carrier-
|
|
2
|
+
import { e as VATClock, k as VATPlaybackTexture, b as VATCarrier, V as VAT, h as VATInstance, f as VATCrowd } from './carrier-VLSJLgjX.js';
|
|
3
3
|
import 'three';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -225,6 +225,12 @@ interface CreateVATMeshOptions {
|
|
|
225
225
|
* call make its own is identical on either path.
|
|
226
226
|
*/
|
|
227
227
|
time?: VATTimeUniform;
|
|
228
|
+
/**
|
|
229
|
+
* The GPU's real texture ceiling — {@link getMaxTextureSize} — which the
|
|
230
|
+
* playback texture checks the crowd against, one row per instance. Defaults
|
|
231
|
+
* to `MAX_TEXTURE_SIZE`, a desktop figure (`VATPlaybackTextureOptions`).
|
|
232
|
+
*/
|
|
233
|
+
maxTextureSize?: number;
|
|
228
234
|
}
|
|
229
235
|
/**
|
|
230
236
|
* Turn a baked VAT and a list of instances into a crowd ready to render: an
|
package/dist/tsl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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-
|
|
2
|
+
import { INFINITE_REPETITIONS, LoopMode, EndMode, createVATPlaybackTexture, PACK_TEXELS, RIG_TEXELS, vertexWidthOf, RIG_TEXELS_PER_SLOT } from './chunk-OEKKEHRQ.js';
|
|
3
3
|
import { InstancedMesh } from 'three';
|
|
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
|
+
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, vec3 } from 'three/tsl';
|
|
5
5
|
|
|
6
6
|
function getMaxTextureSize(renderer) {
|
|
7
7
|
const backend = renderer.backend;
|
|
@@ -103,20 +103,23 @@ function resolveBand(clip, playback, time) {
|
|
|
103
103
|
const started = local.greaterThanEqual(0);
|
|
104
104
|
const finished = started.and(playback.repetitions.notEqual(INFINITE_REPETITIONS)).and(loops.greaterThanEqual(playback.repetitions));
|
|
105
105
|
const isPingPong = playback.loopMode.equal(LoopMode.PingPong);
|
|
106
|
-
const bounce = loops.
|
|
106
|
+
const bounce = loops.sub(loops.mul(0.5).floor().mul(2));
|
|
107
107
|
const pingPongPhase = bounce.lessThan(1).select(bounce, float(2).sub(bounce));
|
|
108
108
|
const endPhase = playback.endMode.equal(EndMode.Clamp).select(float(1), float(0));
|
|
109
109
|
const phase = started.select(
|
|
110
110
|
finished.select(endPhase, isPingPong.select(pingPongPhase, loops.fract())),
|
|
111
111
|
float(0)
|
|
112
112
|
);
|
|
113
|
-
const
|
|
113
|
+
const looping = started.select(
|
|
114
114
|
finished.select(bool(false), isPingPong.select(bool(false), bool(true))),
|
|
115
115
|
bool(false)
|
|
116
116
|
);
|
|
117
|
-
const
|
|
117
|
+
const holds = looping.and(playback.endMode.equal(EndMode.Clamp)).and(playback.repetitions.notEqual(INFINITE_REPETITIONS)).and(loops.floor().add(1).greaterThanEqual(playback.repetitions));
|
|
118
|
+
const wraps = looping.and(holds.not());
|
|
119
|
+
const f = phase.mul(looping.select(frames, last));
|
|
118
120
|
const f0 = f.floor().min(last);
|
|
119
|
-
const
|
|
121
|
+
const next = f0.add(1);
|
|
122
|
+
const f1 = wraps.select(next.greaterThanEqual(frames).select(float(0), next), next.min(last));
|
|
120
123
|
const bandRow = (offset) => int(offset).add(clip.startFrame);
|
|
121
124
|
return {
|
|
122
125
|
row0: bandRow(f0),
|
|
@@ -164,18 +167,50 @@ function vatDecode(vat, options = {}) {
|
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
|
-
function
|
|
170
|
+
function octDecode(stored) {
|
|
171
|
+
const e = stored.mul(2).sub(1);
|
|
172
|
+
const ex = e.x;
|
|
173
|
+
const ey = e.y;
|
|
174
|
+
const z = float(1).sub(ex.abs()).sub(ey.abs());
|
|
175
|
+
const overshoot = z.negate().max(0);
|
|
176
|
+
const back = (component) => component.sub(component.greaterThanEqual(0).select(overshoot, overshoot.negate()));
|
|
177
|
+
return vec3(back(ex), back(ey), z).normalize();
|
|
178
|
+
}
|
|
179
|
+
function texelOf(vat, vertexRow) {
|
|
180
|
+
const { rowsPerFrame } = vat;
|
|
181
|
+
if (rowsPerFrame === 1) return { column: vertexRow, rowOf: (row) => row };
|
|
182
|
+
const width = int(vertexWidthOf(vat));
|
|
183
|
+
const line = vertexRow.div(width);
|
|
184
|
+
const spanned = /* @__PURE__ */ new Map();
|
|
185
|
+
return {
|
|
186
|
+
column: vertexRow.mod(width),
|
|
187
|
+
rowOf: (row) => {
|
|
188
|
+
let at = spanned.get(row);
|
|
189
|
+
if (!at) spanned.set(row, at = row.mul(int(rowsPerFrame)).add(line));
|
|
190
|
+
return at;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function vertexDecode(vat, rows) {
|
|
195
|
+
const { positionTexture, normalTexture } = vat;
|
|
168
196
|
const vertexRow = int(vertexIndex);
|
|
197
|
+
const { column, rowOf } = texelOf(vat, vertexRow);
|
|
169
198
|
const band = (tex, of) => {
|
|
170
|
-
const s0 = textureLoad(tex, ivec2(
|
|
171
|
-
const s1 = textureLoad(tex, ivec2(
|
|
199
|
+
const s0 = textureLoad(tex, ivec2(column, rowOf(of.row0))).xyz;
|
|
200
|
+
const s1 = textureLoad(tex, ivec2(column, rowOf(of.row1))).xyz;
|
|
172
201
|
return mix(s0, s1, of.blend);
|
|
173
202
|
};
|
|
174
203
|
const sample = (tex) => mix(band(tex, rows.live), band(tex, rows.outgoing), rows.weight);
|
|
204
|
+
const bandNormal = (tex, of) => {
|
|
205
|
+
const s0 = octDecode(textureLoad(tex, ivec2(column, rowOf(of.row0))).xy);
|
|
206
|
+
const s1 = octDecode(textureLoad(tex, ivec2(column, rowOf(of.row1))).xy);
|
|
207
|
+
return mix(s0, s1, of.blend);
|
|
208
|
+
};
|
|
209
|
+
const sampleNormal = (tex) => mix(bandNormal(tex, rows.live), bandNormal(tex, rows.outgoing), rows.weight);
|
|
175
210
|
return {
|
|
176
211
|
encoding: "delta",
|
|
177
212
|
position: sample(positionTexture),
|
|
178
|
-
normal: normalTexture ?
|
|
213
|
+
normal: normalTexture ? sampleNormal(normalTexture).normalize() : null
|
|
179
214
|
};
|
|
180
215
|
}
|
|
181
216
|
function compose(q, ts) {
|
|
@@ -247,7 +282,7 @@ function rigDecode({ rigTexture, geometry }, rows) {
|
|
|
247
282
|
}
|
|
248
283
|
function createVATMesh(vat, instances, options = {}) {
|
|
249
284
|
const time = options.time ?? uniform(0);
|
|
250
|
-
const playback = createVATPlaybackTexture(instances);
|
|
285
|
+
const playback = createVATPlaybackTexture(instances, { maxTextureSize: options.maxTextureSize });
|
|
251
286
|
for (const source of vat.materials) assertBakedNormal(vat, source);
|
|
252
287
|
const materials = vat.materials.map((source) => source.clone());
|
|
253
288
|
const mesh = new InstancedMesh(vat.geometry, materials, instances.length);
|
package/dist/webgl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IUniform, MeshDepthMaterial, WebGLRenderer, Material } from 'three';
|
|
2
|
-
import { b as VATCarrier, V as VAT, k as VATPlaybackTexture, h as VATInstance, f as VATCrowd } from './carrier-
|
|
2
|
+
import { b as VATCarrier, V as VAT, k as VATPlaybackTexture, h as VATInstance, f as VATCrowd } from './carrier-VLSJLgjX.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The real maximum texture dimension this GPU accepts, for
|
|
@@ -137,6 +137,12 @@ interface CreateVATMeshOptions {
|
|
|
137
137
|
* untwisted shadow.
|
|
138
138
|
*/
|
|
139
139
|
hook?: VATPostDecodeHook;
|
|
140
|
+
/**
|
|
141
|
+
* The GPU's real texture ceiling — {@link getMaxTextureSize} — which the
|
|
142
|
+
* playback texture checks the crowd against, one row per instance. Defaults
|
|
143
|
+
* to `MAX_TEXTURE_SIZE`, a desktop figure (`VATPlaybackTextureOptions`).
|
|
144
|
+
*/
|
|
145
|
+
maxTextureSize?: number;
|
|
140
146
|
}
|
|
141
147
|
/**
|
|
142
148
|
* Turn a baked VAT and a list of instances into a crowd ready to render: an
|
package/dist/webgl.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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-
|
|
2
|
+
import { PACK_TEXELS, EndMode, LoopMode, RIG_TEXELS_PER_SLOT, RIG_TEXELS, INFINITE_REPETITIONS, createVATPlaybackTexture, vertexWidthOf } from './chunk-OEKKEHRQ.js';
|
|
3
3
|
import { MeshDepthMaterial, RGBADepthPacking, InstancedMesh, MeshDistanceMaterial, Material } from 'three';
|
|
4
4
|
|
|
5
5
|
function getMaxTextureSize(renderer) {
|
|
@@ -62,26 +62,35 @@ var ROW_PRELUDE = (
|
|
|
62
62
|
bool finished = started && repetitions != ${glslFloat(INFINITE_REPETITIONS)} && loops >= repetitions;
|
|
63
63
|
|
|
64
64
|
float phase;
|
|
65
|
-
bool
|
|
65
|
+
bool looping;
|
|
66
66
|
if ( !started ) {
|
|
67
67
|
phase = 0.0;
|
|
68
|
-
|
|
68
|
+
looping = false;
|
|
69
69
|
} else if ( finished ) {
|
|
70
70
|
// Held at an end pose, and in neither case sampling past it.
|
|
71
71
|
phase = vatPlayback.w == ${glslFloat(EndMode.Clamp)} ? 1.0 : 0.0;
|
|
72
|
-
|
|
72
|
+
looping = false;
|
|
73
73
|
} else if ( vatPlayback.y == ${glslFloat(LoopMode.PingPong)} ) {
|
|
74
|
-
|
|
74
|
+
// Halved rather than divided, so the remainder cannot land below zero.
|
|
75
|
+
float m = loops - 2.0 * floor( loops * 0.5 );
|
|
75
76
|
phase = m < 1.0 ? m : 2.0 - m;
|
|
76
|
-
|
|
77
|
+
looping = false; // a ping-pong bounces; it does not wrap
|
|
77
78
|
} else {
|
|
78
79
|
phase = fract( loops );
|
|
79
|
-
|
|
80
|
+
looping = true; // and here the interpolation crossing back is correct
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
// Except across the final repetition of a clip that clamps, which holds its
|
|
84
|
+
// last row rather than blending back toward its first (#88).
|
|
85
|
+
bool holds = looping && vatPlayback.w == ${glslFloat(EndMode.Clamp)} && repetitions != ${glslFloat(INFINITE_REPETITIONS)} && floor( loops ) + 1.0 >= repetitions;
|
|
86
|
+
bool wraps = looping && !holds;
|
|
87
|
+
|
|
88
|
+
float f = phase * ( looping ? frames : last );
|
|
83
89
|
float f0 = min( floor( f ), last );
|
|
84
|
-
|
|
90
|
+
// A compare, not a mod: a mod divides, and at the last row a quotient a hair
|
|
91
|
+
// under 1 leaves f1 at frames, one row past the band (#79).
|
|
92
|
+
float next = f0 + 1.0;
|
|
93
|
+
float f1 = wraps ? ( next >= frames ? 0.0 : next ) : min( next, last );
|
|
85
94
|
|
|
86
95
|
VatBand band;
|
|
87
96
|
band.row0 = int( vatClip.x + f0 );
|
|
@@ -112,10 +121,10 @@ var ROW_PRELUDE = (
|
|
|
112
121
|
// normal decode. Every vertex of an instance reads the same texels of the
|
|
113
122
|
// same row, whichever branch it takes, so the texture cache absorbs them;
|
|
114
123
|
// the demo bench under ADR-0016's 5% bound is what said so for three of
|
|
115
|
-
// them, and
|
|
124
|
+
// them, and #72's idle-crowd bench is what says so for all five.
|
|
116
125
|
//
|
|
117
|
-
// Three texels,
|
|
118
|
-
//
|
|
126
|
+
// Three texels here, the live pair and the crossfade; the other two are
|
|
127
|
+
// vatOutgoingBand's, fetched every frame by every sampler that calls it.
|
|
119
128
|
vec4 vatClip = texelFetch( uVatPlaybackTex, ivec2( ${PACK_TEXELS.clip}, vatInstance ), 0 );
|
|
120
129
|
vec4 vatPlayback = texelFetch( uVatPlaybackTex, ivec2( ${PACK_TEXELS.playback}, vatInstance ), 0 );
|
|
121
130
|
vec4 vatCrossfade = texelFetch( uVatPlaybackTex, ivec2( ${PACK_TEXELS.crossfade}, vatInstance ), 0 );
|
|
@@ -166,17 +175,23 @@ var ROW_PRELUDE = (
|
|
|
166
175
|
}
|
|
167
176
|
`
|
|
168
177
|
);
|
|
169
|
-
|
|
178
|
+
function texelOf(vat) {
|
|
179
|
+
const { rowsPerFrame } = vat;
|
|
180
|
+
if (rowsPerFrame === 1) return (row) => `ivec2( gl_VertexID, ${row} )`;
|
|
181
|
+
const width = vertexWidthOf(vat);
|
|
182
|
+
return (row) => `ivec2( gl_VertexID % ${width}, ${row} * ${rowsPerFrame} + gl_VertexID / ${width} )`;
|
|
183
|
+
}
|
|
184
|
+
var vertexPrelude = (texel) => (
|
|
170
185
|
/* glsl */
|
|
171
186
|
`
|
|
172
187
|
uniform highp sampler2D uVatPosTex;
|
|
173
188
|
|
|
174
|
-
// One band of
|
|
175
|
-
// same function for the live band and the outgoing one, as
|
|
176
|
-
// function for both pairs.
|
|
189
|
+
// One band of the position layer: the two rows this band sits between,
|
|
190
|
+
// mixed. The same function for the live band and the outgoing one, as
|
|
191
|
+
// vatBand is the same function for both pairs.
|
|
177
192
|
vec3 vatBandSample( const in sampler2D tex, const in VatBand band ) {
|
|
178
|
-
vec3 s0 = texelFetch( tex,
|
|
179
|
-
vec3 s1 = texelFetch( tex,
|
|
193
|
+
vec3 s0 = texelFetch( tex, ${texel("band.row0")}, 0 ).xyz;
|
|
194
|
+
vec3 s1 = texelFetch( tex, ${texel("band.row1")}, 0 ).xyz;
|
|
180
195
|
return mix( s0, s1, band.blend );
|
|
181
196
|
}
|
|
182
197
|
|
|
@@ -199,10 +214,37 @@ var VERTEX_PRELUDE = (
|
|
|
199
214
|
}
|
|
200
215
|
`
|
|
201
216
|
);
|
|
202
|
-
var
|
|
217
|
+
var normalPrelude = (texel) => (
|
|
203
218
|
/* glsl */
|
|
204
219
|
`
|
|
205
220
|
uniform highp sampler2D uVatNrmTex;
|
|
221
|
+
|
|
222
|
+
vec2 vatOctSign( const in vec2 v ) {
|
|
223
|
+
return vec2( v.x >= 0.0 ? 1.0 : -1.0, v.y >= 0.0 ? 1.0 : -1.0 );
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
vec3 vatOctDecode( const in vec2 stored ) {
|
|
227
|
+
vec2 e = stored * 2.0 - 1.0;
|
|
228
|
+
float z = 1.0 - abs( e.x ) - abs( e.y );
|
|
229
|
+
vec2 xy = e - vatOctSign( e ) * max( -z, 0.0 );
|
|
230
|
+
return normalize( vec3( xy, z ) );
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// One band of the normal layer \u2014 vatBandSample, over decoded normals.
|
|
234
|
+
vec3 vatBandSampleNormal( const in VatBand band ) {
|
|
235
|
+
vec3 s0 = vatOctDecode( texelFetch( uVatNrmTex, ${texel("band.row0")}, 0 ).xy );
|
|
236
|
+
vec3 s1 = vatOctDecode( texelFetch( uVatNrmTex, ${texel("band.row1")}, 0 ).xy );
|
|
237
|
+
return mix( s0, s1, band.blend );
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// vatSample, for the one layer whose texel is not what it decodes to. The
|
|
241
|
+
// outgoing band is resolved and mixed unconditionally here too, for the
|
|
242
|
+
// reason spelled out on vatSample.
|
|
243
|
+
vec3 vatSampleNormal( const in int vatInstance ) {
|
|
244
|
+
VatRows rows = vatRows( vatInstance );
|
|
245
|
+
VatBand outgoing = vatOutgoingBand( vatInstance, rows.weight > 0.0 );
|
|
246
|
+
return mix( vatBandSampleNormal( rows.live ), vatBandSampleNormal( outgoing ), rows.weight );
|
|
247
|
+
}
|
|
206
248
|
`
|
|
207
249
|
);
|
|
208
250
|
var RIG_PRELUDE = (
|
|
@@ -320,7 +362,7 @@ var vertexPosition = (id) => (
|
|
|
320
362
|
var vertexNormal = (id) => (
|
|
321
363
|
/* glsl */
|
|
322
364
|
`
|
|
323
|
-
vec3 objectNormal = normalize(
|
|
365
|
+
vec3 objectNormal = normalize( vatSampleNormal( ${INSTANCE_ID[id]} ) );
|
|
324
366
|
#ifdef USE_TANGENT
|
|
325
367
|
vec3 objectTangent = vec3( tangent.xyz );
|
|
326
368
|
#endif
|
|
@@ -343,9 +385,12 @@ var rigNormal = (id) => (
|
|
|
343
385
|
#endif
|
|
344
386
|
`
|
|
345
387
|
);
|
|
346
|
-
function vertexDecode(
|
|
388
|
+
function vertexDecode(vat, id) {
|
|
389
|
+
const { positionTexture, normalTexture, rowsPerFrame } = vat;
|
|
390
|
+
const texel = texelOf(vat);
|
|
391
|
+
const span = rowsPerFrame === 1 ? "" : `:rows${rowsPerFrame}x${vertexWidthOf(vat)}`;
|
|
347
392
|
return {
|
|
348
|
-
prelude: (normalTexture ?
|
|
393
|
+
prelude: (normalTexture ? normalPrelude(texel) : "") + vertexPrelude(texel),
|
|
349
394
|
bind(uniforms) {
|
|
350
395
|
uniforms.uVatPosTex = { value: positionTexture };
|
|
351
396
|
if (normalTexture) uniforms.uVatNrmTex = { value: normalTexture };
|
|
@@ -358,7 +403,7 @@ function vertexDecode({ positionTexture, normalTexture }, id) {
|
|
|
358
403
|
normal: normalTexture ? vertexNormal(id) : null,
|
|
359
404
|
// A normal-less VAT injects a different vertex shader off the same material
|
|
360
405
|
// parameters, so the variant is in the key (see `patchVATMaterial`).
|
|
361
|
-
key: `three-vat:${id}${normalTexture ? "" : ":no-normal"}`,
|
|
406
|
+
key: `three-vat:${id}${normalTexture ? "" : ":no-normal"}${span}`,
|
|
362
407
|
instanceIndex: INSTANCE_ID[id]
|
|
363
408
|
};
|
|
364
409
|
}
|
|
@@ -473,7 +518,7 @@ function createVATDepthMaterial(vat, uniforms, playback, options) {
|
|
|
473
518
|
}
|
|
474
519
|
function createVATMesh(vat, instances, options = {}) {
|
|
475
520
|
const uniforms = options.time ? { uVatTime: options.time } : createVATUniforms();
|
|
476
|
-
const playback = createVATPlaybackTexture(instances);
|
|
521
|
+
const playback = createVATPlaybackTexture(instances, { maxTextureSize: options.maxTextureSize });
|
|
477
522
|
const patch = { hook: options.hook };
|
|
478
523
|
const materials = vat.materials.map((source) => patchVATMaterial(source.clone(), vat, uniforms, playback, patch));
|
|
479
524
|
const mesh = new InstancedMesh(vat.geometry, materials, instances.length);
|