babylon.quarks 0.25.0 → 0.27.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/CHANGELOG.md +854 -8
- package/README.md +10 -8
- package/dist/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.cts +6683 -0
- package/dist/types/index.d.ts +2210 -613
- package/package.json +12 -6
- package/src/BatchedRenderer.ts +215 -106
- package/src/EffectPool.ts +4 -1
- package/src/MeshSurfaceEmitter.ts +29 -4
- package/src/ParticleEmitter.ts +45 -15
- package/src/ParticleSystem.ts +1012 -1962
- package/src/QuarksLoader.ts +463 -75
- package/src/QuarksPrefab.ts +91 -4
- package/src/QuarksSerializer.ts +484 -0
- package/src/QuarksUtil.ts +15 -0
- package/src/SpriteBatch.ts +149 -238
- package/src/TrailBatch.ts +586 -286
- package/src/VFXBatch.ts +517 -153
- package/src/batchKey.ts +237 -0
- package/src/effectResources.ts +79 -0
- package/src/envAtlas.ts +119 -17
- package/src/index.ts +51 -35
- package/src/nodeId.ts +13 -0
- package/src/shaderProgram.ts +325 -0
- package/src/shaderVariants.ts +225 -0
- package/src/shaders/chunks/fog.ts +4 -2
- package/src/shaders/chunks/sheet.ts +111 -0
- package/src/shaders/chunks/soft.ts +84 -0
- package/src/shaders/chunks/surface.ts +132 -42
- package/src/shaders/local_particle_physics_vert.glsl.ts +28 -27
- package/src/shaders/local_particle_physics_vert.wgsl.ts +30 -28
- package/src/shaders/particle_frag.glsl.ts +6 -22
- package/src/shaders/particle_frag.wgsl.ts +6 -23
- package/src/shaders/particle_physics_frag.glsl.ts +22 -29
- package/src/shaders/particle_physics_frag.wgsl.ts +22 -30
- package/src/shaders/particle_vert.glsl.ts +11 -26
- package/src/shaders/particle_vert.wgsl.ts +11 -25
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +21 -39
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +22 -40
- package/src/shaders/trail_frag.glsl.ts +14 -1
- package/src/shaders/trail_frag.wgsl.ts +14 -0
- package/src/shaders/trail_vert.glsl.ts +53 -36
- package/src/shaders/trail_vert.wgsl.ts +54 -40
- package/src/softDepth.ts +136 -0
- package/src/system/BehaviorPlan.ts +85 -0
- package/src/system/BoundsTracker.ts +181 -0
- package/src/system/EmissionController.ts +360 -0
- package/src/system/EventDispatcher.ts +44 -0
- package/src/system/ParticleSpawner.ts +274 -0
- package/src/system/SimulationClock.ts +130 -0
- package/src/system/geometry.ts +111 -0
- package/src/system/integrate.ts +428 -0
- package/src/system/materialResolve.ts +116 -0
- package/src/system/normalize.ts +95 -0
- package/src/system/serialization.ts +401 -0
- package/src/system/settingsSchema.ts +317 -0
- package/src/system/types.ts +410 -0
- package/src/textureSource.ts +30 -0
- package/src/trailSettings.ts +51 -0
- package/src/BatchedParticleRenderer.ts +0 -3
- package/src/materials/ParticleMaterials.ts +0 -23
- package/src/materials/index.ts +0 -1
- package/src/shaders/chunks/index.ts +0 -1
- package/src/shaders/chunks/register-shader-chunks.ts +0 -32
- package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
- package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
- package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
- package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
- package/src/shaders/index.ts +0 -9
- package/src/shaders/local_particle_vert.glsl.ts +0 -50
package/src/VFXBatch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {BoundingInfo} from '@babylonjs/core/Culling/boundingInfo';
|
|
2
|
-
import {Constants} from '@babylonjs/core/Engines/constants';
|
|
3
2
|
import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
3
|
+
import type {Effect} from '@babylonjs/core/Materials/effect';
|
|
4
4
|
import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
|
|
5
5
|
import {Color3} from '@babylonjs/core/Maths/math.color';
|
|
6
6
|
import {Vector2 as BVector2, Vector3 as BVector3, Vector4 as BVector4} from '@babylonjs/core/Maths/math.vector';
|
|
@@ -9,6 +9,8 @@ import {Scene} from '@babylonjs/core/scene';
|
|
|
9
9
|
import {IParticleSystem, Vector3} from 'quarks.core';
|
|
10
10
|
import {VFXBatchSettings} from './BatchedRenderer';
|
|
11
11
|
import type {ParticleSystem} from './ParticleSystem';
|
|
12
|
+
import {storedBatchSettings} from './batchKey';
|
|
13
|
+
import {DepthBinding, softDepthParams} from './softDepth';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* The order the particles of a batch are drawn in (Unity's Sort Mode). Alpha
|
|
@@ -23,12 +25,25 @@ import type {ParticleSystem} from './ParticleSystem';
|
|
|
23
25
|
*/
|
|
24
26
|
export type SortMode = 'none' | 'distance' | 'oldestInFront' | 'youngestInFront';
|
|
25
27
|
|
|
28
|
+
/** How a system draws its particles (Unity's Render Mode), see {@link ParticleSystem.renderMode}. */
|
|
26
29
|
export enum RenderMode {
|
|
30
|
+
/** A quad facing the camera (Unity's Billboard). The default. */
|
|
27
31
|
BillBoard = 0,
|
|
32
|
+
/**
|
|
33
|
+
* A quad facing the camera and stretched along the particle's velocity (Unity's Stretched
|
|
34
|
+
* Billboard), by `speedFactor` and `lengthFactor` in the system's `rendererEmitterSettings`.
|
|
35
|
+
*/
|
|
28
36
|
StretchedBillBoard = 1,
|
|
37
|
+
/** The system's mesh, one instance per particle (Unity's Mesh), see {@link ParticleSystem.instancingGeometry}. */
|
|
29
38
|
Mesh = 2,
|
|
39
|
+
/**
|
|
40
|
+
* A ribbon drawn through the particle's past positions (Unity's Trails module, Niagara's
|
|
41
|
+
* ribbon), facing the camera; its settings are `TrailSettings` in `rendererEmitterSettings`.
|
|
42
|
+
*/
|
|
30
43
|
Trail = 3,
|
|
44
|
+
/** A quad lying flat in the world's XZ plane, facing up (Unity's Horizontal Billboard). */
|
|
31
45
|
HorizontalBillBoard = 4,
|
|
46
|
+
/** A quad standing upright and turning only about the world's Y axis to face the camera (Unity's Vertical Billboard). */
|
|
32
47
|
VerticalBillBoard = 5,
|
|
33
48
|
/**
|
|
34
49
|
* A quad laid from each particle to the system's {@link ParticleSystem.beamEnd}
|
|
@@ -87,9 +102,12 @@ export interface MeshRim {
|
|
|
87
102
|
strength: number;
|
|
88
103
|
/**
|
|
89
104
|
* `'add'` (default) adds the colour, for a glowing edge; `'mix'` blends the
|
|
90
|
-
* surface toward it, for an outline (a dark ink line with a dark colour)
|
|
105
|
+
* surface toward it, for an outline (a dark ink line with a dark colour);
|
|
106
|
+
* `'fade'` leaves the colour and fades the faces turned to the camera by
|
|
107
|
+
* `strength` instead, alpha times `1 - strength * (1 - edge)`: a wall of
|
|
108
|
+
* light that reads thick along its silhouette and thin across its face.
|
|
91
109
|
*/
|
|
92
|
-
mode: 'add' | 'mix';
|
|
110
|
+
mode: 'add' | 'mix' | 'fade';
|
|
93
111
|
}
|
|
94
112
|
|
|
95
113
|
/**
|
|
@@ -107,7 +125,7 @@ export function normalizeMeshRim(rim: Partial<MeshRim> | null | undefined): Mesh
|
|
|
107
125
|
color: [channel(color[0], 1), channel(color[1], 1), channel(color[2], 1)],
|
|
108
126
|
power: Math.max(0.01, channel(rim.power, 2)),
|
|
109
127
|
strength: Math.max(0, channel(rim.strength, 1)),
|
|
110
|
-
mode: rim.mode === 'mix'
|
|
128
|
+
mode: rim.mode === 'mix' || rim.mode === 'fade' ? rim.mode : 'add',
|
|
111
129
|
};
|
|
112
130
|
}
|
|
113
131
|
|
|
@@ -129,6 +147,145 @@ export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): bo
|
|
|
129
147
|
);
|
|
130
148
|
}
|
|
131
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Which texture coordinates a texture is sampled with, as Unity's UV channels
|
|
152
|
+
* and Niagara's ribbon UV0 / UV1:
|
|
153
|
+
*
|
|
154
|
+
* - `0`: the particle's first UV, with the sprite sheet's cell applied: on a
|
|
155
|
+
* sheet the current frame's cell, on a trail the cell's stretch along it, on
|
|
156
|
+
* a mesh its first UV set. Read before the map's own layout and distortion,
|
|
157
|
+
* so a texture on it does not move with them.
|
|
158
|
+
* - `1`: the particle's own UV, never moved by the sheet. The whole quad on a
|
|
159
|
+
* billboard, a stretched billboard or a beam (mirrored with the quad when it
|
|
160
|
+
* flips); on a trail U along it the way the map's runs (from the tail, or
|
|
161
|
+
* from the head with `uvFromHead`) and V across it; on a mesh its second UV
|
|
162
|
+
* set (`instancingUVs2`), or its first when it has none.
|
|
163
|
+
*
|
|
164
|
+
* A mesh can then repeat its picture along UV0 while a mask on UV1 runs once
|
|
165
|
+
* across its whole length, as a lightmap UV does in a game engine.
|
|
166
|
+
*/
|
|
167
|
+
export type UVChannel = 0 | 1;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* How a texture is laid over the particle: its UV channel, then tiling and
|
|
171
|
+
* offset as in Unity's material Tiling / Offset, then a scroll on the
|
|
172
|
+
* renderer's clock as Unreal's Panner. Sampled at
|
|
173
|
+
* `uv(channel) * tiling + offset + scroll * time`; the offset is used as
|
|
174
|
+
* given, so a clamped texture can be moved off its edge, and only the
|
|
175
|
+
* scroll's share is wrapped (over two repeats, so a mirrored texture stays
|
|
176
|
+
* seamless). A texture that scrolls or tiles should repeat.
|
|
177
|
+
*/
|
|
178
|
+
export interface TextureUV {
|
|
179
|
+
/** See {@link UVChannel}. Default 0. */
|
|
180
|
+
channel: UVChannel;
|
|
181
|
+
/** Repeats across the channel's 0…1, `[u, v]`. Default `[1, 1]`. */
|
|
182
|
+
tiling: [number, number];
|
|
183
|
+
/** Added after tiling, in texture UV units. Default `[0, 0]`. */
|
|
184
|
+
offset: [number, number];
|
|
185
|
+
/** UV units per second on the renderer's clock. Default `[0, 0]`. */
|
|
186
|
+
scroll: [number, number];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** A complete {@link TextureUV} from a partial one, with `defaults` under the fields left out. */
|
|
190
|
+
export function normalizeTextureUV(
|
|
191
|
+
uv: Partial<TextureUV> | null | undefined,
|
|
192
|
+
defaults: Partial<TextureUV> = {}
|
|
193
|
+
): TextureUV {
|
|
194
|
+
const source = uv && typeof uv === 'object' ? uv : {};
|
|
195
|
+
const num = (value: unknown, fallback: number) =>
|
|
196
|
+
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
197
|
+
const pair = (value: unknown, fallback: [number, number]): [number, number] =>
|
|
198
|
+
Array.isArray(value) ? [num(value[0], fallback[0]), num(value[1], fallback[1])] : [fallback[0], fallback[1]];
|
|
199
|
+
const channel = source.channel ?? defaults.channel;
|
|
200
|
+
return {
|
|
201
|
+
channel: channel === 1 ? 1 : 0,
|
|
202
|
+
tiling: pair(source.tiling, defaults.tiling ?? [1, 1]),
|
|
203
|
+
offset: pair(source.offset, defaults.offset ?? [0, 0]),
|
|
204
|
+
scroll: pair(source.scroll, defaults.scroll ?? [0, 0]),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** True when both lay a texture the same way. */
|
|
209
|
+
export function textureUVEqual(a: TextureUV, b: TextureUV): boolean {
|
|
210
|
+
return (
|
|
211
|
+
a === b ||
|
|
212
|
+
(a.channel === b.channel &&
|
|
213
|
+
a.tiling[0] === b.tiling[0] &&
|
|
214
|
+
a.tiling[1] === b.tiling[1] &&
|
|
215
|
+
a.offset[0] === b.offset[0] &&
|
|
216
|
+
a.offset[1] === b.offset[1] &&
|
|
217
|
+
a.scroll[0] === b.scroll[0] &&
|
|
218
|
+
a.scroll[1] === b.scroll[1])
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The fields of `uv` that differ from `defaults`, for JSON written only when set. */
|
|
223
|
+
export function textureUVToJSON(uv: TextureUV, defaults: Partial<TextureUV> = {}): Partial<TextureUV> | undefined {
|
|
224
|
+
const base = normalizeTextureUV(undefined, defaults);
|
|
225
|
+
const json: Partial<TextureUV> = {};
|
|
226
|
+
if (uv.channel !== base.channel) json.channel = uv.channel;
|
|
227
|
+
if (uv.tiling[0] !== base.tiling[0] || uv.tiling[1] !== base.tiling[1]) json.tiling = [...uv.tiling];
|
|
228
|
+
if (uv.offset[0] !== base.offset[0] || uv.offset[1] !== base.offset[1]) json.offset = [...uv.offset];
|
|
229
|
+
if (uv.scroll[0] !== base.scroll[0] || uv.scroll[1] !== base.scroll[1]) json.scroll = [...uv.scroll];
|
|
230
|
+
return Object.keys(json).length > 0 ? json : undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Which value of the map's texel picks the colour along a gradient map's
|
|
235
|
+
* ramp: one channel, so a texture can pack several masks (Unreal's and
|
|
236
|
+
* Unity's channel-packed textures), or the Rec. 709 luminance of its colour.
|
|
237
|
+
*/
|
|
238
|
+
export type GradientMapChannel = 'r' | 'g' | 'b' | 'a' | 'luminance';
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* A gradient map (colour ramp): the map is recoloured by looking its
|
|
242
|
+
* {@link channel} up along U of {@link ramp}. Without a map the lookup reads
|
|
243
|
+
* white, so the ramp's right-hand end tints every particle, in every render
|
|
244
|
+
* mode alike. See {@link VFXBatchSettings.gradientMap}.
|
|
245
|
+
*/
|
|
246
|
+
export interface ParticleGradientMap {
|
|
247
|
+
/**
|
|
248
|
+
* The ramp, read along U at mid V: low values on the left. Make it without
|
|
249
|
+
* mipmaps and clamped. Required: without one there is no gradient map.
|
|
250
|
+
*/
|
|
251
|
+
ramp: BaseTexture | null;
|
|
252
|
+
/**
|
|
253
|
+
* The texel value the ramp is looked up by. Default `'r'`. With `'a'` the
|
|
254
|
+
* ramp's alpha becomes the texel's alpha, so a soft shape can glow at its
|
|
255
|
+
* fringe and stay solid at its heart; with any other channel the ramp's
|
|
256
|
+
* alpha multiplies the map's.
|
|
257
|
+
*/
|
|
258
|
+
channel: GradientMapChannel;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The weights a texel is dotted with to look up the ramp, per {@link GradientMapChannel}. */
|
|
262
|
+
export const GRADIENT_MAP_WEIGHTS: Readonly<Record<GradientMapChannel, readonly [number, number, number, number]>> = {
|
|
263
|
+
r: [1, 0, 0, 0],
|
|
264
|
+
g: [0, 1, 0, 0],
|
|
265
|
+
b: [0, 0, 1, 0],
|
|
266
|
+
a: [0, 0, 0, 1],
|
|
267
|
+
luminance: [0.2126, 0.7152, 0.0722, 0],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/** A complete gradient map, or undefined when unset or without a ramp. */
|
|
271
|
+
export function normalizeGradientMap(
|
|
272
|
+
gradientMap: Partial<ParticleGradientMap> | null | undefined
|
|
273
|
+
): ParticleGradientMap | undefined {
|
|
274
|
+
if (!gradientMap || typeof gradientMap !== 'object' || !gradientMap.ramp) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
const channel = gradientMap.channel;
|
|
278
|
+
return {
|
|
279
|
+
ramp: gradientMap.ramp,
|
|
280
|
+
channel: channel !== undefined && channel in GRADIENT_MAP_WEIGHTS ? channel : 'r',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** True when both recolour the same way (or both not at all). */
|
|
285
|
+
export function gradientMapEqual(a: ParticleGradientMap | undefined, b: ParticleGradientMap | undefined): boolean {
|
|
286
|
+
return a === b || (!!a && !!b && a.ramp === b.ramp && a.channel === b.channel);
|
|
287
|
+
}
|
|
288
|
+
|
|
132
289
|
/**
|
|
133
290
|
* Alpha erosion: the particle's alpha decides how much of its shape is left
|
|
134
291
|
* instead of how transparent it is, so `ColorOverLife` fading alpha out
|
|
@@ -137,41 +294,51 @@ export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): bo
|
|
|
137
294
|
*/
|
|
138
295
|
export interface ParticleErosion {
|
|
139
296
|
/**
|
|
140
|
-
* Mask whose red channel orders what goes first,
|
|
141
|
-
*
|
|
142
|
-
* own alpha, so a soft shape shrinks from its edges.
|
|
297
|
+
* Mask whose red channel orders what goes first, laid by {@link maskUV}.
|
|
298
|
+
* Null: the map's own alpha, so a soft shape shrinks from its edges.
|
|
143
299
|
*/
|
|
144
300
|
mask: BaseTexture | null;
|
|
301
|
+
/**
|
|
302
|
+
* How the mask is laid over the particle, see {@link TextureUV}. On UV
|
|
303
|
+
* channel 1 a gradient mask wipes a whole trail or mesh however its map
|
|
304
|
+
* repeats. Default channel 0, untiled, still.
|
|
305
|
+
*/
|
|
306
|
+
maskUV: TextureUV;
|
|
145
307
|
/** Width of the soft transition at the cut, in mask units. Default 0.05. */
|
|
146
308
|
softness: number;
|
|
147
309
|
/** Width of the glowing band above the cut, in mask units; 0 (default) draws none. */
|
|
148
310
|
edgeWidth: number;
|
|
149
311
|
/** Colour added in the edge band, linear RGB, may exceed 1. Default `[1, 1, 1]`. */
|
|
150
312
|
edgeColor: [number, number, number];
|
|
151
|
-
/** Mask UV scroll, UV units per second. Default `[0, 0]`. */
|
|
152
|
-
maskScroll: [number, number];
|
|
153
313
|
}
|
|
154
|
-
// Limits worth knowing:
|
|
155
|
-
// the current tile's), erosion only changes alpha (one-one and premultiplied
|
|
314
|
+
// Limits worth knowing: erosion only changes alpha (one-one and premultiplied
|
|
156
315
|
// blending show a hard cut), and with neither a mask nor a map there is
|
|
157
316
|
// nothing to order the cut by, so the shape goes all at once near alpha 0.
|
|
158
317
|
// Scrolling runs on the renderer's clock, not a system's playback speed.
|
|
159
318
|
|
|
319
|
+
/** {@link ParticleErosion} as given: any field may be left out, the mask's layout too. */
|
|
320
|
+
export type ParticleErosionOptions = Partial<Omit<ParticleErosion, 'maskUV'>> & {
|
|
321
|
+
/** See {@link ParticleErosion.maskUV}; fields left out take their defaults. */
|
|
322
|
+
maskUV?: Partial<TextureUV>;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/** The width of the soft band an erosion cut fades over when `softness` is left out, in mask units. */
|
|
326
|
+
export const EROSION_DEFAULT_SOFTNESS = 0.05;
|
|
327
|
+
|
|
160
328
|
/** A complete erosion from a partial one, every field defaulted, or undefined when `erosion` is unset. */
|
|
161
|
-
export function normalizeErosion(erosion:
|
|
329
|
+
export function normalizeErosion(erosion: ParticleErosionOptions | null | undefined): ParticleErosion | undefined {
|
|
162
330
|
if (!erosion || typeof erosion !== 'object') {
|
|
163
331
|
return undefined;
|
|
164
332
|
}
|
|
165
333
|
const num = (value: unknown, fallback: number) =>
|
|
166
334
|
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
167
335
|
const color = Array.isArray(erosion.edgeColor) ? erosion.edgeColor : [1, 1, 1];
|
|
168
|
-
const scroll = Array.isArray(erosion.maskScroll) ? erosion.maskScroll : [0, 0];
|
|
169
336
|
return {
|
|
170
337
|
mask: erosion.mask ?? null,
|
|
171
|
-
|
|
338
|
+
maskUV: normalizeTextureUV(erosion.maskUV),
|
|
339
|
+
softness: Math.max(0.0001, num(erosion.softness, EROSION_DEFAULT_SOFTNESS)),
|
|
172
340
|
edgeWidth: Math.max(0, num(erosion.edgeWidth, 0)),
|
|
173
341
|
edgeColor: [num(color[0], 1), num(color[1], 1), num(color[2], 1)],
|
|
174
|
-
maskScroll: [num(scroll[0], 0), num(scroll[1], 0)],
|
|
175
342
|
};
|
|
176
343
|
}
|
|
177
344
|
|
|
@@ -185,54 +352,161 @@ export function erosionEqual(a: ParticleErosion | undefined, b: ParticleErosion
|
|
|
185
352
|
}
|
|
186
353
|
return (
|
|
187
354
|
a.mask === b.mask &&
|
|
355
|
+
textureUVEqual(a.maskUV, b.maskUV) &&
|
|
188
356
|
a.softness === b.softness &&
|
|
189
357
|
a.edgeWidth === b.edgeWidth &&
|
|
190
358
|
a.edgeColor[0] === b.edgeColor[0] &&
|
|
191
359
|
a.edgeColor[1] === b.edgeColor[1] &&
|
|
192
|
-
a.edgeColor[2] === b.edgeColor[2]
|
|
193
|
-
a.maskScroll[0] === b.maskScroll[0] &&
|
|
194
|
-
a.maskScroll[1] === b.maskScroll[1]
|
|
360
|
+
a.edgeColor[2] === b.edgeColor[2]
|
|
195
361
|
);
|
|
196
362
|
}
|
|
197
363
|
|
|
198
|
-
/**
|
|
199
|
-
export
|
|
200
|
-
|
|
364
|
+
/** The distortion noise's default layout: drifting up half a repeat a second. */
|
|
365
|
+
export const UV_DISTORTION_DEFAULT_UV: Partial<TextureUV> = {scroll: [0, 0.5]};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* UV distortion: the map's UV pushed about by a noise texture, for heat
|
|
369
|
+
* shimmer, licking flames and breathing auras. See
|
|
370
|
+
* {@link VFXBatchSettings.uvDistortion}.
|
|
371
|
+
*/
|
|
372
|
+
export interface ParticleUVDistortion {
|
|
373
|
+
/**
|
|
374
|
+
* Noise whose red and green channels, around 0.5, push U and V. It should
|
|
375
|
+
* wrap (repeat). Required: without one there is no distortion.
|
|
376
|
+
*/
|
|
377
|
+
map: BaseTexture | null;
|
|
378
|
+
/**
|
|
379
|
+
* How the noise is laid over the particle, see {@link TextureUV}. Default
|
|
380
|
+
* channel 0, untiled, scrolling `[0, 0.5]`.
|
|
381
|
+
*/
|
|
382
|
+
mapUV: TextureUV;
|
|
383
|
+
/** How far the map's UV is pushed at most, in UV units. Default 0.05. */
|
|
384
|
+
strength: number;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** {@link ParticleUVDistortion} as given: any field may be left out, the noise's layout too. */
|
|
388
|
+
export type ParticleUVDistortionOptions = Partial<Omit<ParticleUVDistortion, 'mapUV'>> & {
|
|
389
|
+
/** See {@link ParticleUVDistortion.mapUV}; fields left out take their defaults. */
|
|
390
|
+
mapUV?: Partial<TextureUV>;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/** How far a distortion pushes the map's UV at most when `strength` is left out, in UV units. */
|
|
394
|
+
export const UV_DISTORTION_DEFAULT_STRENGTH = 0.05;
|
|
395
|
+
|
|
396
|
+
/** A complete distortion from a partial one, or undefined when unset or without a map. */
|
|
397
|
+
export function normalizeUVDistortion(
|
|
398
|
+
distortion: ParticleUVDistortionOptions | null | undefined
|
|
399
|
+
): ParticleUVDistortion | undefined {
|
|
400
|
+
if (!distortion || typeof distortion !== 'object' || !distortion.map) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
const strength = distortion.strength;
|
|
404
|
+
return {
|
|
405
|
+
map: distortion.map,
|
|
406
|
+
mapUV: normalizeTextureUV(distortion.mapUV, UV_DISTORTION_DEFAULT_UV),
|
|
407
|
+
strength: Math.max(
|
|
408
|
+
0,
|
|
409
|
+
typeof strength === 'number' && Number.isFinite(strength) ? strength : UV_DISTORTION_DEFAULT_STRENGTH
|
|
410
|
+
),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** True when both describe the same distortion (or both none). */
|
|
415
|
+
export function uvDistortionEqual(a: ParticleUVDistortion | undefined, b: ParticleUVDistortion | undefined): boolean {
|
|
416
|
+
if (a === b) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
if (!a || !b) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return a.map === b.map && a.strength === b.strength && textureUVEqual(a.mapUV, b.mapUV);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* The main map's layout, or undefined when it is the identity (channel 0,
|
|
427
|
+
* untiled, unmoved, still), which costs nothing in the shader.
|
|
428
|
+
*/
|
|
429
|
+
export function normalizeMapUV(uv: Partial<TextureUV> | null | undefined): TextureUV | undefined {
|
|
430
|
+
if (!uv || typeof uv !== 'object') {
|
|
201
431
|
return undefined;
|
|
202
432
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
return u === 0 && v === 0 ? undefined : [u, v];
|
|
433
|
+
const normalized = normalizeTextureUV(uv);
|
|
434
|
+
return textureUVToJSON(normalized) === undefined ? undefined : normalized;
|
|
206
435
|
}
|
|
207
436
|
|
|
208
|
-
/** True when both
|
|
209
|
-
export function
|
|
210
|
-
return a === b || (!!a && !!b && a
|
|
437
|
+
/** True when both lay the map the same way (or both as drawn). */
|
|
438
|
+
export function mapUVEqual(a: TextureUV | undefined, b: TextureUV | undefined): boolean {
|
|
439
|
+
return a === b || (!!a && !!b && textureUVEqual(a, b));
|
|
211
440
|
}
|
|
212
441
|
|
|
442
|
+
/**
|
|
443
|
+
* True when a texture effect of `settings` samples UV channel 1, see
|
|
444
|
+
* {@link UVChannel}: the map, the distortion noise or an erosion mask laid
|
|
445
|
+
* on it.
|
|
446
|
+
*/
|
|
447
|
+
export function readsUV1(settings: {
|
|
448
|
+
mapUV?: TextureUV;
|
|
449
|
+
uvDistortion?: ParticleUVDistortion;
|
|
450
|
+
erosion?: ParticleErosion;
|
|
451
|
+
}): boolean {
|
|
452
|
+
const {mapUV, uvDistortion, erosion} = settings;
|
|
453
|
+
return (
|
|
454
|
+
mapUV?.channel === 1 || uvDistortion?.mapUV.channel === 1 || (!!erosion?.mask && erosion.maskUV.channel === 1)
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* A batch's copy of the {@link VFXBatchSettings} its systems share: every optional field
|
|
460
|
+
* defaulted, and every field the batch's render mode ignores at its default (an opaque batch's
|
|
461
|
+
* `materialBlendMode` reads `ALPHA_DISABLE`, a billboard batch's `rim` is unset). The renderer
|
|
462
|
+
* adds a system to the batch whose settings match its own.
|
|
463
|
+
*/
|
|
213
464
|
export interface StoredBatchSettings {
|
|
465
|
+
/** See {@link VFXBatchSettings.instancingGeometry}. */
|
|
214
466
|
instancingGeometry: Float32Array;
|
|
467
|
+
/** See {@link VFXBatchSettings.instancingIndices}. */
|
|
215
468
|
instancingIndices: Uint32Array | Uint16Array;
|
|
469
|
+
/** See {@link VFXBatchSettings.instancingUVs}. */
|
|
216
470
|
instancingUVs?: Float32Array;
|
|
471
|
+
/** The mesh's second UV set, see {@link UVChannel}. */
|
|
472
|
+
instancingUVs2?: Float32Array;
|
|
473
|
+
/** See {@link VFXBatchSettings.instancingNormals}. */
|
|
217
474
|
instancingNormals?: Float32Array;
|
|
475
|
+
/** See {@link VFXBatchSettings.renderMode}. */
|
|
218
476
|
renderMode: RenderMode;
|
|
477
|
+
/** See {@link VFXBatchSettings.renderOrder}. */
|
|
219
478
|
renderOrder: number;
|
|
479
|
+
/** See {@link VFXBatchSettings.uTileCount}. */
|
|
220
480
|
uTileCount: number;
|
|
481
|
+
/** See {@link VFXBatchSettings.vTileCount}. */
|
|
221
482
|
vTileCount: number;
|
|
483
|
+
/** See {@link VFXBatchSettings.blendTiles}. */
|
|
222
484
|
blendTiles: boolean;
|
|
485
|
+
/** See {@link VFXBatchSettings.softParticles}. */
|
|
223
486
|
softParticles: boolean;
|
|
487
|
+
/** See {@link VFXBatchSettings.softNearFade}. */
|
|
224
488
|
softNearFade: number;
|
|
489
|
+
/** See {@link VFXBatchSettings.softFarFade}. */
|
|
225
490
|
softFarFade: number;
|
|
491
|
+
/** See {@link VFXBatchSettings.materialBlendMode}. */
|
|
226
492
|
materialBlendMode: number;
|
|
493
|
+
/** See {@link VFXBatchSettings.materialTransparent}. */
|
|
227
494
|
materialTransparent: boolean;
|
|
495
|
+
/** See {@link VFXBatchSettings.materialDepthTest}. */
|
|
228
496
|
materialDepthTest: boolean;
|
|
497
|
+
/** See {@link VFXBatchSettings.materialDepthWrite}. */
|
|
229
498
|
materialDepthWrite: boolean;
|
|
499
|
+
/** See {@link VFXBatchSettings.materialAlphaTest}. */
|
|
230
500
|
materialAlphaTest: number;
|
|
231
|
-
texture
|
|
501
|
+
/** See {@link VFXBatchSettings.texture}. */
|
|
502
|
+
texture: BaseTexture | null;
|
|
503
|
+
/** See {@link VFXBatchSettings.reflectionTexture}. */
|
|
232
504
|
reflectionTexture: BaseTexture | null;
|
|
505
|
+
/** See {@link VFXBatchSettings.reflectionLevel}. */
|
|
233
506
|
reflectionLevel: number;
|
|
234
|
-
|
|
507
|
+
/** See {@link VFXBatchSettings.reflectionAtlas}. */
|
|
235
508
|
reflectionAtlas: BaseTexture | null;
|
|
509
|
+
/** See {@link VFXBatchSettings.layerMask}. */
|
|
236
510
|
layerMask: number;
|
|
237
511
|
/** Babylon rendering group the batch mesh draws in, see {@link VFXBatchSettings.renderingGroupId}. */
|
|
238
512
|
renderingGroupId: number;
|
|
@@ -250,62 +524,48 @@ export interface StoredBatchSettings {
|
|
|
250
524
|
lighting?: MeshLighting;
|
|
251
525
|
/** Mesh particle rim, see {@link VFXBatchSettings.rim}. */
|
|
252
526
|
rim?: MeshRim;
|
|
253
|
-
/**
|
|
254
|
-
|
|
527
|
+
/** The main map's layout, see {@link VFXBatchSettings.mapUV}. */
|
|
528
|
+
mapUV?: TextureUV;
|
|
529
|
+
/** Map UV distortion, see {@link VFXBatchSettings.uvDistortion}. */
|
|
530
|
+
uvDistortion?: ParticleUVDistortion;
|
|
255
531
|
/** Colour ramp for the map, see {@link VFXBatchSettings.gradientMap}. */
|
|
256
|
-
gradientMap
|
|
532
|
+
gradientMap?: ParticleGradientMap;
|
|
257
533
|
/** Alpha erosion, see {@link VFXBatchSettings.erosion}. */
|
|
258
534
|
erosion?: ParticleErosion;
|
|
259
535
|
}
|
|
260
536
|
|
|
537
|
+
/**
|
|
538
|
+
* One draw call of a {@link BatchedRenderer}: the systems that share a material, texture and
|
|
539
|
+
* geometry, drawn together from one instanced mesh. The renderer makes and frees batches as
|
|
540
|
+
* systems are added and their settings change; read them through `renderer.batches` to inspect
|
|
541
|
+
* what is drawn, not to change it.
|
|
542
|
+
*/
|
|
261
543
|
export abstract class VFXBatch {
|
|
544
|
+
/** The Babylon mesh the batch draws with. */
|
|
262
545
|
mesh: Mesh;
|
|
546
|
+
/** The systems drawn by this batch. */
|
|
263
547
|
systems: Set<IParticleSystem>;
|
|
548
|
+
/** The settings every system in the batch shares, with what the render mode ignores at its default. */
|
|
264
549
|
settings: StoredBatchSettings;
|
|
550
|
+
/** @internal */
|
|
265
551
|
protected maxParticles: number;
|
|
552
|
+
/** @internal */
|
|
266
553
|
protected scene: Scene;
|
|
554
|
+
/** @internal */
|
|
267
555
|
protected readonly visibleSystems: IParticleSystem[] = [];
|
|
556
|
+
/**
|
|
557
|
+
* The depth texture soft particles read, from {@link BatchedRenderer.setDepthTexture}.
|
|
558
|
+
* @internal
|
|
559
|
+
*/
|
|
560
|
+
protected depth: DepthBinding | null;
|
|
268
561
|
|
|
269
|
-
|
|
562
|
+
/** @internal */
|
|
563
|
+
protected constructor(settings: VFXBatchSettings, scene: Scene, depth: DepthBinding | null = null) {
|
|
270
564
|
this.scene = scene;
|
|
565
|
+
this.depth = depth;
|
|
271
566
|
this.maxParticles = 1000;
|
|
272
567
|
this.systems = new Set<IParticleSystem>();
|
|
273
|
-
this.settings =
|
|
274
|
-
instancingGeometry: settings.instancingGeometry,
|
|
275
|
-
instancingIndices: settings.instancingIndices,
|
|
276
|
-
instancingUVs: settings.instancingUVs,
|
|
277
|
-
instancingNormals: settings.instancingNormals,
|
|
278
|
-
renderMode: settings.renderMode,
|
|
279
|
-
renderOrder: settings.renderOrder,
|
|
280
|
-
uTileCount: settings.uTileCount,
|
|
281
|
-
vTileCount: settings.vTileCount,
|
|
282
|
-
blendTiles: settings.blendTiles,
|
|
283
|
-
softParticles: settings.softParticles,
|
|
284
|
-
softNearFade: settings.softNearFade,
|
|
285
|
-
softFarFade: settings.softFarFade,
|
|
286
|
-
materialBlendMode: settings.materialBlendMode,
|
|
287
|
-
materialTransparent: settings.materialTransparent,
|
|
288
|
-
materialDepthTest: settings.materialDepthTest,
|
|
289
|
-
materialDepthWrite: settings.materialDepthWrite,
|
|
290
|
-
materialAlphaTest: settings.materialAlphaTest,
|
|
291
|
-
texture: settings.texture,
|
|
292
|
-
reflectionTexture: settings.reflectionTexture ?? null,
|
|
293
|
-
reflectionLevel: settings.reflectionLevel ?? 1,
|
|
294
|
-
reflectionFaces: settings.reflectionFaces ?? null,
|
|
295
|
-
reflectionAtlas: settings.reflectionAtlas ?? null,
|
|
296
|
-
pivot: settings.pivot?.clone(),
|
|
297
|
-
sortMode: settings.sortMode ?? 'none',
|
|
298
|
-
minParticleSize: settings.minParticleSize ?? 0,
|
|
299
|
-
maxParticleSize: settings.maxParticleSize ?? Infinity,
|
|
300
|
-
layerMask: settings.layerMask,
|
|
301
|
-
renderingGroupId: settings.renderingGroupId ?? 0,
|
|
302
|
-
applyFog: settings.applyFog ?? true,
|
|
303
|
-
lighting: settings.lighting,
|
|
304
|
-
rim: normalizeMeshRim(settings.rim),
|
|
305
|
-
uvScroll: normalizeUVScroll(settings.uvScroll),
|
|
306
|
-
gradientMap: settings.gradientMap ?? null,
|
|
307
|
-
erosion: normalizeErosion(settings.erosion),
|
|
308
|
-
};
|
|
568
|
+
this.settings = storedBatchSettings(settings);
|
|
309
569
|
this.mesh = new Mesh('vfxBatch', scene);
|
|
310
570
|
this.configureMesh();
|
|
311
571
|
}
|
|
@@ -333,6 +593,12 @@ export abstract class VFXBatch {
|
|
|
333
593
|
mesh.alphaIndex = this.settings.renderOrder;
|
|
334
594
|
// The shaders fog themselves; Babylon's own fog pass does not apply to a ShaderMaterial.
|
|
335
595
|
mesh.applyFog = false;
|
|
596
|
+
// Its geometry is one quad or mesh at the origin, not the particles, so a pick or a
|
|
597
|
+
// collision against it would hit nothing that is drawn. The renderer rebuilds batches
|
|
598
|
+
// from its systems, so a scene serializer has nothing to write either.
|
|
599
|
+
mesh.isPickable = false;
|
|
600
|
+
mesh.checkCollisions = false;
|
|
601
|
+
mesh.doNotSerialize = true;
|
|
336
602
|
}
|
|
337
603
|
|
|
338
604
|
private readonly boundsMin_ = new Vector3();
|
|
@@ -395,23 +661,26 @@ export abstract class VFXBatch {
|
|
|
395
661
|
}
|
|
396
662
|
|
|
397
663
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
664
|
+
* Writes a uniform that changes per bind, such as one read off the active camera, to the
|
|
665
|
+
* material for the next bind and to its bound effect for this one: ShaderMaterial uploads its
|
|
666
|
+
* stored values before the bind observers run, so a value written only to the material would
|
|
667
|
+
* reach the GPU one draw late, and with two cameras it would always be the other camera's.
|
|
401
668
|
*/
|
|
402
|
-
protected static
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
669
|
+
protected static writeBound(material: ShaderMaterial, write: (target: ShaderMaterial | Effect) => void): void {
|
|
670
|
+
write(material);
|
|
671
|
+
// An effect still compiling has no pipeline context to write to; it gets the stored value
|
|
672
|
+
// when it is first bound. A sub mesh's effect override is not reached here.
|
|
673
|
+
const effect = material.getEffect();
|
|
674
|
+
if (effect?.isReady()) {
|
|
675
|
+
write(effect);
|
|
407
676
|
}
|
|
408
|
-
const additive =
|
|
409
|
-
this.settings.materialTransparent &&
|
|
410
|
-
(this.settings.materialBlendMode === Constants.ALPHA_ADD ||
|
|
411
|
-
this.settings.materialBlendMode === Constants.ALPHA_ONEONE);
|
|
412
|
-
return additive ? ['FOG', 'FOG_ADDITIVE'] : ['FOG'];
|
|
413
677
|
}
|
|
414
678
|
|
|
679
|
+
/**
|
|
680
|
+
* The per-bind upload of the scene's current fog. Mode 0 (none) when the scene has fog
|
|
681
|
+
* disabled, so toggling fog at runtime needs no recompile.
|
|
682
|
+
* @internal
|
|
683
|
+
*/
|
|
415
684
|
protected bindFog(material: ShaderMaterial): void {
|
|
416
685
|
if (!this.settings.applyFog) {
|
|
417
686
|
return;
|
|
@@ -434,90 +703,71 @@ export abstract class VFXBatch {
|
|
|
434
703
|
*/
|
|
435
704
|
time = 0;
|
|
436
705
|
|
|
437
|
-
/**
|
|
438
|
-
protected
|
|
439
|
-
|
|
440
|
-
if (this.settings.uvScroll) {
|
|
441
|
-
defines.push('UV_SCROLL');
|
|
442
|
-
}
|
|
443
|
-
if (this.settings.gradientMap) {
|
|
444
|
-
defines.push('GRADIENT_MAP');
|
|
445
|
-
}
|
|
446
|
-
const erosion = this.settings.erosion;
|
|
447
|
-
if (erosion) {
|
|
448
|
-
defines.push('EROSION');
|
|
449
|
-
if (erosion.mask) {
|
|
450
|
-
defines.push('EROSION_MASK');
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return defines;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/** Adds the uniforms and samplers of the texture effects this batch uses. */
|
|
457
|
-
protected surfaceUniforms(uniforms: string[], samplers: string[]): void {
|
|
458
|
-
if (this.settings.uvScroll) {
|
|
459
|
-
uniforms.push('uvScrollOffset');
|
|
460
|
-
}
|
|
461
|
-
if (this.settings.gradientMap) {
|
|
462
|
-
samplers.push('gradientMap');
|
|
463
|
-
uniforms.push('gradientMapRange');
|
|
464
|
-
}
|
|
465
|
-
const erosion = this.settings.erosion;
|
|
466
|
-
if (erosion) {
|
|
467
|
-
uniforms.push('erosionParams', 'erosionEdgeColor');
|
|
468
|
-
if (erosion.mask) {
|
|
469
|
-
samplers.push('erosionMask');
|
|
470
|
-
}
|
|
471
|
-
}
|
|
706
|
+
/** True when a texture effect of this batch samples UV channel 1, see {@link UVChannel}. */
|
|
707
|
+
protected usesUV1(): boolean {
|
|
708
|
+
return readsUV1(this.settings);
|
|
472
709
|
}
|
|
473
710
|
|
|
474
711
|
/** Sets the texture effects' textures and constants, and advances their scrolling on every bind. */
|
|
475
712
|
protected bindSurface(material: ShaderMaterial): void {
|
|
476
|
-
const {
|
|
477
|
-
if (
|
|
478
|
-
material.setTexture('
|
|
713
|
+
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
714
|
+
if (uvDistortion?.map) {
|
|
715
|
+
material.setTexture('distortionMap', uvDistortion.map);
|
|
479
716
|
}
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
material.
|
|
485
|
-
'erosionEdgeColor',
|
|
486
|
-
new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
|
|
487
|
-
);
|
|
717
|
+
if (gradientMap?.ramp) {
|
|
718
|
+
material.setTexture('gradientMap', gradientMap.ramp);
|
|
719
|
+
}
|
|
720
|
+
if (erosion?.mask) {
|
|
721
|
+
material.setTexture('erosionMask', erosion.mask);
|
|
488
722
|
}
|
|
489
|
-
|
|
723
|
+
this.writeSurfaceParams(material);
|
|
724
|
+
if (!mapUV && !uvDistortion && !erosion?.mask && !gradientMap) {
|
|
490
725
|
return;
|
|
491
726
|
}
|
|
492
727
|
// Wrapped on the CPU: a raw speed × time grows until float32 UVs lose their fraction.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
728
|
+
// Over two repeats, not one: a mirrored texture repeats every two, and
|
|
729
|
+
// wrapped over one it jumped to its mirror image at every wrap.
|
|
730
|
+
const wrap = (value: number) => value - 2 * Math.floor(value / 2);
|
|
731
|
+
const mapTransform = new BVector4();
|
|
732
|
+
const distortionTransform = new BVector4();
|
|
733
|
+
const maskTransform = new BVector4();
|
|
496
734
|
const gradientRange = new BVector2(0, 1);
|
|
735
|
+
// Only the scroll is wrapped: an offset is kept as given, so a clamped
|
|
736
|
+
// texture moved off its edge stays moved.
|
|
737
|
+
const transform = (uv: TextureUV, time: number, out: BVector4) =>
|
|
738
|
+
out.set(
|
|
739
|
+
uv.tiling[0],
|
|
740
|
+
uv.tiling[1],
|
|
741
|
+
uv.offset[0] + wrap(uv.scroll[0] * time),
|
|
742
|
+
uv.offset[1] + wrap(uv.scroll[1] * time)
|
|
743
|
+
);
|
|
497
744
|
// Written to the material for the next bind and to the bound effect for
|
|
498
745
|
// this one: ShaderMaterial uploads its values before the bind observers run.
|
|
746
|
+
// The layouts are read off the settings on every bind, so a live change
|
|
747
|
+
// to a tiling or scroll reaches the next draw.
|
|
499
748
|
const write = () => {
|
|
500
749
|
const time = this.time;
|
|
501
750
|
const effect = material.getEffect();
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
751
|
+
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
752
|
+
if (mapUV) {
|
|
753
|
+
transform(mapUV, time, mapTransform);
|
|
754
|
+
material.setVector4('mapTransform', mapTransform);
|
|
755
|
+
effect?.setVector4('mapTransform', mapTransform);
|
|
756
|
+
}
|
|
757
|
+
if (uvDistortion) {
|
|
758
|
+
transform(uvDistortion.mapUV, time, distortionTransform);
|
|
759
|
+
material.setVector4('distortionTransform', distortionTransform);
|
|
760
|
+
effect?.setVector4('distortionTransform', distortionTransform);
|
|
506
761
|
}
|
|
507
|
-
if (erosion) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
wrap(erosion.maskScroll[0] * time),
|
|
512
|
-
wrap(erosion.maskScroll[1] * time)
|
|
513
|
-
);
|
|
514
|
-
material.setVector4('erosionParams', erosionParams);
|
|
515
|
-
effect?.setVector4('erosionParams', erosionParams);
|
|
762
|
+
if (erosion?.mask) {
|
|
763
|
+
transform(erosion.maskUV, time, maskTransform);
|
|
764
|
+
material.setVector4('erosionMaskTransform', maskTransform);
|
|
765
|
+
effect?.setVector4('erosionMaskTransform', maskTransform);
|
|
516
766
|
}
|
|
517
|
-
if (gradientMap) {
|
|
767
|
+
if (gradientMap?.ramp) {
|
|
518
768
|
// Texel centres of the first and last texel, so a repeating
|
|
519
769
|
// ramp does not blend its two ends at black and white.
|
|
520
|
-
const width = gradientMap.getSize().width;
|
|
770
|
+
const width = gradientMap.ramp.getSize().width;
|
|
521
771
|
const half = width > 0 ? 0.5 / width : 0;
|
|
522
772
|
gradientRange.set(half, 1 - half);
|
|
523
773
|
material.setVector2('gradientMapRange', gradientRange);
|
|
@@ -528,11 +778,56 @@ export abstract class VFXBatch {
|
|
|
528
778
|
material.onBindObservable.add(write);
|
|
529
779
|
}
|
|
530
780
|
|
|
531
|
-
|
|
781
|
+
/** Writes the texture effects' constants (the uniforms their live parameters set). */
|
|
782
|
+
private writeSurfaceParams(material: ShaderMaterial): void {
|
|
783
|
+
const {uvDistortion, gradientMap, erosion} = this.settings;
|
|
784
|
+
if (uvDistortion?.map) {
|
|
785
|
+
material.setFloat('distortionStrength', uvDistortion.strength);
|
|
786
|
+
}
|
|
787
|
+
if (gradientMap?.ramp) {
|
|
788
|
+
const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
|
|
789
|
+
material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
|
|
790
|
+
}
|
|
791
|
+
if (erosion) {
|
|
792
|
+
material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
|
|
793
|
+
material.setVector3(
|
|
794
|
+
'erosionEdgeColor',
|
|
795
|
+
new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Takes new live parameters (see {@link BatchedRenderer.updateSystem}): settings with the same
|
|
802
|
+
* shader key as the batch's, whose uniform values differ. They are copied into the batch's
|
|
803
|
+
* settings and written to its material, which is kept.
|
|
804
|
+
* @internal
|
|
805
|
+
*/
|
|
806
|
+
applyLiveSettings(settings: StoredBatchSettings): void {
|
|
807
|
+
Object.assign(this.settings, settings);
|
|
808
|
+
const material = this.mesh.material;
|
|
809
|
+
if (material instanceof ShaderMaterial) {
|
|
810
|
+
this.writeLiveUniforms(material);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Writes every uniform a live parameter sets, from the batch's settings. The base writes the
|
|
816
|
+
* ones every render mode has; a mode adds its own.
|
|
817
|
+
* @internal
|
|
818
|
+
*/
|
|
819
|
+
protected writeLiveUniforms(material: ShaderMaterial): void {
|
|
820
|
+
this.writeSoftParams(material);
|
|
821
|
+
this.writeSurfaceParams(material);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** @internal */
|
|
825
|
+
addSystem(system: IParticleSystem): void {
|
|
532
826
|
this.systems.add(system);
|
|
533
827
|
}
|
|
534
828
|
|
|
535
|
-
|
|
829
|
+
/** @internal */
|
|
830
|
+
removeSystem(system: IParticleSystem): void {
|
|
536
831
|
this.systems.delete(system);
|
|
537
832
|
}
|
|
538
833
|
|
|
@@ -555,21 +850,90 @@ export abstract class VFXBatch {
|
|
|
555
850
|
return visibleSystems;
|
|
556
851
|
}
|
|
557
852
|
|
|
558
|
-
|
|
853
|
+
/**
|
|
854
|
+
* Sets the depth texture soft particles read. Soft particles are compiled in only while there
|
|
855
|
+
* is one, as Unity turns them off for a camera without a depth texture, so attaching or
|
|
856
|
+
* removing it, or changing how it is packed, rebuilds a soft batch's material.
|
|
857
|
+
*
|
|
858
|
+
* @param depth - The binding, or a bare texture, read as `'linear'` and unpacked.
|
|
859
|
+
* @internal Use {@link BatchedRenderer.setDepthTexture}.
|
|
860
|
+
*/
|
|
861
|
+
applyDepthTexture(depth: DepthBinding | BaseTexture | null): void {
|
|
862
|
+
if (depth instanceof BaseTexture) {
|
|
863
|
+
depth = {texture: depth, encoding: 'linear', packed: false};
|
|
864
|
+
}
|
|
865
|
+
const previous = this.depth;
|
|
866
|
+
this.depth = depth;
|
|
867
|
+
if (!this.settings.softParticles) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (!previous !== !depth || previous?.packed !== depth?.packed) {
|
|
871
|
+
this.rebuildMaterial();
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
559
874
|
const material = this.mesh.material;
|
|
560
|
-
if (
|
|
561
|
-
material.setTexture('depthTexture',
|
|
875
|
+
if (depth && material instanceof ShaderMaterial) {
|
|
876
|
+
material.setTexture('depthTexture', depth.texture);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Sets the soft-particle and alpha-test values on a material, and decodes the depth for the
|
|
882
|
+
* camera of each draw.
|
|
883
|
+
*/
|
|
884
|
+
protected bindSoft(material: ShaderMaterial): void {
|
|
885
|
+
this.writeSoftParams(material);
|
|
886
|
+
if (this.drawsSoft()) {
|
|
887
|
+
material.setTexture('depthTexture', this.depth!.texture);
|
|
888
|
+
// Reused across binds: this runs on every draw call.
|
|
889
|
+
const projParams = new BVector4(1, 0, 0, 1);
|
|
890
|
+
material.onBindObservable.add(() => {
|
|
891
|
+
const camera = this.scene.activeCamera;
|
|
892
|
+
if (camera && this.depth) {
|
|
893
|
+
softDepthParams(camera, this.depth.encoding, projParams);
|
|
894
|
+
VFXBatch.writeBound(material, (target) => target.setVector4('projParams', projParams));
|
|
895
|
+
}
|
|
896
|
+
});
|
|
562
897
|
}
|
|
563
898
|
}
|
|
564
899
|
|
|
565
|
-
/**
|
|
900
|
+
/** Writes the soft-particle fade and the alpha cutoff, the live parameters every mode shares. */
|
|
901
|
+
private writeSoftParams(material: ShaderMaterial): void {
|
|
902
|
+
if (this.drawsSoft()) {
|
|
903
|
+
material.setVector2(
|
|
904
|
+
'softParams',
|
|
905
|
+
new BVector2(
|
|
906
|
+
this.settings.softNearFade,
|
|
907
|
+
1.0 / Math.max(this.settings.softFarFade - this.settings.softNearFade, 0.0001)
|
|
908
|
+
)
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (this.settings.materialAlphaTest > 0) {
|
|
912
|
+
material.setFloat('alphaTest', this.settings.materialAlphaTest);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** True when this batch draws soft particles: they are on and a depth texture is attached. */
|
|
917
|
+
protected drawsSoft(): boolean {
|
|
918
|
+
return this.settings.softParticles && this.depth !== null;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it.
|
|
923
|
+
* @internal Use {@link BatchedRenderer.setMeshLighting}.
|
|
924
|
+
*/
|
|
566
925
|
applyMeshLighting(_lighting: MeshLightingSettings): void {}
|
|
567
926
|
|
|
927
|
+
/** @internal */
|
|
568
928
|
abstract setupBuffers(): void;
|
|
929
|
+
/** @internal */
|
|
569
930
|
abstract expandBuffers(target: number): void;
|
|
931
|
+
/** @internal */
|
|
570
932
|
abstract rebuildMaterial(): void;
|
|
933
|
+
/** @internal */
|
|
571
934
|
abstract update(): void;
|
|
572
935
|
|
|
936
|
+
/** @internal */
|
|
573
937
|
dispose(): void {
|
|
574
938
|
this.mesh.dispose();
|
|
575
939
|
}
|