@fps-games/vfx 0.3.5 → 0.3.6
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/CHANGELOG.md +10 -0
- package/dist/effects/energy-shield/index.d.ts +2 -3
- package/dist/effects/energy-shield/index.js +80 -37
- package/dist/effects/treasure-attention/index.d.ts +1 -0
- package/dist/effects/treasure-attention/index.js +5 -3
- package/package.json +1 -1
- package/packages/effects/energy-shield/vfx-params.json +2 -1
- package/packages/effects/treasure-attention/vfx-params.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
<!-- release.mjs 在此行下方插入新版本区块 -->
|
|
14
14
|
|
|
15
|
+
## v0.3.6 — 2026-07-01
|
|
16
|
+
|
|
17
|
+
### 改动
|
|
18
|
+
- perf(energy-shield): 去掉 per-instance GlowLayer,改用 shader halo shell,并默认关闭外层壳。
|
|
19
|
+
- perf(treasure-attention): 默认不创建 per-instance GlowLayer,保留 `showGlowLayer` 调试开关用于后续 AB。
|
|
20
|
+
- feat(energy-shield/treasure-attention): 补充 boolean 调试开关,方便在 demo/debug panel 中临时切换。
|
|
21
|
+
|
|
22
|
+
### 特效
|
|
23
|
+
- 无新增 / 删除(仍 37 个)。
|
|
24
|
+
|
|
15
25
|
## v0.3.5 — 2026-07-01
|
|
16
26
|
|
|
17
27
|
### 改动
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { GlowLayer } from '@babylonjs/core/Layers/glowLayer';
|
|
2
1
|
import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial';
|
|
3
2
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
4
3
|
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
|
|
@@ -24,7 +23,8 @@ export interface EnergyShieldParams extends VfxParamValues {
|
|
|
24
23
|
flowIntensity: number;
|
|
25
24
|
marbleContrast: number;
|
|
26
25
|
bloomIntensity: number;
|
|
27
|
-
|
|
26
|
+
haloScale: number;
|
|
27
|
+
showHalo: boolean;
|
|
28
28
|
segments: number;
|
|
29
29
|
}
|
|
30
30
|
export interface CreateEnergyShieldOptions {
|
|
@@ -38,7 +38,6 @@ export interface CreateEnergyShieldOptions {
|
|
|
38
38
|
export interface EnergyShieldHandle extends VfxEffectHandle {
|
|
39
39
|
shield: Mesh;
|
|
40
40
|
material: ShaderMaterial;
|
|
41
|
-
glowLayer: GlowLayer | null;
|
|
42
41
|
update(params: Partial<EnergyShieldParams>): void;
|
|
43
42
|
}
|
|
44
43
|
export declare const energyShieldEffectPackage: VfxEffectPackage<EnergyShieldParams>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Constants } from "@babylonjs/core/Engines/constants";
|
|
2
|
-
import { GlowLayer } from "@babylonjs/core/Layers/glowLayer";
|
|
3
2
|
import { Effect } from "@babylonjs/core/Materials/effect";
|
|
4
3
|
import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
|
|
5
4
|
import { Color3 } from "@babylonjs/core/Maths/math.color";
|
|
@@ -26,7 +25,8 @@ const DEFAULT_PARAMS = {
|
|
|
26
25
|
flowIntensity: 1.15,
|
|
27
26
|
marbleContrast: 0.67,
|
|
28
27
|
bloomIntensity: 0.45,
|
|
29
|
-
|
|
28
|
+
haloScale: 1.012,
|
|
29
|
+
showHalo: false,
|
|
30
30
|
segments: 64
|
|
31
31
|
};
|
|
32
32
|
const VERTEX_SHADER = `
|
|
@@ -69,6 +69,8 @@ uniform float uFlowScale;
|
|
|
69
69
|
uniform float uFlowSpeed;
|
|
70
70
|
uniform float uFlowIntensity;
|
|
71
71
|
uniform float uMarbleContrast;
|
|
72
|
+
uniform float uHaloAlphaScale;
|
|
73
|
+
uniform float uHaloMode;
|
|
72
74
|
|
|
73
75
|
varying vec3 vObjPos;
|
|
74
76
|
varying vec3 vWorldPos;
|
|
@@ -173,6 +175,16 @@ void main(void) {
|
|
|
173
175
|
alpha += fresnel * uOpacity * 0.58;
|
|
174
176
|
alpha += noiseEdge * uNoiseEdgeIntensity * 0.05;
|
|
175
177
|
|
|
178
|
+
if (uHaloMode > 0.5) {
|
|
179
|
+
float haloRim = pow(saturate(1.0 - ndv), max(0.35, uFresnelPower * 0.55));
|
|
180
|
+
float softRim = smoothstep(0.08, 0.86, haloRim);
|
|
181
|
+
float edgeTaper = 1.0 - smoothstep(0.78, 1.0, haloRim) * 0.62;
|
|
182
|
+
color = uColor * (0.82 + softRim * 0.32) * (0.9 + cloud * 0.1);
|
|
183
|
+
alpha = (0.06 + softRim * 0.62 + noiseEdge * 0.12) * edgeTaper;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
alpha *= uHaloAlphaScale;
|
|
187
|
+
|
|
176
188
|
gl_FragColor = vec4(color, saturate(alpha));
|
|
177
189
|
}
|
|
178
190
|
`;
|
|
@@ -205,7 +217,8 @@ const energyShieldEffectPackage = {
|
|
|
205
217
|
{ key: "flowIntensity", label: "流动强度", kind: "number", min: 0, max: 8, step: 0.01 },
|
|
206
218
|
{ key: "marbleContrast", label: "纹路锐度", kind: "number", min: 0.25, max: 4, step: 0.01 },
|
|
207
219
|
{ key: "bloomIntensity", label: "Bloom 强度", kind: "number", min: 0, max: 4, step: 0.01 },
|
|
208
|
-
{ key: "
|
|
220
|
+
{ key: "haloScale", label: "外层光晕缩放", kind: "scale", min: 1, max: 1.08, step: 1e-3 },
|
|
221
|
+
{ key: "showHalo", label: "显示外层光晕", kind: "boolean" }
|
|
209
222
|
],
|
|
210
223
|
create({ scene, root, position, renderingGroupId, params }) {
|
|
211
224
|
return createEnergyShield(scene, { root, position, renderingGroupId, params });
|
|
@@ -239,28 +252,56 @@ function createEnergyShield(scene, options = {}) {
|
|
|
239
252
|
shield.alphaIndex = 20;
|
|
240
253
|
const material = createShieldMaterial(scene);
|
|
241
254
|
shield.material = material;
|
|
242
|
-
let
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
let halo = null;
|
|
256
|
+
let haloMaterial = null;
|
|
257
|
+
const disposeHalo = () => {
|
|
258
|
+
halo?.dispose(false, false);
|
|
259
|
+
haloMaterial?.dispose();
|
|
260
|
+
halo = null;
|
|
261
|
+
haloMaterial = null;
|
|
262
|
+
};
|
|
263
|
+
const ensureHalo = () => {
|
|
264
|
+
if (!params.showHalo || params.bloomIntensity <= 1e-3) {
|
|
265
|
+
disposeHalo();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (halo && haloMaterial) return;
|
|
269
|
+
halo = MeshBuilder.CreateSphere(
|
|
270
|
+
"vfx_energyShield_halo",
|
|
271
|
+
{
|
|
272
|
+
diameter: 2,
|
|
273
|
+
segments: Math.max(16, Math.round(params.segments)),
|
|
274
|
+
slice: 0.5,
|
|
275
|
+
updatable: false
|
|
276
|
+
},
|
|
277
|
+
scene
|
|
278
|
+
);
|
|
279
|
+
halo.parent = shield;
|
|
280
|
+
halo.isPickable = false;
|
|
281
|
+
halo.alwaysSelectAsActiveMesh = true;
|
|
282
|
+
halo.renderingGroupId = shield.renderingGroupId;
|
|
283
|
+
halo.alphaIndex = shield.alphaIndex + 1;
|
|
284
|
+
halo.scaling.setAll(Math.max(1, params.haloScale));
|
|
285
|
+
haloMaterial = createShieldMaterial(scene, {
|
|
286
|
+
name: "vfx_energyShield_halo_material",
|
|
287
|
+
alphaMode: Constants.ALPHA_ADD
|
|
247
288
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
applyParams(material, shield, glowLayer, params);
|
|
289
|
+
halo.material = haloMaterial;
|
|
290
|
+
};
|
|
291
|
+
ensureHalo();
|
|
292
|
+
applyParams(material, shield, halo, haloMaterial, params);
|
|
253
293
|
let elapsed = 0;
|
|
254
294
|
const observer = scene.onBeforeRenderObservable.add(() => {
|
|
255
295
|
elapsed += Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
|
|
256
296
|
material.setFloat("uTime", elapsed);
|
|
297
|
+
haloMaterial?.setFloat("uTime", elapsed);
|
|
257
298
|
});
|
|
258
299
|
let disposed = false;
|
|
259
300
|
const dispose = () => {
|
|
260
301
|
if (disposed) return;
|
|
261
302
|
disposed = true;
|
|
262
303
|
scene.onBeforeRenderObservable.remove(observer);
|
|
263
|
-
|
|
304
|
+
disposeHalo();
|
|
264
305
|
shield.dispose(false, false);
|
|
265
306
|
material.dispose();
|
|
266
307
|
if (disposeRoot) root.dispose();
|
|
@@ -269,28 +310,17 @@ function createEnergyShield(scene, options = {}) {
|
|
|
269
310
|
root,
|
|
270
311
|
shield,
|
|
271
312
|
material,
|
|
272
|
-
glowLayer,
|
|
273
313
|
update(partial) {
|
|
274
314
|
params = mergeParams(partial, params);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
glowLayer = new GlowLayer(`vfx_energyShield_glow_${shield.uniqueId}`, scene, {
|
|
278
|
-
blurKernelSize: params.bloomKernelSize,
|
|
279
|
-
mainTextureRatio: 0.35
|
|
280
|
-
});
|
|
281
|
-
glowLayer.addIncludedOnlyMesh(shield);
|
|
282
|
-
glowLayer.referenceMeshToUseItsOwnMaterial(shield);
|
|
283
|
-
glowLayer.intensity = params.bloomIntensity;
|
|
284
|
-
applyParams(material, shield, glowLayer, params);
|
|
285
|
-
this.glowLayer = glowLayer;
|
|
286
|
-
}
|
|
315
|
+
ensureHalo();
|
|
316
|
+
applyParams(material, shield, halo, haloMaterial, params);
|
|
287
317
|
},
|
|
288
318
|
dispose
|
|
289
319
|
};
|
|
290
320
|
}
|
|
291
|
-
function createShieldMaterial(scene) {
|
|
321
|
+
function createShieldMaterial(scene, options = {}) {
|
|
292
322
|
const material = new ShaderMaterial(
|
|
293
|
-
"vfx_energyShield_material",
|
|
323
|
+
options.name ?? "vfx_energyShield_material",
|
|
294
324
|
scene,
|
|
295
325
|
{ vertex: SHADER_NAME, fragment: SHADER_NAME },
|
|
296
326
|
{
|
|
@@ -313,21 +343,36 @@ function createShieldMaterial(scene) {
|
|
|
313
343
|
"uFlowScale",
|
|
314
344
|
"uFlowSpeed",
|
|
315
345
|
"uFlowIntensity",
|
|
316
|
-
"uMarbleContrast"
|
|
346
|
+
"uMarbleContrast",
|
|
347
|
+
"uHaloAlphaScale",
|
|
348
|
+
"uHaloMode"
|
|
317
349
|
],
|
|
318
350
|
needAlphaBlending: true
|
|
319
351
|
}
|
|
320
352
|
);
|
|
321
|
-
material.alphaMode = Constants.ALPHA_COMBINE;
|
|
353
|
+
material.alphaMode = options.alphaMode ?? Constants.ALPHA_COMBINE;
|
|
322
354
|
material.backFaceCulling = false;
|
|
323
355
|
material.disableDepthWrite = true;
|
|
324
356
|
material.fogEnabled = false;
|
|
325
357
|
return material;
|
|
326
358
|
}
|
|
327
|
-
function applyParams(material, shield,
|
|
359
|
+
function applyParams(material, shield, halo, haloMaterial, params) {
|
|
328
360
|
shield.scaling.set(params.radius, params.radius * params.verticalScale, params.radius);
|
|
361
|
+
applyMaterialParams(material, params, params.opacity, 1, 0);
|
|
362
|
+
if (halo && haloMaterial) {
|
|
363
|
+
halo.scaling.setAll(Math.max(1, params.haloScale));
|
|
364
|
+
applyMaterialParams(
|
|
365
|
+
haloMaterial,
|
|
366
|
+
params,
|
|
367
|
+
1,
|
|
368
|
+
params.opacity * 0.95 * Math.min(2.2, params.bloomIntensity),
|
|
369
|
+
1
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function applyMaterialParams(material, params, opacity, haloAlphaScale, haloMode) {
|
|
329
374
|
material.setColor3("uColor", toColor3(params.color));
|
|
330
|
-
material.setFloat("uOpacity",
|
|
375
|
+
material.setFloat("uOpacity", opacity);
|
|
331
376
|
material.setFloat("uFresnelPower", params.fresnelPower);
|
|
332
377
|
material.setFloat("uFresnelStrength", params.fresnelStrength);
|
|
333
378
|
material.setFloat("uRimIntensity", params.rimIntensity);
|
|
@@ -340,10 +385,8 @@ function applyParams(material, shield, glowLayer, params) {
|
|
|
340
385
|
material.setFloat("uFlowSpeed", params.flowSpeed);
|
|
341
386
|
material.setFloat("uFlowIntensity", params.flowIntensity);
|
|
342
387
|
material.setFloat("uMarbleContrast", params.marbleContrast);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
glowLayer.blurKernelSize = params.bloomKernelSize;
|
|
346
|
-
}
|
|
388
|
+
material.setFloat("uHaloAlphaScale", haloAlphaScale);
|
|
389
|
+
material.setFloat("uHaloMode", haloMode);
|
|
347
390
|
}
|
|
348
391
|
function mergeParams(patch, base = DEFAULT_PARAMS) {
|
|
349
392
|
return {
|
|
@@ -35,7 +35,8 @@ const DEFAULT_PARAMS = {
|
|
|
35
35
|
riseSpeed: 0.72,
|
|
36
36
|
drift: 0.12,
|
|
37
37
|
bloomIntensity: 0.55,
|
|
38
|
-
bloomKernelSize: 32
|
|
38
|
+
bloomKernelSize: 32,
|
|
39
|
+
showGlowLayer: false
|
|
39
40
|
};
|
|
40
41
|
const treasureAttentionEffectPackage = {
|
|
41
42
|
id: "treasure-attention",
|
|
@@ -72,7 +73,8 @@ const treasureAttentionEffectPackage = {
|
|
|
72
73
|
{ key: "riseSpeed", label: "上浮速度", kind: "number", min: 0, max: 2.5, step: 0.01 },
|
|
73
74
|
{ key: "drift", label: "横向漂移", kind: "number", min: 0, max: 1.2, step: 0.01 },
|
|
74
75
|
{ key: "bloomIntensity", label: "Bloom 强度", kind: "number", min: 0, max: 2.5, step: 0.01 },
|
|
75
|
-
{ key: "bloomKernelSize", label: "Bloom 半径", kind: "number", min: 8, max: 96, step: 1 }
|
|
76
|
+
{ key: "bloomKernelSize", label: "Bloom 半径", kind: "number", min: 8, max: 96, step: 1 },
|
|
77
|
+
{ key: "showGlowLayer", label: "显示 GlowLayer", kind: "boolean" }
|
|
76
78
|
],
|
|
77
79
|
create({ scene, root, position, renderingGroupId, params }) {
|
|
78
80
|
return createTreasureAttentionFx(scene, { root, position, renderingGroupId, params, serviceOwnedRoot: true });
|
|
@@ -264,7 +266,7 @@ function createTreasureAttentionFx(scene, options = {}) {
|
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
function syncGlowLayer() {
|
|
267
|
-
const shouldGlow = params.bloomIntensity > 0;
|
|
269
|
+
const shouldGlow = params.showGlowLayer && params.bloomIntensity > 0;
|
|
268
270
|
if (!shouldGlow) {
|
|
269
271
|
if (glowLayer) {
|
|
270
272
|
for (const mesh of meshes) removeGlowMesh(mesh);
|
package/package.json
CHANGED