@newkrok/three-particles 2.15.2 → 2.16.1
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/README.md +80 -0
- package/dist/index.d.ts +362 -7
- package/dist/index.js +1041 -395
- package/dist/index.js.map +1 -1
- package/dist/three-particles.min.js +1 -1
- package/dist/three-particles.min.js.map +1 -1
- package/dist/webgpu.js +1664 -0
- package/dist/webgpu.js.map +1 -0
- package/llms-full.txt +189 -0
- package/llms.txt +89 -0
- package/package.json +15 -10
- package/webgpu.d.ts +88 -0
package/dist/webgpu.js
ADDED
|
@@ -0,0 +1,1664 @@
|
|
|
1
|
+
import { registerTSLMaterialFactory } from '@newkrok/three-particles';
|
|
2
|
+
import { Fn, mod, float, floor, dot, vec3, step, min, max, vec4, vec2, abs, round, If, texture, screenUV, smoothstep, length, Discard, sRGBTransferEOTF, cross, attribute, modelViewMatrix, positionLocal, varyingProperty, pointUV, cos, sin, normalLocal, cameraProjectionMatrix, uv, uniform, normalize, cameraPosition, cameraViewMatrix, mix, storage, instanceIndex, compute, fract, Loop, Continue } from 'three/tsl';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { DoubleSide, Vector3, NoColorSpace, DataTexture } from 'three';
|
|
5
|
+
import { PointsNodeMaterial, MeshBasicNodeMaterial, StorageBufferAttribute, StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
6
|
+
|
|
7
|
+
// src/webgpu.ts
|
|
8
|
+
var PLANE_STRIDE = 12;
|
|
9
|
+
var MAX_COLLISION_PLANES = 16;
|
|
10
|
+
var COLLISION_PLANE_DATA_SIZE = MAX_COLLISION_PLANES * PLANE_STRIDE;
|
|
11
|
+
var _encodeBuf = null;
|
|
12
|
+
function encodeCollisionPlanesForGPU(planes) {
|
|
13
|
+
if (!_encodeBuf || _encodeBuf.length !== COLLISION_PLANE_DATA_SIZE) {
|
|
14
|
+
_encodeBuf = new Float32Array(COLLISION_PLANE_DATA_SIZE);
|
|
15
|
+
}
|
|
16
|
+
const data = _encodeBuf;
|
|
17
|
+
data.fill(0);
|
|
18
|
+
const count = Math.min(planes.length, MAX_COLLISION_PLANES);
|
|
19
|
+
for (let i = 0; i < count; i++) {
|
|
20
|
+
const cp = planes[i];
|
|
21
|
+
const base = i * PLANE_STRIDE;
|
|
22
|
+
data[base] = cp.isActive ? 1 : 0;
|
|
23
|
+
let modeCode = 0;
|
|
24
|
+
if (cp.mode === "CLAMP" /* CLAMP */) modeCode = 1;
|
|
25
|
+
else if (cp.mode === "BOUNCE" /* BOUNCE */) modeCode = 2;
|
|
26
|
+
data[base + 1] = modeCode;
|
|
27
|
+
data[base + 2] = cp.position.x;
|
|
28
|
+
data[base + 3] = cp.position.y;
|
|
29
|
+
data[base + 4] = cp.position.z;
|
|
30
|
+
data[base + 5] = cp.normal.x;
|
|
31
|
+
data[base + 6] = cp.normal.y;
|
|
32
|
+
data[base + 7] = cp.normal.z;
|
|
33
|
+
data[base + 8] = cp.dampen;
|
|
34
|
+
data[base + 9] = cp.lifetimeLoss;
|
|
35
|
+
data[base + 10] = 0;
|
|
36
|
+
data[base + 11] = 0;
|
|
37
|
+
}
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
function createCollisionPlaneTSL(sCurveData, collisionPlaneOffset, collisionPlaneCount) {
|
|
41
|
+
const count = Math.min(collisionPlaneCount, MAX_COLLISION_PLANES);
|
|
42
|
+
const uCollisionPlaneCount = uniform(float(count));
|
|
43
|
+
const cpBase = collisionPlaneOffset;
|
|
44
|
+
const applyCollisionPlanesTSL = Fn(
|
|
45
|
+
({
|
|
46
|
+
pos,
|
|
47
|
+
vel,
|
|
48
|
+
oiaVec,
|
|
49
|
+
sColorNode,
|
|
50
|
+
ps,
|
|
51
|
+
startLife,
|
|
52
|
+
particleIdx,
|
|
53
|
+
sOrbitalIsActiveNode
|
|
54
|
+
}) => {
|
|
55
|
+
Loop(uCollisionPlaneCount, ({ i }) => {
|
|
56
|
+
const base = i.mul(PLANE_STRIDE).add(cpBase);
|
|
57
|
+
const isActive = sCurveData.element(base);
|
|
58
|
+
If(isActive.lessThan(0.5), () => {
|
|
59
|
+
Continue();
|
|
60
|
+
});
|
|
61
|
+
const mode = sCurveData.element(base.add(1));
|
|
62
|
+
const planePos = vec3(
|
|
63
|
+
sCurveData.element(base.add(2)),
|
|
64
|
+
sCurveData.element(base.add(3)),
|
|
65
|
+
sCurveData.element(base.add(4))
|
|
66
|
+
);
|
|
67
|
+
const planeNormal = vec3(
|
|
68
|
+
sCurveData.element(base.add(5)),
|
|
69
|
+
sCurveData.element(base.add(6)),
|
|
70
|
+
sCurveData.element(base.add(7))
|
|
71
|
+
);
|
|
72
|
+
const dampen = sCurveData.element(base.add(8));
|
|
73
|
+
const lifetimeLoss = sCurveData.element(base.add(9));
|
|
74
|
+
const toParticle = pos.sub(planePos);
|
|
75
|
+
const signedDist = dot(toParticle, planeNormal);
|
|
76
|
+
If(signedDist.lessThan(0), () => {
|
|
77
|
+
If(mode.lessThan(0.5), () => {
|
|
78
|
+
ps.x.assign(startLife.add(float(1)));
|
|
79
|
+
});
|
|
80
|
+
If(mode.greaterThanEqual(0.5).and(mode.lessThan(1.5)), () => {
|
|
81
|
+
pos.assign(pos.sub(planeNormal.mul(signedDist)));
|
|
82
|
+
const velDotN = dot(vel, planeNormal);
|
|
83
|
+
If(velDotN.lessThan(0), () => {
|
|
84
|
+
vel.assign(vel.sub(planeNormal.mul(velDotN)));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
If(mode.greaterThanEqual(1.5), () => {
|
|
88
|
+
pos.assign(pos.sub(planeNormal.mul(signedDist)));
|
|
89
|
+
const vDotN = dot(vel, planeNormal);
|
|
90
|
+
const reflected = vel.sub(planeNormal.mul(vDotN.mul(2)));
|
|
91
|
+
vel.assign(reflected.mul(dampen));
|
|
92
|
+
If(lifetimeLoss.greaterThan(0), () => {
|
|
93
|
+
ps.x.assign(ps.x.add(lifetimeLoss.mul(startLife).mul(1e3)));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
"void"
|
|
100
|
+
);
|
|
101
|
+
return {
|
|
102
|
+
/** Uniform for the active collision plane count. */
|
|
103
|
+
countUniform: uCollisionPlaneCount,
|
|
104
|
+
/** TSL function to call in the compute kernel: apply({ pos, vel, ... }) */
|
|
105
|
+
apply: applyCollisionPlanesTSL
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/js/effects/three-particles/three-particles-bezier.ts
|
|
110
|
+
var cache = [];
|
|
111
|
+
var nCr = (n, k) => {
|
|
112
|
+
let z = 1;
|
|
113
|
+
for (let i = 1; i <= k; i++) z *= (n + 1 - i) / i;
|
|
114
|
+
return z;
|
|
115
|
+
};
|
|
116
|
+
var createBezierCurveFunction = (particleSystemId, bezierPoints) => {
|
|
117
|
+
const cacheEntry = cache.find((item) => item.bezierPoints === bezierPoints);
|
|
118
|
+
if (cacheEntry) {
|
|
119
|
+
if (!cacheEntry.referencedBy.includes(particleSystemId))
|
|
120
|
+
cacheEntry.referencedBy.push(particleSystemId);
|
|
121
|
+
return cacheEntry.curveFunction;
|
|
122
|
+
}
|
|
123
|
+
const entry = {
|
|
124
|
+
referencedBy: [particleSystemId],
|
|
125
|
+
bezierPoints,
|
|
126
|
+
curveFunction: (percentage) => {
|
|
127
|
+
if (percentage < 0) return bezierPoints[0].y;
|
|
128
|
+
if (percentage > 1) return bezierPoints[bezierPoints.length - 1].y;
|
|
129
|
+
let start = 0;
|
|
130
|
+
let stop = bezierPoints.length - 1;
|
|
131
|
+
bezierPoints.find((point, index) => {
|
|
132
|
+
const result = percentage < (point.percentage ?? 0);
|
|
133
|
+
if (result) stop = index;
|
|
134
|
+
else if (point.percentage !== void 0) start = index;
|
|
135
|
+
return result;
|
|
136
|
+
});
|
|
137
|
+
const n = stop - start;
|
|
138
|
+
const calculatedPercentage = (percentage - (bezierPoints[start].percentage ?? 0)) / ((bezierPoints[stop].percentage ?? 1) - (bezierPoints[start].percentage ?? 0));
|
|
139
|
+
let value = 0;
|
|
140
|
+
for (let i = 0; i <= n; i++) {
|
|
141
|
+
const p = bezierPoints[start + i];
|
|
142
|
+
const c = nCr(n, i) * Math.pow(1 - calculatedPercentage, n - i) * Math.pow(calculatedPercentage, i);
|
|
143
|
+
value += c * p.y;
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
cache.push(entry);
|
|
149
|
+
return entry.curveFunction;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/js/effects/three-particles/three-particles-utils.ts
|
|
153
|
+
var isLifeTimeCurve = (value) => {
|
|
154
|
+
return typeof value !== "number" && "type" in value;
|
|
155
|
+
};
|
|
156
|
+
var getCurveFunctionFromConfig = (particleSystemId, lifetimeCurve) => {
|
|
157
|
+
if (lifetimeCurve.type === "BEZIER" /* BEZIER */) {
|
|
158
|
+
return createBezierCurveFunction(
|
|
159
|
+
particleSystemId,
|
|
160
|
+
lifetimeCurve.bezierPoints
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
if (lifetimeCurve.type === "EASING" /* EASING */) {
|
|
164
|
+
return lifetimeCurve.curveFunction;
|
|
165
|
+
}
|
|
166
|
+
throw new Error(`Unsupported value type: ${lifetimeCurve}`);
|
|
167
|
+
};
|
|
168
|
+
var calculateValue = (particleSystemId, value, time = 0) => {
|
|
169
|
+
if (typeof value === "number") {
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
if ("min" in value && "max" in value) {
|
|
173
|
+
if (value.min === value.max) {
|
|
174
|
+
return value.min ?? 0;
|
|
175
|
+
}
|
|
176
|
+
return THREE.MathUtils.randFloat(value.min ?? 0, value.max ?? 1);
|
|
177
|
+
}
|
|
178
|
+
const lifetimeCurve = value;
|
|
179
|
+
return getCurveFunctionFromConfig(particleSystemId, lifetimeCurve)(time) * (lifetimeCurve.scale ?? 1);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/js/effects/three-particles/webgpu/compute-force-fields.ts
|
|
183
|
+
var FIELD_STRIDE = 12;
|
|
184
|
+
var MAX_FORCE_FIELDS = 16;
|
|
185
|
+
var FORCE_FIELD_DATA_SIZE = MAX_FORCE_FIELDS * FIELD_STRIDE;
|
|
186
|
+
var GPU_INFINITY = 1e10;
|
|
187
|
+
var _encodeBuf2 = null;
|
|
188
|
+
function encodeForceFieldsForGPU(forceFields, particleSystemId, systemLifetimePercentage) {
|
|
189
|
+
if (!_encodeBuf2 || _encodeBuf2.length !== FORCE_FIELD_DATA_SIZE) {
|
|
190
|
+
_encodeBuf2 = new Float32Array(FORCE_FIELD_DATA_SIZE);
|
|
191
|
+
}
|
|
192
|
+
const data = _encodeBuf2;
|
|
193
|
+
data.fill(0);
|
|
194
|
+
const count = Math.min(forceFields.length, MAX_FORCE_FIELDS);
|
|
195
|
+
for (let i = 0; i < count; i++) {
|
|
196
|
+
const ff = forceFields[i];
|
|
197
|
+
const base = i * FIELD_STRIDE;
|
|
198
|
+
data[base] = ff.isActive ? 1 : 0;
|
|
199
|
+
data[base + 1] = ff.type === "POINT" /* POINT */ ? 0 : 1;
|
|
200
|
+
data[base + 2] = ff.position.x;
|
|
201
|
+
data[base + 3] = ff.position.y;
|
|
202
|
+
data[base + 4] = ff.position.z;
|
|
203
|
+
data[base + 5] = ff.direction.x;
|
|
204
|
+
data[base + 6] = ff.direction.y;
|
|
205
|
+
data[base + 7] = ff.direction.z;
|
|
206
|
+
data[base + 8] = calculateValue(
|
|
207
|
+
particleSystemId,
|
|
208
|
+
ff.strength,
|
|
209
|
+
systemLifetimePercentage
|
|
210
|
+
);
|
|
211
|
+
data[base + 9] = ff.range === Infinity ? GPU_INFINITY : ff.range;
|
|
212
|
+
let falloffCode = 0;
|
|
213
|
+
if (ff.falloff === "LINEAR" /* LINEAR */) falloffCode = 1;
|
|
214
|
+
else if (ff.falloff === "QUADRATIC" /* QUADRATIC */) falloffCode = 2;
|
|
215
|
+
data[base + 10] = falloffCode;
|
|
216
|
+
data[base + 11] = 0;
|
|
217
|
+
}
|
|
218
|
+
return data;
|
|
219
|
+
}
|
|
220
|
+
function createForceFieldTSL(sCurveData, forceFieldOffset, forceFieldCount) {
|
|
221
|
+
const count = Math.min(forceFieldCount, MAX_FORCE_FIELDS);
|
|
222
|
+
const uForceFieldCount = uniform(float(count));
|
|
223
|
+
const ffBase = forceFieldOffset;
|
|
224
|
+
const applyForceFieldsTSL = Fn(
|
|
225
|
+
({
|
|
226
|
+
pos,
|
|
227
|
+
vel,
|
|
228
|
+
delta
|
|
229
|
+
}) => {
|
|
230
|
+
Loop(uForceFieldCount, ({ i }) => {
|
|
231
|
+
const base = i.mul(FIELD_STRIDE).add(ffBase);
|
|
232
|
+
const isActive = sCurveData.element(base);
|
|
233
|
+
If(isActive.lessThan(0.5), () => {
|
|
234
|
+
Continue();
|
|
235
|
+
});
|
|
236
|
+
const fieldType = sCurveData.element(base.add(1));
|
|
237
|
+
const fieldPos = vec3(
|
|
238
|
+
sCurveData.element(base.add(2)),
|
|
239
|
+
sCurveData.element(base.add(3)),
|
|
240
|
+
sCurveData.element(base.add(4))
|
|
241
|
+
);
|
|
242
|
+
const fieldDir = vec3(
|
|
243
|
+
sCurveData.element(base.add(5)),
|
|
244
|
+
sCurveData.element(base.add(6)),
|
|
245
|
+
sCurveData.element(base.add(7))
|
|
246
|
+
);
|
|
247
|
+
const strength = sCurveData.element(base.add(8));
|
|
248
|
+
const range = sCurveData.element(base.add(9));
|
|
249
|
+
const falloffType = sCurveData.element(base.add(10));
|
|
250
|
+
If(strength.equal(0), () => {
|
|
251
|
+
Continue();
|
|
252
|
+
});
|
|
253
|
+
If(fieldType.greaterThan(0.5), () => {
|
|
254
|
+
const force = strength.mul(delta);
|
|
255
|
+
vel.assign(vel.add(fieldDir.mul(force)));
|
|
256
|
+
});
|
|
257
|
+
If(fieldType.lessThan(0.5), () => {
|
|
258
|
+
const toField = fieldPos.sub(pos);
|
|
259
|
+
const dist = length(toField);
|
|
260
|
+
If(dist.greaterThan(1e-4), () => {
|
|
261
|
+
const inRange = dist.lessThan(range);
|
|
262
|
+
If(inRange, () => {
|
|
263
|
+
const dir = normalize(toField);
|
|
264
|
+
const normDist = dist.div(range);
|
|
265
|
+
const falloffNone = float(1);
|
|
266
|
+
const falloffLinear = float(1).sub(normDist);
|
|
267
|
+
const falloffQuadratic = float(1).sub(normDist.mul(normDist));
|
|
268
|
+
const useLinear = falloffType.greaterThan(0.5);
|
|
269
|
+
const useQuadratic = falloffType.greaterThan(1.5);
|
|
270
|
+
const falloff = useQuadratic.select(
|
|
271
|
+
falloffQuadratic,
|
|
272
|
+
useLinear.select(falloffLinear, falloffNone)
|
|
273
|
+
);
|
|
274
|
+
const force = strength.mul(falloff).mul(delta);
|
|
275
|
+
vel.assign(vel.add(dir.mul(force)));
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
"void"
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
/** Uniform for the active force field count. */
|
|
285
|
+
countUniform: uForceFieldCount,
|
|
286
|
+
/** TSL function to call in the compute kernel: apply({ pos, vel, delta }) */
|
|
287
|
+
apply: applyForceFieldsTSL
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/js/effects/three-particles/webgpu/curve-bake.ts
|
|
292
|
+
var CURVE_RESOLUTION = 256;
|
|
293
|
+
function bakeCurveIntoBuffer(buffer, writeOffset, particleSystemId, curve) {
|
|
294
|
+
const curveFn = getCurveFunctionFromConfig(particleSystemId, curve);
|
|
295
|
+
const lastIndex = CURVE_RESOLUTION - 1;
|
|
296
|
+
for (let i = 0; i < CURVE_RESOLUTION; i++) {
|
|
297
|
+
const t = i / lastIndex;
|
|
298
|
+
buffer[writeOffset + i] = curveFn(t);
|
|
299
|
+
}
|
|
300
|
+
return writeOffset + CURVE_RESOLUTION;
|
|
301
|
+
}
|
|
302
|
+
function bakeVelocityAxisIntoBuffer(buffer, writeOffset, particleSystemId, value) {
|
|
303
|
+
if (isLifeTimeCurve(value)) {
|
|
304
|
+
return bakeCurveIntoBuffer(buffer, writeOffset, particleSystemId, value);
|
|
305
|
+
}
|
|
306
|
+
const constantValue = calculateValue(particleSystemId, value, 0.5);
|
|
307
|
+
for (let i = 0; i < CURVE_RESOLUTION; i++) {
|
|
308
|
+
buffer[writeOffset + i] = constantValue;
|
|
309
|
+
}
|
|
310
|
+
return writeOffset + CURVE_RESOLUTION;
|
|
311
|
+
}
|
|
312
|
+
function bakeParticleSystemCurves(normalizedConfig, particleSystemId) {
|
|
313
|
+
let curveCount = 0;
|
|
314
|
+
const {
|
|
315
|
+
sizeOverLifetime,
|
|
316
|
+
opacityOverLifetime,
|
|
317
|
+
colorOverLifetime,
|
|
318
|
+
velocityOverLifetime
|
|
319
|
+
} = normalizedConfig;
|
|
320
|
+
const hasSizeOverLifetime = sizeOverLifetime.isActive;
|
|
321
|
+
const hasOpacityOverLifetime = opacityOverLifetime.isActive;
|
|
322
|
+
const hasColorOverLifetime = colorOverLifetime.isActive;
|
|
323
|
+
const isVelActive = velocityOverLifetime.isActive;
|
|
324
|
+
const hasLinearVelX = isVelActive && velocityOverLifetime.linear.x !== void 0 && velocityOverLifetime.linear.x !== 0;
|
|
325
|
+
const hasLinearVelY = isVelActive && velocityOverLifetime.linear.y !== void 0 && velocityOverLifetime.linear.y !== 0;
|
|
326
|
+
const hasLinearVelZ = isVelActive && velocityOverLifetime.linear.z !== void 0 && velocityOverLifetime.linear.z !== 0;
|
|
327
|
+
const hasOrbitalVelX = isVelActive && velocityOverLifetime.orbital.x !== void 0 && velocityOverLifetime.orbital.x !== 0;
|
|
328
|
+
const hasOrbitalVelY = isVelActive && velocityOverLifetime.orbital.y !== void 0 && velocityOverLifetime.orbital.y !== 0;
|
|
329
|
+
const hasOrbitalVelZ = isVelActive && velocityOverLifetime.orbital.z !== void 0 && velocityOverLifetime.orbital.z !== 0;
|
|
330
|
+
if (hasSizeOverLifetime) curveCount++;
|
|
331
|
+
if (hasOpacityOverLifetime) curveCount++;
|
|
332
|
+
if (hasColorOverLifetime) curveCount += 3;
|
|
333
|
+
if (hasLinearVelX) curveCount++;
|
|
334
|
+
if (hasLinearVelY) curveCount++;
|
|
335
|
+
if (hasLinearVelZ) curveCount++;
|
|
336
|
+
if (hasOrbitalVelX) curveCount++;
|
|
337
|
+
if (hasOrbitalVelY) curveCount++;
|
|
338
|
+
if (hasOrbitalVelZ) curveCount++;
|
|
339
|
+
const data = new Float32Array(curveCount * CURVE_RESOLUTION);
|
|
340
|
+
let writeOffset = 0;
|
|
341
|
+
let nextIndex = 0;
|
|
342
|
+
let sizeOverLifetimeIdx = -1;
|
|
343
|
+
let opacityOverLifetimeIdx = -1;
|
|
344
|
+
let colorRIdx = -1;
|
|
345
|
+
let colorGIdx = -1;
|
|
346
|
+
let colorBIdx = -1;
|
|
347
|
+
let linearVelXIdx = -1;
|
|
348
|
+
let linearVelYIdx = -1;
|
|
349
|
+
let linearVelZIdx = -1;
|
|
350
|
+
let orbitalVelXIdx = -1;
|
|
351
|
+
let orbitalVelYIdx = -1;
|
|
352
|
+
let orbitalVelZIdx = -1;
|
|
353
|
+
if (hasSizeOverLifetime) {
|
|
354
|
+
sizeOverLifetimeIdx = nextIndex++;
|
|
355
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
356
|
+
data,
|
|
357
|
+
writeOffset,
|
|
358
|
+
particleSystemId,
|
|
359
|
+
sizeOverLifetime.lifetimeCurve
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (hasOpacityOverLifetime) {
|
|
363
|
+
opacityOverLifetimeIdx = nextIndex++;
|
|
364
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
365
|
+
data,
|
|
366
|
+
writeOffset,
|
|
367
|
+
particleSystemId,
|
|
368
|
+
opacityOverLifetime.lifetimeCurve
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (hasColorOverLifetime) {
|
|
372
|
+
colorRIdx = nextIndex++;
|
|
373
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
374
|
+
data,
|
|
375
|
+
writeOffset,
|
|
376
|
+
particleSystemId,
|
|
377
|
+
colorOverLifetime.r
|
|
378
|
+
);
|
|
379
|
+
colorGIdx = nextIndex++;
|
|
380
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
381
|
+
data,
|
|
382
|
+
writeOffset,
|
|
383
|
+
particleSystemId,
|
|
384
|
+
colorOverLifetime.g
|
|
385
|
+
);
|
|
386
|
+
colorBIdx = nextIndex++;
|
|
387
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
388
|
+
data,
|
|
389
|
+
writeOffset,
|
|
390
|
+
particleSystemId,
|
|
391
|
+
colorOverLifetime.b
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
if (hasLinearVelX) {
|
|
395
|
+
linearVelXIdx = nextIndex++;
|
|
396
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
397
|
+
data,
|
|
398
|
+
writeOffset,
|
|
399
|
+
particleSystemId,
|
|
400
|
+
velocityOverLifetime.linear.x
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
if (hasLinearVelY) {
|
|
404
|
+
linearVelYIdx = nextIndex++;
|
|
405
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
406
|
+
data,
|
|
407
|
+
writeOffset,
|
|
408
|
+
particleSystemId,
|
|
409
|
+
velocityOverLifetime.linear.y
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
if (hasLinearVelZ) {
|
|
413
|
+
linearVelZIdx = nextIndex++;
|
|
414
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
415
|
+
data,
|
|
416
|
+
writeOffset,
|
|
417
|
+
particleSystemId,
|
|
418
|
+
velocityOverLifetime.linear.z
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (hasOrbitalVelX) {
|
|
422
|
+
orbitalVelXIdx = nextIndex++;
|
|
423
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
424
|
+
data,
|
|
425
|
+
writeOffset,
|
|
426
|
+
particleSystemId,
|
|
427
|
+
velocityOverLifetime.orbital.x
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (hasOrbitalVelY) {
|
|
431
|
+
orbitalVelYIdx = nextIndex++;
|
|
432
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
433
|
+
data,
|
|
434
|
+
writeOffset,
|
|
435
|
+
particleSystemId,
|
|
436
|
+
velocityOverLifetime.orbital.y
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
if (hasOrbitalVelZ) {
|
|
440
|
+
orbitalVelZIdx = nextIndex++;
|
|
441
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
442
|
+
data,
|
|
443
|
+
writeOffset,
|
|
444
|
+
particleSystemId,
|
|
445
|
+
velocityOverLifetime.orbital.z
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
data,
|
|
450
|
+
curveCount,
|
|
451
|
+
sizeOverLifetime: sizeOverLifetimeIdx,
|
|
452
|
+
opacityOverLifetime: opacityOverLifetimeIdx,
|
|
453
|
+
colorR: colorRIdx,
|
|
454
|
+
colorG: colorGIdx,
|
|
455
|
+
colorB: colorBIdx,
|
|
456
|
+
linearVelX: linearVelXIdx,
|
|
457
|
+
linearVelY: linearVelYIdx,
|
|
458
|
+
linearVelZ: linearVelZIdx,
|
|
459
|
+
orbitalVelX: orbitalVelXIdx,
|
|
460
|
+
orbitalVelY: orbitalVelYIdx,
|
|
461
|
+
orbitalVelZ: orbitalVelZIdx
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
var permute = Fn(({ x }) => {
|
|
465
|
+
return mod(x.mul(34).add(10).mul(x), float(289));
|
|
466
|
+
});
|
|
467
|
+
var taylorInvSqrt = Fn(({ r }) => {
|
|
468
|
+
return float(1.79284291400159).sub(float(0.85373472095314).mul(r));
|
|
469
|
+
});
|
|
470
|
+
var snoise3D = Fn(
|
|
471
|
+
({ v }) => {
|
|
472
|
+
const ONE_THIRD = float(1 / 3);
|
|
473
|
+
const ONE_SIXTH = float(1 / 6);
|
|
474
|
+
const i = floor(
|
|
475
|
+
v.add(dot(v, vec3(ONE_THIRD, ONE_THIRD, ONE_THIRD)))
|
|
476
|
+
).toVar();
|
|
477
|
+
const x0 = v.sub(i).add(dot(i, vec3(ONE_SIXTH, ONE_SIXTH, ONE_SIXTH))).toVar();
|
|
478
|
+
const g = step(x0.yzx, x0.xyz).toVar();
|
|
479
|
+
const l = float(1).sub(g).toVar();
|
|
480
|
+
const i1 = min(g.xyz, l.zxy).toVar();
|
|
481
|
+
const i2 = max(g.xyz, l.zxy).toVar();
|
|
482
|
+
const x1 = x0.sub(i1).add(ONE_SIXTH).toVar();
|
|
483
|
+
const x2 = x0.sub(i2).add(ONE_SIXTH.mul(2)).toVar();
|
|
484
|
+
const x3 = x0.sub(float(1)).add(ONE_SIXTH.mul(3)).toVar();
|
|
485
|
+
const iw = mod(i, float(289)).toVar();
|
|
486
|
+
const p0_yz = permute({
|
|
487
|
+
x: permute({
|
|
488
|
+
x: vec4(
|
|
489
|
+
vec2(iw.z, iw.z.add(i1.z)),
|
|
490
|
+
vec2(iw.z.add(i2.z), iw.z.add(1))
|
|
491
|
+
)
|
|
492
|
+
}).add(
|
|
493
|
+
vec4(vec2(iw.y, iw.y.add(i1.y)), vec2(iw.y.add(i2.y), iw.y.add(1)))
|
|
494
|
+
)
|
|
495
|
+
});
|
|
496
|
+
const p = permute({
|
|
497
|
+
x: p0_yz.add(
|
|
498
|
+
vec4(vec2(iw.x, iw.x.add(i1.x)), vec2(iw.x.add(i2.x), iw.x.add(1)))
|
|
499
|
+
)
|
|
500
|
+
});
|
|
501
|
+
const n_ = float(0.142857142857142);
|
|
502
|
+
const j = p.sub(float(49).mul(floor(p.mul(n_).mul(n_)))).toVar();
|
|
503
|
+
const x_ = floor(j.mul(n_)).toVar();
|
|
504
|
+
const y_ = floor(j.sub(float(7).mul(x_))).toVar();
|
|
505
|
+
const NS_X = float(0.285714285714286);
|
|
506
|
+
const NS_Y = float(-0.928571428571429);
|
|
507
|
+
const gx = x_.mul(NS_X).add(NS_Y);
|
|
508
|
+
const gy = y_.mul(NS_X).add(NS_Y);
|
|
509
|
+
const gz = float(1).sub(abs(gx)).sub(abs(gy)).toVar();
|
|
510
|
+
const gz_neg = step(gz, vec4(0));
|
|
511
|
+
const ox = gz_neg.mul(floor(gx).add(0.5));
|
|
512
|
+
const oy = gz_neg.mul(floor(gy).add(0.5));
|
|
513
|
+
const gx_final = gx.sub(ox);
|
|
514
|
+
const gy_final = gy.sub(oy);
|
|
515
|
+
const g0 = vec3(gx_final.x, gy_final.x, gz.x).toVar();
|
|
516
|
+
const g1 = vec3(gx_final.y, gy_final.y, gz.y).toVar();
|
|
517
|
+
const g2 = vec3(gx_final.z, gy_final.z, gz.z).toVar();
|
|
518
|
+
const g3 = vec3(gx_final.w, gy_final.w, gz.w).toVar();
|
|
519
|
+
const norm = taylorInvSqrt({
|
|
520
|
+
r: vec4(vec2(dot(g0, g0), dot(g1, g1)), vec2(dot(g2, g2), dot(g3, g3)))
|
|
521
|
+
});
|
|
522
|
+
g0.assign(g0.mul(norm.x));
|
|
523
|
+
g1.assign(g1.mul(norm.y));
|
|
524
|
+
g2.assign(g2.mul(norm.z));
|
|
525
|
+
g3.assign(g3.mul(norm.w));
|
|
526
|
+
const m = max(
|
|
527
|
+
vec4(
|
|
528
|
+
vec2(float(0.5).sub(dot(x0, x0)), float(0.5).sub(dot(x1, x1))),
|
|
529
|
+
vec2(float(0.5).sub(dot(x2, x2)), float(0.5).sub(dot(x3, x3)))
|
|
530
|
+
),
|
|
531
|
+
float(0)
|
|
532
|
+
).toVar();
|
|
533
|
+
const m2 = m.mul(m).toVar();
|
|
534
|
+
const m4 = m2.mul(m2).toVar();
|
|
535
|
+
const gdot = vec4(
|
|
536
|
+
vec2(dot(g0, x0), dot(g1, x1)),
|
|
537
|
+
vec2(dot(g2, x2), dot(g3, x3))
|
|
538
|
+
);
|
|
539
|
+
return float(42).mul(dot(m4, gdot));
|
|
540
|
+
}
|
|
541
|
+
);
|
|
542
|
+
Fn(
|
|
543
|
+
({ t }) => {
|
|
544
|
+
const noiseX = snoise3D({ v: vec3(t, float(0), float(0)) });
|
|
545
|
+
const noiseY = snoise3D({ v: vec3(t, t, float(0)) });
|
|
546
|
+
const noiseZ = snoise3D({ v: vec3(t, t, t) });
|
|
547
|
+
return vec3(noiseX, noiseY, noiseZ);
|
|
548
|
+
}
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
// src/js/effects/three-particles/webgpu/compute-modifiers.ts
|
|
552
|
+
var INIT_STRIDE = 28;
|
|
553
|
+
function createModifierStorageBuffers(maxParticles, instanced, curveData, hasForceFields = false, hasCollisionPlanes = false) {
|
|
554
|
+
const Cls = instanced ? StorageInstancedBufferAttribute : StorageBufferAttribute;
|
|
555
|
+
const curveLen = Math.max(curveData.length, 1);
|
|
556
|
+
const ffSize = hasForceFields ? FORCE_FIELD_DATA_SIZE : 0;
|
|
557
|
+
const cpSize = hasCollisionPlanes ? COLLISION_PLANE_DATA_SIZE : 0;
|
|
558
|
+
const totalLen = curveLen + maxParticles * INIT_STRIDE + ffSize + cpSize;
|
|
559
|
+
const combined = new Float32Array(totalLen);
|
|
560
|
+
combined.set(curveData.length > 0 ? curveData : new Float32Array([0]));
|
|
561
|
+
return {
|
|
562
|
+
// Position and velocity use vec4 (w=padding) to avoid WebGPU vec3→vec4
|
|
563
|
+
// storage buffer alignment conversion that breaks itemSize-based type resolution.
|
|
564
|
+
position: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
565
|
+
velocity: new StorageBufferAttribute(new Float32Array(maxParticles * 4), 4),
|
|
566
|
+
color: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
567
|
+
// (lifetime, size, rotation, startFrame)
|
|
568
|
+
particleState: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
569
|
+
// (startLifetime, startSize, startOpacity, startColorR)
|
|
570
|
+
startValues: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
571
|
+
// (startColorG, startColorB, rotationSpeed, noiseOffset)
|
|
572
|
+
startColorsExt: new StorageBufferAttribute(
|
|
573
|
+
new Float32Array(maxParticles * 4),
|
|
574
|
+
4
|
|
575
|
+
),
|
|
576
|
+
// (orbitalOffset.x, .y, .z, isActive)
|
|
577
|
+
orbitalIsActive: new StorageBufferAttribute(
|
|
578
|
+
new Float32Array(maxParticles * 4),
|
|
579
|
+
4
|
|
580
|
+
),
|
|
581
|
+
// Curve data + emit queue tail (single buffer, 8th binding)
|
|
582
|
+
curveData: new StorageBufferAttribute(combined, 1)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
var _emitCounts = /* @__PURE__ */ new WeakMap();
|
|
586
|
+
var _curveDataLengths = /* @__PURE__ */ new WeakMap();
|
|
587
|
+
var _currentEmitIndices = /* @__PURE__ */ new WeakMap();
|
|
588
|
+
var _previousEmitIndices = /* @__PURE__ */ new WeakMap();
|
|
589
|
+
function writeParticleToModifierBuffers(buffers, index, data) {
|
|
590
|
+
const curveLen = _curveDataLengths.get(buffers.curveData) ?? 0;
|
|
591
|
+
const arr = buffers.curveData.array;
|
|
592
|
+
const base = curveLen + index * INIT_STRIDE;
|
|
593
|
+
arr[base] = data.position.x;
|
|
594
|
+
arr[base + 1] = data.position.y;
|
|
595
|
+
arr[base + 2] = data.position.z;
|
|
596
|
+
arr[base + 3] = 1;
|
|
597
|
+
arr[base + 4] = data.velocity.x;
|
|
598
|
+
arr[base + 5] = data.velocity.y;
|
|
599
|
+
arr[base + 6] = data.velocity.z;
|
|
600
|
+
arr[base + 7] = 0;
|
|
601
|
+
arr[base + 8] = data.colorR;
|
|
602
|
+
arr[base + 9] = data.colorG;
|
|
603
|
+
arr[base + 10] = data.colorB;
|
|
604
|
+
arr[base + 11] = data.colorA;
|
|
605
|
+
arr[base + 12] = 0;
|
|
606
|
+
arr[base + 13] = data.size;
|
|
607
|
+
arr[base + 14] = data.rotation;
|
|
608
|
+
arr[base + 15] = data.startFrame;
|
|
609
|
+
arr[base + 16] = data.orbitalOffset.x;
|
|
610
|
+
arr[base + 17] = data.orbitalOffset.y;
|
|
611
|
+
arr[base + 18] = data.orbitalOffset.z;
|
|
612
|
+
arr[base + 19] = 1;
|
|
613
|
+
arr[base + 20] = data.startLifetime;
|
|
614
|
+
arr[base + 21] = data.startSize;
|
|
615
|
+
arr[base + 22] = data.startOpacity;
|
|
616
|
+
arr[base + 23] = data.startColorR;
|
|
617
|
+
arr[base + 24] = data.startColorG;
|
|
618
|
+
arr[base + 25] = data.startColorB;
|
|
619
|
+
arr[base + 26] = data.rotationSpeed;
|
|
620
|
+
arr[base + 27] = data.noiseOffset;
|
|
621
|
+
_emitCounts.set(
|
|
622
|
+
buffers.curveData,
|
|
623
|
+
(_emitCounts.get(buffers.curveData) ?? 0) + 1
|
|
624
|
+
);
|
|
625
|
+
let indices = _currentEmitIndices.get(buffers.curveData);
|
|
626
|
+
if (!indices) {
|
|
627
|
+
indices = [];
|
|
628
|
+
_currentEmitIndices.set(buffers.curveData, indices);
|
|
629
|
+
}
|
|
630
|
+
indices.push(index);
|
|
631
|
+
const i4 = index * 4;
|
|
632
|
+
const svArr = buffers.startValues.array;
|
|
633
|
+
svArr[i4] = data.startLifetime;
|
|
634
|
+
svArr[i4 + 1] = data.startSize;
|
|
635
|
+
svArr[i4 + 2] = data.startOpacity;
|
|
636
|
+
svArr[i4 + 3] = data.startColorR;
|
|
637
|
+
const sceArr = buffers.startColorsExt.array;
|
|
638
|
+
sceArr[i4] = data.startColorG;
|
|
639
|
+
sceArr[i4 + 1] = data.startColorB;
|
|
640
|
+
sceArr[i4 + 2] = data.rotationSpeed;
|
|
641
|
+
sceArr[i4 + 3] = data.noiseOffset;
|
|
642
|
+
}
|
|
643
|
+
function registerCurveDataLength(buffers, curveDataLength) {
|
|
644
|
+
_curveDataLengths.set(buffers.curveData, curveDataLength);
|
|
645
|
+
}
|
|
646
|
+
function flushEmitQueue(buffers) {
|
|
647
|
+
const count = _emitCounts.get(buffers.curveData) ?? 0;
|
|
648
|
+
const curveLen = _curveDataLengths.get(buffers.curveData) ?? 0;
|
|
649
|
+
const arr = buffers.curveData.array;
|
|
650
|
+
const current = _currentEmitIndices.get(buffers.curveData);
|
|
651
|
+
const previous = _previousEmitIndices.get(buffers.curveData);
|
|
652
|
+
let clearedAny = false;
|
|
653
|
+
if (previous && previous.length > 0) {
|
|
654
|
+
const currentSet = current && current.length > 0 ? new Set(current) : null;
|
|
655
|
+
for (let i = 0; i < previous.length; i++) {
|
|
656
|
+
const p = previous[i];
|
|
657
|
+
if (!currentSet || !currentSet.has(p)) {
|
|
658
|
+
const flagOffset = curveLen + p * INIT_STRIDE + 3;
|
|
659
|
+
if (arr[flagOffset] > 0.5) {
|
|
660
|
+
arr[flagOffset] = 0;
|
|
661
|
+
clearedAny = true;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (count > 0 || clearedAny) {
|
|
667
|
+
buffers.curveData.needsUpdate = true;
|
|
668
|
+
}
|
|
669
|
+
if (current && current.length > 0) {
|
|
670
|
+
let prevArr = _previousEmitIndices.get(buffers.curveData);
|
|
671
|
+
if (!prevArr) {
|
|
672
|
+
prevArr = [];
|
|
673
|
+
_previousEmitIndices.set(buffers.curveData, prevArr);
|
|
674
|
+
}
|
|
675
|
+
prevArr.length = current.length;
|
|
676
|
+
for (let i = 0; i < current.length; i++) {
|
|
677
|
+
prevArr[i] = current[i];
|
|
678
|
+
}
|
|
679
|
+
current.length = 0;
|
|
680
|
+
} else {
|
|
681
|
+
const prevArr = _previousEmitIndices.get(buffers.curveData);
|
|
682
|
+
if (prevArr) prevArr.length = 0;
|
|
683
|
+
if (current) current.length = 0;
|
|
684
|
+
}
|
|
685
|
+
_emitCounts.set(buffers.curveData, 0);
|
|
686
|
+
return count;
|
|
687
|
+
}
|
|
688
|
+
function deactivateParticleInModifierBuffers(_buffers, _index) {
|
|
689
|
+
}
|
|
690
|
+
function createCurveLookup(sCurveData) {
|
|
691
|
+
return Fn(
|
|
692
|
+
({
|
|
693
|
+
curveIndex,
|
|
694
|
+
t
|
|
695
|
+
}) => {
|
|
696
|
+
const clamped = min(t, float(1));
|
|
697
|
+
const pos = clamped.mul(CURVE_RESOLUTION - 1);
|
|
698
|
+
const idx0 = floor(pos);
|
|
699
|
+
const f = fract(pos);
|
|
700
|
+
const base = curveIndex.mul(CURVE_RESOLUTION);
|
|
701
|
+
const v0 = sCurveData.element(base.add(idx0));
|
|
702
|
+
const v1 = sCurveData.element(
|
|
703
|
+
base.add(min(idx0.add(1), float(CURVE_RESOLUTION - 1)))
|
|
704
|
+
);
|
|
705
|
+
return mix(v0, v1, f);
|
|
706
|
+
}
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
function createModifierComputeUpdate(buffers, maxParticles, curveMap, flags, forceFieldCount = 0, collisionPlaneCount = 0) {
|
|
710
|
+
const uDelta = uniform(float(0));
|
|
711
|
+
const uDeltaMs = uniform(float(0));
|
|
712
|
+
const uGravityVelocity = uniform(new Vector3(0, 0, 0));
|
|
713
|
+
const uWorldPositionChange = uniform(new Vector3(0, 0, 0));
|
|
714
|
+
const uSimSpaceWorld = uniform(float(0));
|
|
715
|
+
const uNoiseStrength = uniform(float(0));
|
|
716
|
+
const uNoisePower = uniform(float(0));
|
|
717
|
+
const uNoiseFrequency = uniform(float(1));
|
|
718
|
+
const uNoisePosAmount = uniform(float(0));
|
|
719
|
+
const uNoiseRotAmount = uniform(float(0));
|
|
720
|
+
const uNoiseSizeAmount = uniform(float(0));
|
|
721
|
+
const sPosition = storage(buffers.position, "vec4", maxParticles);
|
|
722
|
+
const sVelocity = storage(buffers.velocity, "vec4", maxParticles);
|
|
723
|
+
const sColor = storage(buffers.color, "vec4", maxParticles);
|
|
724
|
+
const sParticleState = storage(buffers.particleState, "vec4", maxParticles);
|
|
725
|
+
const sStartValues = storage(buffers.startValues, "vec4", maxParticles);
|
|
726
|
+
const sStartColorsExt = storage(buffers.startColorsExt, "vec4", maxParticles);
|
|
727
|
+
const sOrbitalIsActive = storage(
|
|
728
|
+
buffers.orbitalIsActive,
|
|
729
|
+
"vec4",
|
|
730
|
+
maxParticles
|
|
731
|
+
);
|
|
732
|
+
const sCurveData = storage(
|
|
733
|
+
buffers.curveData,
|
|
734
|
+
"float",
|
|
735
|
+
buffers.curveData.array.length
|
|
736
|
+
);
|
|
737
|
+
const curveLen = Math.max(curveMap.data.length, 1);
|
|
738
|
+
const lookupCurve = createCurveLookup(sCurveData);
|
|
739
|
+
const forceFieldOffset = curveLen + maxParticles * INIT_STRIDE;
|
|
740
|
+
const forceFieldNodes = flags.forceFields ? createForceFieldTSL(sCurveData, forceFieldOffset, forceFieldCount) : null;
|
|
741
|
+
const ffSize = flags.forceFields ? FORCE_FIELD_DATA_SIZE : 0;
|
|
742
|
+
const collisionPlaneOffset = forceFieldOffset + ffSize;
|
|
743
|
+
const collisionPlaneNodes = flags.collisionPlanes ? createCollisionPlaneTSL(
|
|
744
|
+
sCurveData,
|
|
745
|
+
collisionPlaneOffset,
|
|
746
|
+
collisionPlaneCount
|
|
747
|
+
) : null;
|
|
748
|
+
const computeKernel = Fn(() => {
|
|
749
|
+
const i = instanceIndex;
|
|
750
|
+
const initBase = i.mul(INIT_STRIDE).add(curveLen);
|
|
751
|
+
const initFlag = sCurveData.element(initBase.add(3));
|
|
752
|
+
If(initFlag.greaterThan(0.5), () => {
|
|
753
|
+
sPosition.element(i).assign(
|
|
754
|
+
vec4(
|
|
755
|
+
sCurveData.element(initBase),
|
|
756
|
+
sCurveData.element(initBase.add(1)),
|
|
757
|
+
sCurveData.element(initBase.add(2)),
|
|
758
|
+
0
|
|
759
|
+
)
|
|
760
|
+
);
|
|
761
|
+
sVelocity.element(i).assign(
|
|
762
|
+
vec4(
|
|
763
|
+
sCurveData.element(initBase.add(4)),
|
|
764
|
+
sCurveData.element(initBase.add(5)),
|
|
765
|
+
sCurveData.element(initBase.add(6)),
|
|
766
|
+
0
|
|
767
|
+
)
|
|
768
|
+
);
|
|
769
|
+
sColor.element(i).assign(
|
|
770
|
+
vec4(
|
|
771
|
+
sCurveData.element(initBase.add(8)),
|
|
772
|
+
sCurveData.element(initBase.add(9)),
|
|
773
|
+
sCurveData.element(initBase.add(10)),
|
|
774
|
+
sCurveData.element(initBase.add(11))
|
|
775
|
+
)
|
|
776
|
+
);
|
|
777
|
+
sParticleState.element(i).assign(
|
|
778
|
+
vec4(
|
|
779
|
+
sCurveData.element(initBase.add(12)),
|
|
780
|
+
sCurveData.element(initBase.add(13)),
|
|
781
|
+
sCurveData.element(initBase.add(14)),
|
|
782
|
+
sCurveData.element(initBase.add(15))
|
|
783
|
+
)
|
|
784
|
+
);
|
|
785
|
+
sOrbitalIsActive.element(i).assign(
|
|
786
|
+
vec4(
|
|
787
|
+
sCurveData.element(initBase.add(16)),
|
|
788
|
+
sCurveData.element(initBase.add(17)),
|
|
789
|
+
sCurveData.element(initBase.add(18)),
|
|
790
|
+
sCurveData.element(initBase.add(19))
|
|
791
|
+
)
|
|
792
|
+
);
|
|
793
|
+
sStartValues.element(i).assign(
|
|
794
|
+
vec4(
|
|
795
|
+
sCurveData.element(initBase.add(20)),
|
|
796
|
+
sCurveData.element(initBase.add(21)),
|
|
797
|
+
sCurveData.element(initBase.add(22)),
|
|
798
|
+
sCurveData.element(initBase.add(23))
|
|
799
|
+
)
|
|
800
|
+
);
|
|
801
|
+
sStartColorsExt.element(i).assign(
|
|
802
|
+
vec4(
|
|
803
|
+
sCurveData.element(initBase.add(24)),
|
|
804
|
+
sCurveData.element(initBase.add(25)),
|
|
805
|
+
sCurveData.element(initBase.add(26)),
|
|
806
|
+
sCurveData.element(initBase.add(27))
|
|
807
|
+
)
|
|
808
|
+
);
|
|
809
|
+
sCurveData.element(initBase.add(3)).assign(float(0));
|
|
810
|
+
});
|
|
811
|
+
const oiaVec = sOrbitalIsActive.element(i).toVar();
|
|
812
|
+
If(oiaVec.w.greaterThanEqual(float(0.5)), () => {
|
|
813
|
+
const pos = sPosition.element(i).xyz.toVar();
|
|
814
|
+
const vel = sVelocity.element(i).xyz.toVar();
|
|
815
|
+
const ps = sParticleState.element(i).toVar();
|
|
816
|
+
const sv = sStartValues.element(i);
|
|
817
|
+
ps.x;
|
|
818
|
+
const startLife = sv.x;
|
|
819
|
+
vel.assign(vel.sub(vec3(uGravityVelocity).mul(uDelta)));
|
|
820
|
+
if (forceFieldNodes) {
|
|
821
|
+
forceFieldNodes.apply({ pos, vel, delta: uDelta });
|
|
822
|
+
}
|
|
823
|
+
If(uSimSpaceWorld.greaterThan(0.5), () => {
|
|
824
|
+
pos.assign(pos.sub(vec3(uWorldPositionChange)));
|
|
825
|
+
});
|
|
826
|
+
pos.assign(pos.add(vel.mul(uDelta)));
|
|
827
|
+
if (collisionPlaneNodes) {
|
|
828
|
+
collisionPlaneNodes.apply({
|
|
829
|
+
pos,
|
|
830
|
+
vel,
|
|
831
|
+
oiaVec,
|
|
832
|
+
sColorNode: sColor,
|
|
833
|
+
ps,
|
|
834
|
+
startLife,
|
|
835
|
+
particleIdx: i,
|
|
836
|
+
sOrbitalIsActiveNode: sOrbitalIsActive
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
const lifePct = min(ps.x.div(startLife), float(1));
|
|
840
|
+
ps.x.assign(ps.x.add(uDeltaMs));
|
|
841
|
+
if (flags.linearVelocity) {
|
|
842
|
+
const lvx = curveMap.linearVelX >= 0 ? lookupCurve({
|
|
843
|
+
curveIndex: float(curveMap.linearVelX),
|
|
844
|
+
t: lifePct
|
|
845
|
+
}) : float(0);
|
|
846
|
+
const lvy = curveMap.linearVelY >= 0 ? lookupCurve({
|
|
847
|
+
curveIndex: float(curveMap.linearVelY),
|
|
848
|
+
t: lifePct
|
|
849
|
+
}) : float(0);
|
|
850
|
+
const lvz = curveMap.linearVelZ >= 0 ? lookupCurve({
|
|
851
|
+
curveIndex: float(curveMap.linearVelZ),
|
|
852
|
+
t: lifePct
|
|
853
|
+
}) : float(0);
|
|
854
|
+
pos.assign(pos.add(vec3(lvx, lvy, lvz).mul(uDelta)));
|
|
855
|
+
}
|
|
856
|
+
if (flags.orbitalVelocity) {
|
|
857
|
+
const offset = vec3(oiaVec.x, oiaVec.y, oiaVec.z).toVar();
|
|
858
|
+
pos.assign(pos.sub(offset));
|
|
859
|
+
const ovx = curveMap.orbitalVelX >= 0 ? lookupCurve({
|
|
860
|
+
curveIndex: float(curveMap.orbitalVelX),
|
|
861
|
+
t: lifePct
|
|
862
|
+
}) : float(0);
|
|
863
|
+
const ovy = curveMap.orbitalVelY >= 0 ? lookupCurve({
|
|
864
|
+
curveIndex: float(curveMap.orbitalVelY),
|
|
865
|
+
t: lifePct
|
|
866
|
+
}) : float(0);
|
|
867
|
+
const ovz = curveMap.orbitalVelZ >= 0 ? lookupCurve({
|
|
868
|
+
curveIndex: float(curveMap.orbitalVelZ),
|
|
869
|
+
t: lifePct
|
|
870
|
+
}) : float(0);
|
|
871
|
+
const ax = ovx.mul(uDelta);
|
|
872
|
+
const ay = ovz.mul(uDelta);
|
|
873
|
+
const az = ovy.mul(uDelta);
|
|
874
|
+
const cosAz = cos(az);
|
|
875
|
+
const sinAz = sin(az);
|
|
876
|
+
const zx = offset.x.mul(cosAz).sub(offset.y.mul(sinAz));
|
|
877
|
+
const zy = offset.x.mul(sinAz).add(offset.y.mul(cosAz));
|
|
878
|
+
const zz = offset.z;
|
|
879
|
+
const cosAy = cos(ay);
|
|
880
|
+
const sinAy = sin(ay);
|
|
881
|
+
const yx = zx.mul(cosAy).add(zz.mul(sinAy));
|
|
882
|
+
const yy = zy;
|
|
883
|
+
const yz = zx.negate().mul(sinAy).add(zz.mul(cosAy));
|
|
884
|
+
const cosAx = cos(ax);
|
|
885
|
+
const sinAx = sin(ax);
|
|
886
|
+
const fx = yx;
|
|
887
|
+
const fy = yy.mul(cosAx).sub(yz.mul(sinAx));
|
|
888
|
+
const fz = yy.mul(sinAx).add(yz.mul(cosAx));
|
|
889
|
+
offset.assign(vec3(fx, fy, fz));
|
|
890
|
+
oiaVec.x.assign(offset.x);
|
|
891
|
+
oiaVec.y.assign(offset.y);
|
|
892
|
+
oiaVec.z.assign(offset.z);
|
|
893
|
+
pos.assign(pos.add(offset));
|
|
894
|
+
}
|
|
895
|
+
if (flags.sizeOverLifetime && curveMap.sizeOverLifetime >= 0) {
|
|
896
|
+
const multiplier = lookupCurve({
|
|
897
|
+
curveIndex: float(curveMap.sizeOverLifetime),
|
|
898
|
+
t: lifePct
|
|
899
|
+
});
|
|
900
|
+
ps.y.assign(sv.y.mul(multiplier));
|
|
901
|
+
}
|
|
902
|
+
if (flags.opacityOverLifetime && curveMap.opacityOverLifetime >= 0) {
|
|
903
|
+
const multiplier = lookupCurve({
|
|
904
|
+
curveIndex: float(curveMap.opacityOverLifetime),
|
|
905
|
+
t: lifePct
|
|
906
|
+
});
|
|
907
|
+
const col = sColor.element(i).toVar();
|
|
908
|
+
col.w.assign(sv.z.mul(multiplier));
|
|
909
|
+
sColor.element(i).assign(col);
|
|
910
|
+
}
|
|
911
|
+
if (flags.colorOverLifetime) {
|
|
912
|
+
const col = sColor.element(i).toVar();
|
|
913
|
+
const sce = sStartColorsExt.element(i);
|
|
914
|
+
if (curveMap.colorR >= 0) {
|
|
915
|
+
const rMul = lookupCurve({
|
|
916
|
+
curveIndex: float(curveMap.colorR),
|
|
917
|
+
t: lifePct
|
|
918
|
+
});
|
|
919
|
+
col.x.assign(sv.w.mul(rMul));
|
|
920
|
+
}
|
|
921
|
+
if (curveMap.colorG >= 0) {
|
|
922
|
+
const gMul = lookupCurve({
|
|
923
|
+
curveIndex: float(curveMap.colorG),
|
|
924
|
+
t: lifePct
|
|
925
|
+
});
|
|
926
|
+
col.y.assign(sce.x.mul(gMul));
|
|
927
|
+
}
|
|
928
|
+
if (curveMap.colorB >= 0) {
|
|
929
|
+
const bMul = lookupCurve({
|
|
930
|
+
curveIndex: float(curveMap.colorB),
|
|
931
|
+
t: lifePct
|
|
932
|
+
});
|
|
933
|
+
col.z.assign(sce.y.mul(bMul));
|
|
934
|
+
}
|
|
935
|
+
sColor.element(i).assign(col);
|
|
936
|
+
}
|
|
937
|
+
if (flags.rotationOverLifetime) {
|
|
938
|
+
const sce = sStartColorsExt.element(i);
|
|
939
|
+
ps.z.assign(ps.z.add(sce.z.mul(uDelta).mul(float(0.02))));
|
|
940
|
+
}
|
|
941
|
+
if (flags.noise) {
|
|
942
|
+
const sce = sStartColorsExt.element(i);
|
|
943
|
+
const noisePos = lifePct.add(sce.w).mul(10).mul(uNoiseStrength).mul(uNoiseFrequency);
|
|
944
|
+
const noiseX = snoise3D({ v: vec3(noisePos, float(0), float(0)) });
|
|
945
|
+
const noiseY = snoise3D({
|
|
946
|
+
v: vec3(noisePos, noisePos, float(0))
|
|
947
|
+
});
|
|
948
|
+
const noiseZ = snoise3D({
|
|
949
|
+
v: vec3(noisePos, noisePos, noisePos)
|
|
950
|
+
});
|
|
951
|
+
pos.assign(
|
|
952
|
+
pos.add(
|
|
953
|
+
vec3(noiseX, noiseY, noiseZ).mul(uNoisePower).mul(uNoisePosAmount)
|
|
954
|
+
)
|
|
955
|
+
);
|
|
956
|
+
If(uNoiseRotAmount.greaterThan(1e-3), () => {
|
|
957
|
+
ps.z.assign(ps.z.add(noiseX.mul(uNoisePower).mul(uNoiseRotAmount)));
|
|
958
|
+
});
|
|
959
|
+
If(uNoiseSizeAmount.greaterThan(1e-3), () => {
|
|
960
|
+
ps.y.assign(ps.y.add(noiseX.mul(uNoisePower).mul(uNoiseSizeAmount)));
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
sPosition.element(i).assign(vec4(pos, 0));
|
|
964
|
+
sVelocity.element(i).assign(vec4(vel, 0));
|
|
965
|
+
sParticleState.element(i).assign(ps);
|
|
966
|
+
sOrbitalIsActive.element(i).assign(oiaVec);
|
|
967
|
+
If(ps.x.greaterThan(startLife), () => {
|
|
968
|
+
const deadOia = sOrbitalIsActive.element(i).toVar();
|
|
969
|
+
deadOia.w.assign(float(0));
|
|
970
|
+
sOrbitalIsActive.element(i).assign(deadOia);
|
|
971
|
+
sColor.element(i).assign(vec4(0));
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
});
|
|
975
|
+
const computeNode = compute(computeKernel(), maxParticles);
|
|
976
|
+
return {
|
|
977
|
+
computeNode,
|
|
978
|
+
uniforms: {
|
|
979
|
+
delta: uDelta,
|
|
980
|
+
deltaMs: uDeltaMs,
|
|
981
|
+
gravityVelocity: uGravityVelocity,
|
|
982
|
+
worldPositionChange: uWorldPositionChange,
|
|
983
|
+
simulationSpaceWorld: uSimSpaceWorld,
|
|
984
|
+
noiseStrength: uNoiseStrength,
|
|
985
|
+
noisePower: uNoisePower,
|
|
986
|
+
noiseFrequency: uNoiseFrequency,
|
|
987
|
+
noisePositionAmount: uNoisePosAmount,
|
|
988
|
+
noiseRotationAmount: uNoiseRotAmount,
|
|
989
|
+
noiseSizeAmount: uNoiseSizeAmount
|
|
990
|
+
},
|
|
991
|
+
buffers,
|
|
992
|
+
curveDataLength: curveLen,
|
|
993
|
+
/** Force field offset and count uniform (null if no force fields). */
|
|
994
|
+
forceFieldInfo: forceFieldNodes ? {
|
|
995
|
+
offset: forceFieldOffset,
|
|
996
|
+
countUniform: forceFieldNodes.countUniform
|
|
997
|
+
} : null,
|
|
998
|
+
/** Collision plane offset and count uniform (null if no collision planes). */
|
|
999
|
+
collisionPlaneInfo: collisionPlaneNodes ? {
|
|
1000
|
+
offset: collisionPlaneOffset,
|
|
1001
|
+
countUniform: collisionPlaneNodes.countUniform
|
|
1002
|
+
} : null
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/js/effects/three-particles/three-particles-constants.ts
|
|
1007
|
+
var POINT_SIZE_SCALE = 100;
|
|
1008
|
+
var ALPHA_DISCARD_THRESHOLD = 1e-3;
|
|
1009
|
+
var _dummyTexture = null;
|
|
1010
|
+
function getDummyTexture() {
|
|
1011
|
+
if (!_dummyTexture) {
|
|
1012
|
+
_dummyTexture = new DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
|
|
1013
|
+
_dummyTexture.needsUpdate = true;
|
|
1014
|
+
}
|
|
1015
|
+
return _dummyTexture;
|
|
1016
|
+
}
|
|
1017
|
+
function createParticleUniforms(sharedUniforms) {
|
|
1018
|
+
const dummy = getDummyTexture();
|
|
1019
|
+
const map = sharedUniforms.map.value ?? dummy;
|
|
1020
|
+
if (map) map.colorSpace = NoColorSpace;
|
|
1021
|
+
return {
|
|
1022
|
+
uMap: map,
|
|
1023
|
+
uElapsed: uniform(float(sharedUniforms.elapsed.value)),
|
|
1024
|
+
uFps: uniform(float(sharedUniforms.fps.value)),
|
|
1025
|
+
uUseFPSForFrameIndex: uniform(
|
|
1026
|
+
float(sharedUniforms.useFPSForFrameIndex.value ? 1 : 0)
|
|
1027
|
+
),
|
|
1028
|
+
uTiles: uniform(sharedUniforms.tiles.value),
|
|
1029
|
+
uDiscardBg: uniform(
|
|
1030
|
+
float(sharedUniforms.discardBackgroundColor.value ? 1 : 0)
|
|
1031
|
+
),
|
|
1032
|
+
uBgColor: uniform(
|
|
1033
|
+
new Vector3(
|
|
1034
|
+
sharedUniforms.backgroundColor.value.r,
|
|
1035
|
+
sharedUniforms.backgroundColor.value.g,
|
|
1036
|
+
sharedUniforms.backgroundColor.value.b
|
|
1037
|
+
)
|
|
1038
|
+
),
|
|
1039
|
+
uBgTolerance: uniform(float(sharedUniforms.backgroundColorTolerance.value)),
|
|
1040
|
+
uSoftEnabled: uniform(
|
|
1041
|
+
float(sharedUniforms.softParticlesEnabled.value ? 1 : 0)
|
|
1042
|
+
),
|
|
1043
|
+
uSoftIntensity: uniform(float(sharedUniforms.softParticlesIntensity.value)),
|
|
1044
|
+
uSceneDepthTex: sharedUniforms.sceneDepthTexture.value ?? dummy,
|
|
1045
|
+
uCameraNearFar: uniform(sharedUniforms.cameraNearFar.value)
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
var computeFrameIndex = Fn(
|
|
1049
|
+
({
|
|
1050
|
+
vLifetime,
|
|
1051
|
+
vStartLifetime,
|
|
1052
|
+
vStartFrame,
|
|
1053
|
+
uFps,
|
|
1054
|
+
uUseFPSForFrameIndex,
|
|
1055
|
+
uTiles
|
|
1056
|
+
}) => {
|
|
1057
|
+
const totalFrames = uTiles.x.mul(uTiles.y);
|
|
1058
|
+
const lifePercent = min(vLifetime.div(vStartLifetime), float(1));
|
|
1059
|
+
const fpsBased = max(vLifetime.div(1e3).mul(uFps), float(0));
|
|
1060
|
+
const lifetimeBased = max(
|
|
1061
|
+
min(floor(lifePercent.mul(totalFrames)), totalFrames.sub(1)),
|
|
1062
|
+
float(0)
|
|
1063
|
+
);
|
|
1064
|
+
const fpsResult = uFps.equal(0).select(float(0), fpsBased);
|
|
1065
|
+
const frameOffset = uUseFPSForFrameIndex.greaterThan(0.5).select(fpsResult, lifetimeBased);
|
|
1066
|
+
return round(vStartFrame).add(frameOffset);
|
|
1067
|
+
}
|
|
1068
|
+
);
|
|
1069
|
+
var computeSpriteSheetUV = Fn(
|
|
1070
|
+
({ baseUV, frameIndex, uTiles }) => {
|
|
1071
|
+
const spriteX = floor(mod(frameIndex, uTiles.x));
|
|
1072
|
+
const spriteY = floor(mod(frameIndex.div(uTiles.x), uTiles.y));
|
|
1073
|
+
return vec2(
|
|
1074
|
+
baseUV.x.div(uTiles.x).add(spriteX.div(uTiles.x)),
|
|
1075
|
+
baseUV.y.div(uTiles.y).add(spriteY.div(uTiles.y))
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
);
|
|
1079
|
+
var linearizeDepth = Fn(
|
|
1080
|
+
({ depthSample, near, far }) => {
|
|
1081
|
+
const zNdc = depthSample.mul(2).sub(1);
|
|
1082
|
+
return near.mul(2).mul(far).div(far.add(near).sub(zNdc.mul(far.sub(near))));
|
|
1083
|
+
}
|
|
1084
|
+
);
|
|
1085
|
+
var computeSoftParticleFade = Fn(
|
|
1086
|
+
({
|
|
1087
|
+
viewZ,
|
|
1088
|
+
uSoftEnabled,
|
|
1089
|
+
uSoftIntensity,
|
|
1090
|
+
uSceneDepthTex,
|
|
1091
|
+
uCameraNearFar
|
|
1092
|
+
}) => {
|
|
1093
|
+
const softFade = float(1).toVar();
|
|
1094
|
+
If(uSoftEnabled.greaterThan(0.5), () => {
|
|
1095
|
+
const depthSample = texture(uSceneDepthTex, screenUV).x;
|
|
1096
|
+
const sceneDepthLinear = linearizeDepth({
|
|
1097
|
+
depthSample,
|
|
1098
|
+
near: uCameraNearFar.x,
|
|
1099
|
+
far: uCameraNearFar.y
|
|
1100
|
+
});
|
|
1101
|
+
const depthDiff = sceneDepthLinear.sub(viewZ);
|
|
1102
|
+
softFade.assign(smoothstep(float(0), uSoftIntensity, depthDiff));
|
|
1103
|
+
});
|
|
1104
|
+
return softFade;
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
1107
|
+
var applyBackgroundDiscard = Fn(
|
|
1108
|
+
({
|
|
1109
|
+
texColor,
|
|
1110
|
+
uDiscardBg,
|
|
1111
|
+
uBgColor,
|
|
1112
|
+
uBgTolerance
|
|
1113
|
+
}) => {
|
|
1114
|
+
If(uDiscardBg.greaterThan(0.5), () => {
|
|
1115
|
+
const diff = vec3(
|
|
1116
|
+
texColor.x.sub(uBgColor.x),
|
|
1117
|
+
texColor.y.sub(uBgColor.y),
|
|
1118
|
+
texColor.z.sub(uBgColor.z)
|
|
1119
|
+
);
|
|
1120
|
+
If(abs(length(diff)).lessThan(uBgTolerance), () => {
|
|
1121
|
+
Discard();
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
);
|
|
1126
|
+
var compensateOutputSRGB = Fn(
|
|
1127
|
+
({ color }) => {
|
|
1128
|
+
return vec4(sRGBTransferEOTF(color.rgb), color.a);
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
// src/js/effects/three-particles/webgpu/tsl-instanced-billboard-material.ts
|
|
1133
|
+
function createInstancedBillboardTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
1134
|
+
const u = createParticleUniforms(sharedUniforms);
|
|
1135
|
+
const uViewportHeight = uniform(
|
|
1136
|
+
typeof sharedUniforms.viewportHeight?.value === "number" ? sharedUniforms.viewportHeight.value : 1
|
|
1137
|
+
);
|
|
1138
|
+
sharedUniforms.viewportHeight = uViewportHeight;
|
|
1139
|
+
const aInstanceOffset = attribute("instanceOffset");
|
|
1140
|
+
const aColor = attribute("instanceColor");
|
|
1141
|
+
const aParticleState = gpuCompute ? attribute("instanceParticleState") : null;
|
|
1142
|
+
const aStartValues = gpuCompute ? attribute("instanceStartValues") : null;
|
|
1143
|
+
const aSize = gpuCompute ? null : attribute("instanceSize");
|
|
1144
|
+
const aLifetime = gpuCompute ? null : attribute("instanceLifetime");
|
|
1145
|
+
const aStartLifetime = gpuCompute ? null : attribute("instanceStartLifetime");
|
|
1146
|
+
const aRotation = gpuCompute ? null : attribute("instanceRotation");
|
|
1147
|
+
const aStartFrame = gpuCompute ? null : attribute("instanceStartFrame");
|
|
1148
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
1149
|
+
const vLifetime = varyingProperty("float", "vLifetime");
|
|
1150
|
+
const vStartLifetime = varyingProperty("float", "vStartLifetime");
|
|
1151
|
+
const vRotation = varyingProperty("float", "vRotation");
|
|
1152
|
+
const vStartFrame = varyingProperty("float", "vStartFrame");
|
|
1153
|
+
const vUv = varyingProperty("vec2", "vUv");
|
|
1154
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
1155
|
+
const vertexNode = Fn(() => {
|
|
1156
|
+
const clipPos = vec4(0, 0, 0, 0).toVar();
|
|
1157
|
+
If(aColor.w.greaterThan(0), () => {
|
|
1158
|
+
vColor.assign(aColor.toVar());
|
|
1159
|
+
if (gpuCompute) {
|
|
1160
|
+
vLifetime.assign(aParticleState.x);
|
|
1161
|
+
vStartLifetime.assign(aStartValues.x);
|
|
1162
|
+
vRotation.assign(aParticleState.z);
|
|
1163
|
+
vStartFrame.assign(aParticleState.w);
|
|
1164
|
+
} else {
|
|
1165
|
+
vLifetime.assign(aLifetime);
|
|
1166
|
+
vStartLifetime.assign(aStartLifetime);
|
|
1167
|
+
vRotation.assign(aRotation);
|
|
1168
|
+
vStartFrame.assign(aStartFrame);
|
|
1169
|
+
}
|
|
1170
|
+
vUv.assign(
|
|
1171
|
+
vec2(positionLocal.x.add(0.5), float(0.5).sub(positionLocal.y))
|
|
1172
|
+
);
|
|
1173
|
+
const mvPosition = modelViewMatrix.mul(vec4(aInstanceOffset.xyz, 1)).toVar();
|
|
1174
|
+
const dist = length(mvPosition.xyz);
|
|
1175
|
+
const sizeVal = gpuCompute ? aParticleState.y : aSize;
|
|
1176
|
+
const pointSizePx = sizeVal.mul(POINT_SIZE_SCALE).div(dist);
|
|
1177
|
+
const projY = cameraProjectionMatrix.element(1).element(1);
|
|
1178
|
+
const perspectiveSize = pointSizePx.mul(mvPosition.z.negate()).div(projY.mul(uViewportHeight).mul(0.5));
|
|
1179
|
+
mvPosition.x.addAssign(positionLocal.x.mul(perspectiveSize));
|
|
1180
|
+
mvPosition.y.addAssign(positionLocal.y.mul(perspectiveSize));
|
|
1181
|
+
vViewZ.assign(mvPosition.z.negate());
|
|
1182
|
+
clipPos.assign(cameraProjectionMatrix.mul(mvPosition));
|
|
1183
|
+
});
|
|
1184
|
+
return clipPos;
|
|
1185
|
+
})();
|
|
1186
|
+
const fragmentColor = Fn(() => {
|
|
1187
|
+
const outColor = vColor.toVar();
|
|
1188
|
+
const center = vec2(0.5, 0.5);
|
|
1189
|
+
const centered = vUv.sub(center);
|
|
1190
|
+
const cosR = cos(vRotation);
|
|
1191
|
+
const sinR = sin(vRotation);
|
|
1192
|
+
const rotated = vec2(
|
|
1193
|
+
centered.x.mul(cosR).add(centered.y.mul(sinR)),
|
|
1194
|
+
centered.x.mul(sinR).negate().add(centered.y.mul(cosR))
|
|
1195
|
+
);
|
|
1196
|
+
const rotatedUV = rotated.add(center);
|
|
1197
|
+
const dist = length(rotatedUV.sub(center));
|
|
1198
|
+
If(dist.greaterThan(0.5), () => {
|
|
1199
|
+
Discard();
|
|
1200
|
+
});
|
|
1201
|
+
const frameIndex = computeFrameIndex({
|
|
1202
|
+
vLifetime,
|
|
1203
|
+
vStartLifetime,
|
|
1204
|
+
vStartFrame,
|
|
1205
|
+
uFps: u.uFps,
|
|
1206
|
+
uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
|
|
1207
|
+
uTiles: u.uTiles
|
|
1208
|
+
});
|
|
1209
|
+
const uvPoint = computeSpriteSheetUV({
|
|
1210
|
+
baseUV: rotatedUV,
|
|
1211
|
+
frameIndex,
|
|
1212
|
+
uTiles: u.uTiles
|
|
1213
|
+
});
|
|
1214
|
+
const texColor = texture(u.uMap, uvPoint);
|
|
1215
|
+
outColor.assign(outColor.mul(texColor));
|
|
1216
|
+
applyBackgroundDiscard({
|
|
1217
|
+
texColor,
|
|
1218
|
+
uDiscardBg: u.uDiscardBg,
|
|
1219
|
+
uBgColor: u.uBgColor,
|
|
1220
|
+
uBgTolerance: u.uBgTolerance
|
|
1221
|
+
});
|
|
1222
|
+
const softFade = computeSoftParticleFade({
|
|
1223
|
+
viewZ: vViewZ,
|
|
1224
|
+
uSoftEnabled: u.uSoftEnabled,
|
|
1225
|
+
uSoftIntensity: u.uSoftIntensity,
|
|
1226
|
+
uSceneDepthTex: u.uSceneDepthTex,
|
|
1227
|
+
uCameraNearFar: u.uCameraNearFar
|
|
1228
|
+
});
|
|
1229
|
+
outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
|
|
1230
|
+
Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
1231
|
+
return compensateOutputSRGB({ color: outColor });
|
|
1232
|
+
})();
|
|
1233
|
+
const material = new MeshBasicNodeMaterial();
|
|
1234
|
+
material.transparent = rendererConfig.transparent;
|
|
1235
|
+
material.blending = rendererConfig.blending;
|
|
1236
|
+
material.depthTest = rendererConfig.depthTest;
|
|
1237
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
1238
|
+
material.toneMapped = false;
|
|
1239
|
+
material.fog = false;
|
|
1240
|
+
material.vertexNode = vertexNode;
|
|
1241
|
+
material.colorNode = fragmentColor;
|
|
1242
|
+
return material;
|
|
1243
|
+
}
|
|
1244
|
+
var applyQuaternion = Fn(
|
|
1245
|
+
({ v, q }) => {
|
|
1246
|
+
const t = cross(q.xyz, v).mul(2);
|
|
1247
|
+
return v.add(t.mul(q.w)).add(cross(q.xyz, t));
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
1250
|
+
function createMeshParticleTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
1251
|
+
const u = createParticleUniforms(sharedUniforms);
|
|
1252
|
+
const aInstanceOffset = attribute("instanceOffset");
|
|
1253
|
+
const aColor = attribute("instanceColor");
|
|
1254
|
+
const aParticleState = gpuCompute ? attribute("instanceParticleState") : null;
|
|
1255
|
+
const aStartValues = gpuCompute ? attribute("instanceStartValues") : null;
|
|
1256
|
+
const aInstanceQuat = gpuCompute ? null : attribute("instanceQuat");
|
|
1257
|
+
const aSize = gpuCompute ? null : attribute("instanceSize");
|
|
1258
|
+
const aLifetime = gpuCompute ? null : attribute("instanceLifetime");
|
|
1259
|
+
const aStartLifetime = gpuCompute ? null : attribute("instanceStartLifetime");
|
|
1260
|
+
const aRotation = gpuCompute ? null : attribute("instanceRotation");
|
|
1261
|
+
const aStartFrame = gpuCompute ? null : attribute("instanceStartFrame");
|
|
1262
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
1263
|
+
const vLifetime = varyingProperty("float", "vLifetime");
|
|
1264
|
+
const vStartLifetime = varyingProperty("float", "vStartLifetime");
|
|
1265
|
+
const vStartFrame = varyingProperty("float", "vStartFrame");
|
|
1266
|
+
const vRotation = varyingProperty("float", "vRotation");
|
|
1267
|
+
const vNormal = varyingProperty("vec3", "vNormal");
|
|
1268
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
1269
|
+
const vertexSetup = Fn(() => {
|
|
1270
|
+
const clipPos = vec4(0, 0, 0, 0).toVar();
|
|
1271
|
+
If(aColor.w.greaterThan(0), () => {
|
|
1272
|
+
vColor.assign(aColor.toVar());
|
|
1273
|
+
if (gpuCompute) {
|
|
1274
|
+
vLifetime.assign(aParticleState.x);
|
|
1275
|
+
vStartLifetime.assign(aStartValues.x);
|
|
1276
|
+
vStartFrame.assign(aParticleState.w);
|
|
1277
|
+
vRotation.assign(aParticleState.z);
|
|
1278
|
+
} else {
|
|
1279
|
+
vLifetime.assign(aLifetime);
|
|
1280
|
+
vStartLifetime.assign(aStartLifetime);
|
|
1281
|
+
vStartFrame.assign(aStartFrame);
|
|
1282
|
+
vRotation.assign(aRotation);
|
|
1283
|
+
}
|
|
1284
|
+
let quat;
|
|
1285
|
+
if (gpuCompute) {
|
|
1286
|
+
const halfZ = aParticleState.z.mul(0.5);
|
|
1287
|
+
quat = vec4(0, 0, sin(halfZ), cos(halfZ));
|
|
1288
|
+
} else {
|
|
1289
|
+
quat = aInstanceQuat;
|
|
1290
|
+
}
|
|
1291
|
+
const rotatedPos = applyQuaternion({
|
|
1292
|
+
v: positionLocal,
|
|
1293
|
+
q: quat
|
|
1294
|
+
});
|
|
1295
|
+
const scaledPos = rotatedPos.mul(gpuCompute ? aParticleState.y : aSize);
|
|
1296
|
+
const worldPos = scaledPos.add(aInstanceOffset.xyz);
|
|
1297
|
+
const mvPos = modelViewMatrix.mul(vec4(worldPos, 1));
|
|
1298
|
+
vViewZ.assign(mvPos.z.negate());
|
|
1299
|
+
const rotatedNormal = applyQuaternion({
|
|
1300
|
+
v: normalLocal,
|
|
1301
|
+
q: quat
|
|
1302
|
+
});
|
|
1303
|
+
const mvNormal = modelViewMatrix.mul(vec4(rotatedNormal, 0)).xyz;
|
|
1304
|
+
vNormal.assign(mvNormal.normalize());
|
|
1305
|
+
clipPos.assign(cameraProjectionMatrix.mul(mvPos));
|
|
1306
|
+
});
|
|
1307
|
+
return clipPos;
|
|
1308
|
+
})();
|
|
1309
|
+
const fragmentColor = Fn(() => {
|
|
1310
|
+
const outColor = vColor.toVar();
|
|
1311
|
+
const uvPoint = vec2(uv()).toVar();
|
|
1312
|
+
If(u.uTiles.x.greaterThan(1).or(u.uTiles.y.greaterThan(1)), () => {
|
|
1313
|
+
const frameIndex = computeFrameIndex({
|
|
1314
|
+
vLifetime,
|
|
1315
|
+
vStartLifetime,
|
|
1316
|
+
vStartFrame,
|
|
1317
|
+
uFps: u.uFps,
|
|
1318
|
+
uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
|
|
1319
|
+
uTiles: u.uTiles
|
|
1320
|
+
});
|
|
1321
|
+
uvPoint.assign(
|
|
1322
|
+
computeSpriteSheetUV({
|
|
1323
|
+
baseUV: uv(),
|
|
1324
|
+
frameIndex,
|
|
1325
|
+
uTiles: u.uTiles
|
|
1326
|
+
})
|
|
1327
|
+
);
|
|
1328
|
+
});
|
|
1329
|
+
const texColor = texture(u.uMap, uvPoint);
|
|
1330
|
+
outColor.assign(outColor.mul(texColor));
|
|
1331
|
+
applyBackgroundDiscard({
|
|
1332
|
+
texColor,
|
|
1333
|
+
uDiscardBg: u.uDiscardBg,
|
|
1334
|
+
uBgColor: u.uBgColor,
|
|
1335
|
+
uBgTolerance: u.uBgTolerance
|
|
1336
|
+
});
|
|
1337
|
+
const lightIntensity = float(0.5).add(
|
|
1338
|
+
float(0.5).mul(max(dot(vNormal, vec3(0, 0, 1)), float(0)))
|
|
1339
|
+
);
|
|
1340
|
+
outColor.assign(vec4(outColor.xyz.mul(lightIntensity), outColor.w));
|
|
1341
|
+
const softFade = computeSoftParticleFade({
|
|
1342
|
+
viewZ: vViewZ,
|
|
1343
|
+
uSoftEnabled: u.uSoftEnabled,
|
|
1344
|
+
uSoftIntensity: u.uSoftIntensity,
|
|
1345
|
+
uSceneDepthTex: u.uSceneDepthTex,
|
|
1346
|
+
uCameraNearFar: u.uCameraNearFar
|
|
1347
|
+
});
|
|
1348
|
+
outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
|
|
1349
|
+
Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
1350
|
+
return compensateOutputSRGB({ color: outColor });
|
|
1351
|
+
})();
|
|
1352
|
+
const material = new MeshBasicNodeMaterial();
|
|
1353
|
+
material.transparent = rendererConfig.transparent;
|
|
1354
|
+
material.blending = rendererConfig.blending;
|
|
1355
|
+
material.depthTest = rendererConfig.depthTest;
|
|
1356
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
1357
|
+
material.toneMapped = false;
|
|
1358
|
+
material.fog = false;
|
|
1359
|
+
material.vertexNode = vertexSetup;
|
|
1360
|
+
material.colorNode = fragmentColor;
|
|
1361
|
+
return material;
|
|
1362
|
+
}
|
|
1363
|
+
function createPointSpriteTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
1364
|
+
const u = createParticleUniforms(sharedUniforms);
|
|
1365
|
+
const aColor = attribute("color");
|
|
1366
|
+
const aParticleState = gpuCompute ? attribute("particleState") : null;
|
|
1367
|
+
const aStartValues = gpuCompute ? attribute("startValues") : null;
|
|
1368
|
+
const aSize = gpuCompute ? null : attribute("size");
|
|
1369
|
+
const aLifetime = gpuCompute ? null : attribute("lifetime");
|
|
1370
|
+
const aStartLifetime = gpuCompute ? null : attribute("startLifetime");
|
|
1371
|
+
const aRotation = gpuCompute ? null : attribute("rotation");
|
|
1372
|
+
const aStartFrame = gpuCompute ? null : attribute("startFrame");
|
|
1373
|
+
const mvPos = modelViewMatrix.mul(vec4(positionLocal, 1));
|
|
1374
|
+
const sizeVal = gpuCompute ? aParticleState.y : aSize;
|
|
1375
|
+
const sizeNode = aColor.w.greaterThan(0).select(sizeVal.mul(POINT_SIZE_SCALE).div(length(mvPos.xyz)), float(0));
|
|
1376
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
1377
|
+
const vLifetime = varyingProperty("float", "vLifetime");
|
|
1378
|
+
const vStartLifetime = varyingProperty("float", "vStartLifetime");
|
|
1379
|
+
const vRotation = varyingProperty("float", "vRotation");
|
|
1380
|
+
const vStartFrame = varyingProperty("float", "vStartFrame");
|
|
1381
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
1382
|
+
const vertexSetup = Fn(() => {
|
|
1383
|
+
If(aColor.w.greaterThan(0), () => {
|
|
1384
|
+
vColor.assign(aColor.toVar());
|
|
1385
|
+
if (gpuCompute) {
|
|
1386
|
+
vLifetime.assign(aParticleState.x);
|
|
1387
|
+
vStartLifetime.assign(aStartValues.x);
|
|
1388
|
+
vRotation.assign(aParticleState.z);
|
|
1389
|
+
vStartFrame.assign(aParticleState.w);
|
|
1390
|
+
} else {
|
|
1391
|
+
vLifetime.assign(aLifetime);
|
|
1392
|
+
vStartLifetime.assign(aStartLifetime);
|
|
1393
|
+
vRotation.assign(aRotation);
|
|
1394
|
+
vStartFrame.assign(aStartFrame);
|
|
1395
|
+
}
|
|
1396
|
+
vViewZ.assign(mvPos.z.negate());
|
|
1397
|
+
});
|
|
1398
|
+
return positionLocal;
|
|
1399
|
+
})();
|
|
1400
|
+
const fragmentColor = Fn(() => {
|
|
1401
|
+
const outColor = vColor.toVar();
|
|
1402
|
+
const frameIndex = computeFrameIndex({
|
|
1403
|
+
vLifetime,
|
|
1404
|
+
vStartLifetime,
|
|
1405
|
+
vStartFrame,
|
|
1406
|
+
uFps: u.uFps,
|
|
1407
|
+
uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
|
|
1408
|
+
uTiles: u.uTiles
|
|
1409
|
+
});
|
|
1410
|
+
const center = vec2(0.5, 0.5);
|
|
1411
|
+
const centered = pointUV.sub(center);
|
|
1412
|
+
const cosR = cos(vRotation);
|
|
1413
|
+
const sinR = sin(vRotation);
|
|
1414
|
+
const rotated = vec2(
|
|
1415
|
+
centered.x.mul(cosR).add(centered.y.mul(sinR)),
|
|
1416
|
+
centered.x.mul(sinR).negate().add(centered.y.mul(cosR))
|
|
1417
|
+
);
|
|
1418
|
+
const rotatedUV = rotated.add(center);
|
|
1419
|
+
const dist = length(rotatedUV.sub(center));
|
|
1420
|
+
Discard(dist.greaterThan(0.5));
|
|
1421
|
+
const uvPoint = computeSpriteSheetUV({
|
|
1422
|
+
baseUV: rotatedUV,
|
|
1423
|
+
frameIndex,
|
|
1424
|
+
uTiles: u.uTiles
|
|
1425
|
+
});
|
|
1426
|
+
const texColor = texture(u.uMap, uvPoint);
|
|
1427
|
+
outColor.assign(outColor.mul(texColor));
|
|
1428
|
+
applyBackgroundDiscard({
|
|
1429
|
+
texColor,
|
|
1430
|
+
uDiscardBg: u.uDiscardBg,
|
|
1431
|
+
uBgColor: u.uBgColor,
|
|
1432
|
+
uBgTolerance: u.uBgTolerance
|
|
1433
|
+
});
|
|
1434
|
+
const softFade = computeSoftParticleFade({
|
|
1435
|
+
viewZ: vViewZ,
|
|
1436
|
+
uSoftEnabled: u.uSoftEnabled,
|
|
1437
|
+
uSoftIntensity: u.uSoftIntensity,
|
|
1438
|
+
uSceneDepthTex: u.uSceneDepthTex,
|
|
1439
|
+
uCameraNearFar: u.uCameraNearFar
|
|
1440
|
+
});
|
|
1441
|
+
outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
|
|
1442
|
+
Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
1443
|
+
return compensateOutputSRGB({ color: outColor });
|
|
1444
|
+
})();
|
|
1445
|
+
const material = new PointsNodeMaterial();
|
|
1446
|
+
material.transparent = rendererConfig.transparent;
|
|
1447
|
+
material.blending = rendererConfig.blending;
|
|
1448
|
+
material.depthTest = rendererConfig.depthTest;
|
|
1449
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
1450
|
+
material.toneMapped = false;
|
|
1451
|
+
material.fog = false;
|
|
1452
|
+
material.sizeNode = sizeNode;
|
|
1453
|
+
material.positionNode = vertexSetup;
|
|
1454
|
+
material.colorNode = fragmentColor;
|
|
1455
|
+
return material;
|
|
1456
|
+
}
|
|
1457
|
+
function createTrailUniforms(trailUniforms) {
|
|
1458
|
+
const dummy = getDummyTexture();
|
|
1459
|
+
const map = trailUniforms.map.value ?? dummy;
|
|
1460
|
+
if (map) map.colorSpace = NoColorSpace;
|
|
1461
|
+
return {
|
|
1462
|
+
uMap: map,
|
|
1463
|
+
uUseMap: uniform(float(trailUniforms.useMap.value ? 1 : 0)),
|
|
1464
|
+
uDiscardBg: uniform(
|
|
1465
|
+
float(trailUniforms.discardBackgroundColor.value ? 1 : 0)
|
|
1466
|
+
),
|
|
1467
|
+
uBgColor: uniform(
|
|
1468
|
+
new trailUniforms.cameraNearFar.value.constructor(
|
|
1469
|
+
trailUniforms.backgroundColor.value.r,
|
|
1470
|
+
trailUniforms.backgroundColor.value.g,
|
|
1471
|
+
trailUniforms.backgroundColor.value.b
|
|
1472
|
+
)
|
|
1473
|
+
),
|
|
1474
|
+
uBgTolerance: uniform(float(trailUniforms.backgroundColorTolerance.value)),
|
|
1475
|
+
uSoftEnabled: uniform(
|
|
1476
|
+
float(trailUniforms.softParticlesEnabled.value ? 1 : 0)
|
|
1477
|
+
),
|
|
1478
|
+
uSoftIntensity: uniform(float(trailUniforms.softParticlesIntensity.value)),
|
|
1479
|
+
uSceneDepthTex: trailUniforms.sceneDepthTexture.value ?? dummy,
|
|
1480
|
+
uCameraNearFar: uniform(trailUniforms.cameraNearFar.value)
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
function createTrailRibbonTSLMaterial(trailUniforms, rendererConfig) {
|
|
1484
|
+
const u = createTrailUniforms(trailUniforms);
|
|
1485
|
+
const aTrailAlpha = attribute("trailAlpha");
|
|
1486
|
+
const aTrailColor = attribute("trailColor", "vec4");
|
|
1487
|
+
const aTrailOffset = attribute("trailOffset");
|
|
1488
|
+
const aTrailHalfWidth = attribute("trailHalfWidth");
|
|
1489
|
+
const aTrailNext = attribute("trailNext", "vec3");
|
|
1490
|
+
const aTrailUV = attribute("trailUV", "vec2");
|
|
1491
|
+
const vAlpha = varyingProperty("float", "vAlpha");
|
|
1492
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
1493
|
+
const vUv = varyingProperty("vec2", "vUv");
|
|
1494
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
1495
|
+
const positionNode = Fn(() => {
|
|
1496
|
+
vAlpha.assign(aTrailAlpha);
|
|
1497
|
+
vColor.assign(aTrailColor);
|
|
1498
|
+
vUv.assign(aTrailUV);
|
|
1499
|
+
const current = vec3(positionLocal);
|
|
1500
|
+
const next = vec3(aTrailNext);
|
|
1501
|
+
const rawTangent = next.sub(current);
|
|
1502
|
+
const tangentLen = length(rawTangent);
|
|
1503
|
+
const tangent = normalize(
|
|
1504
|
+
tangentLen.lessThan(1e-4).select(vec3(0, 1, 0), rawTangent)
|
|
1505
|
+
);
|
|
1506
|
+
modelViewMatrix.mul(vec4(current, 1));
|
|
1507
|
+
const viewDir = normalize(cameraPosition.sub(current));
|
|
1508
|
+
const rawPerp = cross(tangent, viewDir);
|
|
1509
|
+
const perpLen = length(rawPerp);
|
|
1510
|
+
const camRight = vec3(
|
|
1511
|
+
cameraViewMatrix.element(0).element(0),
|
|
1512
|
+
cameraViewMatrix.element(1).element(0),
|
|
1513
|
+
cameraViewMatrix.element(2).element(0)
|
|
1514
|
+
);
|
|
1515
|
+
const camRightDotTangent = dot(camRight, tangent);
|
|
1516
|
+
const fallbackPerp = normalize(
|
|
1517
|
+
camRight.sub(tangent.mul(camRightDotTangent))
|
|
1518
|
+
);
|
|
1519
|
+
const perp = normalize(
|
|
1520
|
+
perpLen.lessThan(1e-4).select(
|
|
1521
|
+
fallbackPerp,
|
|
1522
|
+
normalize(
|
|
1523
|
+
mix(
|
|
1524
|
+
fallbackPerp,
|
|
1525
|
+
normalize(rawPerp),
|
|
1526
|
+
smoothstep(float(0), float(0.7), perpLen)
|
|
1527
|
+
)
|
|
1528
|
+
)
|
|
1529
|
+
)
|
|
1530
|
+
);
|
|
1531
|
+
const offsetPos = current.add(perp.mul(aTrailOffset).mul(aTrailHalfWidth));
|
|
1532
|
+
const mvOffset = modelViewMatrix.mul(vec4(offsetPos, 1));
|
|
1533
|
+
vViewZ.assign(mvOffset.z.negate());
|
|
1534
|
+
return offsetPos;
|
|
1535
|
+
})();
|
|
1536
|
+
const colorNode = Fn(() => {
|
|
1537
|
+
const outColor = vColor.toVar();
|
|
1538
|
+
const edgeDist = float(1).sub(abs(vUv.x.mul(2).sub(1)));
|
|
1539
|
+
const edgeFade = smoothstep(float(0), float(0.4), edgeDist);
|
|
1540
|
+
If(u.uUseMap.greaterThan(0.5), () => {
|
|
1541
|
+
const texColor = texture(u.uMap, vUv);
|
|
1542
|
+
const texBrightness = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
|
1543
|
+
outColor.rgb.assign(
|
|
1544
|
+
outColor.rgb.mul(float(0.5).add(texBrightness.mul(0.5)))
|
|
1545
|
+
);
|
|
1546
|
+
outColor.a.assign(outColor.a.mul(texColor.a));
|
|
1547
|
+
});
|
|
1548
|
+
outColor.a.assign(outColor.a.mul(vAlpha).mul(edgeFade));
|
|
1549
|
+
Discard(outColor.a.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
1550
|
+
If(u.uSoftEnabled.greaterThan(0.5), () => {
|
|
1551
|
+
const depthSample = texture(u.uSceneDepthTex, screenUV).x;
|
|
1552
|
+
const sceneDepthLinear = linearizeDepth({
|
|
1553
|
+
depthSample,
|
|
1554
|
+
near: u.uCameraNearFar.x,
|
|
1555
|
+
far: u.uCameraNearFar.y
|
|
1556
|
+
});
|
|
1557
|
+
const depthDiff = sceneDepthLinear.sub(vViewZ);
|
|
1558
|
+
const softFade = smoothstep(float(0), u.uSoftIntensity, depthDiff);
|
|
1559
|
+
outColor.a.assign(outColor.a.mul(softFade));
|
|
1560
|
+
});
|
|
1561
|
+
Discard(outColor.a.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
1562
|
+
const diff = vec3(
|
|
1563
|
+
outColor.r.sub(u.uBgColor.x),
|
|
1564
|
+
outColor.g.sub(u.uBgColor.y),
|
|
1565
|
+
outColor.b.sub(u.uBgColor.z)
|
|
1566
|
+
);
|
|
1567
|
+
Discard(
|
|
1568
|
+
u.uDiscardBg.greaterThan(0.5).and(abs(length(diff)).lessThan(u.uBgTolerance))
|
|
1569
|
+
);
|
|
1570
|
+
return compensateOutputSRGB({ color: outColor });
|
|
1571
|
+
})();
|
|
1572
|
+
const material = new MeshBasicNodeMaterial();
|
|
1573
|
+
material.transparent = rendererConfig.transparent;
|
|
1574
|
+
material.blending = rendererConfig.blending;
|
|
1575
|
+
material.depthTest = rendererConfig.depthTest;
|
|
1576
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
1577
|
+
material.toneMapped = false;
|
|
1578
|
+
material.fog = false;
|
|
1579
|
+
material.side = DoubleSide;
|
|
1580
|
+
material.positionNode = positionNode;
|
|
1581
|
+
material.colorNode = colorNode;
|
|
1582
|
+
return material;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/js/effects/three-particles/webgpu/tsl-materials.ts
|
|
1586
|
+
function createTSLParticleMaterial(rendererType, sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
1587
|
+
switch (rendererType) {
|
|
1588
|
+
case "INSTANCED" /* INSTANCED */:
|
|
1589
|
+
return createInstancedBillboardTSLMaterial(
|
|
1590
|
+
sharedUniforms,
|
|
1591
|
+
rendererConfig,
|
|
1592
|
+
gpuCompute
|
|
1593
|
+
);
|
|
1594
|
+
case "MESH" /* MESH */:
|
|
1595
|
+
return createMeshParticleTSLMaterial(
|
|
1596
|
+
sharedUniforms,
|
|
1597
|
+
rendererConfig,
|
|
1598
|
+
gpuCompute
|
|
1599
|
+
);
|
|
1600
|
+
case "POINTS" /* POINTS */:
|
|
1601
|
+
default:
|
|
1602
|
+
return createPointSpriteTSLMaterial(
|
|
1603
|
+
sharedUniforms,
|
|
1604
|
+
rendererConfig,
|
|
1605
|
+
gpuCompute
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
function createTSLTrailMaterial(trailUniforms, rendererConfig) {
|
|
1610
|
+
return createTrailRibbonTSLMaterial(trailUniforms, rendererConfig);
|
|
1611
|
+
}
|
|
1612
|
+
function createComputePipeline(maxParticles, instanced, normalizedConfig, particleSystemId, forceFieldCount, collisionPlaneCount = 0) {
|
|
1613
|
+
const bakedCurves = bakeParticleSystemCurves(
|
|
1614
|
+
normalizedConfig,
|
|
1615
|
+
particleSystemId
|
|
1616
|
+
);
|
|
1617
|
+
const { velocityOverLifetime } = normalizedConfig;
|
|
1618
|
+
const flags = {
|
|
1619
|
+
sizeOverLifetime: normalizedConfig.sizeOverLifetime.isActive,
|
|
1620
|
+
opacityOverLifetime: normalizedConfig.opacityOverLifetime.isActive,
|
|
1621
|
+
colorOverLifetime: normalizedConfig.colorOverLifetime.isActive,
|
|
1622
|
+
rotationOverLifetime: normalizedConfig.rotationOverLifetime.isActive,
|
|
1623
|
+
linearVelocity: velocityOverLifetime.isActive && (isLifeTimeCurve(velocityOverLifetime.linear.x ?? 0) || isLifeTimeCurve(velocityOverLifetime.linear.y ?? 0) || isLifeTimeCurve(velocityOverLifetime.linear.z ?? 0) || velocityOverLifetime.linear.x !== 0 || velocityOverLifetime.linear.y !== 0 || velocityOverLifetime.linear.z !== 0),
|
|
1624
|
+
orbitalVelocity: velocityOverLifetime.isActive && (isLifeTimeCurve(velocityOverLifetime.orbital.x ?? 0) || isLifeTimeCurve(velocityOverLifetime.orbital.y ?? 0) || isLifeTimeCurve(velocityOverLifetime.orbital.z ?? 0) || velocityOverLifetime.orbital.x !== 0 || velocityOverLifetime.orbital.y !== 0 || velocityOverLifetime.orbital.z !== 0),
|
|
1625
|
+
noise: normalizedConfig.noise.isActive,
|
|
1626
|
+
forceFields: forceFieldCount > 0,
|
|
1627
|
+
collisionPlanes: collisionPlaneCount > 0
|
|
1628
|
+
};
|
|
1629
|
+
const buffers = createModifierStorageBuffers(
|
|
1630
|
+
maxParticles,
|
|
1631
|
+
instanced,
|
|
1632
|
+
bakedCurves.data,
|
|
1633
|
+
flags.forceFields,
|
|
1634
|
+
flags.collisionPlanes
|
|
1635
|
+
);
|
|
1636
|
+
return createModifierComputeUpdate(
|
|
1637
|
+
buffers,
|
|
1638
|
+
maxParticles,
|
|
1639
|
+
bakedCurves,
|
|
1640
|
+
flags,
|
|
1641
|
+
forceFieldCount,
|
|
1642
|
+
collisionPlaneCount
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// src/webgpu.ts
|
|
1647
|
+
function enableWebGPU() {
|
|
1648
|
+
const factory = {
|
|
1649
|
+
createTSLParticleMaterial,
|
|
1650
|
+
createTSLTrailMaterial,
|
|
1651
|
+
createComputePipeline,
|
|
1652
|
+
writeParticleToModifierBuffers,
|
|
1653
|
+
deactivateParticleInModifierBuffers,
|
|
1654
|
+
flushEmitQueue,
|
|
1655
|
+
registerCurveDataLength,
|
|
1656
|
+
encodeForceFieldsForGPU,
|
|
1657
|
+
encodeCollisionPlanesForGPU
|
|
1658
|
+
};
|
|
1659
|
+
registerTSLMaterialFactory(factory);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
export { createComputePipeline, createTSLParticleMaterial, createTSLTrailMaterial, deactivateParticleInModifierBuffers, enableWebGPU, encodeCollisionPlanesForGPU, encodeForceFieldsForGPU, flushEmitQueue, registerCurveDataLength, writeParticleToModifierBuffers };
|
|
1663
|
+
//# sourceMappingURL=webgpu.js.map
|
|
1664
|
+
//# sourceMappingURL=webgpu.js.map
|