@fps-games/vfx 0.3.17 → 0.3.19

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,19 @@ 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
+ showSurfaceTexture: boolean;
25
+ showSurfaceFlow: boolean;
17
26
  surfaceIntensity: number;
18
27
  noiseScale: number;
19
28
  noiseEdgeWidth: number;
@@ -42,6 +51,8 @@ export interface CreateEnergyShieldOptions {
42
51
  export interface EnergyShieldHandle extends VfxEffectHandle {
43
52
  shield: Mesh;
44
53
  material: ShaderMaterial;
54
+ backShield: Mesh;
55
+ backMaterial: ShaderMaterial;
45
56
  glowLayer: GlowLayer | null;
46
57
  update(params: Partial<EnergyShieldParams>): void;
47
58
  }
@@ -9,29 +9,37 @@ 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
+ showSurfaceTexture: false,
29
+ showSurfaceFlow: false,
30
+ surfaceIntensity: 0.04,
31
+ noiseScale: 0.9,
32
+ noiseEdgeWidth: 0.08,
33
+ noiseEdgeIntensity: 0.62,
34
+ noiseEdgeSmoothness: 0.3,
35
+ flowScale: 2.15,
36
+ flowSpeed: 0.18,
37
+ flowIntensity: 0.2,
38
+ marbleContrast: 1,
39
+ bloomIntensity: 0.4,
32
40
  bloomKernelSize: 36,
33
- showGlowLayer: true,
34
- haloScale: 1.018,
41
+ showGlowLayer: false,
42
+ haloScale: 1.006,
35
43
  showHalo: false,
36
44
  segments: 64
37
45
  };
@@ -61,11 +69,17 @@ precision highp float;
61
69
 
62
70
  uniform float uTime;
63
71
  uniform vec3 cameraPosition;
64
- uniform vec3 uColor;
72
+ uniform vec3 uTopColor;
73
+ uniform vec3 uBaseColor;
65
74
  uniform float uOpacity;
66
75
  uniform float uFresnelPower;
67
- uniform float uFresnelStrength;
76
+ uniform float uRimStrength;
68
77
  uniform float uRimIntensity;
78
+ uniform float uHorizonStrength;
79
+ uniform float uHorizonHeight;
80
+ uniform float uSurfaceVariation;
81
+ uniform float uSurfaceTextureEnabled;
82
+ uniform float uSurfaceFlowEnabled;
69
83
  uniform float uSurfaceIntensity;
70
84
  uniform float uNoiseScale;
71
85
  uniform float uNoiseEdgeWidth;
@@ -75,137 +89,69 @@ uniform float uFlowScale;
75
89
  uniform float uFlowSpeed;
76
90
  uniform float uFlowIntensity;
77
91
  uniform float uMarbleContrast;
78
- uniform float uHaloAlphaScale;
79
- uniform float uHaloMode;
92
+ uniform float uAlphaScale;
80
93
 
81
94
  varying vec3 vObjPos;
82
95
  varying vec3 vWorldPos;
83
96
  varying vec3 vNormalW;
84
97
 
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)));
98
+ float hash(vec3 position) {
99
+ position = fract(position * 0.3183099 + 0.1);
100
+ position *= 17.0;
101
+ return fract(position.x * position.y * position.z * (position.x + position.y + position.z));
136
102
  }
137
103
 
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;
104
+ float valueNoise(vec3 position) {
105
+ vec3 cell = floor(position);
106
+ vec3 local = fract(position);
107
+ local = local * local * (3.0 - 2.0 * local);
108
+
109
+ return mix(
110
+ mix(
111
+ mix(hash(cell), hash(cell + vec3(1.0, 0.0, 0.0)), local.x),
112
+ mix(hash(cell + vec3(0.0, 1.0, 0.0)), hash(cell + vec3(1.0, 1.0, 0.0)), local.x),
113
+ local.y
114
+ ),
115
+ mix(
116
+ mix(hash(cell + vec3(0.0, 0.0, 1.0)), hash(cell + vec3(1.0, 0.0, 1.0)), local.x),
117
+ mix(hash(cell + vec3(0.0, 1.0, 1.0)), hash(cell + vec3(1.0, 1.0, 1.0)), local.x),
118
+ local.y
119
+ ),
120
+ local.z
121
+ );
149
122
  }
150
123
 
151
124
  void main(void) {
152
- vec3 normalW = normalize(vNormalW);
153
125
  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;
126
+ float facing = abs(dot(normalize(vNormalW), viewDir));
127
+ float rim = pow(1.0 - facing, max(0.05, uFresnelPower));
128
+ float horizon = pow(1.0 - clamp(vObjPos.y / max(0.001, uHorizonHeight), 0.0, 1.0), 2.5);
129
+ float heightFactor = smoothstep(0.0, 0.92, vObjPos.y);
184
130
 
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;
131
+ vec3 motion = vec3(0.0);
132
+ if (uSurfaceFlowEnabled > 0.5) {
133
+ motion = vec3(uTime * uFlowSpeed, -uTime * uFlowSpeed * 0.62, uTime * uFlowSpeed * 0.38);
204
134
  }
135
+ float cloud = valueNoise(vObjPos * max(0.05, uNoiseScale)) * 0.65;
136
+ cloud += valueNoise(vObjPos * max(0.05, uFlowScale) + motion) * 0.35;
137
+ float shapedCloud = pow(clamp(cloud, 0.0, 1.0), max(0.25, uMarbleContrast));
138
+ float surfaceTexture = step(0.5, uSurfaceTextureEnabled);
139
+ float variation = mix(1.0, mix(1.0 - uSurfaceVariation, 1.0 + uSurfaceVariation, shapedCloud), surfaceTexture);
140
+ float cloudShade = mix(1.0, mix(0.92, 1.08, shapedCloud), surfaceTexture);
141
+ rim *= cloudShade;
205
142
 
206
- alpha *= uHaloAlphaScale;
143
+ float edgeWidth = max(0.005, uNoiseEdgeWidth);
144
+ float edgeSoftness = max(0.005, uNoiseEdgeSmoothness * 0.2);
145
+ float flowBand = 1.0 - smoothstep(edgeWidth, edgeWidth + edgeSoftness, abs(shapedCloud - 0.5));
146
+ float flowGlow = flowBand * uFlowIntensity * uNoiseEdgeIntensity * uSurfaceFlowEnabled * surfaceTexture;
207
147
 
208
- gl_FragColor = vec4(color, saturate(alpha));
148
+ float alpha = uOpacity * variation + rim * uRimStrength + horizon * uHorizonStrength * variation;
149
+ alpha += flowGlow * uOpacity * 0.5;
150
+ vec3 gradientColor = mix(uBaseColor, uTopColor, heightFactor);
151
+ vec3 color = gradientColor * (0.25 + rim * 1.3 + horizon * 0.85) * cloudShade * uRimIntensity;
152
+ color += gradientColor * flowGlow * uSurfaceIntensity;
153
+
154
+ gl_FragColor = vec4(color, clamp(alpha * uAlphaScale, 0.0, 0.88));
209
155
  }
210
156
  `;
211
157
  const energyShieldEffectPackage = {
@@ -222,11 +168,20 @@ const energyShieldEffectPackage = {
222
168
  debugParams: [
223
169
  { key: "radius", label: "玻璃罩半径", kind: "scale", min: 0.4, max: 10, step: 0.01 },
224
170
  { 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 },
171
+ { key: "color", label: "顶部颜色", kind: "color" },
172
+ { key: "baseColor", label: "底部颜色", kind: "color" },
173
+ { key: "opacity", label: "球壳透明度", kind: "opacity", min: 0, max: 1, step: 0.01 },
174
+ { key: "fresnelPower", label: "轮廓衰减", kind: "number", min: 0.2, max: 8, step: 0.01 },
175
+ { key: "fresnelStrength", label: "正面轮廓强度", kind: "number", min: 0, max: 2.5, step: 0.01 },
229
176
  { key: "rimIntensity", label: "轮廓亮度", kind: "number", min: 0, max: 4, step: 0.01 },
177
+ { key: "baseGlowStrength", label: "正面底部辉光", kind: "number", min: 0, max: 1, step: 0.01 },
178
+ { key: "baseGlowHeight", label: "正面辉光高度", kind: "number", min: 0.04, max: 1, step: 0.01 },
179
+ { key: "surfaceVariation", label: "表面明暗变化", kind: "number", min: 0, max: 0.6, step: 0.01 },
180
+ { key: "backRimStrength", label: "背面轮廓强度", kind: "number", min: 0, max: 1.5, step: 0.01 },
181
+ { key: "backGlowStrength", label: "背面底部辉光", kind: "number", min: 0, max: 1, step: 0.01 },
182
+ { key: "backGlowHeight", label: "背面辉光高度", kind: "number", min: 0.04, max: 1, step: 0.01 },
183
+ { key: "showSurfaceTexture", label: "显示表面纹理", kind: "boolean" },
184
+ { key: "showSurfaceFlow", label: "显示表面流动", kind: "boolean" },
230
185
  { key: "surfaceIntensity", label: "表面亮度", kind: "number", min: 0, max: 3, step: 0.01 },
231
186
  { key: "noiseScale", label: "噪声缩放", kind: "number", min: 0.2, max: 5, step: 0.01 },
232
187
  { key: "noiseEdgeWidth", label: "噪声亮边宽度", kind: "number", min: 0.01, max: 0.5, step: 0.01 },
@@ -237,10 +192,10 @@ const energyShieldEffectPackage = {
237
192
  { key: "flowIntensity", label: "流动强度", kind: "number", min: 0, max: 8, step: 0.01 },
238
193
  { key: "marbleContrast", label: "纹路锐度", kind: "number", min: 0.25, max: 4, step: 0.01 },
239
194
  { 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" }
195
+ { key: "bloomKernelSize", label: "Bloom/Glow 半径", kind: "number", min: 8, max: 128, step: 1 },
196
+ { key: "showGlowLayer", label: "显示 Bloom/Glow", kind: "boolean" },
197
+ { key: "haloScale", label: "Halo 缩放", kind: "scale", min: 1, max: 1.08, step: 1e-3 },
198
+ { key: "showHalo", label: "显示额外 Halo", kind: "boolean" }
244
199
  ],
245
200
  create({ scene, root, position, renderingGroupId, params, runtime }) {
246
201
  return createEnergyShield(scene, {
@@ -268,7 +223,7 @@ function createEnergyShield(scene, options = {}) {
268
223
  {
269
224
  diameter: 2,
270
225
  segments: Math.max(16, Math.round(params.segments)),
271
- slice: 0.5,
226
+ slice: SHIELD_SLICE,
272
227
  updatable: false
273
228
  },
274
229
  scene
@@ -278,25 +233,40 @@ function createEnergyShield(scene, options = {}) {
278
233
  shield.alwaysSelectAsActiveMesh = true;
279
234
  shield.renderingGroupId = options.renderingGroupId ?? VFX_RENDERING_GROUP_ID;
280
235
  shield.alphaIndex = 20;
281
- const material = createShieldMaterial(scene);
236
+ const material = createShieldMaterial(scene, { face: "front" });
282
237
  shield.material = material;
238
+ const backShield = MeshBuilder.CreateSphere(
239
+ "vfx_energyShield_back_hemisphere",
240
+ {
241
+ diameter: 2,
242
+ segments: Math.max(16, Math.round(params.segments)),
243
+ slice: SHIELD_SLICE,
244
+ updatable: false
245
+ },
246
+ scene
247
+ );
248
+ backShield.parent = root;
249
+ backShield.isPickable = false;
250
+ backShield.alwaysSelectAsActiveMesh = true;
251
+ backShield.renderingGroupId = shield.renderingGroupId;
252
+ backShield.alphaIndex = shield.alphaIndex - 1;
253
+ const backMaterial = createShieldMaterial(scene, {
254
+ name: "vfx_energyShield_back_material",
255
+ alphaMode: Constants.ALPHA_ADD,
256
+ face: "back"
257
+ });
258
+ backShield.material = backMaterial;
283
259
  const sharedGlowLayer = options.glowLayer ?? null;
284
260
  let glowLayer = null;
285
261
  let ownsGlowLayer = false;
286
262
  let glowMeshIncluded = false;
287
263
  let halo = null;
288
264
  let haloMaterial = null;
289
- let haloFeather = null;
290
- let haloFeatherMaterial = null;
291
265
  const disposeHalo = () => {
292
266
  halo?.dispose(false, false);
293
267
  haloMaterial?.dispose();
294
- haloFeather?.dispose(false, false);
295
- haloFeatherMaterial?.dispose();
296
268
  halo = null;
297
269
  haloMaterial = null;
298
- haloFeather = null;
299
- haloFeatherMaterial = null;
300
270
  };
301
271
  const addGlowMesh = () => {
302
272
  if (!glowLayer || glowMeshIncluded) return;
@@ -348,7 +318,7 @@ function createEnergyShield(scene, options = {}) {
348
318
  {
349
319
  diameter: 2,
350
320
  segments: Math.max(16, Math.round(params.segments)),
351
- slice: 0.5,
321
+ slice: SHIELD_SLICE,
352
322
  updatable: false
353
323
  },
354
324
  scene
@@ -361,43 +331,21 @@ function createEnergyShield(scene, options = {}) {
361
331
  halo.scaling.setAll(Math.max(1, params.haloScale));
362
332
  haloMaterial = createShieldMaterial(scene, {
363
333
  name: "vfx_energyShield_halo_material",
364
- alphaMode: Constants.ALPHA_ADD
334
+ alphaMode: Constants.ALPHA_ADD,
335
+ face: "front"
365
336
  });
366
337
  halo.material = haloMaterial;
367
338
  }
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
339
  };
392
340
  ensureGlowLayer();
393
341
  ensureHalo();
394
- applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params);
342
+ applyParams(material, shield, backMaterial, backShield, halo, haloMaterial, params);
395
343
  let elapsed = 0;
396
344
  const observer = scene.onBeforeRenderObservable.add(() => {
397
345
  elapsed += Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
398
346
  material.setFloat("uTime", elapsed);
347
+ backMaterial.setFloat("uTime", elapsed);
399
348
  haloMaterial?.setFloat("uTime", elapsed);
400
- haloFeatherMaterial?.setFloat("uTime", elapsed);
401
349
  });
402
350
  let disposed = false;
403
351
  const dispose = () => {
@@ -406,6 +354,8 @@ function createEnergyShield(scene, options = {}) {
406
354
  scene.onBeforeRenderObservable.remove(observer);
407
355
  disposeGlowLayer();
408
356
  disposeHalo();
357
+ backShield.dispose(false, false);
358
+ backMaterial.dispose();
409
359
  shield.dispose(false, false);
410
360
  material.dispose();
411
361
  if (disposeRoot) root.dispose();
@@ -414,6 +364,8 @@ function createEnergyShield(scene, options = {}) {
414
364
  root,
415
365
  shield,
416
366
  material,
367
+ backShield,
368
+ backMaterial,
417
369
  get glowLayer() {
418
370
  return glowLayer;
419
371
  },
@@ -421,7 +373,7 @@ function createEnergyShield(scene, options = {}) {
421
373
  params = mergeParams(partial, params);
422
374
  ensureGlowLayer();
423
375
  ensureHalo();
424
- applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params);
376
+ applyParams(material, shield, backMaterial, backShield, halo, haloMaterial, params);
425
377
  },
426
378
  dispose
427
379
  };
@@ -438,11 +390,17 @@ function createShieldMaterial(scene, options = {}) {
438
390
  "worldViewProjection",
439
391
  "cameraPosition",
440
392
  "uTime",
441
- "uColor",
393
+ "uTopColor",
394
+ "uBaseColor",
442
395
  "uOpacity",
443
396
  "uFresnelPower",
444
- "uFresnelStrength",
397
+ "uRimStrength",
445
398
  "uRimIntensity",
399
+ "uHorizonStrength",
400
+ "uHorizonHeight",
401
+ "uSurfaceVariation",
402
+ "uSurfaceTextureEnabled",
403
+ "uSurfaceFlowEnabled",
446
404
  "uSurfaceIntensity",
447
405
  "uNoiseScale",
448
406
  "uNoiseEdgeWidth",
@@ -452,49 +410,69 @@ function createShieldMaterial(scene, options = {}) {
452
410
  "uFlowSpeed",
453
411
  "uFlowIntensity",
454
412
  "uMarbleContrast",
455
- "uHaloAlphaScale",
456
- "uHaloMode"
413
+ "uAlphaScale"
457
414
  ],
458
415
  needAlphaBlending: true
459
416
  }
460
417
  );
461
418
  material.alphaMode = options.alphaMode ?? Constants.ALPHA_COMBINE;
462
- material.backFaceCulling = false;
419
+ material.backFaceCulling = true;
420
+ material.cullBackFaces = options.face !== "back";
463
421
  material.disableDepthWrite = true;
464
422
  material.fogEnabled = false;
465
423
  return material;
466
424
  }
467
- function applyParams(material, shield, halo, haloMaterial, haloFeather, haloFeatherMaterial, params) {
425
+ function applyParams(material, shield, backMaterial, backShield, halo, haloMaterial, params) {
468
426
  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));
427
+ backShield.scaling.copyFrom(shield.scaling);
428
+ applyMaterialParams(material, params, {
429
+ opacity: params.opacity,
430
+ rimStrength: params.fresnelStrength,
431
+ horizonStrength: params.baseGlowStrength,
432
+ horizonHeight: params.baseGlowHeight,
433
+ alphaScale: 1
434
+ });
435
+ applyMaterialParams(backMaterial, params, {
436
+ opacity: 0,
437
+ rimStrength: params.backRimStrength,
438
+ horizonStrength: params.backGlowStrength,
439
+ horizonHeight: params.backGlowHeight,
440
+ alphaScale: 1
441
+ });
442
+ const haloAlphaScale = Math.min(
443
+ HALO_ALPHA_SCALE_MAX,
444
+ Math.max(
445
+ params.opacity * Math.min(1, params.bloomIntensity),
446
+ Math.min(1, params.fresnelStrength) * 0.2
447
+ )
448
+ );
471
449
  if (halo && haloMaterial) {
472
450
  halo.scaling.setAll(Math.max(1, params.haloScale));
473
451
  applyMaterialParams(
474
452
  haloMaterial,
475
453
  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
454
+ {
455
+ opacity: 0,
456
+ rimStrength: params.fresnelStrength,
457
+ horizonStrength: 0,
458
+ horizonHeight: params.baseGlowHeight,
459
+ alphaScale: haloAlphaScale
460
+ }
489
461
  );
490
462
  }
491
463
  }
492
- function applyMaterialParams(material, params, opacity, haloAlphaScale, haloMode) {
493
- material.setColor3("uColor", toColor3(params.color));
494
- material.setFloat("uOpacity", opacity);
464
+ function applyMaterialParams(material, params, settings) {
465
+ material.setColor3("uTopColor", toColor3(params.color));
466
+ material.setColor3("uBaseColor", toColor3(params.baseColor));
467
+ material.setFloat("uOpacity", settings.opacity);
495
468
  material.setFloat("uFresnelPower", params.fresnelPower);
496
- material.setFloat("uFresnelStrength", params.fresnelStrength);
469
+ material.setFloat("uRimStrength", settings.rimStrength);
497
470
  material.setFloat("uRimIntensity", params.rimIntensity);
471
+ material.setFloat("uHorizonStrength", settings.horizonStrength);
472
+ material.setFloat("uHorizonHeight", settings.horizonHeight);
473
+ material.setFloat("uSurfaceVariation", params.surfaceVariation);
474
+ material.setFloat("uSurfaceTextureEnabled", params.showSurfaceTexture ? 1 : 0);
475
+ material.setFloat("uSurfaceFlowEnabled", params.showSurfaceFlow ? 1 : 0);
498
476
  material.setFloat("uSurfaceIntensity", params.surfaceIntensity);
499
477
  material.setFloat("uNoiseScale", params.noiseScale);
500
478
  material.setFloat("uNoiseEdgeWidth", params.noiseEdgeWidth);
@@ -504,21 +482,18 @@ function applyMaterialParams(material, params, opacity, haloAlphaScale, haloMode
504
482
  material.setFloat("uFlowSpeed", params.flowSpeed);
505
483
  material.setFloat("uFlowIntensity", params.flowIntensity);
506
484
  material.setFloat("uMarbleContrast", params.marbleContrast);
507
- material.setFloat("uHaloAlphaScale", haloAlphaScale);
508
- material.setFloat("uHaloMode", haloMode);
485
+ material.setFloat("uAlphaScale", settings.alphaScale);
509
486
  }
510
487
  function mergeParams(patch, base = DEFAULT_PARAMS) {
511
488
  return {
512
489
  ...base,
513
490
  ...patch,
514
- color: patch?.color ? { ...patch.color } : { ...base.color }
491
+ color: patch?.color ? { ...patch.color } : { ...base.color },
492
+ baseColor: patch?.baseColor ? { ...patch.baseColor } : { ...base.baseColor }
515
493
  };
516
494
  }
517
495
  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);
496
+ return new Color3(color.r, color.g, color.b).toLinearSpace();
522
497
  }
523
498
  function registerShaders() {
524
499
  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.19",
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,42 @@
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
+ "showSurfaceTexture": false,
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,
26
39
  "bloomKernelSize": 36,
27
- "showGlowLayer": true,
28
- "haloScale": 1.018,
40
+ "showGlowLayer": false,
41
+ "haloScale": 1.006,
29
42
  "showHalo": false,
30
43
  "segments": 64
31
44
  }