@fps-games/vfx 0.3.17 → 0.3.18

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 CHANGED
@@ -12,6 +12,16 @@
12
12
 
13
13
  <!-- release.mjs 在此行下方插入新版本区块 -->
14
14
 
15
+ ## v0.3.18 — 2026-07-14
16
+
17
+ ### 改动
18
+ - fix(tooling): support validation on Windows
19
+ - feat(energy-shield): align dome shader rendering
20
+ - feat(energy-shield): refine glass shield rendering
21
+
22
+ ### 特效
23
+ - 无新增 / 删除(仍 41 个)。
24
+
15
25
  ## v0.3.16 — 2026-07-08
16
26
 
17
27
  ### 改动
@@ -10,10 +10,18 @@ export interface EnergyShieldParams extends VfxParamValues {
10
10
  radius: number;
11
11
  verticalScale: number;
12
12
  color: VfxColorValue;
13
+ baseColor: VfxColorValue;
13
14
  opacity: number;
14
15
  fresnelPower: number;
15
16
  fresnelStrength: number;
16
17
  rimIntensity: number;
18
+ baseGlowStrength: number;
19
+ baseGlowHeight: number;
20
+ surfaceVariation: number;
21
+ backRimStrength: number;
22
+ backGlowStrength: number;
23
+ backGlowHeight: number;
24
+ showSurfaceFlow: boolean;
17
25
  surfaceIntensity: number;
18
26
  noiseScale: number;
19
27
  noiseEdgeWidth: number;
@@ -42,6 +50,8 @@ export interface CreateEnergyShieldOptions {
42
50
  export interface EnergyShieldHandle extends VfxEffectHandle {
43
51
  shield: Mesh;
44
52
  material: ShaderMaterial;
53
+ backShield: Mesh;
54
+ backMaterial: ShaderMaterial;
45
55
  glowLayer: GlowLayer | null;
46
56
  update(params: Partial<EnergyShieldParams>): void;
47
57
  }
@@ -9,29 +9,36 @@ import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
9
9
  import { V as VFX_RENDERING_GROUP_ID } from "../../chunks/rendering-CV3nhygX.js";
10
10
  const SHADER_NAME = "vfxEnergyShield";
11
11
  const HALO_ALPHA_SCALE_MAX = 0.3;
12
- const HALO_FEATHER_ALPHA_RATIO = 0.72;
13
- const HALO_FEATHER_SCALE_OFFSET = 0.018;
12
+ const SHIELD_SLICE = 0.5;
14
13
  const DEFAULT_PARAMS = {
15
14
  radius: 3.18,
16
- verticalScale: 1.02,
17
- color: { r: 0.34902, g: 0.568627, b: 0.968627 },
18
- opacity: 0.2,
19
- fresnelPower: 1.8,
20
- fresnelStrength: 0.66,
21
- rimIntensity: 0.4,
22
- surfaceIntensity: 0.3,
23
- noiseScale: 1,
24
- noiseEdgeWidth: 0.1,
25
- noiseEdgeIntensity: 0.42,
26
- noiseEdgeSmoothness: 0.5,
27
- flowScale: 0.36,
28
- flowSpeed: 0.25,
29
- flowIntensity: 1.15,
30
- marbleContrast: 0.67,
31
- bloomIntensity: 0.45,
15
+ verticalScale: 1,
16
+ color: { r: 0.28627451, g: 0.50980392, b: 1 },
17
+ baseColor: { r: 0.28627451, g: 0.50980392, b: 1 },
18
+ opacity: 0.02,
19
+ fresnelPower: 2,
20
+ fresnelStrength: 1,
21
+ rimIntensity: 1,
22
+ baseGlowStrength: 0.32,
23
+ baseGlowHeight: 0.46,
24
+ surfaceVariation: 0,
25
+ backRimStrength: 1,
26
+ backGlowStrength: 0.36,
27
+ backGlowHeight: 0.49,
28
+ showSurfaceFlow: false,
29
+ surfaceIntensity: 0.04,
30
+ noiseScale: 0.9,
31
+ noiseEdgeWidth: 0.08,
32
+ noiseEdgeIntensity: 0.62,
33
+ noiseEdgeSmoothness: 0.3,
34
+ flowScale: 2.15,
35
+ flowSpeed: 0.18,
36
+ flowIntensity: 0.2,
37
+ marbleContrast: 1,
38
+ bloomIntensity: 0.4,
32
39
  bloomKernelSize: 36,
33
- showGlowLayer: true,
34
- haloScale: 1.018,
40
+ showGlowLayer: false,
41
+ haloScale: 1.006,
35
42
  showHalo: false,
36
43
  segments: 64
37
44
  };
@@ -61,11 +68,16 @@ precision highp float;
61
68
 
62
69
  uniform float uTime;
63
70
  uniform vec3 cameraPosition;
64
- uniform vec3 uColor;
71
+ uniform vec3 uTopColor;
72
+ uniform vec3 uBaseColor;
65
73
  uniform float uOpacity;
66
74
  uniform float uFresnelPower;
67
- uniform float uFresnelStrength;
75
+ uniform float uRimStrength;
68
76
  uniform float uRimIntensity;
77
+ uniform float uHorizonStrength;
78
+ uniform float uHorizonHeight;
79
+ uniform float uSurfaceVariation;
80
+ uniform float uSurfaceFlowEnabled;
69
81
  uniform float uSurfaceIntensity;
70
82
  uniform float uNoiseScale;
71
83
  uniform float uNoiseEdgeWidth;
@@ -75,137 +87,68 @@ uniform float uFlowScale;
75
87
  uniform float uFlowSpeed;
76
88
  uniform float uFlowIntensity;
77
89
  uniform float uMarbleContrast;
78
- uniform float uHaloAlphaScale;
79
- uniform float uHaloMode;
90
+ uniform float uAlphaScale;
80
91
 
81
92
  varying vec3 vObjPos;
82
93
  varying vec3 vWorldPos;
83
94
  varying vec3 vNormalW;
84
95
 
85
- vec3 mod289v3(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
86
- vec4 mod289v4(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
87
- vec4 permute(vec4 x) { return mod289v4(((x * 34.0) + 1.0) * x); }
88
- vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
89
-
90
- float saturate(float v) { return clamp(v, 0.0, 1.0); }
91
-
92
- float snoise(vec3 v) {
93
- const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
94
- const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
95
- vec3 i = floor(v + dot(v, C.yyy));
96
- vec3 x0 = v - i + dot(i, C.xxx);
97
- vec3 g = step(x0.yzx, x0.xyz);
98
- vec3 l = 1.0 - g;
99
- vec3 i1 = min(g.xyz, l.zxy);
100
- vec3 i2 = max(g.xyz, l.zxy);
101
- vec3 x1 = x0 - i1 + C.xxx;
102
- vec3 x2 = x0 - i2 + C.yyy;
103
- vec3 x3 = x0 - D.yyy;
104
- i = mod289v3(i);
105
- vec4 p = permute(permute(permute(
106
- i.z + vec4(0.0, i1.z, i2.z, 1.0))
107
- + i.y + vec4(0.0, i1.y, i2.y, 1.0))
108
- + i.x + vec4(0.0, i1.x, i2.x, 1.0));
109
- float n_ = 0.142857142857;
110
- vec3 ns = n_ * D.wyz - D.xzx;
111
- vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
112
- vec4 x_ = floor(j * ns.z);
113
- vec4 y_ = floor(j - 7.0 * x_);
114
- vec4 x = x_ * ns.x + ns.yyyy;
115
- vec4 y = y_ * ns.x + ns.yyyy;
116
- vec4 h = 1.0 - abs(x) - abs(y);
117
- vec4 b0 = vec4(x.xy, y.xy);
118
- vec4 b1 = vec4(x.zw, y.zw);
119
- vec4 s0 = floor(b0) * 2.0 + 1.0;
120
- vec4 s1 = floor(b1) * 2.0 + 1.0;
121
- vec4 sh = -step(h, vec4(0.0));
122
- vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
123
- vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
124
- vec3 p0 = vec3(a0.xy, h.x);
125
- vec3 p1 = vec3(a0.zw, h.y);
126
- vec3 p2 = vec3(a1.xy, h.z);
127
- vec3 p3 = vec3(a1.zw, h.w);
128
- vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
129
- p0 *= norm.x;
130
- p1 *= norm.y;
131
- p2 *= norm.z;
132
- p3 *= norm.w;
133
- vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
134
- m = m * m;
135
- return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
96
+ float hash(vec3 position) {
97
+ position = fract(position * 0.3183099 + 0.1);
98
+ position *= 17.0;
99
+ return fract(position.x * position.y * position.z * (position.x + position.y + position.z));
136
100
  }
137
101
 
138
- float fbm(vec3 p) {
139
- float total = 0.0;
140
- float amp = 0.55;
141
- total += snoise(p) * amp;
142
- p = p * 2.03 + vec3(17.1, 3.7, 9.2);
143
- amp *= 0.52;
144
- total += snoise(p) * amp;
145
- p = p * 2.11 + vec3(4.3, 13.9, 6.4);
146
- amp *= 0.5;
147
- total += snoise(p) * amp;
148
- return total;
102
+ float valueNoise(vec3 position) {
103
+ vec3 cell = floor(position);
104
+ vec3 local = fract(position);
105
+ local = local * local * (3.0 - 2.0 * local);
106
+
107
+ return mix(
108
+ mix(
109
+ mix(hash(cell), hash(cell + vec3(1.0, 0.0, 0.0)), local.x),
110
+ mix(hash(cell + vec3(0.0, 1.0, 0.0)), hash(cell + vec3(1.0, 1.0, 0.0)), local.x),
111
+ local.y
112
+ ),
113
+ mix(
114
+ mix(hash(cell + vec3(0.0, 0.0, 1.0)), hash(cell + vec3(1.0, 0.0, 1.0)), local.x),
115
+ mix(hash(cell + vec3(0.0, 1.0, 1.0)), hash(cell + vec3(1.0, 1.0, 1.0)), local.x),
116
+ local.y
117
+ ),
118
+ local.z
119
+ );
149
120
  }
150
121
 
151
122
  void main(void) {
152
- vec3 normalW = normalize(vNormalW);
153
123
  vec3 viewDir = normalize(cameraPosition - vWorldPos);
154
- float ndv = saturate(dot(normalW, viewDir));
155
- float viewRim = saturate(1.0 - ndv);
156
- float fresnel = pow(viewRim, max(0.05, uFresnelPower)) * uFresnelStrength;
157
-
158
- float t = uTime * uFlowSpeed;
159
- vec3 spherePos = normalize(vObjPos + vec3(0.0001)) * max(0.05, uNoiseScale);
160
- vec3 flowA = spherePos * uFlowScale + vec3(t, t * 0.62, t * 0.38);
161
- vec3 flowB = spherePos * (uFlowScale * 1.91) + vec3(-t * 0.46, t * 0.82, -t * 0.27);
162
- float n1 = fbm(flowA);
163
- float n2 = fbm(flowB);
164
- float cloud = saturate((n1 * 0.58 + n2 * 0.42) * 0.5 + 0.5);
165
-
166
- float veinInput = spherePos.y * 0.92 + n1 * 1.65 + n2 * 0.74 + t * 0.13;
167
- float ribbon = abs(sin(veinInput * 3.14159265));
168
- float marble = pow(1.0 - ribbon, max(0.25, uMarbleContrast));
169
- float nebula = saturate(mix(cloud, marble, 0.58) * uFlowIntensity * 0.34);
170
-
171
- float edgeWidth = max(0.01, uNoiseEdgeWidth);
172
- float edgeSoft = max(0.01, uNoiseEdgeSmoothness) * 0.22;
173
- float noiseEdge = smoothstep(1.0 - edgeWidth - edgeSoft, 1.0 - edgeWidth * 0.22, nebula);
174
-
175
- vec3 deepBlue = uColor * (0.12 + 0.42 * cloud);
176
- vec3 marbleBlue = uColor * (0.34 + 0.9 * marble) * uSurfaceIntensity;
177
- vec3 rimBlue = uColor * fresnel * uRimIntensity;
178
- vec3 edgeBlue = uColor * noiseEdge * uNoiseEdgeIntensity;
179
- vec3 color = min((deepBlue + marbleBlue + rimBlue + edgeBlue) * 0.72, vec3(1.12));
180
-
181
- float alpha = uOpacity * (0.16 + 0.34 * cloud + 0.24 * marble);
182
- alpha += fresnel * uOpacity * 0.58;
183
- alpha += noiseEdge * uNoiseEdgeIntensity * 0.05;
124
+ float facing = abs(dot(normalize(vNormalW), viewDir));
125
+ float rim = pow(1.0 - facing, max(0.05, uFresnelPower));
126
+ float horizon = pow(1.0 - clamp(vObjPos.y / max(0.001, uHorizonHeight), 0.0, 1.0), 2.5);
127
+ float heightFactor = smoothstep(0.0, 0.92, vObjPos.y);
184
128
 
185
- if (uHaloMode > 1.5) {
186
- float haloRim = pow(viewRim, max(0.28, uFresnelPower * 0.44));
187
- float featherIn = smoothstep(0.1, 0.46, haloRim);
188
- float featherOut = 1.0 - smoothstep(0.72, 1.0, haloRim);
189
- float featherRim = featherIn * featherOut;
190
- float outerFade = 1.0 - smoothstep(0.88, 1.0, haloRim);
191
- color = uColor * (0.2 + featherRim * 0.24 + cloud * 0.04);
192
- alpha = featherRim * 0.58 * outerFade;
193
- } else if (uHaloMode > 0.5) {
194
- float haloRim = pow(viewRim, max(0.3, uFresnelPower * 0.48));
195
- float bodyIn = smoothstep(0.34, 0.68, haloRim);
196
- float bodyOut = 1.0 - smoothstep(0.72, 1.0, haloRim);
197
- float bodyRim = bodyIn * bodyOut;
198
- float featherIn = smoothstep(0.12, 0.44, haloRim);
199
- float featherOut = 1.0 - smoothstep(0.48, 1.0, haloRim);
200
- float featherRim = featherIn * featherOut;
201
- float outerFade = 1.0 - smoothstep(0.86, 1.0, haloRim);
202
- color = uColor * (0.24 + bodyRim * 0.34 + featherRim * 0.12 + cloud * 0.05);
203
- alpha = (bodyRim * 0.5 + featherRim * 0.14 + noiseEdge * 0.01) * outerFade;
129
+ vec3 motion = vec3(0.0);
130
+ if (uSurfaceFlowEnabled > 0.5) {
131
+ motion = vec3(uTime * uFlowSpeed, -uTime * uFlowSpeed * 0.62, uTime * uFlowSpeed * 0.38);
204
132
  }
133
+ float cloud = valueNoise(vObjPos * max(0.05, uNoiseScale)) * 0.65;
134
+ cloud += valueNoise(vObjPos * max(0.05, uFlowScale) + motion) * 0.35;
135
+ float shapedCloud = pow(clamp(cloud, 0.0, 1.0), max(0.25, uMarbleContrast));
136
+ float variation = mix(1.0 - uSurfaceVariation, 1.0 + uSurfaceVariation, shapedCloud);
137
+ float cloudShade = mix(0.92, 1.08, shapedCloud);
138
+ rim *= cloudShade;
205
139
 
206
- alpha *= uHaloAlphaScale;
140
+ float edgeWidth = max(0.005, uNoiseEdgeWidth);
141
+ float edgeSoftness = max(0.005, uNoiseEdgeSmoothness * 0.2);
142
+ float flowBand = 1.0 - smoothstep(edgeWidth, edgeWidth + edgeSoftness, abs(shapedCloud - 0.5));
143
+ float flowGlow = flowBand * uFlowIntensity * uNoiseEdgeIntensity * uSurfaceFlowEnabled;
207
144
 
208
- gl_FragColor = vec4(color, saturate(alpha));
145
+ float alpha = uOpacity * variation + rim * uRimStrength + horizon * uHorizonStrength * variation;
146
+ alpha += flowGlow * uOpacity * 0.5;
147
+ vec3 gradientColor = mix(uBaseColor, uTopColor, heightFactor);
148
+ vec3 color = gradientColor * (0.25 + rim * 1.3 + horizon * 0.85) * cloudShade * uRimIntensity;
149
+ color += gradientColor * flowGlow * uSurfaceIntensity;
150
+
151
+ gl_FragColor = vec4(color, clamp(alpha * uAlphaScale, 0.0, 0.88));
209
152
  }
210
153
  `;
211
154
  const energyShieldEffectPackage = {
@@ -222,11 +165,19 @@ const energyShieldEffectPackage = {
222
165
  debugParams: [
223
166
  { key: "radius", label: "玻璃罩半径", kind: "scale", min: 0.4, max: 10, step: 0.01 },
224
167
  { key: "verticalScale", label: "纵向缩放", kind: "scale", min: 0.5, max: 1.8, step: 0.01 },
225
- { key: "color", label: "主体颜色", kind: "color" },
226
- { key: "opacity", label: "透明度", kind: "opacity", min: 0.02, max: 1, step: 0.01 },
227
- { key: "fresnelPower", label: "边缘曲线", kind: "number", min: 0.2, max: 6, step: 0.01 },
228
- { key: "fresnelStrength", label: "边缘强度", kind: "number", min: 0, max: 6, step: 0.01 },
168
+ { key: "color", label: "顶部颜色", kind: "color" },
169
+ { key: "baseColor", label: "底部颜色", kind: "color" },
170
+ { key: "opacity", label: "球壳透明度", kind: "opacity", min: 0, max: 1, step: 0.01 },
171
+ { key: "fresnelPower", label: "轮廓衰减", kind: "number", min: 0.2, max: 8, step: 0.01 },
172
+ { key: "fresnelStrength", label: "正面轮廓强度", kind: "number", min: 0, max: 2.5, step: 0.01 },
229
173
  { key: "rimIntensity", label: "轮廓亮度", kind: "number", min: 0, max: 4, step: 0.01 },
174
+ { key: "baseGlowStrength", label: "正面底部辉光", kind: "number", min: 0, max: 1, step: 0.01 },
175
+ { key: "baseGlowHeight", label: "正面辉光高度", kind: "number", min: 0.04, max: 1, step: 0.01 },
176
+ { key: "surfaceVariation", label: "表面明暗变化", kind: "number", min: 0, max: 0.6, step: 0.01 },
177
+ { key: "backRimStrength", label: "背面轮廓强度", kind: "number", min: 0, max: 1.5, step: 0.01 },
178
+ { key: "backGlowStrength", label: "背面底部辉光", kind: "number", min: 0, max: 1, step: 0.01 },
179
+ { key: "backGlowHeight", label: "背面辉光高度", kind: "number", min: 0.04, max: 1, step: 0.01 },
180
+ { key: "showSurfaceFlow", label: "显示表面流动", kind: "boolean" },
230
181
  { key: "surfaceIntensity", label: "表面亮度", kind: "number", min: 0, max: 3, step: 0.01 },
231
182
  { key: "noiseScale", label: "噪声缩放", kind: "number", min: 0.2, max: 5, step: 0.01 },
232
183
  { key: "noiseEdgeWidth", label: "噪声亮边宽度", kind: "number", min: 0.01, max: 0.5, step: 0.01 },
@@ -237,10 +188,10 @@ const energyShieldEffectPackage = {
237
188
  { key: "flowIntensity", label: "流动强度", kind: "number", min: 0, max: 8, step: 0.01 },
238
189
  { key: "marbleContrast", label: "纹路锐度", kind: "number", min: 0.25, max: 4, step: 0.01 },
239
190
  { key: "bloomIntensity", label: "Bloom 强度", kind: "number", min: 0, max: 4, step: 0.01 },
240
- { key: "bloomKernelSize", label: "GlowLayer 半径", kind: "number", min: 8, max: 128, step: 1 },
241
- { key: "showGlowLayer", label: "显示 GlowLayer", kind: "boolean" },
242
- { key: "haloScale", label: "外层光晕缩放", kind: "scale", min: 1, max: 1.08, step: 1e-3 },
243
- { key: "showHalo", label: "显示外层光晕", kind: "boolean" }
191
+ { key: "bloomKernelSize", label: "Bloom/Glow 半径", kind: "number", min: 8, max: 128, step: 1 },
192
+ { key: "showGlowLayer", label: "显示 Bloom/Glow", kind: "boolean" },
193
+ { key: "haloScale", label: "Halo 缩放", kind: "scale", min: 1, max: 1.08, step: 1e-3 },
194
+ { key: "showHalo", label: "显示额外 Halo", kind: "boolean" }
244
195
  ],
245
196
  create({ scene, root, position, renderingGroupId, params, runtime }) {
246
197
  return createEnergyShield(scene, {
@@ -268,7 +219,7 @@ function createEnergyShield(scene, options = {}) {
268
219
  {
269
220
  diameter: 2,
270
221
  segments: Math.max(16, Math.round(params.segments)),
271
- slice: 0.5,
222
+ slice: SHIELD_SLICE,
272
223
  updatable: false
273
224
  },
274
225
  scene
@@ -278,25 +229,40 @@ function createEnergyShield(scene, options = {}) {
278
229
  shield.alwaysSelectAsActiveMesh = true;
279
230
  shield.renderingGroupId = options.renderingGroupId ?? VFX_RENDERING_GROUP_ID;
280
231
  shield.alphaIndex = 20;
281
- const material = createShieldMaterial(scene);
232
+ const material = createShieldMaterial(scene, { face: "front" });
282
233
  shield.material = material;
234
+ const backShield = MeshBuilder.CreateSphere(
235
+ "vfx_energyShield_back_hemisphere",
236
+ {
237
+ diameter: 2,
238
+ segments: Math.max(16, Math.round(params.segments)),
239
+ slice: SHIELD_SLICE,
240
+ updatable: false
241
+ },
242
+ scene
243
+ );
244
+ backShield.parent = root;
245
+ backShield.isPickable = false;
246
+ backShield.alwaysSelectAsActiveMesh = true;
247
+ backShield.renderingGroupId = shield.renderingGroupId;
248
+ backShield.alphaIndex = shield.alphaIndex - 1;
249
+ const backMaterial = createShieldMaterial(scene, {
250
+ name: "vfx_energyShield_back_material",
251
+ alphaMode: Constants.ALPHA_ADD,
252
+ face: "back"
253
+ });
254
+ backShield.material = backMaterial;
283
255
  const sharedGlowLayer = options.glowLayer ?? null;
284
256
  let glowLayer = null;
285
257
  let ownsGlowLayer = false;
286
258
  let glowMeshIncluded = false;
287
259
  let halo = null;
288
260
  let haloMaterial = null;
289
- let haloFeather = null;
290
- let haloFeatherMaterial = null;
291
261
  const disposeHalo = () => {
292
262
  halo?.dispose(false, false);
293
263
  haloMaterial?.dispose();
294
- haloFeather?.dispose(false, false);
295
- haloFeatherMaterial?.dispose();
296
264
  halo = null;
297
265
  haloMaterial = null;
298
- haloFeather = null;
299
- haloFeatherMaterial = null;
300
266
  };
301
267
  const addGlowMesh = () => {
302
268
  if (!glowLayer || glowMeshIncluded) return;
@@ -348,7 +314,7 @@ function createEnergyShield(scene, options = {}) {
348
314
  {
349
315
  diameter: 2,
350
316
  segments: Math.max(16, Math.round(params.segments)),
351
- slice: 0.5,
317
+ slice: SHIELD_SLICE,
352
318
  updatable: false
353
319
  },
354
320
  scene
@@ -361,43 +327,21 @@ function createEnergyShield(scene, options = {}) {
361
327
  halo.scaling.setAll(Math.max(1, params.haloScale));
362
328
  haloMaterial = createShieldMaterial(scene, {
363
329
  name: "vfx_energyShield_halo_material",
364
- alphaMode: Constants.ALPHA_ADD
330
+ alphaMode: Constants.ALPHA_ADD,
331
+ face: "front"
365
332
  });
366
333
  halo.material = haloMaterial;
367
334
  }
368
- if (!haloFeather || !haloFeatherMaterial) {
369
- haloFeather = MeshBuilder.CreateSphere(
370
- "vfx_energyShield_halo_feather",
371
- {
372
- diameter: 2,
373
- segments: Math.max(16, Math.round(params.segments)),
374
- slice: 0.5,
375
- updatable: false
376
- },
377
- scene
378
- );
379
- haloFeather.parent = shield;
380
- haloFeather.isPickable = false;
381
- haloFeather.alwaysSelectAsActiveMesh = true;
382
- haloFeather.renderingGroupId = shield.renderingGroupId;
383
- haloFeather.alphaIndex = shield.alphaIndex + 2;
384
- haloFeather.scaling.setAll(getHaloFeatherScale(params));
385
- haloFeatherMaterial = createShieldMaterial(scene, {
386
- name: "vfx_energyShield_halo_feather_material",
387
- alphaMode: Constants.ALPHA_ADD
388
- });
389
- haloFeather.material = haloFeatherMaterial;
390
- }
391
335
  };
392
336
  ensureGlowLayer();
393
337
  ensureHalo();
394
- applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params);
338
+ applyParams(material, shield, backMaterial, backShield, halo, haloMaterial, params);
395
339
  let elapsed = 0;
396
340
  const observer = scene.onBeforeRenderObservable.add(() => {
397
341
  elapsed += Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
398
342
  material.setFloat("uTime", elapsed);
343
+ backMaterial.setFloat("uTime", elapsed);
399
344
  haloMaterial?.setFloat("uTime", elapsed);
400
- haloFeatherMaterial?.setFloat("uTime", elapsed);
401
345
  });
402
346
  let disposed = false;
403
347
  const dispose = () => {
@@ -406,6 +350,8 @@ function createEnergyShield(scene, options = {}) {
406
350
  scene.onBeforeRenderObservable.remove(observer);
407
351
  disposeGlowLayer();
408
352
  disposeHalo();
353
+ backShield.dispose(false, false);
354
+ backMaterial.dispose();
409
355
  shield.dispose(false, false);
410
356
  material.dispose();
411
357
  if (disposeRoot) root.dispose();
@@ -414,6 +360,8 @@ function createEnergyShield(scene, options = {}) {
414
360
  root,
415
361
  shield,
416
362
  material,
363
+ backShield,
364
+ backMaterial,
417
365
  get glowLayer() {
418
366
  return glowLayer;
419
367
  },
@@ -421,7 +369,7 @@ function createEnergyShield(scene, options = {}) {
421
369
  params = mergeParams(partial, params);
422
370
  ensureGlowLayer();
423
371
  ensureHalo();
424
- applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params);
372
+ applyParams(material, shield, backMaterial, backShield, halo, haloMaterial, params);
425
373
  },
426
374
  dispose
427
375
  };
@@ -438,11 +386,16 @@ function createShieldMaterial(scene, options = {}) {
438
386
  "worldViewProjection",
439
387
  "cameraPosition",
440
388
  "uTime",
441
- "uColor",
389
+ "uTopColor",
390
+ "uBaseColor",
442
391
  "uOpacity",
443
392
  "uFresnelPower",
444
- "uFresnelStrength",
393
+ "uRimStrength",
445
394
  "uRimIntensity",
395
+ "uHorizonStrength",
396
+ "uHorizonHeight",
397
+ "uSurfaceVariation",
398
+ "uSurfaceFlowEnabled",
446
399
  "uSurfaceIntensity",
447
400
  "uNoiseScale",
448
401
  "uNoiseEdgeWidth",
@@ -452,49 +405,68 @@ function createShieldMaterial(scene, options = {}) {
452
405
  "uFlowSpeed",
453
406
  "uFlowIntensity",
454
407
  "uMarbleContrast",
455
- "uHaloAlphaScale",
456
- "uHaloMode"
408
+ "uAlphaScale"
457
409
  ],
458
410
  needAlphaBlending: true
459
411
  }
460
412
  );
461
413
  material.alphaMode = options.alphaMode ?? Constants.ALPHA_COMBINE;
462
- material.backFaceCulling = false;
414
+ material.backFaceCulling = true;
415
+ material.cullBackFaces = options.face !== "back";
463
416
  material.disableDepthWrite = true;
464
417
  material.fogEnabled = false;
465
418
  return material;
466
419
  }
467
- function applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params) {
420
+ function applyParams(material, shield, backMaterial, backShield, halo, haloMaterial, params) {
468
421
  shield.scaling.set(params.radius, params.radius * params.verticalScale, params.radius);
469
- applyMaterialParams(material, params, params.opacity, 1, 0);
470
- const haloAlphaScale = Math.min(HALO_ALPHA_SCALE_MAX, params.opacity * Math.min(1, params.bloomIntensity));
422
+ backShield.scaling.copyFrom(shield.scaling);
423
+ applyMaterialParams(material, params, {
424
+ opacity: params.opacity,
425
+ rimStrength: params.fresnelStrength,
426
+ horizonStrength: params.baseGlowStrength,
427
+ horizonHeight: params.baseGlowHeight,
428
+ alphaScale: 1
429
+ });
430
+ applyMaterialParams(backMaterial, params, {
431
+ opacity: 0,
432
+ rimStrength: params.backRimStrength,
433
+ horizonStrength: params.backGlowStrength,
434
+ horizonHeight: params.backGlowHeight,
435
+ alphaScale: 1
436
+ });
437
+ const haloAlphaScale = Math.min(
438
+ HALO_ALPHA_SCALE_MAX,
439
+ Math.max(
440
+ params.opacity * Math.min(1, params.bloomIntensity),
441
+ Math.min(1, params.fresnelStrength) * 0.2
442
+ )
443
+ );
471
444
  if (halo && haloMaterial) {
472
445
  halo.scaling.setAll(Math.max(1, params.haloScale));
473
446
  applyMaterialParams(
474
447
  haloMaterial,
475
448
  params,
476
- 1,
477
- haloAlphaScale,
478
- 1
479
- );
480
- }
481
- if (haloFeather && haloFeatherMaterial) {
482
- haloFeather.scaling.setAll(getHaloFeatherScale(params));
483
- applyMaterialParams(
484
- haloFeatherMaterial,
485
- params,
486
- 1,
487
- haloAlphaScale * HALO_FEATHER_ALPHA_RATIO,
488
- 2
449
+ {
450
+ opacity: 0,
451
+ rimStrength: params.fresnelStrength,
452
+ horizonStrength: 0,
453
+ horizonHeight: params.baseGlowHeight,
454
+ alphaScale: haloAlphaScale
455
+ }
489
456
  );
490
457
  }
491
458
  }
492
- function applyMaterialParams(material, params, opacity, haloAlphaScale, haloMode) {
493
- material.setColor3("uColor", toColor3(params.color));
494
- material.setFloat("uOpacity", opacity);
459
+ function applyMaterialParams(material, params, settings) {
460
+ material.setColor3("uTopColor", toColor3(params.color));
461
+ material.setColor3("uBaseColor", toColor3(params.baseColor));
462
+ material.setFloat("uOpacity", settings.opacity);
495
463
  material.setFloat("uFresnelPower", params.fresnelPower);
496
- material.setFloat("uFresnelStrength", params.fresnelStrength);
464
+ material.setFloat("uRimStrength", settings.rimStrength);
497
465
  material.setFloat("uRimIntensity", params.rimIntensity);
466
+ material.setFloat("uHorizonStrength", settings.horizonStrength);
467
+ material.setFloat("uHorizonHeight", settings.horizonHeight);
468
+ material.setFloat("uSurfaceVariation", params.surfaceVariation);
469
+ material.setFloat("uSurfaceFlowEnabled", params.showSurfaceFlow ? 1 : 0);
498
470
  material.setFloat("uSurfaceIntensity", params.surfaceIntensity);
499
471
  material.setFloat("uNoiseScale", params.noiseScale);
500
472
  material.setFloat("uNoiseEdgeWidth", params.noiseEdgeWidth);
@@ -504,21 +476,18 @@ function applyMaterialParams(material, params, opacity, haloAlphaScale, haloMode
504
476
  material.setFloat("uFlowSpeed", params.flowSpeed);
505
477
  material.setFloat("uFlowIntensity", params.flowIntensity);
506
478
  material.setFloat("uMarbleContrast", params.marbleContrast);
507
- material.setFloat("uHaloAlphaScale", haloAlphaScale);
508
- material.setFloat("uHaloMode", haloMode);
479
+ material.setFloat("uAlphaScale", settings.alphaScale);
509
480
  }
510
481
  function mergeParams(patch, base = DEFAULT_PARAMS) {
511
482
  return {
512
483
  ...base,
513
484
  ...patch,
514
- color: patch?.color ? { ...patch.color } : { ...base.color }
485
+ color: patch?.color ? { ...patch.color } : { ...base.color },
486
+ baseColor: patch?.baseColor ? { ...patch.baseColor } : { ...base.baseColor }
515
487
  };
516
488
  }
517
489
  function toColor3(color) {
518
- return new Color3(color.r, color.g, color.b);
519
- }
520
- function getHaloFeatherScale(params) {
521
- return Math.max(1, params.haloScale + HALO_FEATHER_SCALE_OFFSET);
490
+ return new Color3(color.r, color.g, color.b).toLinearSpace();
522
491
  }
523
492
  function registerShaders() {
524
493
  Effect.ShadersStore[`${SHADER_NAME}VertexShader`] = VERTEX_SHADER;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fps-games/vfx",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
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": {
@@ -3,29 +3,41 @@
3
3
  "effectId": "energy-shield",
4
4
  "params": {
5
5
  "radius": 3.18,
6
- "verticalScale": 1.02,
6
+ "verticalScale": 1,
7
7
  "color": {
8
- "r": 0.34902,
9
- "g": 0.568627,
10
- "b": 0.968627
8
+ "r": 0.28627451,
9
+ "g": 0.50980392,
10
+ "b": 1
11
11
  },
12
- "opacity": 0.2,
13
- "fresnelPower": 1.8,
14
- "fresnelStrength": 0.66,
15
- "rimIntensity": 0.4,
16
- "surfaceIntensity": 0.3,
17
- "noiseScale": 1,
18
- "noiseEdgeWidth": 0.1,
19
- "noiseEdgeIntensity": 0.42,
20
- "noiseEdgeSmoothness": 0.5,
21
- "flowScale": 0.36,
22
- "flowSpeed": 0.25,
23
- "flowIntensity": 1.15,
24
- "marbleContrast": 0.67,
25
- "bloomIntensity": 0.45,
12
+ "baseColor": {
13
+ "r": 0.28627451,
14
+ "g": 0.50980392,
15
+ "b": 1
16
+ },
17
+ "opacity": 0.02,
18
+ "fresnelPower": 2,
19
+ "fresnelStrength": 1,
20
+ "rimIntensity": 1,
21
+ "baseGlowStrength": 0.32,
22
+ "baseGlowHeight": 0.46,
23
+ "surfaceVariation": 0,
24
+ "backRimStrength": 1,
25
+ "backGlowStrength": 0.36,
26
+ "backGlowHeight": 0.49,
27
+ "showSurfaceFlow": false,
28
+ "surfaceIntensity": 0.04,
29
+ "noiseScale": 0.9,
30
+ "noiseEdgeWidth": 0.08,
31
+ "noiseEdgeIntensity": 0.62,
32
+ "noiseEdgeSmoothness": 0.3,
33
+ "flowScale": 2.15,
34
+ "flowSpeed": 0.18,
35
+ "flowIntensity": 0.2,
36
+ "marbleContrast": 1,
37
+ "bloomIntensity": 0.4,
26
38
  "bloomKernelSize": 36,
27
- "showGlowLayer": true,
28
- "haloScale": 1.018,
39
+ "showGlowLayer": false,
40
+ "haloScale": 1.006,
29
41
  "showHalo": false,
30
42
  "segments": 64
31
43
  }