babylon.quarks 0.24.1 → 0.26.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.
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Texture effects shared by the sprite, mesh and trail fragment shaders, in
3
+ * both shader languages: UV scrolling, UV distortion, a gradient map and
4
+ * alpha erosion.
5
+ *
6
+ * - `UV1`: the vertex shader passes `vUV1`, the particle's own UV that the
7
+ * sprite sheet never moves (see `UVChannel`). The distortion noise and the
8
+ * erosion mask read it under `UV_DISTORTION_UV1` / `EROSION_MASK_UV1`, the
9
+ * map's UV (`vUV`) otherwise.
10
+ * - Each texture can be laid by a transform, `(tiling u, tiling v, offset u,
11
+ * offset v)`: sampled at `uv * transform.xy + transform.zw`, the offset
12
+ * holding its scroll, wrapped on the CPU so it never loses precision (see
13
+ * `TextureUV`).
14
+ * - `MAP_TRANSFORM`: the map is laid by `mapTransform`, on `vUV1` under
15
+ * `MAP_UV1` (where the sheet's second tile is not blended, the sheet not
16
+ * applying), on the sheet's UV otherwise, where a scroll runs into the
17
+ * neighbouring tiles.
18
+ * - `UV_DISTORTION`: the map's UV is pushed about by a noise texture
19
+ * (`distortionMap`, red and green around 0.5, laid by `distortionTransform`)
20
+ * by up to `distortionStrength` UV units: the heat shimmer, licking and
21
+ * breathing of fire and auras. On a sprite sheet the pushed UV can reach
22
+ * the neighbouring tile.
23
+ * - `GRADIENT_MAP`: the map's texel dotted with `gradientMapChannel` (one
24
+ * channel, or luminance weights) picks a colour along U of `gradientMap`.
25
+ * The ramp's alpha multiplies the map's, or replaces it when the lookup is
26
+ * by alpha (`gradientMapChannel.a` 1). The particle colour still tints the
27
+ * result. `gradientMapRange` keeps the lookup between the first and last
28
+ * texel centres. Make the ramp without mipmaps: the lookup's UV jumps with
29
+ * the map's detail, and the hardware would pick a blurred mip for it.
30
+ * - `EROSION`: the particle's alpha stops being a transparency and becomes how
31
+ * much of the shape is left. A fragment shows where the mask (the red
32
+ * channel of `erosionMask` under `EROSION_MASK`, laid by
33
+ * `erosionMaskTransform`; the map's alpha otherwise) is above the cut that
34
+ * alpha sets: alpha 1 shows everything the map shows, alpha 0 nothing, and
35
+ * on the way down the shape eats away from its lowest mask values first — a
36
+ * noise mask dissolves it, a gradient mask wipes it. `erosionParams` is
37
+ * (softness, edge width); the edge band just above the cut glows in
38
+ * `erosionEdgeColor`. Erosion only changes alpha, so blend modes that
39
+ * ignore alpha (one-one, premultiplied) show a hard cut.
40
+ */
41
+
42
+ /** Fragment-side declarations, GLSL. */
43
+ export const surfaceFragmentDeclarationGLSL = /* glsl */ `
44
+ #ifdef MAP_TRANSFORM
45
+ uniform vec4 mapTransform;
46
+ #endif
47
+ #ifdef UV_DISTORTION
48
+ uniform sampler2D distortionMap;
49
+ uniform vec4 distortionTransform;
50
+ uniform float distortionStrength;
51
+ #endif
52
+ #ifdef GRADIENT_MAP
53
+ uniform sampler2D gradientMap;
54
+ uniform vec2 gradientMapRange;
55
+ uniform vec4 gradientMapChannel;
56
+ #endif
57
+ #ifdef EROSION
58
+ uniform vec2 erosionParams;
59
+ uniform vec3 erosionEdgeColor;
60
+ #ifdef EROSION_MASK
61
+ uniform sampler2D erosionMask;
62
+ uniform vec4 erosionMaskTransform;
63
+ #endif
64
+
65
+ vec4 quarksErode(vec4 color, float level, float mask) {
66
+ float softness = erosionParams.x;
67
+ float edgeWidth = erosionParams.y;
68
+ // At level 1 the cut sits below the lowest mask value by the soft band
69
+ // and the edge band, so nothing is cut and no edge shows; at level 0 above
70
+ // the highest. The edge then grows in as the cut rises, rather than popping.
71
+ float cut = mix(-(softness + edgeWidth), 1.0, 1.0 - level);
72
+ float visible = smoothstep(cut, cut + softness, mask);
73
+ if (edgeWidth > 0.0) {
74
+ float edge = 1.0 - smoothstep(cut + softness, cut + softness + edgeWidth, mask);
75
+ color.rgb += erosionEdgeColor * edge;
76
+ }
77
+ color.a *= visible;
78
+ return color;
79
+ }
80
+ #endif
81
+ `;
82
+
83
+ /**
84
+ * Declares `baseColor` from `vColor` and the map, with the effects applied,
85
+ * GLSL. `tileBlend` adds the second tile of `TILE_BLEND`.
86
+ */
87
+ export function surfaceColorGLSL(tileBlend: boolean): string {
88
+ return /* glsl */ `
89
+ vec4 baseColor = vColor;
90
+ #if defined(MAP_TRANSFORM) && defined(MAP_UV1)
91
+ vec2 mapUV = vUV1 * mapTransform.xy + mapTransform.zw;
92
+ #elif defined(MAP_TRANSFORM)
93
+ vec2 mapUV = vUV * mapTransform.xy + mapTransform.zw;
94
+ #else
95
+ vec2 mapUV = vUV;
96
+ #endif
97
+ #ifdef UV_DISTORTION
98
+ #ifdef UV_DISTORTION_UV1
99
+ vec2 distortionUV = vUV1;
100
+ #else
101
+ vec2 distortionUV = vUV;
102
+ #endif
103
+ vec2 distortion = (texture2D(distortionMap, distortionUV * distortionTransform.xy + distortionTransform.zw).rg - 0.5) * 2.0 * distortionStrength;
104
+ mapUV += distortion;
105
+ #endif
106
+ #ifdef EROSION
107
+ float erosionLevel = baseColor.a;
108
+ baseColor.a = 1.0;
109
+ float erosionMaskValue = 1.0;
110
+ #endif
111
+ #ifdef USE_MAP
112
+ vec4 texColor = texture2D(map, mapUV);
113
+ ${
114
+ tileBlend
115
+ ? `#ifdef TILE_BLEND
116
+ #if defined(MAP_TRANSFORM) && defined(MAP_UV1)
117
+ vec2 mapUV2 = mapUV;
118
+ #else
119
+ #ifdef MAP_TRANSFORM
120
+ vec2 mapUV2 = vUV2 * mapTransform.xy + mapTransform.zw;
121
+ #else
122
+ vec2 mapUV2 = vUV2;
123
+ #endif
124
+ #ifdef UV_DISTORTION
125
+ mapUV2 += distortion;
126
+ #endif
127
+ #endif
128
+ vec4 texColor2 = texture2D(map, mapUV2);
129
+ texColor = mix(texColor, texColor2, vTileBlend);
130
+ #endif`
131
+ : ''
132
+ }
133
+ #ifdef GRADIENT_MAP
134
+ float rampAt = clamp(dot(texColor, gradientMapChannel), 0.0, 1.0);
135
+ vec4 ramp = texture2D(gradientMap, vec2(mix(gradientMapRange.x, gradientMapRange.y, rampAt), 0.5));
136
+ texColor = vec4(ramp.rgb, ramp.a * mix(texColor.a, 1.0, gradientMapChannel.a));
137
+ #endif
138
+ #ifdef EROSION
139
+ erosionMaskValue = texColor.a;
140
+ #endif
141
+ baseColor *= texColor;
142
+ #endif
143
+ #ifdef EROSION
144
+ #ifdef EROSION_MASK
145
+ #ifdef EROSION_MASK_UV1
146
+ vec2 erosionUV = vUV1;
147
+ #else
148
+ vec2 erosionUV = vUV;
149
+ #endif
150
+ erosionMaskValue = texture2D(erosionMask, erosionUV * erosionMaskTransform.xy + erosionMaskTransform.zw).r;
151
+ #endif
152
+ baseColor = quarksErode(baseColor, erosionLevel, erosionMaskValue);
153
+ #endif
154
+ `;
155
+ }
156
+
157
+ /** Fragment-side declarations, WGSL. */
158
+ export const surfaceFragmentDeclarationWGSL = /* wgsl */ `
159
+ #ifdef MAP_TRANSFORM
160
+ uniform mapTransform: vec4f;
161
+ #endif
162
+ #ifdef UV_DISTORTION
163
+ var distortionMapSampler: sampler;
164
+ var distortionMap: texture_2d<f32>;
165
+ uniform distortionTransform: vec4f;
166
+ uniform distortionStrength: f32;
167
+ #endif
168
+ #ifdef GRADIENT_MAP
169
+ var gradientMapSampler: sampler;
170
+ var gradientMap: texture_2d<f32>;
171
+ uniform gradientMapRange: vec2f;
172
+ uniform gradientMapChannel: vec4f;
173
+ #endif
174
+ #ifdef EROSION
175
+ uniform erosionParams: vec2f;
176
+ uniform erosionEdgeColor: vec3f;
177
+ #ifdef EROSION_MASK
178
+ var erosionMaskSampler: sampler;
179
+ var erosionMask: texture_2d<f32>;
180
+ uniform erosionMaskTransform: vec4f;
181
+ #endif
182
+
183
+ fn quarksErode(colorIn: vec4f, level: f32, mask: f32) -> vec4f {
184
+ var color = colorIn;
185
+ let softness = uniforms.erosionParams.x;
186
+ let edgeWidth = uniforms.erosionParams.y;
187
+ let cut = mix(-(softness + edgeWidth), 1.0, 1.0 - level);
188
+ let visible = smoothstep(cut, cut + softness, mask);
189
+ if (edgeWidth > 0.0) {
190
+ let edge = 1.0 - smoothstep(cut + softness, cut + softness + edgeWidth, mask);
191
+ color = vec4f(color.rgb + uniforms.erosionEdgeColor * edge, color.a);
192
+ }
193
+ color.a *= visible;
194
+ return color;
195
+ }
196
+ #endif
197
+ `;
198
+
199
+ /** Declares `baseColor` from `vColor` and the map, with the effects applied, WGSL. */
200
+ export function surfaceColorWGSL(tileBlend: boolean): string {
201
+ return /* wgsl */ `
202
+ var baseColor = fragmentInputs.vColor;
203
+ #if defined(MAP_TRANSFORM) && defined(MAP_UV1)
204
+ var mapUV = fragmentInputs.vUV1 * uniforms.mapTransform.xy + uniforms.mapTransform.zw;
205
+ #elif defined(MAP_TRANSFORM)
206
+ var mapUV = fragmentInputs.vUV * uniforms.mapTransform.xy + uniforms.mapTransform.zw;
207
+ #else
208
+ var mapUV = fragmentInputs.vUV;
209
+ #endif
210
+ #ifdef UV_DISTORTION
211
+ #ifdef UV_DISTORTION_UV1
212
+ let distortionUV = fragmentInputs.vUV1;
213
+ #else
214
+ let distortionUV = fragmentInputs.vUV;
215
+ #endif
216
+ let distortion = (textureSample(distortionMap, distortionMapSampler, distortionUV * uniforms.distortionTransform.xy + uniforms.distortionTransform.zw).rg - 0.5) * 2.0 * uniforms.distortionStrength;
217
+ mapUV += distortion;
218
+ #endif
219
+ #ifdef EROSION
220
+ let erosionLevel = baseColor.a;
221
+ baseColor.a = 1.0;
222
+ var erosionMaskValue = 1.0;
223
+ #endif
224
+ #ifdef USE_MAP
225
+ var texColor = textureSample(map, mapSampler, mapUV);
226
+ ${
227
+ tileBlend
228
+ ? `#ifdef TILE_BLEND
229
+ #if defined(MAP_TRANSFORM) && defined(MAP_UV1)
230
+ let mapUV2 = mapUV;
231
+ #else
232
+ #ifdef MAP_TRANSFORM
233
+ var mapUV2 = fragmentInputs.vUV2 * uniforms.mapTransform.xy + uniforms.mapTransform.zw;
234
+ #else
235
+ var mapUV2 = fragmentInputs.vUV2;
236
+ #endif
237
+ #ifdef UV_DISTORTION
238
+ mapUV2 += distortion;
239
+ #endif
240
+ #endif
241
+ let texColor2 = textureSample(map, mapSampler, mapUV2);
242
+ texColor = mix(texColor, texColor2, fragmentInputs.vTileBlend);
243
+ #endif`
244
+ : ''
245
+ }
246
+ #ifdef GRADIENT_MAP
247
+ let rampAt = clamp(dot(texColor, uniforms.gradientMapChannel), 0.0, 1.0);
248
+ let ramp = textureSample(gradientMap, gradientMapSampler, vec2f(mix(uniforms.gradientMapRange.x, uniforms.gradientMapRange.y, rampAt), 0.5));
249
+ texColor = vec4f(ramp.rgb, ramp.a * mix(texColor.a, 1.0, uniforms.gradientMapChannel.a));
250
+ #endif
251
+ #ifdef EROSION
252
+ erosionMaskValue = texColor.a;
253
+ #endif
254
+ baseColor *= texColor;
255
+ #endif
256
+ #ifdef EROSION
257
+ #ifdef EROSION_MASK
258
+ #ifdef EROSION_MASK_UV1
259
+ let erosionUV = fragmentInputs.vUV1;
260
+ #else
261
+ let erosionUV = fragmentInputs.vUV;
262
+ #endif
263
+ erosionMaskValue = textureSample(erosionMask, erosionMaskSampler, erosionUV * uniforms.erosionMaskTransform.xy + uniforms.erosionMaskTransform.zw).r;
264
+ #endif
265
+ baseColor = quarksErode(baseColor, erosionLevel, erosionMaskValue);
266
+ #endif
267
+ `;
268
+ }
@@ -21,6 +21,14 @@ uniform float tileCountY;
21
21
 
22
22
  varying vec2 vUV;
23
23
  varying vec4 vColor;
24
+ #ifdef UV1
25
+ #ifdef MESH_UV2
26
+ // The mesh's second UV set.
27
+ attribute vec2 uv2;
28
+ #endif
29
+ // UV channel 1: the mesh's second UV set, or its first when it has none.
30
+ varying vec2 vUV1;
31
+ #endif
24
32
  varying vec3 vNormal;
25
33
  varying vec3 vWorldPos;
26
34
 
@@ -78,6 +86,13 @@ ${fogVertexGLSL('viewPos.xyz')}
78
86
  vUV = uv;
79
87
  #endif
80
88
 
89
+ #ifdef UV1
90
+ #ifdef MESH_UV2
91
+ vUV1 = uv2;
92
+ #else
93
+ vUV1 = uv;
94
+ #endif
95
+ #endif
81
96
  vColor = color;
82
97
  }
83
98
  `;
@@ -21,6 +21,14 @@ uniform tileCountY: f32;
21
21
 
22
22
  varying vUV: vec2f;
23
23
  varying vColor: vec4f;
24
+ #ifdef UV1
25
+ #ifdef MESH_UV2
26
+ // The mesh's second UV set.
27
+ attribute uv2: vec2f;
28
+ #endif
29
+ // UV channel 1: the mesh's second UV set, or its first when it has none.
30
+ varying vUV1: vec2f;
31
+ #endif
24
32
  varying vNormal: vec3f;
25
33
  varying vWorldPos: vec3f;
26
34
 
@@ -80,6 +88,13 @@ ${fogVertexWGSL('viewPos.xyz')}
80
88
  vertexOutputs.vUV = vertexInputs.uv;
81
89
  #endif
82
90
 
91
+ #ifdef UV1
92
+ #ifdef MESH_UV2
93
+ vertexOutputs.vUV1 = vertexInputs.uv2;
94
+ #else
95
+ vertexOutputs.vUV1 = vertexInputs.uv;
96
+ #endif
97
+ #endif
83
98
  vertexOutputs.vColor = vertexInputs.color;
84
99
  }
85
100
  `;
@@ -1,8 +1,12 @@
1
1
  import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
2
+ import {surfaceColorGLSL, surfaceFragmentDeclarationGLSL} from './chunks/surface';
2
3
 
3
4
  export default /* glsl */ `
4
5
  varying vec2 vUV;
5
6
  varying vec4 vColor;
7
+ #ifdef UV1
8
+ varying vec2 vUV1;
9
+ #endif
6
10
 
7
11
  #ifdef TILE_BLEND
8
12
  varying vec2 vUV2;
@@ -26,18 +30,9 @@ uniform float alphaTest;
26
30
  #endif
27
31
 
28
32
  ${fogFragmentDeclarationGLSL}
33
+ ${surfaceFragmentDeclarationGLSL}
29
34
  void main() {
30
- vec4 baseColor = vColor;
31
-
32
- #ifdef USE_MAP
33
- vec4 texColor = texture2D(map, vUV);
34
- #ifdef TILE_BLEND
35
- vec4 texColor2 = texture2D(map, vUV2);
36
- texColor = mix(texColor, texColor2, vTileBlend);
37
- #endif
38
- baseColor *= texColor;
39
- #endif
40
-
35
+ ${surfaceColorGLSL(true)}
41
36
  #ifdef USE_COLOR_AS_ALPHA
42
37
  baseColor.a *= (baseColor.r + baseColor.g + baseColor.b) / 3.0;
43
38
  #endif
@@ -1,8 +1,12 @@
1
1
  import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
2
+ import {surfaceColorWGSL, surfaceFragmentDeclarationWGSL} from './chunks/surface';
2
3
 
3
4
  export default /* wgsl */ `
4
5
  varying vUV: vec2f;
5
6
  varying vColor: vec4f;
7
+ #ifdef UV1
8
+ varying vUV1: vec2f;
9
+ #endif
6
10
 
7
11
  #ifdef TILE_BLEND
8
12
  varying vUV2: vec2f;
@@ -28,19 +32,10 @@ uniform alphaTest: f32;
28
32
  #endif
29
33
 
30
34
  ${fogFragmentDeclarationWGSL}
35
+ ${surfaceFragmentDeclarationWGSL}
31
36
  @fragment
32
37
  fn main(input: FragmentInputs) -> FragmentOutputs {
33
- var baseColor = fragmentInputs.vColor;
34
-
35
- #ifdef USE_MAP
36
- var texColor = textureSample(map, mapSampler, fragmentInputs.vUV);
37
- #ifdef TILE_BLEND
38
- let texColor2 = textureSample(map, mapSampler, fragmentInputs.vUV2);
39
- texColor = mix(texColor, texColor2, fragmentInputs.vTileBlend);
40
- #endif
41
- baseColor *= texColor;
42
- #endif
43
-
38
+ ${surfaceColorWGSL(true)}
44
39
  #ifdef USE_COLOR_AS_ALPHA
45
40
  baseColor.a *= (baseColor.r + baseColor.g + baseColor.b) / 3.0;
46
41
  #endif
@@ -1,8 +1,12 @@
1
1
  import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
2
+ import {surfaceColorGLSL, surfaceFragmentDeclarationGLSL} from './chunks/surface';
2
3
 
3
4
  export default /* glsl */ `
4
5
  varying vec2 vUV;
5
6
  varying vec4 vColor;
7
+ #ifdef UV1
8
+ varying vec2 vUV1;
9
+ #endif
6
10
  varying vec3 vNormal;
7
11
  varying vec3 vWorldPos;
8
12
 
@@ -88,18 +92,9 @@ uniform float alphaTest;
88
92
  #endif
89
93
 
90
94
  ${fogFragmentDeclarationGLSL}
95
+ ${surfaceFragmentDeclarationGLSL}
91
96
  void main() {
92
- vec4 baseColor = vColor;
93
-
94
- #ifdef USE_MAP
95
- vec4 texColor = texture2D(map, vUV);
96
- #ifdef TILE_BLEND
97
- vec4 texColor2 = texture2D(map, vUV2);
98
- texColor = mix(texColor, texColor2, vTileBlend);
99
- #endif
100
- baseColor *= texColor;
101
- #endif
102
-
97
+ ${surfaceColorGLSL(true)}
103
98
  #ifdef USE_ALPHATEST
104
99
  if (baseColor.a < alphaTest) discard;
105
100
  #else
@@ -126,11 +121,14 @@ void main() {
126
121
  #ifdef MESH_RIM
127
122
  // Fresnel: strongest where the surface turns away from the camera.
128
123
  vec3 V = normalize(eyePosition - vWorldPos);
129
- float rim = pow(1.0 - abs(dot(N, V)), rimParams.x) * rimParams.y;
130
- #ifdef MESH_RIM_MIX
131
- color = mix(color, rimColor, clamp(rim, 0.0, 1.0));
124
+ float edgeTerm = pow(1.0 - abs(dot(N, V)), rimParams.x);
125
+ #if defined(MESH_RIM_FADE)
126
+ // Faces turned to the camera fade by the strength; edges stay.
127
+ baseColor.a *= clamp(1.0 - rimParams.y * (1.0 - edgeTerm), 0.0, 1.0);
128
+ #elif defined(MESH_RIM_MIX)
129
+ color = mix(color, rimColor, clamp(edgeTerm * rimParams.y, 0.0, 1.0));
132
130
  #else
133
- color += rimColor * rim;
131
+ color += rimColor * edgeTerm * rimParams.y;
134
132
  #endif
135
133
  #endif
136
134
 
@@ -1,8 +1,12 @@
1
1
  import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
2
+ import {surfaceColorWGSL, surfaceFragmentDeclarationWGSL} from './chunks/surface';
2
3
 
3
4
  export default /* wgsl */ `
4
5
  varying vUV: vec2f;
5
6
  varying vColor: vec4f;
7
+ #ifdef UV1
8
+ varying vUV1: vec2f;
9
+ #endif
6
10
  varying vNormal: vec3f;
7
11
  varying vWorldPos: vec3f;
8
12
 
@@ -88,19 +92,10 @@ uniform alphaTest: f32;
88
92
  #endif
89
93
 
90
94
  ${fogFragmentDeclarationWGSL}
95
+ ${surfaceFragmentDeclarationWGSL}
91
96
  @fragment
92
97
  fn main(input: FragmentInputs) -> FragmentOutputs {
93
- var baseColor = fragmentInputs.vColor;
94
-
95
- #ifdef USE_MAP
96
- var texColor = textureSample(map, mapSampler, fragmentInputs.vUV);
97
- #ifdef TILE_BLEND
98
- let texColor2 = textureSample(map, mapSampler, fragmentInputs.vUV2);
99
- texColor = mix(texColor, texColor2, fragmentInputs.vTileBlend);
100
- #endif
101
- baseColor *= texColor;
102
- #endif
103
-
98
+ ${surfaceColorWGSL(true)}
104
99
  #ifdef USE_ALPHATEST
105
100
  if (baseColor.a < uniforms.alphaTest) { discard; }
106
101
  #else
@@ -127,11 +122,14 @@ fn main(input: FragmentInputs) -> FragmentOutputs {
127
122
  #ifdef MESH_RIM
128
123
  // Fresnel: strongest where the surface turns away from the camera.
129
124
  let V = normalize(uniforms.eyePosition - fragmentInputs.vWorldPos);
130
- let rimTerm = pow(1.0 - abs(dot(N, V)), uniforms.rimParams.x) * uniforms.rimParams.y;
131
- #ifdef MESH_RIM_MIX
132
- color = mix(color, uniforms.rimColor, clamp(rimTerm, 0.0, 1.0));
125
+ let edgeTerm = pow(1.0 - abs(dot(N, V)), uniforms.rimParams.x);
126
+ #if defined(MESH_RIM_FADE)
127
+ // Faces turned to the camera fade by the strength; edges stay.
128
+ baseColor.a *= clamp(1.0 - uniforms.rimParams.y * (1.0 - edgeTerm), 0.0, 1.0);
129
+ #elif defined(MESH_RIM_MIX)
130
+ color = mix(color, uniforms.rimColor, clamp(edgeTerm * uniforms.rimParams.y, 0.0, 1.0));
133
131
  #else
134
- color += uniforms.rimColor * rimTerm;
132
+ color += uniforms.rimColor * edgeTerm * uniforms.rimParams.y;
135
133
  #endif
136
134
  #endif
137
135
 
@@ -31,6 +31,10 @@ uniform vec3 pivot;
31
31
  // Varyings
32
32
  varying vec2 vUV;
33
33
  varying vec4 vColor;
34
+ #ifdef UV1
35
+ // The particle's own UV, never moved by the sheet (UV channel 1).
36
+ varying vec2 vUV1;
37
+ #endif
34
38
  #ifdef TILE_BLEND
35
39
  varying vec2 vUV2;
36
40
  varying float vTileBlend;
@@ -119,6 +123,9 @@ ${fogVertexGLSL('mvPosition.xyz')}
119
123
  vUV = uv;
120
124
  #endif
121
125
 
126
+ #ifdef UV1
127
+ vUV1 = uv;
128
+ #endif
122
129
  vColor = color;
123
130
  }
124
131
  `;
@@ -30,6 +30,10 @@ uniform pivot: vec3f;
30
30
  // Varyings
31
31
  varying vUV: vec2f;
32
32
  varying vColor: vec4f;
33
+ #ifdef UV1
34
+ // The particle's own UV, never moved by the sheet (UV channel 1).
35
+ varying vUV1: vec2f;
36
+ #endif
33
37
  #ifdef TILE_BLEND
34
38
  varying vUV2: vec2f;
35
39
  varying vTileBlend: f32;
@@ -121,6 +125,9 @@ ${fogVertexWGSL('mvPosition.xyz')}
121
125
  vertexOutputs.vUV = vertexInputs.uv;
122
126
  #endif
123
127
 
128
+ #ifdef UV1
129
+ vertexOutputs.vUV1 = vertexInputs.uv;
130
+ #endif
124
131
  vertexOutputs.vColor = vertexInputs.color;
125
132
  }
126
133
  `;
@@ -32,6 +32,10 @@ uniform vec3 pivot;
32
32
  // Varyings
33
33
  varying vec2 vUV;
34
34
  varying vec4 vColor;
35
+ #ifdef UV1
36
+ // The particle's own UV, never moved by the sheet (UV channel 1).
37
+ varying vec2 vUV1;
38
+ #endif
35
39
  #ifdef TILE_BLEND
36
40
  varying vec2 vUV2;
37
41
  varying float vTileBlend;
@@ -45,6 +49,7 @@ varying float linearDepth;
45
49
  // Smallest and largest size a particle may have on screen, as fractions of the viewport height.
46
50
  uniform vec2 sizeLimits;
47
51
  #endif
52
+
48
53
  ${fogVertexDeclarationGLSL}
49
54
  void main() {
50
55
  float lengthFactor = velocity.w;
@@ -52,6 +57,19 @@ void main() {
52
57
  // extent and mirrors its texture instead (see quadUV below).
53
58
  float avgSize = (abs(size.x) + abs(size.y)) * 0.5;
54
59
  vec4 mvPosition = view * world * vec4(offset, 1.0);
60
+ vec3 viewVelocity = mat3(view * world) * velocity.xyz;
61
+ float vlength = length(viewVelocity);
62
+ // Stretch direction is the velocity direction; only a genuinely motionless particle has none.
63
+ // Deriving it via normalize keeps the stretch aligned even when the speed contribution is ~0
64
+ // (e.g. speedFactor 0), instead of collapsing the whole burst onto a fixed screen axis.
65
+ vec3 vdir = vlength > 0.000001 ? viewVelocity / vlength : vec3(0.0, 0.0, 1.0);
66
+ #ifdef BEAM
67
+ // Seen straight along its length a beam has no side to face; any will do.
68
+ vec3 sideRaw = cross(mvPosition.xyz, vdir);
69
+ vec3 side = dot(sideRaw, sideRaw) > 1e-12 ? normalize(sideRaw) : vec3(1.0, 0.0, 0.0);
70
+ #else
71
+ vec3 side = normalize(cross(mvPosition.xyz, vdir));
72
+ #endif
55
73
  #ifdef SIZE_LIMITS
56
74
  // The streak's width on screen as a fraction of the viewport height, held
57
75
  // between the limits; a perspective projection divides by the view depth.
@@ -61,17 +79,16 @@ void main() {
61
79
  avgSize *= clamp(screenSize, sizeLimits.x, sizeLimits.y) / screenSize;
62
80
  }
63
81
  #endif
64
- vec3 viewVelocity = mat3(view * world) * velocity.xyz;
65
- float vlength = length(viewVelocity);
66
- // Stretch direction is the velocity direction; only a genuinely motionless particle has none.
67
- // Deriving it via normalize keeps the stretch aligned even when the speed contribution is ~0
68
- // (e.g. speedFactor 0), instead of collapsing the whole burst onto a fixed screen axis.
69
- vec3 vdir = vlength > 0.000001 ? viewVelocity / vlength : vec3(0.0, 0.0, 1.0);
70
- vec3 side = normalize(cross(mvPosition.xyz, vdir));
71
82
  mvPosition.xyz += position.y * side * avgSize;
83
+ #ifdef BEAM
84
+ // The vector runs from the particle to the beam's end: the quad spans it
85
+ // exactly, U from the particle (0) to the end (1), as wide as the size.
86
+ mvPosition.xyz += (position.x + 0.5) * viewVelocity;
87
+ #else
72
88
  // Equivalent to viewVelocity * (1.0 + lengthFactor / vlength) for moving particles, but the
73
89
  // size-based term (vdir * lengthFactor) survives when the velocity contribution vanishes.
74
90
  mvPosition.xyz -= (position.x + 0.5) * (viewVelocity + vdir * lengthFactor) * avgSize;
91
+ #endif
75
92
  #ifdef PIVOT
76
93
  // In particle-size units along the quad's own axes: x from head to tail, y across.
77
94
  mvPosition.xyz += (pivot.y * side - pivot.x * vdir) * avgSize;
@@ -101,6 +118,9 @@ ${fogVertexGLSL('mvPosition.xyz')}
101
118
  vUV = quadUV;
102
119
  #endif
103
120
 
121
+ #ifdef UV1
122
+ vUV1 = quadUV;
123
+ #endif
104
124
  vColor = color;
105
125
  }
106
126
  `;