@wave3d/core 0.6.0 → 0.8.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.
- package/dist/config/model.d.ts +100 -8
- package/dist/config/model.js +47 -1
- package/dist/config/model.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/presets.d.ts +8 -2
- package/dist/presets.js +472 -1
- package/dist/presets.js.map +1 -1
- package/dist/renderer/WaveGeometry.d.ts +22 -3
- package/dist/renderer/WaveGeometry.js +22 -3
- package/dist/renderer/WaveGeometry.js.map +1 -1
- package/dist/renderer/WaveRenderer.d.ts +22 -0
- package/dist/renderer/WaveRenderer.js +107 -1
- package/dist/renderer/WaveRenderer.js.map +1 -1
- package/dist/renderer/palette.d.ts +1 -1
- package/dist/renderer/palette.js +7 -5
- package/dist/renderer/palette.js.map +1 -1
- package/dist/renderer/particleField.d.ts +81 -0
- package/dist/renderer/particleField.js +357 -0
- package/dist/renderer/particleField.js.map +1 -0
- package/dist/renderer/shaders.js +377 -127
- package/dist/renderer/shaders.js.map +1 -1
- package/dist/standalone/wave3d.standalone.js +2621 -1787
- package/dist/standalone.d.ts +2 -2
- package/dist/standalone.js +2 -2
- package/dist/studio/thumbnail.d.ts +2 -2
- package/dist/studio/thumbnail.js +16 -7
- package/dist/studio/thumbnail.js.map +1 -1
- package/package.json +6 -4
- package/skills/wave3d/SKILL.md +1 -1
package/dist/renderer/shaders.js
CHANGED
|
@@ -157,57 +157,7 @@ vec3 applyColorGrade(vec3 c){
|
|
|
157
157
|
return hueShift(c, radians(uHueShift));
|
|
158
158
|
}
|
|
159
159
|
`;
|
|
160
|
-
const
|
|
161
|
-
${simplex2d}
|
|
162
|
-
|
|
163
|
-
uniform float uTime, uSpeed, uSeed;
|
|
164
|
-
uniform float uDispFreqX, uDispFreqZ, uDispAmount;
|
|
165
|
-
uniform float uDetailFreq, uDetailAmount; // 2nd displacement octave (only read under DETAIL_OCTAVE)
|
|
166
|
-
uniform float uTwFreqX, uTwFreqY, uTwFreqZ, uTwPowX, uTwPowY, uTwPowZ;
|
|
167
|
-
uniform float uLoopSeconds; // seamless-loop period (only read under LOOP_MOTION)
|
|
168
|
-
|
|
169
|
-
// Helix (optional). Behind HELIX so a wave without one compiles the exact same program — same
|
|
170
|
-
// byte-identity contract as the pointer block below.
|
|
171
|
-
#ifdef HELIX
|
|
172
|
-
uniform float uHelixTurns; // full turns from one end of the ribbon to the other
|
|
173
|
-
uniform float uHelixRadius; // orbit radius: carries the whole ribbon around the axis
|
|
174
|
-
uniform float uHelixRoll; // cross-section roll, as a fraction of the turns (1 = rigid ladder)
|
|
175
|
-
uniform float uHelixPhase; // degrees
|
|
176
|
-
#endif
|
|
177
|
-
|
|
178
|
-
varying vec2 vUv;
|
|
179
|
-
varying vec3 vWorldPos;
|
|
180
|
-
varying vec3 vViewDir;
|
|
181
|
-
varying vec4 vClipPosition; // = gl_Position, for the wireframe theme's depth fade
|
|
182
|
-
|
|
183
|
-
// Pointer field (optional, additive). ALL declarations here sit behind POINTER_FX so a wave with
|
|
184
|
-
// no interaction config compiles the exact same program (JS-side uniform entries are always present
|
|
185
|
-
// — see makeUniforms — but three only uploads uniforms the compiled program actually declares).
|
|
186
|
-
#ifdef POINTER_FX
|
|
187
|
-
uniform vec2 uPointer; // smoothed pointer, NDC (-1..1)
|
|
188
|
-
uniform float uPointerActive; // presence ramp 0..1 × per-wave influence
|
|
189
|
-
uniform float uPointerRadius; // falloff radius in NDC-y units (config radius × 2)
|
|
190
|
-
uniform float uPointerAspect; // drawing-buffer dw/dh (circular screen falloff)
|
|
191
|
-
uniform float uPointerAgitate;
|
|
192
|
-
uniform float uPointerPush; // signed membrane dome at the cursor (+ repel / − attract)
|
|
193
|
-
uniform float uPointerWake; // drag-wake trough amplitude (behind the moving cursor)
|
|
194
|
-
uniform vec2 uPointerVel; // smoothed pointer velocity, NDC/s (drag-wake direction)
|
|
195
|
-
// Ribbon flow: stretch the falloff along the strip's length axis so the field reaches ALONG the
|
|
196
|
-
// ribbon rather than as a screen disc. 0 = the plain circular smoothstep (byte-identical when off).
|
|
197
|
-
uniform float uShapeFlow;
|
|
198
|
-
varying float vPointerFall; // falloff × presence — consumed by both fragment themes
|
|
199
|
-
#ifdef POINTER_RIPPLES
|
|
200
|
-
uniform vec2 uRippleOrigin[4]; // NDC
|
|
201
|
-
uniform float uRippleAge[4]; // seconds since spawn (CPU-computed)
|
|
202
|
-
uniform float uRippleAmp[4]; // shared 0..1 decay envelope per slot (CPU-computed; 0 = slot free)
|
|
203
|
-
uniform float uPointerRipple; // THIS wave's ripple amplitude (scales the shared envelope)
|
|
204
|
-
const float RIPPLE_WAVE_SPEED = 0.85; // NDC/s the ring crest travels outward
|
|
205
|
-
const float RIPPLE_SIGMA = 0.14; // gaussian half-width of the travelling packet (NDC)
|
|
206
|
-
const float RIPPLE_FREQ = 11.0; // oscillation within the packet (one crest + faint troughs)
|
|
207
|
-
const float RIPPLE_MAX_R = 1.2; // reach where the crest has fully left the frame
|
|
208
|
-
#endif
|
|
209
|
-
#endif
|
|
210
|
-
|
|
160
|
+
const waveShapeChunk = `
|
|
211
161
|
// expStep: a falloff from 1 (at x=0) toward 0, sharpness set by n. The
|
|
212
162
|
// max() guards pow(0, n) (= Infinity → NaN) so negative n is safe — negative n
|
|
213
163
|
// just concentrates the twist toward the OTHER end instead.
|
|
@@ -225,27 +175,15 @@ mat4 rotationMatrix(vec3 axis, float angle){
|
|
|
225
175
|
);
|
|
226
176
|
}
|
|
227
177
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
//
|
|
237
|
-
// uLoopSeconds. The tangential speed loopR·dθ/dt equals uSpeed, so the looped motion advances
|
|
238
|
-
// at the same rate as the linear drift, just curved into a closed orbit (it orbits rather than
|
|
239
|
-
// drifts — the trade-off for a seamless loop, hence opt-in). uSeed offsets the phase so stacked
|
|
240
|
-
// waves keep their relative motion while sharing the single period.
|
|
241
|
-
float loopTheta = uTime * (6.28318530718 / uLoopSeconds) + uSeed;
|
|
242
|
-
float loopR = uSpeed * uLoopSeconds * 0.159154943092; // = uSpeed·uLoopSeconds / (2π)
|
|
243
|
-
vec2 loopOff = loopR * vec2(cos(loopTheta), sin(loopTheta));
|
|
244
|
-
#endif
|
|
245
|
-
|
|
246
|
-
// The base geometry is already a baked hairpin fold. On top of it we deform the
|
|
247
|
-
// vertices: a displacement lifts Y by simplex noise of the (x,z) position, then
|
|
248
|
-
// three axis-rotations twist the strip.
|
|
178
|
+
// The wave SHAPE deform, shared by the wave vertex shader (below) and the particle SHED emitter
|
|
179
|
+
// (particleVertexShader): a base hairpin position + its uv → the displaced / helixed / twisted /
|
|
180
|
+
// fanned LOCAL position, plus the three twist matrices (the pointer field reads them). Every branch
|
|
181
|
+
// sits behind the SAME #ifdef gates as the code it replaces, so a given compiled program is
|
|
182
|
+
// byte-identical to the former inline version. t / loopOff are the linear / orbit time the
|
|
183
|
+
// caller computed; only the one selected by LOOP_MOTION is read (the other is a dead argument).
|
|
184
|
+
struct WaveShape { vec3 pos; mat4 rotA; mat4 rotB; mat4 rotC; };
|
|
185
|
+
WaveShape waveShape(vec3 position, vec2 uv, float t, vec2 loopOff){
|
|
186
|
+
// Displacement lifts Y by simplex noise of the (x,z) position.
|
|
249
187
|
vec3 pos = position;
|
|
250
188
|
#ifdef LOOP_MOTION
|
|
251
189
|
pos.y += uDispAmount * simplexNoise(vec2(pos.x * uDispFreqX, pos.z * uDispFreqZ) + loopOff);
|
|
@@ -253,9 +191,7 @@ void main(){
|
|
|
253
191
|
pos.y += uDispAmount * simplexNoise(vec2(pos.x * uDispFreqX + t, pos.z * uDispFreqZ + t));
|
|
254
192
|
#endif
|
|
255
193
|
#ifdef DETAIL_OCTAVE
|
|
256
|
-
// A second, finer octave riding on the broad swell
|
|
257
|
-
// shape vocabulary single-octave displacement can't reach. Shares the loop orbit so it stays
|
|
258
|
-
// periodic when looping.
|
|
194
|
+
// A second, finer octave riding on the broad swell (loop-orbit shared so it stays periodic).
|
|
259
195
|
#ifdef LOOP_MOTION
|
|
260
196
|
pos.y += uDetailAmount * simplexNoise(vec2(pos.x * uDetailFreq, pos.z * uDetailFreq) + loopOff);
|
|
261
197
|
#else
|
|
@@ -264,16 +200,8 @@ void main(){
|
|
|
264
200
|
#endif
|
|
265
201
|
|
|
266
202
|
#ifdef HELIX
|
|
267
|
-
// Helix — the
|
|
268
|
-
//
|
|
269
|
-
// uHelixTurns full turns land evenly from end to end. Runs AFTER the displacement so the noise
|
|
270
|
-
// above still samples the undeformed pos.x/pos.z (byte-identical sampling), and BEFORE the twist
|
|
271
|
-
// so the two compose.
|
|
272
|
-
// roll rolls the ribbon's own cross-section about the axis in step with the sweep, swinging
|
|
273
|
-
// its two long edges onto opposite sides — one wave becomes a ladder whose edges are
|
|
274
|
-
// both strands (pair with the wireframe theme's rungs for the rungs between them).
|
|
275
|
-
// radius carries the whole ribbon around the axis instead, orientation intact — a narrow ribbon
|
|
276
|
-
// then reads as ONE strand, and a second wave at phase+180 is the other.
|
|
203
|
+
// Helix — the periodic sweep the three twists (monotone falloffs) can't reach. Runs AFTER the
|
|
204
|
+
// displacement (so the noise still samples undeformed pos) and BEFORE the twist (so they compose).
|
|
277
205
|
float hAng = 6.28318530718 * uHelixTurns * uv.y + radians(uHelixPhase);
|
|
278
206
|
// Roll about the ribbon's width centre, not the origin — see RIBBON_Z_CENTER in WaveGeometry.
|
|
279
207
|
float rollA = hAng * uHelixRoll;
|
|
@@ -285,23 +213,19 @@ void main(){
|
|
|
285
213
|
pos.z += uHelixRadius * sin(hAng);
|
|
286
214
|
#endif
|
|
287
215
|
|
|
288
|
-
// The X-twist frequency feeding rotB
|
|
289
|
-
//
|
|
290
|
-
// simplex noise indexed along the ribbon (uv.y) so the twist breathes over time.
|
|
291
|
-
// We gate the wobble with a #define so the compiled program is unchanged when off.
|
|
216
|
+
// The X-twist frequency feeding rotB; the TWIST_MOTION variant modulates it with simplex noise
|
|
217
|
+
// indexed along the ribbon (uv.y) so the twist breathes over time.
|
|
292
218
|
float twistXFreq = uTwFreqX;
|
|
293
219
|
#ifdef TWIST_MOTION
|
|
294
220
|
#ifdef LOOP_MOTION
|
|
295
|
-
float twistXNoise = simplexNoise(vec2(
|
|
221
|
+
float twistXNoise = simplexNoise(vec2(uv.y * 2.0, 0.0) + loopOff);
|
|
296
222
|
#else
|
|
297
|
-
float twistXNoise = simplexNoise(vec2(
|
|
223
|
+
float twistXNoise = simplexNoise(vec2(uv.y * 2.0, t));
|
|
298
224
|
#endif
|
|
299
225
|
twistXFreq = uTwFreqX - twistXNoise * 0.1;
|
|
300
226
|
#endif
|
|
301
227
|
|
|
302
|
-
// Three-axis twist
|
|
303
|
-
// sharply each rotation concentrates toward an edge. rotA keys off uv.x, rotB/rotC
|
|
304
|
-
// off uv.y; axes (0.5,0,0.5) and (0,0.5,0.5) are normalised inside rotationMatrix.
|
|
228
|
+
// Three-axis twist (see the falloff-axis note: rotA keys off uv.x/WIDTH, rotB/rotC off uv.y/LENGTH).
|
|
305
229
|
mat4 rotA = rotationMatrix(vec3(0.5, 0.0, 0.5), uTwFreqY * expStep(uv.x, uTwPowY));
|
|
306
230
|
mat4 rotB = rotationMatrix(vec3(0.0, 0.5, 0.5), twistXFreq * expStep(uv.y, uTwPowX));
|
|
307
231
|
mat4 rotC = rotationMatrix(vec3(0.5, 0.0, 0.5), uTwFreqZ * expStep(uv.y, uTwPowZ));
|
|
@@ -309,19 +233,66 @@ void main(){
|
|
|
309
233
|
pos = (vec4(pos, 1.0) * rotB).xyz;
|
|
310
234
|
pos = (vec4(pos, 1.0) * rotC).xyz;
|
|
311
235
|
|
|
312
|
-
#ifdef
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
// byte-identical
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
236
|
+
#ifdef RADIAL
|
|
237
|
+
// Radial fan: remap the ribbon to polar around the LOCAL origin so its LENGTH fans into a plume.
|
|
238
|
+
// uv.x (folded WIDTH) → fan ANGLE across uRadialArc; uv.y (LENGTH) → RADIUS, so a constant-uv.x
|
|
239
|
+
// combed fiber becomes a constant-angle radial spoke. mix(pos, fanned, 0) is identity → off is
|
|
240
|
+
// byte-identical. (Placement is the wave's position transform — the fan has no separate pivot.)
|
|
241
|
+
{
|
|
242
|
+
float rAng = radians(uRadialCenter) + (clamp(uv.x, 0.0, 1.0) - 0.5) * radians(uRadialArc);
|
|
243
|
+
float rRho = uRadialRadius + uv.y * 400.0 * uRadialSpread; // 400 = native ribbon length
|
|
244
|
+
vec3 rEr = vec3(cos(rAng), sin(rAng), 0.0); // radial dir, in local X–Y (screen plane)
|
|
245
|
+
vec3 rEt = vec3(-sin(rAng), cos(rAng), 0.0); // tangential
|
|
246
|
+
vec3 fanned = rEr * rRho
|
|
247
|
+
+ rEt * (pos.z - ${(-8).toFixed(1)}) * 0.5
|
|
248
|
+
+ vec3(0.0, 0.0, pos.y);
|
|
249
|
+
pos = mix(pos, fanned, clamp(uRadialAmount, 0.0, 1.0));
|
|
250
|
+
}
|
|
251
|
+
#endif
|
|
252
|
+
|
|
253
|
+
WaveShape s;
|
|
254
|
+
s.pos = pos;
|
|
255
|
+
s.rotA = rotA;
|
|
256
|
+
s.rotB = rotB;
|
|
257
|
+
s.rotC = rotC;
|
|
258
|
+
return s;
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
const pointerFieldChunk = `
|
|
262
|
+
uniform vec2 uPointer; // smoothed pointer, NDC (-1..1)
|
|
263
|
+
uniform float uPointerActive; // presence ramp 0..1 × per-wave influence
|
|
264
|
+
uniform float uPointerRadius; // falloff radius in NDC-y units (config radius × 2)
|
|
265
|
+
uniform float uPointerAspect; // drawing-buffer dw/dh (circular screen falloff)
|
|
266
|
+
uniform float uPointerAgitate;
|
|
267
|
+
uniform float uPointerPush; // signed membrane dome at the cursor (+ repel / − attract)
|
|
268
|
+
uniform float uPointerWake; // drag-wake trough amplitude (behind the moving cursor)
|
|
269
|
+
uniform vec2 uPointerVel; // smoothed pointer velocity, NDC/s (drag-wake direction)
|
|
270
|
+
// Ribbon flow: stretch the falloff along the strip's length axis so the field reaches ALONG the
|
|
271
|
+
// ribbon rather than as a screen disc. 0 = the plain circular smoothstep (byte-identical when off).
|
|
272
|
+
uniform float uShapeFlow;
|
|
273
|
+
#ifdef POINTER_RIPPLES
|
|
274
|
+
uniform vec2 uRippleOrigin[4]; // NDC
|
|
275
|
+
uniform float uRippleAge[4]; // seconds since spawn (CPU-computed)
|
|
276
|
+
uniform float uRippleAmp[4]; // shared 0..1 decay envelope per slot (CPU-computed; 0 = slot free)
|
|
277
|
+
uniform float uPointerRipple; // THIS wave's ripple amplitude (scales the shared envelope)
|
|
278
|
+
const float RIPPLE_WAVE_SPEED = 0.85; // NDC/s the ring crest travels outward
|
|
279
|
+
const float RIPPLE_SIGMA = 0.14; // gaussian half-width of the travelling packet (NDC)
|
|
280
|
+
const float RIPPLE_FREQ = 11.0; // oscillation within the packet (one crest + faint troughs)
|
|
281
|
+
const float RIPPLE_MAX_R = 1.2; // reach where the crest has fully left the frame
|
|
282
|
+
#endif
|
|
283
|
+
|
|
284
|
+
// fall = screen falloff × presence (the wave's vPointerFall, which both fragment themes consume);
|
|
285
|
+
// disp = the signed displacement along the surface's own up-axis, which the CALLER applies (the wave
|
|
286
|
+
// in its local space, the dust through the wave's world matrix).
|
|
287
|
+
struct PointerHit { float fall; float disp; };
|
|
288
|
+
|
|
289
|
+
// Sample the field for ONE point. ndc is that point's screen position; mvp the clip transform of the
|
|
290
|
+
// space rotA/rotB/rotC and churnPos live in (the owning wave's local space); t / loopOff the caller's
|
|
291
|
+
// linear / orbit time — only the one selected by LOOP_MOTION is read.
|
|
292
|
+
PointerHit pointerField(vec2 ndc, mat4 mvp, mat4 rotA, mat4 rotB, mat4 rotC, vec3 churnPos,
|
|
293
|
+
float t, vec2 loopOff){
|
|
322
294
|
// Screen-space offset from the cursor (aspect-corrected → round in pixels). The DEFAULT metric.
|
|
323
|
-
vec2
|
|
324
|
-
vec2 dp = (ndcHere - uPointer) * vec2(uPointerAspect, 1.0);
|
|
295
|
+
vec2 dp = (ndc - uPointer) * vec2(uPointerAspect, 1.0);
|
|
325
296
|
// Ribbon flow: stretch the metric along the strip's own LENGTH axis so the field reaches ALONG the
|
|
326
297
|
// ribbon and stays tight across it — the "flows with the material" feel, per-vertex (so it follows
|
|
327
298
|
// the strip's curve) with no CPU surface pick. The length axis is local +X (uv.x runs with x)
|
|
@@ -339,23 +310,19 @@ void main(){
|
|
|
339
310
|
dp = vec2(dot(dp, tang) / (1.0 + uShapeFlow * 2.5), dot(dp, nrm)); // up to 3.5× reach along length
|
|
340
311
|
}
|
|
341
312
|
}
|
|
342
|
-
float fall = smoothstep(uPointerRadius, 0.0, length(dp));
|
|
343
|
-
vPointerFall = fall * uPointerActive;
|
|
344
|
-
// Displacement axis = local +Y carried through the SAME three twist rotations as pos (row-vector
|
|
345
|
-
// convention). Rotations are linear, so post-twist axis displacement equals pre-twist Y displacement.
|
|
346
|
-
vec3 dispAxis = (((vec4(0.0, 1.0, 0.0, 0.0) * rotA) * rotB) * rotC).xyz;
|
|
313
|
+
float fall = smoothstep(uPointerRadius, 0.0, length(dp)) * uPointerActive;
|
|
347
314
|
// Agitation: a fast churn octave near the cursor (additive — never rewrites base noise t, which
|
|
348
315
|
// would force restructuring the shared path). Loop-safe under both time variants.
|
|
349
316
|
#ifdef LOOP_MOTION
|
|
350
|
-
float disp = uPointerAgitate *
|
|
351
|
-
* simplexNoise(vec2(
|
|
317
|
+
float disp = uPointerAgitate * fall
|
|
318
|
+
* simplexNoise(vec2(churnPos.x * uDispFreqX * 3.0, churnPos.z * uDispFreqZ * 3.0) + loopOff * 4.0);
|
|
352
319
|
#else
|
|
353
|
-
float disp = uPointerAgitate *
|
|
354
|
-
* simplexNoise(vec2(
|
|
320
|
+
float disp = uPointerAgitate * fall
|
|
321
|
+
* simplexNoise(vec2(churnPos.x * uDispFreqX * 3.0 + t * 4.0, churnPos.z * uDispFreqZ * 3.0));
|
|
355
322
|
#endif
|
|
356
|
-
// Membrane push/pull: a smooth dome (
|
|
357
|
-
//
|
|
358
|
-
disp += uPointerPush *
|
|
323
|
+
// Membrane push/pull: a smooth dome (fall is the falloff) that swells toward you (+ repel) or dents
|
|
324
|
+
// away (− attract) at the cursor, riding along with the sprung field.
|
|
325
|
+
disp += uPointerPush * fall;
|
|
359
326
|
// Drag-wake: pull the surface just BEHIND the moving cursor into a trailing trough. dp points
|
|
360
327
|
// from cursor to vertex; "behind" is how far the vertex sits opposite the velocity (0 ahead → 1 a
|
|
361
328
|
// radius behind), gated by speed so it only forms while dragging and heals when the cursor stops.
|
|
@@ -363,12 +330,12 @@ void main(){
|
|
|
363
330
|
float wakeSpeed = length(velC);
|
|
364
331
|
if (uPointerWake != 0.0 && wakeSpeed > 1.0e-4) {
|
|
365
332
|
float behind = clamp(dot(-dp, velC) / (wakeSpeed * uPointerRadius), 0.0, 1.0);
|
|
366
|
-
disp -= uPointerWake *
|
|
333
|
+
disp -= uPointerWake * fall * behind * smoothstep(0.05, 0.6, wakeSpeed);
|
|
367
334
|
}
|
|
368
335
|
#ifdef POINTER_RIPPLES
|
|
369
336
|
for (int i = 0; i < 4; i++) {
|
|
370
337
|
if (uRippleAmp[i] > 0.0) {
|
|
371
|
-
float rd = length((
|
|
338
|
+
float rd = length((ndc - uRippleOrigin[i]) * vec2(uPointerAspect, 1.0));
|
|
372
339
|
// A wave PACKET whose crest travels outward at RIPPLE_WAVE_SPEED: a gaussian window centred on
|
|
373
340
|
// the moving front carrying a short oscillation (a raised ring with faint trailing troughs),
|
|
374
341
|
// so the energy radiates instead of throbbing at the click point. The shared uRippleAmp
|
|
@@ -381,7 +348,99 @@ void main(){
|
|
|
381
348
|
}
|
|
382
349
|
}
|
|
383
350
|
#endif
|
|
384
|
-
|
|
351
|
+
PointerHit hit;
|
|
352
|
+
hit.fall = fall;
|
|
353
|
+
hit.disp = disp;
|
|
354
|
+
return hit;
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
const vertexShader = `
|
|
358
|
+
${simplex2d}
|
|
359
|
+
|
|
360
|
+
uniform float uTime, uSpeed, uSeed;
|
|
361
|
+
uniform float uDispFreqX, uDispFreqZ, uDispAmount;
|
|
362
|
+
uniform float uDetailFreq, uDetailAmount; // 2nd displacement octave (only read under DETAIL_OCTAVE)
|
|
363
|
+
uniform float uTwFreqX, uTwFreqY, uTwFreqZ, uTwPowX, uTwPowY, uTwPowZ;
|
|
364
|
+
uniform float uLoopSeconds; // seamless-loop period (only read under LOOP_MOTION)
|
|
365
|
+
|
|
366
|
+
// Helix (optional). Behind HELIX so a wave without one compiles the exact same program — same
|
|
367
|
+
// byte-identity contract as the pointer block below.
|
|
368
|
+
#ifdef HELIX
|
|
369
|
+
uniform float uHelixTurns; // full turns from one end of the ribbon to the other
|
|
370
|
+
uniform float uHelixRadius; // orbit radius: carries the whole ribbon around the axis
|
|
371
|
+
uniform float uHelixRoll; // cross-section roll, as a fraction of the turns (1 = rigid ladder)
|
|
372
|
+
uniform float uHelixPhase; // degrees
|
|
373
|
+
#endif
|
|
374
|
+
|
|
375
|
+
// Radial fan (optional). Behind RADIAL so a wave without one compiles the exact same program (same
|
|
376
|
+
// byte-identity contract as HELIX / POINTER_FX above).
|
|
377
|
+
#ifdef RADIAL
|
|
378
|
+
uniform float uRadialAmount; // 0..1 blend (0 = identity)
|
|
379
|
+
uniform float uRadialArc; // fan spread, degrees
|
|
380
|
+
uniform float uRadialSpread; // length → radius scale
|
|
381
|
+
uniform float uRadialRadius; // source / inner radius
|
|
382
|
+
uniform float uRadialCenter; // base angle, degrees
|
|
383
|
+
#endif
|
|
384
|
+
|
|
385
|
+
varying vec2 vUv;
|
|
386
|
+
varying vec3 vWorldPos;
|
|
387
|
+
varying vec3 vViewDir;
|
|
388
|
+
varying vec4 vClipPosition; // = gl_Position, for the wireframe theme's depth fade
|
|
389
|
+
|
|
390
|
+
// Pointer field (optional, additive) — the shared chunk, gated so a wave with no interaction config
|
|
391
|
+
// compiles the exact same program. The particle emitter interpolates the SAME chunk, so dust reacts
|
|
392
|
+
// through one implementation of the footprint / falloff / displacement.
|
|
393
|
+
#ifdef POINTER_FX
|
|
394
|
+
${pointerFieldChunk}
|
|
395
|
+
varying float vPointerFall; // falloff × presence — consumed by both fragment themes
|
|
396
|
+
#endif
|
|
397
|
+
|
|
398
|
+
${waveShapeChunk}
|
|
399
|
+
|
|
400
|
+
void main(){
|
|
401
|
+
vUv = uv;
|
|
402
|
+
#ifndef LOOP_MOTION
|
|
403
|
+
float t = uTime * uSpeed + uSeed;
|
|
404
|
+
vec2 loopOff = vec2(0.0); // unused under linear time; kept so waveShape's signature is uniform
|
|
405
|
+
#endif
|
|
406
|
+
|
|
407
|
+
#ifdef LOOP_MOTION
|
|
408
|
+
// Seamless loop: rather than scrolling the noise field linearly by t (which never repeats),
|
|
409
|
+
// sample it on a circle of radius loopR at angle loopTheta — exactly periodic with period
|
|
410
|
+
// uLoopSeconds. The tangential speed loopR·dθ/dt equals uSpeed, so the looped motion advances
|
|
411
|
+
// at the same rate as the linear drift, just curved into a closed orbit (it orbits rather than
|
|
412
|
+
// drifts — the trade-off for a seamless loop, hence opt-in). uSeed offsets the phase so stacked
|
|
413
|
+
// waves keep their relative motion while sharing the single period.
|
|
414
|
+
float loopTheta = uTime * (6.28318530718 / uLoopSeconds) + uSeed;
|
|
415
|
+
float loopR = uSpeed * uLoopSeconds * 0.159154943092; // = uSpeed·uLoopSeconds / (2π)
|
|
416
|
+
vec2 loopOff = loopR * vec2(cos(loopTheta), sin(loopTheta));
|
|
417
|
+
float t = 0.0; // unused under loop time
|
|
418
|
+
#endif
|
|
419
|
+
|
|
420
|
+
// Deform the baked hairpin via the shared waveShape chunk (displacement + helix + twist + radial),
|
|
421
|
+
// which also drives the particle shed emitter. It returns the deformed local pos + the twist
|
|
422
|
+
// matrices the pointer field reads below.
|
|
423
|
+
WaveShape ws = waveShape(position, uv, t, loopOff);
|
|
424
|
+
vec3 pos = ws.pos;
|
|
425
|
+
|
|
426
|
+
#ifdef POINTER_FX
|
|
427
|
+
// Pointer field: displace along the wave's own (post-twist) up-axis, weighted by a screen-space
|
|
428
|
+
// falloff around the smoothed cursor — a circle at uShapeFlow 0, stretched along the ribbon as it
|
|
429
|
+
// rises. Everything here is ADDITIVE and fenced, so the shared path above/below is untouched and
|
|
430
|
+
// byte-identical when POINTER_FX is off. The field itself lives in pointerFieldChunk, which the
|
|
431
|
+
// particle emitter also calls — so dust reacts through this exact footprint and falloff.
|
|
432
|
+
// Shared clip-space transform, computed once and reused for the cursor metric and the ribbon
|
|
433
|
+
// tangent (the compiler is not guaranteed to CSE the triple product otherwise). Associativity is
|
|
434
|
+
// unchanged, so preClip is bit-for-bit what the plain P*V*M*v product produced.
|
|
435
|
+
mat4 mvp = projectionMatrix * viewMatrix * modelMatrix;
|
|
436
|
+
vec4 preClip = mvp * vec4(pos, 1.0);
|
|
437
|
+
PointerHit hit = pointerField(preClip.xy / max(preClip.w, 1.0e-6), mvp,
|
|
438
|
+
ws.rotA, ws.rotB, ws.rotC, pos, t, loopOff);
|
|
439
|
+
vPointerFall = hit.fall;
|
|
440
|
+
// Displacement axis = local +Y carried through the SAME three twist rotations as pos (row-vector
|
|
441
|
+
// convention). Rotations are linear, so post-twist axis displacement equals pre-twist Y displacement.
|
|
442
|
+
vec3 dispAxis = (((vec4(0.0, 1.0, 0.0, 0.0) * ws.rotA) * ws.rotB) * ws.rotC).xyz;
|
|
443
|
+
pos += dispAxis * hit.disp;
|
|
385
444
|
#endif
|
|
386
445
|
|
|
387
446
|
// The scale / rotation / position transform lives on the mesh (modelMatrix), so the
|
|
@@ -434,7 +493,7 @@ uniform vec3 uDepthTintColor;
|
|
|
434
493
|
varying vec4 vClipPosition; // clip-space depth (written by the vertex shader for both programs)
|
|
435
494
|
#endif
|
|
436
495
|
#ifdef EDGE_FEATHER
|
|
437
|
-
uniform float uEdgeFeather; // ribbon
|
|
496
|
+
uniform float uEdgeFeather; // softness of the ribbon's two ENDS (only when it differs from 0.1)
|
|
438
497
|
#endif
|
|
439
498
|
#ifdef POINTER_FX
|
|
440
499
|
uniform float uPointerThin; // 0..1 local translucency near the cursor
|
|
@@ -474,8 +533,10 @@ vec3 surfaceStreaks(vec2 uv, vec3 color, float crease){
|
|
|
474
533
|
colorAtten = mix(colorAtten, prm.w, blend);
|
|
475
534
|
paraPow = mix(paraPow, uNoiseBandParaPow[i], blend);
|
|
476
535
|
}
|
|
477
|
-
// The high frequency runs along uv.x (the
|
|
478
|
-
//
|
|
536
|
+
// The high frequency runs along uv.x (the folded WIDTH — see WaveGeometry's UV AXES note),
|
|
537
|
+
// packing many thin stripes across the cross-section while uv.y is barely scaled, so each
|
|
538
|
+
// one stretches out into a fine LENGTHWISE fiber. 1 - parabola(uv.x) then weights them
|
|
539
|
+
// toward the two long edges and away from the width centreline.
|
|
479
540
|
float p = 1.0 - parabola(uv.x, paraPow);
|
|
480
541
|
float n0 = simplexNoise(vec2(uv.x * 0.1, uv.y * 0.5));
|
|
481
542
|
float n1 = simplexNoise(vec2(uv.x * (freq + freq * 0.5 * n0), uv.y * 4.0 * n0));
|
|
@@ -564,7 +625,8 @@ void main(){
|
|
|
564
625
|
|
|
565
626
|
if (uTexture > 0.001) col *= 1.0 + (grainHash(vUv * 850.0) - 0.5) * uTexture * 0.25;
|
|
566
627
|
|
|
567
|
-
// Soft
|
|
628
|
+
// Soft ribbon ENDS (it fades on vUv.y, the length) + optional viewport-edge fade. The edge
|
|
629
|
+
// softness is the hardcoded 0.1 by
|
|
568
630
|
// default (literal branch → byte-identical); EDGE_FEATHER swaps in the uEdgeFeather knob only
|
|
569
631
|
// when it differs, so razor-crisp or vapor-soft edges are both reachable.
|
|
570
632
|
#ifdef EDGE_FEATHER
|
|
@@ -638,7 +700,7 @@ void main(){
|
|
|
638
700
|
color *= 1.0 + uPointerLighten * vPointerFall;
|
|
639
701
|
#endif
|
|
640
702
|
|
|
641
|
-
// Carve into fine
|
|
703
|
+
// Carve into fine lengthwise strands; thickness from the screen-space uv derivative.
|
|
642
704
|
vec2 dy = dFdy(vUv);
|
|
643
705
|
float lineThickness = uLineThickness * pow(abs(dy.x * uMaxWidth), uLineDerivativePower);
|
|
644
706
|
#ifdef POINTER_FX
|
|
@@ -897,7 +959,195 @@ void main(){
|
|
|
897
959
|
gl_FragColor = vec4(mix(src.rgb, outc, clamp(uHalftoneCmyk, 0.0, 1.0)), src.a);
|
|
898
960
|
}
|
|
899
961
|
`;
|
|
962
|
+
const particleVertexShader = `
|
|
963
|
+
attribute float aSeed;
|
|
964
|
+
attribute vec4 aRnd;
|
|
965
|
+
attribute vec2 aUv; // where this particle spawns on the ribbon (x = flank, y = along length; edge-biased at build)
|
|
966
|
+
|
|
967
|
+
uniform float uTime, uLoopSeconds, uLife, uSize, uSizeJitter, uTwinkle, uPixelRatio;
|
|
968
|
+
uniform float uPartSpeed;
|
|
969
|
+
uniform vec3 uColor, uColor2, uCenter, uRight, uUp;
|
|
970
|
+
uniform float uDrift, uRise, uSwirl, uWander;
|
|
971
|
+
|
|
972
|
+
// The owning wave's shape, mirrored in configure() so the dust rides the SAME deform as the ribbon.
|
|
973
|
+
// The HELIX/RADIAL uniform blocks are declared only when the matching #define is set.
|
|
974
|
+
${simplex2d}
|
|
975
|
+
uniform float uDispFreqX, uDispFreqZ, uDispAmount;
|
|
976
|
+
uniform float uDetailFreq, uDetailAmount;
|
|
977
|
+
uniform float uTwFreqX, uTwFreqY, uTwFreqZ, uTwPowX, uTwPowY, uTwPowZ;
|
|
978
|
+
#ifdef HELIX
|
|
979
|
+
uniform float uHelixTurns, uHelixRadius, uHelixRoll, uHelixPhase;
|
|
980
|
+
#endif
|
|
981
|
+
#ifdef RADIAL
|
|
982
|
+
uniform float uRadialAmount, uRadialArc, uRadialSpread, uRadialRadius, uRadialCenter;
|
|
983
|
+
#endif
|
|
984
|
+
uniform mat4 uShedModel; // the wave's matrixWorld (deformed LOCAL → world)
|
|
985
|
+
uniform float uShedSpeed, uShedSeed;
|
|
986
|
+
${waveShapeChunk}
|
|
987
|
+
|
|
988
|
+
// The cursor. Same chunk the ribbon uses, mirrored onto this material in ParticleField.configure(),
|
|
989
|
+
// and behind the same POINTER_FX gate — a wave with no hover field compiles the point program it
|
|
990
|
+
// always did. uPartShove is the one particle-only knob (see the two samples in main).
|
|
991
|
+
#ifdef POINTER_FX
|
|
992
|
+
${pointerFieldChunk}
|
|
993
|
+
uniform float uPartShove; // how hard the cursor shoves dust that has already drifted free (0 = off)
|
|
994
|
+
#endif
|
|
995
|
+
|
|
996
|
+
varying float vAlpha;
|
|
997
|
+
varying vec3 vColor;
|
|
998
|
+
varying vec2 vDir; // screen-space motion direction (for the streak sprite)
|
|
999
|
+
|
|
1000
|
+
const float TAU = 6.28318530718;
|
|
1001
|
+
|
|
1002
|
+
void main(){
|
|
1003
|
+
// Deterministic life: age 0..1 from uTime + a per-particle seed. Advances once per loop period when
|
|
1004
|
+
// looping (so the whole field repeats seamlessly), else once per uLife seconds. uPartSpeed scales the
|
|
1005
|
+
// cadence (motion speed); under a loop it snaps to a whole number of cycles so the seam stays seamless.
|
|
1006
|
+
float cyc = max(1.0, floor(uPartSpeed + 0.5));
|
|
1007
|
+
float rate = (uLoopSeconds > 0.0) ? (uTime / uLoopSeconds * cyc) : (uTime * uPartSpeed / max(uLife, 0.001));
|
|
1008
|
+
float age = fract(rate + aSeed);
|
|
1009
|
+
float fade = sin(3.14159265 * age); // 0 at birth/death, 1 mid-life
|
|
1010
|
+
|
|
1011
|
+
// Spawn on the owning wave's DEFORMED surface / edge at aUv (via the shared waveShape), then peel
|
|
1012
|
+
// outward from the wave centre as the particle ages — silk dissolving into glitter.
|
|
1013
|
+
float ts = uTime * uShedSpeed + uShedSeed;
|
|
1014
|
+
vec2 loopOff = vec2(0.0);
|
|
1015
|
+
#ifdef LOOP_MOTION
|
|
1016
|
+
float loopTheta = uTime * (TAU / uLoopSeconds) + uShedSeed;
|
|
1017
|
+
float loopR = uShedSpeed * uLoopSeconds * 0.159154943092;
|
|
1018
|
+
loopOff = loopR * vec2(cos(loopTheta), sin(loopTheta));
|
|
1019
|
+
ts = 0.0;
|
|
1020
|
+
#endif
|
|
1021
|
+
// Approximate the base hairpin point for this uv (length from uv.y; width centre), then deform it
|
|
1022
|
+
// exactly as the wave does. Good enough for dust — the fan / displacement dominate.
|
|
1023
|
+
vec3 base = vec3((aUv.y - 0.5) * 400.0, 0.0, ${(-8).toFixed(1)});
|
|
1024
|
+
WaveShape ws = waveShape(base, aUv, ts, loopOff);
|
|
1025
|
+
vec3 origin = (uShedModel * vec4(ws.pos, 1.0)).xyz;
|
|
1026
|
+
vec3 outward = normalize(origin - uCenter + vec3(1e-4));
|
|
1027
|
+
|
|
1028
|
+
#ifdef POINTER_FX
|
|
1029
|
+
// WELD (applied below, once the mote's own motion is known). The ribbon displaces its surface by
|
|
1030
|
+
// pointerField() along its own post-twist up-axis; a mote sitting ON that surface has to take the
|
|
1031
|
+
// same ride, or the cursor's dome lifts the silk out from under its own glitter. Sampled at the
|
|
1032
|
+
// SPAWN point and carried through the local→world matrix exactly as the ribbon's own
|
|
1033
|
+
// pos += dispAxis * disp is, so the two land on the same place. Sampling at the spawn point also
|
|
1034
|
+
// leaves outward derived from the UNDISPLACED origin, so a poke never bends the drift direction.
|
|
1035
|
+
mat4 pMvp = projectionMatrix * viewMatrix * uShedModel;
|
|
1036
|
+
vec4 originClip = pMvp * vec4(ws.pos, 1.0);
|
|
1037
|
+
vec3 dispAxis = mat3(uShedModel) * (((vec4(0.0, 1.0, 0.0, 0.0) * ws.rotA) * ws.rotB) * ws.rotC).xyz;
|
|
1038
|
+
PointerHit weld = pointerField(originClip.xy / max(originClip.w, 1.0e-6), pMvp,
|
|
1039
|
+
ws.rotA, ws.rotB, ws.rotC, ws.pos, ts, loopOff);
|
|
1040
|
+
#endif
|
|
1041
|
+
|
|
1042
|
+
vec3 p = origin + outward * age * uDrift + (aRnd.xyz - 0.5) * age * uDrift * 0.35;
|
|
1043
|
+
|
|
1044
|
+
// Motion styles, each 0 = off, all riding age so they stay loop-safe (the age wrap is hidden by
|
|
1045
|
+
// fade→0 at birth/death). rise = screen-vertical buoyancy (embers up / snow down); swirl = orbit
|
|
1046
|
+
// around the wave centre in the screen plane; wander = curl-noise turbulence (fireflies / motes).
|
|
1047
|
+
p += uUp * age * uRise;
|
|
1048
|
+
// How far this mote travels away from its birth patch over a WHOLE life — the straight-line terms
|
|
1049
|
+
// plus, under swirl, the arc it sweeps at its own orbit radius. Only the pointer weld reads it
|
|
1050
|
+
// (0 for dust that merely clings to the surface, which is exactly the case age would get wrong),
|
|
1051
|
+
// so it is fenced like everything else the cursor drives.
|
|
1052
|
+
#ifdef POINTER_FX
|
|
1053
|
+
float span = abs(uDrift) * 1.35 + abs(uRise) + uWander;
|
|
1054
|
+
#endif
|
|
1055
|
+
if (uSwirl != 0.0) {
|
|
1056
|
+
vec3 nrm = cross(uRight, uUp);
|
|
1057
|
+
vec3 rel = p - uCenter;
|
|
1058
|
+
#ifdef POINTER_FX
|
|
1059
|
+
span += abs(uSwirl) * TAU * length(rel);
|
|
1060
|
+
#endif
|
|
1061
|
+
float rx = dot(rel, uRight), ry = dot(rel, uUp), rz = dot(rel, nrm);
|
|
1062
|
+
float a = age * uSwirl * TAU;
|
|
1063
|
+
float ca = cos(a), sa = sin(a);
|
|
1064
|
+
p = uCenter + uRight * (rx * ca - ry * sa) + uUp * (rx * sa + ry * ca) + nrm * rz;
|
|
1065
|
+
}
|
|
1066
|
+
if (uWander != 0.0) {
|
|
1067
|
+
vec2 wan = vec2(simplexNoise(vec2(aSeed * 17.0, age * 3.0)),
|
|
1068
|
+
simplexNoise(vec2(age * 3.0, aSeed * 23.0)));
|
|
1069
|
+
p += (uRight * wan.x + uUp * wan.y) * uWander;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
#ifdef POINTER_FX
|
|
1073
|
+
// How attached to its birth patch this mote still is: 1 while it sits on the surface, 0 once it
|
|
1074
|
+
// has travelled a full life's worth away. Measured from the DISTANCE it actually moved rather than
|
|
1075
|
+
// from age, because dust with no drift / rise / swirl / wander never leaves the surface at all —
|
|
1076
|
+
// an age fade would quietly stop that dust from following the ribbon halfway through its life.
|
|
1077
|
+
float attach = span > 1.0e-4 ? 1.0 - clamp(length(p - origin) / span, 0.0, 1.0) : 1.0;
|
|
1078
|
+
p += dispAxis * (weld.disp * attach);
|
|
1079
|
+
// SHOVE: the exact complement. The same field sampled at the mote's OWN screen position, so the
|
|
1080
|
+
// cursor also pushes dust that has already left the surface — and a click ripple visibly blows
|
|
1081
|
+
// through the cloud instead of stopping dead at the ribbon. Uniform branch (warp-coherent), so
|
|
1082
|
+
// uPartShove 0 costs nothing.
|
|
1083
|
+
if (uPartShove != 0.0) {
|
|
1084
|
+
vec4 pClip = projectionMatrix * viewMatrix * vec4(p, 1.0);
|
|
1085
|
+
PointerHit shove = pointerField(pClip.xy / max(pClip.w, 1.0e-6), pMvp,
|
|
1086
|
+
ws.rotA, ws.rotB, ws.rotC, ws.pos, ts, loopOff);
|
|
1087
|
+
p += dispAxis * (shove.disp * (1.0 - attach) * uPartShove);
|
|
1088
|
+
}
|
|
1089
|
+
#endif
|
|
1090
|
+
|
|
1091
|
+
float tw = 0.5 + 0.5 * sin((age * 9.0 + aSeed) * TAU); // loop-safe flicker (rides age)
|
|
1092
|
+
vAlpha = fade * mix(1.0, tw, clamp(uTwinkle, 0.0, 1.0));
|
|
1093
|
+
vColor = mix(uColor, uColor2, aRnd.w); // two-tone dust: per-particle blend of the two colours
|
|
1094
|
+
vDir = normalize(vec2(dot(outward, uRight), dot(outward, uUp)) + vec2(1e-4)); // outward, in screen space
|
|
1095
|
+
gl_Position = projectionMatrix * viewMatrix * vec4(p, 1.0);
|
|
1096
|
+
// Orthographic camera → point size is constant in device pixels (no perspective depth divide).
|
|
1097
|
+
float jitter = 1.0 + uSizeJitter * (aSeed - 0.5) * 2.0;
|
|
1098
|
+
gl_PointSize = max(uSize * uPixelRatio * jitter * fade, 0.0);
|
|
1099
|
+
}
|
|
1100
|
+
`;
|
|
1101
|
+
const particleFragmentShader = `
|
|
1102
|
+
precision highp float;
|
|
1103
|
+
uniform float uShape; // 0 glitter · 1 soft · 2 ring · 3 star · 4 streak
|
|
1104
|
+
varying float vAlpha;
|
|
1105
|
+
varying vec3 vColor;
|
|
1106
|
+
varying vec2 vDir;
|
|
1107
|
+
// User artwork (shape "sprite"), behind a define so a field without one compiles the exact same
|
|
1108
|
+
// program — and so the sampler only exists once a texture is actually bound to it. ONE texture is
|
|
1109
|
+
// shared by every particle in the field; see ParticleField.loadSprite for the rasterization.
|
|
1110
|
+
#ifdef PARTICLE_SPRITE
|
|
1111
|
+
uniform sampler2D uSprite;
|
|
1112
|
+
#endif
|
|
1113
|
+
void main(){
|
|
1114
|
+
#ifdef PARTICLE_SPRITE
|
|
1115
|
+
// gl_PointCoord's origin is the sprite's TOP-left with y running DOWN, so it has to be flipped or
|
|
1116
|
+
// every sprite draws upside down. The procedural shapes below never needed this — they are all
|
|
1117
|
+
// symmetric about y, which is exactly why the bug would have gone unnoticed.
|
|
1118
|
+
vec4 tex = texture2D(uSprite, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y));
|
|
1119
|
+
float a = tex.a * vAlpha;
|
|
1120
|
+
if (a <= 0.0) discard;
|
|
1121
|
+
// Tinted by the dust colour so color / color2 keep working: white artwork takes the tint
|
|
1122
|
+
// exactly, coloured artwork multiplies it.
|
|
1123
|
+
gl_FragColor = vec4(vColor * tex.rgb, a);
|
|
1124
|
+
#else
|
|
1125
|
+
vec2 pc = gl_PointCoord - 0.5;
|
|
1126
|
+
float d = length(pc);
|
|
1127
|
+
int s = int(uShape + 0.5);
|
|
1128
|
+
float a;
|
|
1129
|
+
if (s == 1) { // soft: a diffuse gaussian blob (motes / pollen)
|
|
1130
|
+
a = exp(-d * d * 7.0);
|
|
1131
|
+
} else if (s == 2) { // ring: a hollow band (bubbles)
|
|
1132
|
+
a = smoothstep(0.09, 0.0, abs(d - 0.34));
|
|
1133
|
+
} else if (s == 3) { // star: a 4-point sparkle
|
|
1134
|
+
float ang = atan(pc.y, pc.x);
|
|
1135
|
+
float spike = pow(abs(cos(ang * 2.0)), 6.0);
|
|
1136
|
+
a = smoothstep(1.0, 0.0, d / (0.14 + 0.5 * spike));
|
|
1137
|
+
} else if (s == 4) { // streak: an elongated comet along the motion direction
|
|
1138
|
+
float along = dot(pc, vDir);
|
|
1139
|
+
float perp = dot(pc, vec2(-vDir.y, vDir.x));
|
|
1140
|
+
a = smoothstep(0.5, 0.0, length(vec2(along * 0.42, perp * 2.2)));
|
|
1141
|
+
} else { // glitter (0): the soft round additive disc
|
|
1142
|
+
a = smoothstep(0.5, 0.0, d);
|
|
1143
|
+
}
|
|
1144
|
+
a *= vAlpha;
|
|
1145
|
+
if (a <= 0.0) discard;
|
|
1146
|
+
gl_FragColor = vec4(vColor, a); // AdditiveBlending (src = SrcAlpha) → adds vColor·a
|
|
1147
|
+
#endif
|
|
1148
|
+
}
|
|
1149
|
+
`;
|
|
900
1150
|
//#endregion
|
|
901
|
-
export { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, postFragmentShader, postVertexShader, vertexShader };
|
|
1151
|
+
export { ditherFragmentShader, fragmentShader, halftoneCmykFragmentShader, halftoneFragmentShader, heatmapFragmentShader, innerLightFragmentShader, lineFragmentShader, paperTextureFragmentShader, particleFragmentShader, particleVertexShader, postFragmentShader, postVertexShader, vertexShader };
|
|
902
1152
|
|
|
903
1153
|
//# sourceMappingURL=shaders.js.map
|