@fps-games/vfx 0.3.14 → 0.3.15
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.
|
@@ -20,13 +20,14 @@ const manifest = {
|
|
|
20
20
|
assets,
|
|
21
21
|
source
|
|
22
22
|
};
|
|
23
|
-
const BOLT_SEGMENTS = 6;
|
|
24
23
|
const DEFAULT_PARAMS = {
|
|
25
24
|
lifetimeMode: "infinite",
|
|
26
25
|
lifetime: 2.5,
|
|
27
26
|
scale: 1.15,
|
|
28
27
|
initialHeight: 0,
|
|
29
|
-
color: { r: 0.
|
|
28
|
+
color: { r: 0.82, g: 0.95, b: 1 },
|
|
29
|
+
lineColor: { r: 0.82, g: 0.95, b: 1 },
|
|
30
|
+
particleColor: { r: 0.82, g: 0.95, b: 1 },
|
|
30
31
|
opacity: 1,
|
|
31
32
|
intensity: 1,
|
|
32
33
|
forwardOffset: 0,
|
|
@@ -38,18 +39,18 @@ const DEFAULT_PARAMS = {
|
|
|
38
39
|
fadeDistance: 0.58,
|
|
39
40
|
spawnFade: 0.45,
|
|
40
41
|
suctionSpeed: 1.65,
|
|
41
|
-
lineCount:
|
|
42
|
+
lineCount: 32,
|
|
42
43
|
lineLength: 0.46,
|
|
43
|
-
lineWidth: 0.
|
|
44
|
+
lineWidth: 0.045,
|
|
44
45
|
lineOpacity: 1,
|
|
45
46
|
lineSpread: 1,
|
|
46
47
|
lineTwist: 0,
|
|
47
|
-
particleCount:
|
|
48
|
-
particleSize: 0.
|
|
49
|
-
particleSpeed:
|
|
48
|
+
particleCount: 22,
|
|
49
|
+
particleSize: 0.06,
|
|
50
|
+
particleSpeed: 2.15,
|
|
50
51
|
particleDrift: 0,
|
|
51
52
|
particleOuterOffset: 0,
|
|
52
|
-
particleOpacity: 0.
|
|
53
|
+
particleOpacity: 0.85,
|
|
53
54
|
directionX: 0,
|
|
54
55
|
directionZ: -1
|
|
55
56
|
};
|
|
@@ -69,7 +70,8 @@ const magnetSuctionEffectPackage = {
|
|
|
69
70
|
{ key: "lifetime", label: "自定义时长", kind: "lifetime", min: 0.1, max: 30, step: 0.01 },
|
|
70
71
|
{ key: "scale", label: "整体缩放", kind: "scale", min: 0.05, max: 6, step: 0.01 },
|
|
71
72
|
{ key: "initialHeight", label: "局部高度", kind: "number", min: -2, max: 4, step: 0.01 },
|
|
72
|
-
{ key: "
|
|
73
|
+
{ key: "lineColor", label: "线条颜色", kind: "color" },
|
|
74
|
+
{ key: "particleColor", label: "圆点颜色", kind: "color" },
|
|
73
75
|
{ key: "opacity", label: "整体透明度", kind: "opacity", min: 0, max: 1, step: 0.01 },
|
|
74
76
|
{ key: "intensity", label: "强度", kind: "opacity", min: 0, max: 1, step: 0.01 },
|
|
75
77
|
{ key: "forwardOffset", label: "入口前偏移", kind: "number", min: -2, max: 4, step: 0.01 },
|
|
@@ -80,14 +82,16 @@ const magnetSuctionEffectPackage = {
|
|
|
80
82
|
{ key: "mouthPadding", label: "入口保护距离", kind: "number", min: 0.01, max: 2, step: 0.01 },
|
|
81
83
|
{ key: "fadeDistance", label: "入口淡出距离", kind: "number", min: 0.01, max: 3, step: 0.01 },
|
|
82
84
|
{ key: "spawnFade", label: "外侧淡入距离", kind: "number", min: 0.01, max: 3, step: 0.01 },
|
|
83
|
-
{ key: "
|
|
84
|
-
{ key: "
|
|
85
|
-
{ key: "
|
|
85
|
+
{ key: "suctionSpeed", label: "线条速度", kind: "number", min: 0, max: 8, step: 0.01 },
|
|
86
|
+
{ key: "lineCount", label: "白线数量", kind: "number", min: 0, max: 96, step: 1 },
|
|
87
|
+
{ key: "lineLength", label: "白线长度", kind: "number", min: 0.02, max: 2, step: 0.01 },
|
|
88
|
+
{ key: "lineWidth", label: "白线尾宽", kind: "number", min: 2e-3, max: 0.18, step: 1e-3 },
|
|
89
|
+
{ key: "lineOpacity", label: "白线透明度", kind: "opacity", min: 0, max: 1.5, step: 0.01 },
|
|
86
90
|
{ key: "lineSpread", label: "散布缩放", kind: "number", min: 0.1, max: 2, step: 0.01 },
|
|
87
|
-
{ key: "particleCount", label: "
|
|
88
|
-
{ key: "particleSize", label: "
|
|
89
|
-
{ key: "particleSpeed", label: "
|
|
90
|
-
{ key: "particleOpacity", label: "
|
|
91
|
+
{ key: "particleCount", label: "粒子数量", kind: "number", min: 0, max: 80, step: 1 },
|
|
92
|
+
{ key: "particleSize", label: "粒子大小", kind: "number", min: 5e-3, max: 0.3, step: 1e-3 },
|
|
93
|
+
{ key: "particleSpeed", label: "粒子速度", kind: "number", min: 0, max: 10, step: 0.01 },
|
|
94
|
+
{ key: "particleOpacity", label: "粒子透明度", kind: "opacity", min: 0, max: 1, step: 0.01 },
|
|
91
95
|
{ key: "directionX", label: "吸入方向 X", kind: "number", min: -1, max: 1, step: 0.01 },
|
|
92
96
|
{ key: "directionZ", label: "吸入方向 Z", kind: "number", min: -1, max: 1, step: 0.01 }
|
|
93
97
|
],
|
|
@@ -138,15 +142,26 @@ function createMagnetSuctionField(scene, root, params, renderingGroupId) {
|
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
145
|
+
const lineCount = Math.max(0, Math.round(params.lineCount));
|
|
146
|
+
const particleCount = Math.max(0, Math.round(params.particleCount));
|
|
147
|
+
const lineVertexCount = lineCount * 3;
|
|
148
|
+
const particleVertexCount = particleCount * 4;
|
|
149
|
+
const totalVertexCount = lineVertexCount + particleVertexCount;
|
|
144
150
|
const positions = new Float32Array(totalVertexCount * 3);
|
|
145
151
|
const colors = new Float32Array(totalVertexCount * 4);
|
|
146
152
|
const indices = [];
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
const lines = [];
|
|
154
|
+
const particles = [];
|
|
155
|
+
let resetIndex = 1;
|
|
156
|
+
for (let index = 0; index < lineCount; index += 1) {
|
|
157
|
+
const base = index * 3;
|
|
158
|
+
indices.push(base, base + 1, base + 2);
|
|
159
|
+
lines.push(resetLine({}, true));
|
|
160
|
+
}
|
|
161
|
+
for (let index = 0; index < particleCount; index += 1) {
|
|
162
|
+
const base = lineVertexCount + index * 4;
|
|
163
|
+
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
164
|
+
particles.push(resetParticle({}, true));
|
|
150
165
|
}
|
|
151
166
|
const mesh = new Mesh("magnet-suction.mesh", scene);
|
|
152
167
|
mesh.parent = root;
|
|
@@ -164,7 +179,15 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
|
164
179
|
writeGeometry();
|
|
165
180
|
return { update, dispose };
|
|
166
181
|
function update(deltaSeconds) {
|
|
167
|
-
|
|
182
|
+
const basis = createSuctionBasis(params);
|
|
183
|
+
for (const line of lines) {
|
|
184
|
+
line.d -= line.speed * deltaSeconds;
|
|
185
|
+
if (line.d < basis.mouthPadding) resetLine(line, false);
|
|
186
|
+
}
|
|
187
|
+
for (const particle of particles) {
|
|
188
|
+
particle.d -= particle.speed * deltaSeconds;
|
|
189
|
+
if (particle.d < basis.mouthPadding) resetParticle(particle, false);
|
|
190
|
+
}
|
|
168
191
|
writeGeometry();
|
|
169
192
|
}
|
|
170
193
|
function dispose() {
|
|
@@ -173,9 +196,8 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
|
173
196
|
function writeGeometry() {
|
|
174
197
|
const basis = createSuctionBasis(params);
|
|
175
198
|
const cameraBasis = createCameraBasis(scene, root, basis);
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const boltBlue = new Color3(0.25, 0.62, 1);
|
|
199
|
+
const lineColor = toColor3(params.lineColor ?? params.color);
|
|
200
|
+
const particleColor = toColor3(params.particleColor ?? params.color);
|
|
179
201
|
const alphaScale = clamp01(params.opacity) * Math.max(0, params.intensity);
|
|
180
202
|
let vertexIndex = 0;
|
|
181
203
|
const putVertex = (position, color, alpha, brightness = 1) => {
|
|
@@ -190,66 +212,61 @@ function createSuctionMesh(scene, root, params, material, renderingGroupId) {
|
|
|
190
212
|
colors[colorOffset + 3] = clamp01(alpha * alphaScale);
|
|
191
213
|
vertexIndex += 1;
|
|
192
214
|
};
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
const alpha = lineFade(line.d, basis) * params.lineOpacity * line.alphaMul;
|
|
217
|
+
const headD = Math.max(basis.mouthPadding, line.d);
|
|
218
|
+
const tailD = Math.min(basis.length, line.d + line.len);
|
|
219
|
+
const head = suctionPosition(headD, line.u, line.v, basis);
|
|
220
|
+
const tail = suctionPosition(tailD, line.u, line.v, basis);
|
|
221
|
+
const shrink = Math.max(lateralScale(headD, basis), 0.18);
|
|
222
|
+
const half = cameraBasis.ribbonSide.scale(line.width * shrink * 0.5);
|
|
223
|
+
putVertex(tail.subtract(half), lineColor, alpha, line.brightness);
|
|
224
|
+
putVertex(tail.add(half), lineColor, alpha * 0.92, line.brightness);
|
|
225
|
+
putVertex(head, lineColor, alpha * 0.22, line.brightness);
|
|
226
|
+
}
|
|
227
|
+
for (const particle of particles) {
|
|
228
|
+
const fade = lineFade(particle.d, basis);
|
|
229
|
+
const alpha = Math.sqrt(fade) * params.particleOpacity * particle.alphaMul;
|
|
230
|
+
const center = suctionPosition(particle.d, particle.u, particle.v, basis);
|
|
231
|
+
const shrink = Math.max(lateralScale(particle.d, basis), 0.48);
|
|
232
|
+
const right = cameraBasis.particleRight.scale(particle.size * shrink);
|
|
233
|
+
const up = cameraBasis.particleUp.scale(particle.size * shrink * 0.7);
|
|
234
|
+
putVertex(center.subtract(right).subtractInPlace(up), particleColor, alpha, particle.brightness);
|
|
235
|
+
putVertex(center.add(right).subtractInPlace(up), particleColor, alpha, particle.brightness);
|
|
236
|
+
putVertex(center.add(right).addInPlace(up), particleColor, alpha * 0.8, particle.brightness);
|
|
237
|
+
putVertex(center.subtract(right).addInPlace(up), particleColor, alpha * 0.8, particle.brightness);
|
|
210
238
|
}
|
|
211
239
|
mesh.updateVerticesData(VertexBuffer.PositionKind, positions, false, false);
|
|
212
240
|
mesh.updateVerticesData(VertexBuffer.ColorKind, colors, false, false);
|
|
213
|
-
function writeBolts(currentBasis, boltColor, edgeColor, writeRibbon) {
|
|
214
|
-
for (let bolt = 0; bolt < boltCount; bolt += 1) {
|
|
215
|
-
const seed = sequence01(bolt + 7, bolt % 8);
|
|
216
|
-
const speedScale = Math.max(0, params.particleSpeed) / DEFAULT_PARAMS.particleSpeed;
|
|
217
|
-
const cycle = (time * speedScale * (1.45 + seed * 0.55) + seed) % 1;
|
|
218
|
-
const flicker = sequence01(bolt * 17 + Math.floor(time * 20), bolt % 8);
|
|
219
|
-
const active = flicker > 0.28 ? 1 : 0.18;
|
|
220
|
-
const angle = bolt * 2.399 + time * (1.7 + seed * 0.9);
|
|
221
|
-
const lengthScale = Math.max(0.04, params.lineLength / DEFAULT_PARAMS.lineLength);
|
|
222
|
-
const boltLength = lerp(currentBasis.length * 0.16, currentBasis.length * 0.28, seed) * lengthScale;
|
|
223
|
-
const headDistance = lerp(currentBasis.length * 0.92, currentBasis.mouthPadding * 1.15, cycle);
|
|
224
|
-
let previousDistance = Math.min(currentBasis.length, headDistance + boltLength);
|
|
225
|
-
let previous = suctionPosition(
|
|
226
|
-
previousDistance,
|
|
227
|
-
Math.cos(angle) * currentBasis.spreadX * 0.5 * lateralScale(previousDistance, currentBasis),
|
|
228
|
-
Math.sin(angle * 1.3) * currentBasis.spreadY * 0.5 * lateralScale(previousDistance, currentBasis),
|
|
229
|
-
currentBasis
|
|
230
|
-
);
|
|
231
|
-
for (let segment = 0; segment < BOLT_SEGMENTS; segment += 1) {
|
|
232
|
-
const t = (segment + 1) / BOLT_SEGMENTS;
|
|
233
|
-
const distance = Math.max(currentBasis.mouthPadding * 1.08, lerp(previousDistance, headDistance, t));
|
|
234
|
-
const suckScale = lateralScale(distance, currentBasis);
|
|
235
|
-
const jitter = (sequence01(bolt * 19 + segment + Math.floor(time * 18), segment) - 0.5) * 0.34 * currentBasis.spreadX * suckScale;
|
|
236
|
-
const curl = angle + t * (2.6 + seed * 1.8) + Math.sin(time * 4 + bolt) * 0.35;
|
|
237
|
-
const next = suctionPosition(
|
|
238
|
-
distance,
|
|
239
|
-
Math.cos(curl) * currentBasis.spreadX * suckScale * (1 - t * 0.45) + jitter,
|
|
240
|
-
Math.sin(curl * 1.25) * currentBasis.spreadY * suckScale * (1 - t * 0.5),
|
|
241
|
-
currentBasis
|
|
242
|
-
);
|
|
243
|
-
const mouthFade = smoothstep(0.04, 0.18, cycle) * (1 - smoothstep(0.88, 1, cycle));
|
|
244
|
-
writeRibbon(previous, next, basis.lineWidth * 0.58, segment % 2 === 0 ? boltColor : edgeColor, 0.38 * params.lineOpacity * active * mouthFade, 1.85);
|
|
245
|
-
previous = next;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
241
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
242
|
+
function resetLine(line, initial) {
|
|
243
|
+
const basis = createSuctionBasis(params);
|
|
244
|
+
line.d = initial ? randomRange(basis.mouthPadding + 0.05, basis.length) : basis.length + randomRange(0, basis.spawnFade);
|
|
245
|
+
line.u = randomRange(-basis.spreadX * 0.5, basis.spreadX * 0.5);
|
|
246
|
+
line.v = randomRange(-basis.spreadY * 0.5, basis.spreadY * 0.5);
|
|
247
|
+
line.len = randomRange(basis.lineLength * 0.65, basis.lineLength * 1.35);
|
|
248
|
+
line.width = randomRange(basis.lineWidth * 0.65, basis.lineWidth * 1.35);
|
|
249
|
+
line.speed = Math.max(0, params.suctionSpeed * Math.max(0.01, params.scale)) * randomRange(0.78, 1.25);
|
|
250
|
+
line.alphaMul = randomRange(0.65, 1);
|
|
251
|
+
line.brightness = randomRange(0.82, 1);
|
|
252
|
+
return line;
|
|
253
|
+
}
|
|
254
|
+
function resetParticle(particle, initial) {
|
|
255
|
+
const basis = createSuctionBasis(params);
|
|
256
|
+
particle.d = initial ? randomRange(basis.mouthPadding + 0.05, basis.length) : basis.length + randomRange(0, basis.spawnFade);
|
|
257
|
+
particle.u = randomRange(-basis.spreadX * 0.55, basis.spreadX * 0.55);
|
|
258
|
+
particle.v = randomRange(-basis.spreadY * 0.55, basis.spreadY * 0.55);
|
|
259
|
+
particle.size = randomRange(basis.particleSize * 0.45, basis.particleSize * 1.15);
|
|
260
|
+
particle.speed = Math.max(0, params.particleSpeed * Math.max(0.01, params.scale)) * randomRange(0.75, 1.35);
|
|
261
|
+
particle.alphaMul = randomRange(0.35, 0.9);
|
|
262
|
+
particle.brightness = randomRange(0.75, 1);
|
|
263
|
+
return particle;
|
|
264
|
+
}
|
|
265
|
+
function randomRange(min, max) {
|
|
266
|
+
const id2 = resetIndex;
|
|
267
|
+
resetIndex += 1;
|
|
268
|
+
return lerp(min, max, sequence01(id2, resetIndex % 8));
|
|
269
|
+
}
|
|
253
270
|
}
|
|
254
271
|
function createSuctionBasis(params) {
|
|
255
272
|
const scale = Math.max(0.01, params.scale);
|
|
@@ -348,7 +365,7 @@ function createSuctionMaterial(scene) {
|
|
|
348
365
|
});
|
|
349
366
|
material.backFaceCulling = false;
|
|
350
367
|
material.transparencyMode = Material.MATERIAL_ALPHABLEND;
|
|
351
|
-
material.alphaMode = Constants.
|
|
368
|
+
material.alphaMode = Constants.ALPHA_COMBINE;
|
|
352
369
|
material.disableDepthWrite = true;
|
|
353
370
|
return material;
|
|
354
371
|
}
|
|
@@ -377,6 +394,12 @@ function registerSuctionShaders() {
|
|
|
377
394
|
`;
|
|
378
395
|
}
|
|
379
396
|
}
|
|
397
|
+
function lineFade(distance, basis) {
|
|
398
|
+
const mouthFade = smoothstep(basis.mouthPadding, basis.mouthPadding + basis.fadeDistance, distance);
|
|
399
|
+
const clampedDistance = Math.min(distance, basis.length);
|
|
400
|
+
const outerFade = smoothstep(0, basis.spawnFade, basis.length - clampedDistance);
|
|
401
|
+
return mouthFade * outerFade;
|
|
402
|
+
}
|
|
380
403
|
function toColor3(color) {
|
|
381
404
|
return new Color3(clamp01(color.r), clamp01(color.g), clamp01(color.b));
|
|
382
405
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fps-games/vfx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared VFX library for Playable projects: engine-injected effect packages + runtime. Project-pure (no imports of host project code).",
|
|
6
6
|
"publishConfig": {
|