babylon.quarks 0.26.0 → 0.28.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 +1194 -8
- package/README.md +13 -9
- 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 +10108 -0
- package/dist/types/index.d.ts +5794 -998
- package/package.json +18 -7
- package/src/BatchedRenderer.ts +274 -156
- package/src/EffectPool.ts +94 -12
- package/src/MeshSurfaceEmitter.ts +50 -14
- package/src/ParticleEmitter.ts +75 -17
- package/src/ParticleSystem.ts +981 -2066
- package/src/QuarksLoader.ts +666 -194
- package/src/QuarksPrefab.ts +367 -73
- package/src/QuarksSerializer.ts +512 -0
- package/src/QuarksUtil.ts +77 -7
- package/src/SpriteBatch.ts +127 -252
- package/src/TrailBatch.ts +545 -348
- package/src/VFXBatch.ts +238 -596
- package/src/batchKey.ts +239 -0
- package/src/batchSettings.ts +546 -0
- package/src/effectDocumentJSON.ts +261 -0
- package/src/effectResources.ts +76 -0
- package/src/envAtlas.ts +126 -18
- package/src/geometryUtil.ts +1 -1
- package/src/index.ts +69 -35
- package/src/nodeId.ts +36 -0
- package/src/shaderProgram.ts +419 -0
- package/src/shaderVariants.ts +245 -0
- package/src/shaders/chunks/blend.ts +31 -0
- package/src/shaders/chunks/fog.ts +12 -5
- package/src/shaders/chunks/sheet.ts +111 -0
- package/src/shaders/chunks/soft.ts +85 -0
- package/src/shaders/chunks/surface.ts +12 -2
- package/src/shaders/local_particle_physics_vert.glsl.ts +14 -28
- package/src/shaders/local_particle_physics_vert.wgsl.ts +16 -29
- package/src/shaders/particle_frag.glsl.ts +5 -22
- package/src/shaders/particle_frag.wgsl.ts +5 -23
- package/src/shaders/particle_physics_frag.glsl.ts +16 -26
- package/src/shaders/particle_physics_frag.wgsl.ts +14 -26
- package/src/shaders/particle_vert.glsl.ts +5 -27
- package/src/shaders/particle_vert.wgsl.ts +5 -26
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +7 -33
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +7 -33
- package/src/shaders/trail_frag.glsl.ts +13 -0
- package/src/shaders/trail_frag.wgsl.ts +13 -0
- package/src/shaders/trail_vert.glsl.ts +45 -36
- package/src/shaders/trail_vert.wgsl.ts +46 -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 +358 -0
- package/src/system/EventDispatcher.ts +60 -0
- package/src/system/ParticleSpawner.ts +274 -0
- package/src/system/SimulationClock.ts +130 -0
- package/src/system/geometry.ts +104 -0
- package/src/system/integrate.ts +428 -0
- package/src/system/materialResolve.ts +165 -0
- package/src/system/normalize.ts +100 -0
- package/src/system/serialization.ts +422 -0
- package/src/system/settingsSchema.ts +339 -0
- package/src/system/types.ts +449 -0
- package/src/textureSource.ts +30 -0
- package/src/trailSettings.ts +54 -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,533 +1,68 @@
|
|
|
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
|
-
import {
|
|
6
|
-
import {Vector2 as BVector2, Vector3 as BVector3, Vector4 as BVector4} from '@babylonjs/core/Maths/math.vector';
|
|
5
|
+
import {Vector2 as BVector2, Vector3 as BVector3, Vector4 as BVector4, Matrix} from '@babylonjs/core/Maths/math.vector';
|
|
7
6
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
7
|
+
import type {Node} from '@babylonjs/core/node';
|
|
8
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 {GRADIENT_MAP_WEIGHTS, MeshLightingSettings, readsUV1, StoredBatchSettings, TextureUV} from './batchSettings';
|
|
14
|
+
import {DepthBinding, softDepthParams} from './softDepth';
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
* The order the particles of a batch are drawn in (Unity's Sort Mode). Alpha
|
|
15
|
-
* blended particles cover what was drawn before them, so the order shows.
|
|
16
|
-
*
|
|
17
|
-
* - `'none'`: as they are, system by system. Right for additive blending.
|
|
18
|
-
* - `'distance'`: farthest from the active camera first, so nearer particles
|
|
19
|
-
* cover farther ones.
|
|
20
|
-
* - `'oldestInFront'` / `'youngestInFront'`: by age.
|
|
21
|
-
*
|
|
22
|
-
* Sorting runs over the whole batch after each fill. Trails are not sorted.
|
|
23
|
-
*/
|
|
24
|
-
export type SortMode = 'none' | 'distance' | 'oldestInFront' | 'youngestInFront';
|
|
25
|
-
|
|
26
|
-
export enum RenderMode {
|
|
27
|
-
BillBoard = 0,
|
|
28
|
-
StretchedBillBoard = 1,
|
|
29
|
-
Mesh = 2,
|
|
30
|
-
Trail = 3,
|
|
31
|
-
HorizontalBillBoard = 4,
|
|
32
|
-
VerticalBillBoard = 5,
|
|
33
|
-
/**
|
|
34
|
-
* A quad laid from each particle to the system's {@link ParticleSystem.beamEnd}
|
|
35
|
-
* and turned about its length to face the camera (a line renderer): U runs
|
|
36
|
-
* from the particle (0) to the end (1), V across, the particle's size is
|
|
37
|
-
* the width. Lightning, tethers, lasers.
|
|
38
|
-
*/
|
|
39
|
-
Beam = 6,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* How mesh particles are lit.
|
|
44
|
-
*
|
|
45
|
-
* - `'fixed'` (default): one directional light plus ambient, the same for every
|
|
46
|
-
* mesh batch of a renderer, set with {@link BatchedRenderer.setMeshLighting}.
|
|
47
|
-
* - `'unlit'`: no lighting term — the texture times the particle colour, for
|
|
48
|
-
* emissive meshes (fire, energy, glowing blades).
|
|
49
|
-
* - `'scene'`: follows the scene's first enabled `DirectionalLight` or
|
|
50
|
-
* `HemisphericLight` every frame, falling back to the fixed light when there
|
|
51
|
-
* is none.
|
|
52
|
-
*
|
|
53
|
-
* Only mesh particles are lit; other render modes ignore it.
|
|
54
|
-
*/
|
|
55
|
-
export type MeshLighting = 'unlit' | 'fixed' | 'scene';
|
|
56
|
-
|
|
57
|
-
/** The light {@link MeshLighting} `'fixed'` mesh particles are lit by. */
|
|
58
|
-
export interface MeshLightingSettings {
|
|
59
|
-
/** Direction the light travels in, world space. Default `(0.4, -1, 0.6)`. */
|
|
60
|
-
direction: BVector3;
|
|
61
|
-
/** Light colour, intensity folded in. Default white. */
|
|
62
|
-
color: Color3;
|
|
63
|
-
/** Light every face gets regardless of its direction. Default `(0.35, 0.35, 0.35)`. */
|
|
64
|
-
ambient: Color3;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** The light mesh particles have always been lit by. */
|
|
68
|
-
export function defaultMeshLighting(): MeshLightingSettings {
|
|
69
|
-
return {
|
|
70
|
-
direction: new BVector3(0.4, -1, 0.6),
|
|
71
|
-
color: new Color3(1, 1, 1),
|
|
72
|
-
ambient: new Color3(0.35, 0.35, 0.35),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* A rim (Fresnel) term on mesh particles: a colour that grows where the surface
|
|
78
|
-
* turns away from the camera, `pow(1 - |N·V|, power) * strength`. Unset: no rim.
|
|
79
|
-
* Only mesh particles draw it.
|
|
80
|
-
*/
|
|
81
|
-
export interface MeshRim {
|
|
82
|
-
/** Edge colour, linear RGB. Default `[1, 1, 1]`. */
|
|
83
|
-
color: [number, number, number];
|
|
84
|
-
/** Falloff exponent; higher keeps the rim to a thinner edge. Default 2. */
|
|
85
|
-
power: number;
|
|
86
|
-
/** How much of the rim is applied, usually 0…1. Default 1. */
|
|
87
|
-
strength: number;
|
|
88
|
-
/**
|
|
89
|
-
* `'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);
|
|
91
|
-
* `'fade'` leaves the colour and fades the faces turned to the camera by
|
|
92
|
-
* `strength` instead, alpha times `1 - strength * (1 - edge)`: a wall of
|
|
93
|
-
* light that reads thick along its silhouette and thin across its face.
|
|
94
|
-
*/
|
|
95
|
-
mode: 'add' | 'mix' | 'fade';
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* A complete rim from a partial one, every field defaulted and kept in range,
|
|
100
|
-
* or undefined when `rim` is unset.
|
|
101
|
-
*/
|
|
102
|
-
export function normalizeMeshRim(rim: Partial<MeshRim> | null | undefined): MeshRim | undefined {
|
|
103
|
-
if (!rim || typeof rim !== 'object') {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
const color = Array.isArray(rim.color) ? rim.color : [1, 1, 1];
|
|
107
|
-
const channel = (value: unknown, fallback: number) =>
|
|
108
|
-
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
109
|
-
return {
|
|
110
|
-
color: [channel(color[0], 1), channel(color[1], 1), channel(color[2], 1)],
|
|
111
|
-
power: Math.max(0.01, channel(rim.power, 2)),
|
|
112
|
-
strength: Math.max(0, channel(rim.strength, 1)),
|
|
113
|
-
mode: rim.mode === 'mix' || rim.mode === 'fade' ? rim.mode : 'add',
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** True when both describe the same rim (or both none). */
|
|
118
|
-
export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): boolean {
|
|
119
|
-
if (a === b) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
if (!a || !b) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
return (
|
|
126
|
-
a.mode === b.mode &&
|
|
127
|
-
a.power === b.power &&
|
|
128
|
-
a.strength === b.strength &&
|
|
129
|
-
a.color[0] === b.color[0] &&
|
|
130
|
-
a.color[1] === b.color[1] &&
|
|
131
|
-
a.color[2] === b.color[2]
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Which texture coordinates a texture is sampled with, as Unity's UV channels
|
|
137
|
-
* and Niagara's ribbon UV0 / UV1:
|
|
138
|
-
*
|
|
139
|
-
* - `0`: the particle's first UV, with the sprite sheet's cell applied: on a
|
|
140
|
-
* sheet the current frame's cell, on a trail the cell's stretch along it, on
|
|
141
|
-
* a mesh its first UV set. Read before the map's own layout and distortion,
|
|
142
|
-
* so a texture on it does not move with them.
|
|
143
|
-
* - `1`: the particle's own UV, never moved by the sheet. The whole quad on a
|
|
144
|
-
* billboard, a stretched billboard or a beam (mirrored with the quad when it
|
|
145
|
-
* flips); on a trail U along it the way the map's runs (from the tail, or
|
|
146
|
-
* from the head with `uvFromHead`) and V across it; on a mesh its second UV
|
|
147
|
-
* set (`instancingUVs2`), or its first when it has none.
|
|
148
|
-
*
|
|
149
|
-
* A mesh can then repeat its picture along UV0 while a mask on UV1 runs once
|
|
150
|
-
* across its whole length, as a lightmap UV does in a game engine.
|
|
151
|
-
*/
|
|
152
|
-
export type UVChannel = 0 | 1;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* How a texture is laid over the particle: its UV channel, then tiling and
|
|
156
|
-
* offset as in Unity's material Tiling / Offset, then a scroll on the
|
|
157
|
-
* renderer's clock as Unreal's Panner. Sampled at
|
|
158
|
-
* `uv(channel) * tiling + offset + scroll * time`; the offset is used as
|
|
159
|
-
* given, so a clamped texture can be moved off its edge, and only the
|
|
160
|
-
* scroll's share is wrapped (over two repeats, so a mirrored texture stays
|
|
161
|
-
* seamless). A texture that scrolls or tiles should repeat.
|
|
162
|
-
*/
|
|
163
|
-
export interface TextureUV {
|
|
164
|
-
/** See {@link UVChannel}. Default 0. */
|
|
165
|
-
channel: UVChannel;
|
|
166
|
-
/** Repeats across the channel's 0…1, `[u, v]`. Default `[1, 1]`. */
|
|
167
|
-
tiling: [number, number];
|
|
168
|
-
/** Added after tiling, in texture UV units. Default `[0, 0]`. */
|
|
169
|
-
offset: [number, number];
|
|
170
|
-
/** UV units per second on the renderer's clock. Default `[0, 0]`. */
|
|
171
|
-
scroll: [number, number];
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** A complete {@link TextureUV} from a partial one, with `defaults` under the fields left out. */
|
|
175
|
-
export function normalizeTextureUV(
|
|
176
|
-
uv: Partial<TextureUV> | null | undefined,
|
|
177
|
-
defaults: Partial<TextureUV> = {}
|
|
178
|
-
): TextureUV {
|
|
179
|
-
const source = uv && typeof uv === 'object' ? uv : {};
|
|
180
|
-
const num = (value: unknown, fallback: number) =>
|
|
181
|
-
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
182
|
-
const pair = (value: unknown, fallback: [number, number]): [number, number] =>
|
|
183
|
-
Array.isArray(value) ? [num(value[0], fallback[0]), num(value[1], fallback[1])] : [fallback[0], fallback[1]];
|
|
184
|
-
const channel = source.channel ?? defaults.channel;
|
|
185
|
-
return {
|
|
186
|
-
channel: channel === 1 ? 1 : 0,
|
|
187
|
-
tiling: pair(source.tiling, defaults.tiling ?? [1, 1]),
|
|
188
|
-
offset: pair(source.offset, defaults.offset ?? [0, 0]),
|
|
189
|
-
scroll: pair(source.scroll, defaults.scroll ?? [0, 0]),
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** True when both lay a texture the same way. */
|
|
194
|
-
export function textureUVEqual(a: TextureUV, b: TextureUV): boolean {
|
|
195
|
-
return (
|
|
196
|
-
a === b ||
|
|
197
|
-
(a.channel === b.channel &&
|
|
198
|
-
a.tiling[0] === b.tiling[0] &&
|
|
199
|
-
a.tiling[1] === b.tiling[1] &&
|
|
200
|
-
a.offset[0] === b.offset[0] &&
|
|
201
|
-
a.offset[1] === b.offset[1] &&
|
|
202
|
-
a.scroll[0] === b.scroll[0] &&
|
|
203
|
-
a.scroll[1] === b.scroll[1])
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** The fields of `uv` that differ from `defaults`, for JSON written only when set. */
|
|
208
|
-
export function textureUVToJSON(uv: TextureUV, defaults: Partial<TextureUV> = {}): Partial<TextureUV> | undefined {
|
|
209
|
-
const base = normalizeTextureUV(undefined, defaults);
|
|
210
|
-
const json: Partial<TextureUV> = {};
|
|
211
|
-
if (uv.channel !== base.channel) json.channel = uv.channel;
|
|
212
|
-
if (uv.tiling[0] !== base.tiling[0] || uv.tiling[1] !== base.tiling[1]) json.tiling = [...uv.tiling];
|
|
213
|
-
if (uv.offset[0] !== base.offset[0] || uv.offset[1] !== base.offset[1]) json.offset = [...uv.offset];
|
|
214
|
-
if (uv.scroll[0] !== base.scroll[0] || uv.scroll[1] !== base.scroll[1]) json.scroll = [...uv.scroll];
|
|
215
|
-
return Object.keys(json).length > 0 ? json : undefined;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Which value of the map's texel picks the colour along a gradient map's
|
|
220
|
-
* ramp: one channel, so a texture can pack several masks (Unreal's and
|
|
221
|
-
* Unity's channel-packed textures), or the Rec. 709 luminance of its colour.
|
|
222
|
-
*/
|
|
223
|
-
export type GradientMapChannel = 'r' | 'g' | 'b' | 'a' | 'luminance';
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* A gradient map (colour ramp): the map is recoloured by looking its
|
|
227
|
-
* {@link channel} up along U of {@link ramp}. See
|
|
228
|
-
* {@link VFXBatchSettings.gradientMap}.
|
|
229
|
-
*/
|
|
230
|
-
export interface ParticleGradientMap {
|
|
231
|
-
/**
|
|
232
|
-
* The ramp, read along U at mid V: low values on the left. Make it without
|
|
233
|
-
* mipmaps and clamped. Required: without one there is no gradient map.
|
|
234
|
-
*/
|
|
235
|
-
ramp: BaseTexture | null;
|
|
236
|
-
/**
|
|
237
|
-
* The texel value the ramp is looked up by. Default `'r'`. With `'a'` the
|
|
238
|
-
* ramp's alpha becomes the texel's alpha, so a soft shape can glow at its
|
|
239
|
-
* fringe and stay solid at its heart; with any other channel the ramp's
|
|
240
|
-
* alpha multiplies the map's.
|
|
241
|
-
*/
|
|
242
|
-
channel: GradientMapChannel;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** The weights a texel is dotted with to look up the ramp, per {@link GradientMapChannel}. */
|
|
246
|
-
export const GRADIENT_MAP_WEIGHTS: Readonly<Record<GradientMapChannel, readonly [number, number, number, number]>> = {
|
|
247
|
-
r: [1, 0, 0, 0],
|
|
248
|
-
g: [0, 1, 0, 0],
|
|
249
|
-
b: [0, 0, 1, 0],
|
|
250
|
-
a: [0, 0, 0, 1],
|
|
251
|
-
luminance: [0.2126, 0.7152, 0.0722, 0],
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
/** A complete gradient map, or undefined when unset or without a ramp. */
|
|
255
|
-
export function normalizeGradientMap(
|
|
256
|
-
gradientMap: Partial<ParticleGradientMap> | null | undefined
|
|
257
|
-
): ParticleGradientMap | undefined {
|
|
258
|
-
if (!gradientMap || typeof gradientMap !== 'object' || !gradientMap.ramp) {
|
|
259
|
-
return undefined;
|
|
260
|
-
}
|
|
261
|
-
const channel = gradientMap.channel;
|
|
262
|
-
return {
|
|
263
|
-
ramp: gradientMap.ramp,
|
|
264
|
-
channel: channel !== undefined && channel in GRADIENT_MAP_WEIGHTS ? channel : 'r',
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** True when both recolour the same way (or both not at all). */
|
|
269
|
-
export function gradientMapEqual(a: ParticleGradientMap | undefined, b: ParticleGradientMap | undefined): boolean {
|
|
270
|
-
return a === b || (!!a && !!b && a.ramp === b.ramp && a.channel === b.channel);
|
|
271
|
-
}
|
|
16
|
+
export * from './batchSettings';
|
|
272
17
|
|
|
273
18
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
19
|
+
* One draw call of a {@link BatchedRenderer}: the systems that share a material, texture and
|
|
20
|
+
* geometry, drawn together from one instanced mesh. The renderer makes and frees batches as
|
|
21
|
+
* systems are added and their settings change; read them through `renderer.batches` to inspect
|
|
22
|
+
* what is drawn, not to change it.
|
|
278
23
|
*/
|
|
279
|
-
export
|
|
280
|
-
/**
|
|
281
|
-
* Mask whose red channel orders what goes first, laid by {@link maskUV}.
|
|
282
|
-
* Null: the map's own alpha, so a soft shape shrinks from its edges.
|
|
283
|
-
*/
|
|
284
|
-
mask: BaseTexture | null;
|
|
285
|
-
/**
|
|
286
|
-
* How the mask is laid over the particle, see {@link TextureUV}. On UV
|
|
287
|
-
* channel 1 a gradient mask wipes a whole trail or mesh however its map
|
|
288
|
-
* repeats. Default channel 0, untiled, still.
|
|
289
|
-
*/
|
|
290
|
-
maskUV: TextureUV;
|
|
291
|
-
/** Width of the soft transition at the cut, in mask units. Default 0.05. */
|
|
292
|
-
softness: number;
|
|
293
|
-
/** Width of the glowing band above the cut, in mask units; 0 (default) draws none. */
|
|
294
|
-
edgeWidth: number;
|
|
295
|
-
/** Colour added in the edge band, linear RGB, may exceed 1. Default `[1, 1, 1]`. */
|
|
296
|
-
edgeColor: [number, number, number];
|
|
297
|
-
}
|
|
298
|
-
// Limits worth knowing: erosion only changes alpha (one-one and premultiplied
|
|
299
|
-
// blending show a hard cut), and with neither a mask nor a map there is
|
|
300
|
-
// nothing to order the cut by, so the shape goes all at once near alpha 0.
|
|
301
|
-
// Scrolling runs on the renderer's clock, not a system's playback speed.
|
|
302
|
-
|
|
303
|
-
/** {@link ParticleErosion} as given: any field may be left out, the mask's layout too. */
|
|
304
|
-
export type ParticleErosionOptions = Partial<Omit<ParticleErosion, 'maskUV'>> & {maskUV?: Partial<TextureUV>};
|
|
305
|
-
|
|
306
|
-
/** A complete erosion from a partial one, every field defaulted, or undefined when `erosion` is unset. */
|
|
307
|
-
export function normalizeErosion(erosion: ParticleErosionOptions | null | undefined): ParticleErosion | undefined {
|
|
308
|
-
if (!erosion || typeof erosion !== 'object') {
|
|
309
|
-
return undefined;
|
|
310
|
-
}
|
|
311
|
-
const num = (value: unknown, fallback: number) =>
|
|
312
|
-
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
313
|
-
const color = Array.isArray(erosion.edgeColor) ? erosion.edgeColor : [1, 1, 1];
|
|
314
|
-
return {
|
|
315
|
-
mask: erosion.mask ?? null,
|
|
316
|
-
maskUV: normalizeTextureUV(erosion.maskUV),
|
|
317
|
-
softness: Math.max(0.0001, num(erosion.softness, 0.05)),
|
|
318
|
-
edgeWidth: Math.max(0, num(erosion.edgeWidth, 0)),
|
|
319
|
-
edgeColor: [num(color[0], 1), num(color[1], 1), num(color[2], 1)],
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** True when both describe the same erosion (or both none). */
|
|
324
|
-
export function erosionEqual(a: ParticleErosion | undefined, b: ParticleErosion | undefined): boolean {
|
|
325
|
-
if (a === b) {
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
if (!a || !b) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
return (
|
|
332
|
-
a.mask === b.mask &&
|
|
333
|
-
textureUVEqual(a.maskUV, b.maskUV) &&
|
|
334
|
-
a.softness === b.softness &&
|
|
335
|
-
a.edgeWidth === b.edgeWidth &&
|
|
336
|
-
a.edgeColor[0] === b.edgeColor[0] &&
|
|
337
|
-
a.edgeColor[1] === b.edgeColor[1] &&
|
|
338
|
-
a.edgeColor[2] === b.edgeColor[2]
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** The distortion noise's default layout: drifting up half a repeat a second. */
|
|
343
|
-
export const UV_DISTORTION_DEFAULT_UV: Partial<TextureUV> = {scroll: [0, 0.5]};
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* UV distortion: the map's UV pushed about by a noise texture, for heat
|
|
347
|
-
* shimmer, licking flames and breathing auras. See
|
|
348
|
-
* {@link VFXBatchSettings.uvDistortion}.
|
|
349
|
-
*/
|
|
350
|
-
export interface ParticleUVDistortion {
|
|
351
|
-
/**
|
|
352
|
-
* Noise whose red and green channels, around 0.5, push U and V. It should
|
|
353
|
-
* wrap (repeat). Required: without one there is no distortion.
|
|
354
|
-
*/
|
|
355
|
-
map: BaseTexture | null;
|
|
24
|
+
export abstract class VFXBatch {
|
|
356
25
|
/**
|
|
357
|
-
*
|
|
358
|
-
*
|
|
26
|
+
* The Babylon mesh the batch draws with. Its world matrix is frozen at identity: the instance
|
|
27
|
+
* data is already in world space, so no parent's transform moves the particles again.
|
|
359
28
|
*/
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
/** {@link ParticleUVDistortion} as given: any field may be left out, the noise's layout too. */
|
|
366
|
-
export type ParticleUVDistortionOptions = Partial<Omit<ParticleUVDistortion, 'mapUV'>> & {mapUV?: Partial<TextureUV>};
|
|
367
|
-
|
|
368
|
-
/** A complete distortion from a partial one, or undefined when unset or without a map. */
|
|
369
|
-
export function normalizeUVDistortion(
|
|
370
|
-
distortion: ParticleUVDistortionOptions | null | undefined
|
|
371
|
-
): ParticleUVDistortion | undefined {
|
|
372
|
-
if (!distortion || typeof distortion !== 'object' || !distortion.map) {
|
|
373
|
-
return undefined;
|
|
374
|
-
}
|
|
375
|
-
const strength = distortion.strength;
|
|
376
|
-
return {
|
|
377
|
-
map: distortion.map,
|
|
378
|
-
mapUV: normalizeTextureUV(distortion.mapUV, UV_DISTORTION_DEFAULT_UV),
|
|
379
|
-
strength: Math.max(0, typeof strength === 'number' && Number.isFinite(strength) ? strength : 0.05),
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/** True when both describe the same distortion (or both none). */
|
|
384
|
-
export function uvDistortionEqual(a: ParticleUVDistortion | undefined, b: ParticleUVDistortion | undefined): boolean {
|
|
385
|
-
if (a === b) {
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
|
-
if (!a || !b) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
return a.map === b.map && a.strength === b.strength && textureUVEqual(a.mapUV, b.mapUV);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* The main map's layout, or undefined when it is the identity (channel 0,
|
|
396
|
-
* untiled, unmoved, still), which costs nothing in the shader.
|
|
397
|
-
*/
|
|
398
|
-
export function normalizeMapUV(uv: Partial<TextureUV> | null | undefined): TextureUV | undefined {
|
|
399
|
-
if (!uv || typeof uv !== 'object') {
|
|
400
|
-
return undefined;
|
|
401
|
-
}
|
|
402
|
-
const normalized = normalizeTextureUV(uv);
|
|
403
|
-
return textureUVToJSON(normalized) === undefined ? undefined : normalized;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/** True when both lay the map the same way (or both as drawn). */
|
|
407
|
-
export function mapUVEqual(a: TextureUV | undefined, b: TextureUV | undefined): boolean {
|
|
408
|
-
return a === b || (!!a && !!b && textureUVEqual(a, b));
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* True when a texture effect of `settings` samples UV channel 1, see
|
|
413
|
-
* {@link UVChannel}: the map, the distortion noise or an erosion mask laid
|
|
414
|
-
* on it.
|
|
415
|
-
*/
|
|
416
|
-
export function readsUV1(settings: {
|
|
417
|
-
mapUV?: TextureUV;
|
|
418
|
-
uvDistortion?: ParticleUVDistortion;
|
|
419
|
-
erosion?: ParticleErosion;
|
|
420
|
-
}): boolean {
|
|
421
|
-
const {mapUV, uvDistortion, erosion} = settings;
|
|
422
|
-
return (
|
|
423
|
-
mapUV?.channel === 1 || uvDistortion?.mapUV.channel === 1 || (!!erosion?.mask && erosion.maskUV.channel === 1)
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export interface StoredBatchSettings {
|
|
428
|
-
instancingGeometry: Float32Array;
|
|
429
|
-
instancingIndices: Uint32Array | Uint16Array;
|
|
430
|
-
instancingUVs?: Float32Array;
|
|
431
|
-
/** The mesh's second UV set, see {@link UVChannel}. */
|
|
432
|
-
instancingUVs2?: Float32Array;
|
|
433
|
-
instancingNormals?: Float32Array;
|
|
434
|
-
renderMode: RenderMode;
|
|
435
|
-
renderOrder: number;
|
|
436
|
-
uTileCount: number;
|
|
437
|
-
vTileCount: number;
|
|
438
|
-
blendTiles: boolean;
|
|
439
|
-
softParticles: boolean;
|
|
440
|
-
softNearFade: number;
|
|
441
|
-
softFarFade: number;
|
|
442
|
-
materialBlendMode: number;
|
|
443
|
-
materialTransparent: boolean;
|
|
444
|
-
materialDepthTest: boolean;
|
|
445
|
-
materialDepthWrite: boolean;
|
|
446
|
-
materialAlphaTest: number;
|
|
447
|
-
texture: any;
|
|
448
|
-
reflectionTexture: BaseTexture | null;
|
|
449
|
-
reflectionLevel: number;
|
|
450
|
-
reflectionFaces: BaseTexture[] | null;
|
|
451
|
-
reflectionAtlas: BaseTexture | null;
|
|
452
|
-
layerMask: number;
|
|
453
|
-
/** Babylon rendering group the batch mesh draws in, see {@link VFXBatchSettings.renderingGroupId}. */
|
|
454
|
-
renderingGroupId: number;
|
|
455
|
-
/** Whether the scene's fog is applied, see {@link VFXBatchSettings.applyFog}. */
|
|
456
|
-
applyFog: boolean;
|
|
457
|
-
/** Billboard pivot in particle-size units, see {@link VFXBatchSettings.pivot}. */
|
|
458
|
-
pivot?: Vector3;
|
|
459
|
-
/** Draw order within the batch, see {@link SortMode}. */
|
|
460
|
-
sortMode: SortMode;
|
|
461
|
-
/** Smallest size a particle may have on screen, see {@link VFXBatchSettings.minParticleSize}. */
|
|
462
|
-
minParticleSize: number;
|
|
463
|
-
/** Largest size a particle may have on screen, see {@link VFXBatchSettings.maxParticleSize}. */
|
|
464
|
-
maxParticleSize: number;
|
|
465
|
-
/** Mesh particle lighting, see {@link VFXBatchSettings.lighting}. */
|
|
466
|
-
lighting?: MeshLighting;
|
|
467
|
-
/** Mesh particle rim, see {@link VFXBatchSettings.rim}. */
|
|
468
|
-
rim?: MeshRim;
|
|
469
|
-
/** The main map's layout, see {@link VFXBatchSettings.mapUV}. */
|
|
470
|
-
mapUV?: TextureUV;
|
|
471
|
-
/** Map UV distortion, see {@link VFXBatchSettings.uvDistortion}. */
|
|
472
|
-
uvDistortion?: ParticleUVDistortion;
|
|
473
|
-
/** Colour ramp for the map, see {@link VFXBatchSettings.gradientMap}. */
|
|
474
|
-
gradientMap?: ParticleGradientMap;
|
|
475
|
-
/** Alpha erosion, see {@link VFXBatchSettings.erosion}. */
|
|
476
|
-
erosion?: ParticleErosion;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export abstract class VFXBatch {
|
|
480
|
-
mesh: Mesh;
|
|
29
|
+
mesh!: Mesh;
|
|
30
|
+
/** The node the batch mesh lives under, kept across the meshes the batch rebuilds. */
|
|
31
|
+
private parentNode: Node | null = null;
|
|
32
|
+
/** The systems drawn by this batch. */
|
|
481
33
|
systems: Set<IParticleSystem>;
|
|
34
|
+
/** The settings every system in the batch shares, with what the render mode ignores at its default. */
|
|
482
35
|
settings: StoredBatchSettings;
|
|
36
|
+
/** @internal */
|
|
483
37
|
protected maxParticles: number;
|
|
38
|
+
/** @internal */
|
|
484
39
|
protected scene: Scene;
|
|
40
|
+
/** @internal */
|
|
485
41
|
protected readonly visibleSystems: IParticleSystem[] = [];
|
|
42
|
+
/**
|
|
43
|
+
* The depth texture soft particles read, from {@link BatchedRenderer.setDepthTexture}.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
protected depth: DepthBinding | null;
|
|
486
47
|
|
|
487
|
-
|
|
48
|
+
/** @internal */
|
|
49
|
+
protected constructor(settings: VFXBatchSettings, scene: Scene, depth: DepthBinding | null = null) {
|
|
488
50
|
this.scene = scene;
|
|
489
|
-
this.
|
|
51
|
+
this.depth = depth;
|
|
52
|
+
this.maxParticles = 0;
|
|
490
53
|
this.systems = new Set<IParticleSystem>();
|
|
491
|
-
this.settings =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
softNearFade: settings.softNearFade,
|
|
504
|
-
softFarFade: settings.softFarFade,
|
|
505
|
-
materialBlendMode: settings.materialBlendMode,
|
|
506
|
-
materialTransparent: settings.materialTransparent,
|
|
507
|
-
materialDepthTest: settings.materialDepthTest,
|
|
508
|
-
materialDepthWrite: settings.materialDepthWrite,
|
|
509
|
-
materialAlphaTest: settings.materialAlphaTest,
|
|
510
|
-
texture: settings.texture,
|
|
511
|
-
reflectionTexture: settings.reflectionTexture ?? null,
|
|
512
|
-
reflectionLevel: settings.reflectionLevel ?? 1,
|
|
513
|
-
reflectionFaces: settings.reflectionFaces ?? null,
|
|
514
|
-
reflectionAtlas: settings.reflectionAtlas ?? null,
|
|
515
|
-
pivot: settings.pivot?.clone(),
|
|
516
|
-
sortMode: settings.sortMode ?? 'none',
|
|
517
|
-
minParticleSize: settings.minParticleSize ?? 0,
|
|
518
|
-
maxParticleSize: settings.maxParticleSize ?? Infinity,
|
|
519
|
-
layerMask: settings.layerMask,
|
|
520
|
-
renderingGroupId: settings.renderingGroupId ?? 0,
|
|
521
|
-
applyFog: settings.applyFog ?? true,
|
|
522
|
-
lighting: settings.lighting,
|
|
523
|
-
rim: normalizeMeshRim(settings.rim),
|
|
524
|
-
mapUV: normalizeMapUV(settings.mapUV),
|
|
525
|
-
uvDistortion: normalizeUVDistortion(settings.uvDistortion),
|
|
526
|
-
gradientMap: normalizeGradientMap(settings.gradientMap),
|
|
527
|
-
erosion: normalizeErosion(settings.erosion),
|
|
528
|
-
};
|
|
529
|
-
this.mesh = new Mesh('vfxBatch', scene);
|
|
530
|
-
this.configureMesh();
|
|
54
|
+
this.settings = storedBatchSettings(settings);
|
|
55
|
+
// The subclass builds the mesh, in setupBuffers.
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Puts the batch mesh under `node` (the renderer), for the scene graph and the node's enabled
|
|
60
|
+
* state; the node's transform does not reach the particles.
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
attachTo(node: Node | null): void {
|
|
64
|
+
this.parentNode = node;
|
|
65
|
+
this.mesh.parent = node;
|
|
531
66
|
}
|
|
532
67
|
|
|
533
68
|
/**
|
|
@@ -547,12 +82,25 @@ export abstract class VFXBatch {
|
|
|
547
82
|
// world space, so the bounds never need the mesh's own matrix.
|
|
548
83
|
mesh.alwaysSelectAsActiveMesh = true;
|
|
549
84
|
mesh.doNotSyncBoundingInfo = true;
|
|
85
|
+
mesh.parent = this.parentNode;
|
|
86
|
+
// World-space instance data: the parent's transform must not apply, nor the bounds move
|
|
87
|
+
// with it. Frozen at identity, and kept there: a parent's markAsDirty would otherwise make
|
|
88
|
+
// the frozen mesh recompute from its parents once.
|
|
89
|
+
mesh.freezeWorldMatrix(Matrix.Identity());
|
|
90
|
+
const world = mesh.getWorldMatrix();
|
|
91
|
+
mesh.computeWorldMatrix = () => world;
|
|
550
92
|
mesh.setBoundingInfo(new BoundingInfo(BVector3.Zero(), BVector3.Zero()));
|
|
551
93
|
mesh.layerMask = this.settings.layerMask;
|
|
552
94
|
mesh.renderingGroupId = this.settings.renderingGroupId;
|
|
553
95
|
mesh.alphaIndex = this.settings.renderOrder;
|
|
554
96
|
// The shaders fog themselves; Babylon's own fog pass does not apply to a ShaderMaterial.
|
|
555
97
|
mesh.applyFog = false;
|
|
98
|
+
// Its geometry is one quad or mesh at the origin, not the particles, so a pick or a
|
|
99
|
+
// collision against it would hit nothing that is drawn. The renderer rebuilds batches
|
|
100
|
+
// from its systems, so a scene serializer has nothing to write either.
|
|
101
|
+
mesh.isPickable = false;
|
|
102
|
+
mesh.checkCollisions = false;
|
|
103
|
+
mesh.doNotSerialize = true;
|
|
556
104
|
}
|
|
557
105
|
|
|
558
106
|
private readonly boundsMin_ = new Vector3();
|
|
@@ -615,23 +163,26 @@ export abstract class VFXBatch {
|
|
|
615
163
|
}
|
|
616
164
|
|
|
617
165
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
166
|
+
* Writes a uniform that changes per bind, such as one read off the active camera, to the
|
|
167
|
+
* material for the next bind and to its bound effect for this one: ShaderMaterial uploads its
|
|
168
|
+
* stored values before the bind observers run, so a value written only to the material would
|
|
169
|
+
* reach the GPU one draw late, and with two cameras it would always be the other camera's.
|
|
621
170
|
*/
|
|
622
|
-
protected static
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
171
|
+
protected static writeBound(material: ShaderMaterial, write: (target: ShaderMaterial | Effect) => void): void {
|
|
172
|
+
write(material);
|
|
173
|
+
// An effect still compiling has no pipeline context to write to; it gets the stored value
|
|
174
|
+
// when it is first bound. A sub mesh's effect override is not reached here.
|
|
175
|
+
const effect = material.getEffect();
|
|
176
|
+
if (effect?.isReady()) {
|
|
177
|
+
write(effect);
|
|
627
178
|
}
|
|
628
|
-
const additive =
|
|
629
|
-
this.settings.materialTransparent &&
|
|
630
|
-
(this.settings.materialBlendMode === Constants.ALPHA_ADD ||
|
|
631
|
-
this.settings.materialBlendMode === Constants.ALPHA_ONEONE);
|
|
632
|
-
return additive ? ['FOG', 'FOG_ADDITIVE'] : ['FOG'];
|
|
633
179
|
}
|
|
634
180
|
|
|
181
|
+
/**
|
|
182
|
+
* The per-bind upload of the scene's current fog. Mode 0 (none) when the scene has fog
|
|
183
|
+
* disabled, so toggling fog at runtime needs no recompile.
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
635
186
|
protected bindFog(material: ShaderMaterial): void {
|
|
636
187
|
if (!this.settings.applyFog) {
|
|
637
188
|
return;
|
|
@@ -659,85 +210,19 @@ export abstract class VFXBatch {
|
|
|
659
210
|
return readsUV1(this.settings);
|
|
660
211
|
}
|
|
661
212
|
|
|
662
|
-
/** Defines of the texture effects (see `shaders/chunks/surface.ts`) this batch uses. */
|
|
663
|
-
protected surfaceDefines(): string[] {
|
|
664
|
-
const defines: string[] = [];
|
|
665
|
-
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
666
|
-
if (this.usesUV1()) {
|
|
667
|
-
defines.push('UV1');
|
|
668
|
-
}
|
|
669
|
-
if (mapUV) {
|
|
670
|
-
defines.push('MAP_TRANSFORM');
|
|
671
|
-
if (mapUV.channel === 1) {
|
|
672
|
-
defines.push('MAP_UV1');
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
if (uvDistortion) {
|
|
676
|
-
defines.push('UV_DISTORTION');
|
|
677
|
-
if (uvDistortion.mapUV.channel === 1) {
|
|
678
|
-
defines.push('UV_DISTORTION_UV1');
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
if (gradientMap) {
|
|
682
|
-
defines.push('GRADIENT_MAP');
|
|
683
|
-
}
|
|
684
|
-
if (erosion) {
|
|
685
|
-
defines.push('EROSION');
|
|
686
|
-
if (erosion.mask) {
|
|
687
|
-
defines.push('EROSION_MASK');
|
|
688
|
-
if (erosion.maskUV.channel === 1) {
|
|
689
|
-
defines.push('EROSION_MASK_UV1');
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return defines;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
/** Adds the uniforms and samplers of the texture effects this batch uses. */
|
|
697
|
-
protected surfaceUniforms(uniforms: string[], samplers: string[]): void {
|
|
698
|
-
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
699
|
-
if (mapUV) {
|
|
700
|
-
uniforms.push('mapTransform');
|
|
701
|
-
}
|
|
702
|
-
if (uvDistortion) {
|
|
703
|
-
samplers.push('distortionMap');
|
|
704
|
-
uniforms.push('distortionTransform', 'distortionStrength');
|
|
705
|
-
}
|
|
706
|
-
if (gradientMap) {
|
|
707
|
-
samplers.push('gradientMap');
|
|
708
|
-
uniforms.push('gradientMapRange', 'gradientMapChannel');
|
|
709
|
-
}
|
|
710
|
-
if (erosion) {
|
|
711
|
-
uniforms.push('erosionParams', 'erosionEdgeColor');
|
|
712
|
-
if (erosion.mask) {
|
|
713
|
-
samplers.push('erosionMask');
|
|
714
|
-
uniforms.push('erosionMaskTransform');
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
213
|
/** Sets the texture effects' textures and constants, and advances their scrolling on every bind. */
|
|
720
214
|
protected bindSurface(material: ShaderMaterial): void {
|
|
721
215
|
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
722
216
|
if (uvDistortion?.map) {
|
|
723
217
|
material.setTexture('distortionMap', uvDistortion.map);
|
|
724
|
-
material.setFloat('distortionStrength', uvDistortion.strength);
|
|
725
218
|
}
|
|
726
219
|
if (gradientMap?.ramp) {
|
|
727
220
|
material.setTexture('gradientMap', gradientMap.ramp);
|
|
728
|
-
const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
|
|
729
|
-
material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
|
|
730
221
|
}
|
|
731
|
-
if (erosion) {
|
|
732
|
-
|
|
733
|
-
material.setTexture('erosionMask', erosion.mask);
|
|
734
|
-
}
|
|
735
|
-
material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
|
|
736
|
-
material.setVector3(
|
|
737
|
-
'erosionEdgeColor',
|
|
738
|
-
new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
|
|
739
|
-
);
|
|
222
|
+
if (erosion?.mask) {
|
|
223
|
+
material.setTexture('erosionMask', erosion.mask);
|
|
740
224
|
}
|
|
225
|
+
this.writeSurfaceParams(material);
|
|
741
226
|
if (!mapUV && !uvDistortion && !erosion?.mask && !gradientMap) {
|
|
742
227
|
return;
|
|
743
228
|
}
|
|
@@ -760,9 +245,12 @@ export abstract class VFXBatch {
|
|
|
760
245
|
);
|
|
761
246
|
// Written to the material for the next bind and to the bound effect for
|
|
762
247
|
// this one: ShaderMaterial uploads its values before the bind observers run.
|
|
248
|
+
// The layouts are read off the settings on every bind, so a live change
|
|
249
|
+
// to a tiling or scroll reaches the next draw.
|
|
763
250
|
const write = () => {
|
|
764
251
|
const time = this.time;
|
|
765
252
|
const effect = material.getEffect();
|
|
253
|
+
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
766
254
|
if (mapUV) {
|
|
767
255
|
transform(mapUV, time, mapTransform);
|
|
768
256
|
material.setVector4('mapTransform', mapTransform);
|
|
@@ -792,11 +280,56 @@ export abstract class VFXBatch {
|
|
|
792
280
|
material.onBindObservable.add(write);
|
|
793
281
|
}
|
|
794
282
|
|
|
795
|
-
|
|
283
|
+
/** Writes the texture effects' constants (the uniforms their live parameters set). */
|
|
284
|
+
private writeSurfaceParams(material: ShaderMaterial): void {
|
|
285
|
+
const {uvDistortion, gradientMap, erosion} = this.settings;
|
|
286
|
+
if (uvDistortion?.map) {
|
|
287
|
+
material.setFloat('distortionStrength', uvDistortion.strength);
|
|
288
|
+
}
|
|
289
|
+
if (gradientMap?.ramp) {
|
|
290
|
+
const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
|
|
291
|
+
material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
|
|
292
|
+
}
|
|
293
|
+
if (erosion) {
|
|
294
|
+
material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
|
|
295
|
+
material.setVector3(
|
|
296
|
+
'erosionEdgeColor',
|
|
297
|
+
new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Takes new live parameters (see {@link BatchedRenderer.updateSystem}): settings with the same
|
|
304
|
+
* shader key as the batch's, whose uniform values differ. They are copied into the batch's
|
|
305
|
+
* settings and written to its material, which is kept.
|
|
306
|
+
* @internal
|
|
307
|
+
*/
|
|
308
|
+
applyLiveSettings(settings: StoredBatchSettings): void {
|
|
309
|
+
Object.assign(this.settings, settings);
|
|
310
|
+
const material = this.mesh.material;
|
|
311
|
+
if (material instanceof ShaderMaterial) {
|
|
312
|
+
this.writeLiveUniforms(material);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Writes every uniform a live parameter sets, from the batch's settings. The base writes the
|
|
318
|
+
* ones every render mode has; a mode adds its own.
|
|
319
|
+
* @internal
|
|
320
|
+
*/
|
|
321
|
+
protected writeLiveUniforms(material: ShaderMaterial): void {
|
|
322
|
+
this.writeSoftParams(material);
|
|
323
|
+
this.writeSurfaceParams(material);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** @internal */
|
|
327
|
+
addSystem(system: IParticleSystem): void {
|
|
796
328
|
this.systems.add(system);
|
|
797
329
|
}
|
|
798
330
|
|
|
799
|
-
|
|
331
|
+
/** @internal */
|
|
332
|
+
removeSystem(system: IParticleSystem): void {
|
|
800
333
|
this.systems.delete(system);
|
|
801
334
|
}
|
|
802
335
|
|
|
@@ -819,21 +352,130 @@ export abstract class VFXBatch {
|
|
|
819
352
|
return visibleSystems;
|
|
820
353
|
}
|
|
821
354
|
|
|
822
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Sets the depth texture soft particles read. Soft particles are compiled in only while there
|
|
357
|
+
* is one, as Unity turns them off for a camera without a depth texture, so attaching or
|
|
358
|
+
* removing it, or changing how it is packed, rebuilds a soft batch's material.
|
|
359
|
+
*
|
|
360
|
+
* @param depth - The binding, or a bare texture, read as `'linear'` and unpacked.
|
|
361
|
+
* @internal Use {@link BatchedRenderer.setDepthTexture}.
|
|
362
|
+
*/
|
|
363
|
+
applyDepthTexture(depth: DepthBinding | BaseTexture | null): void {
|
|
364
|
+
if (depth instanceof BaseTexture) {
|
|
365
|
+
depth = {texture: depth, encoding: 'linear', packed: false};
|
|
366
|
+
}
|
|
367
|
+
const previous = this.depth;
|
|
368
|
+
this.depth = depth;
|
|
369
|
+
if (!this.settings.softParticles) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (!previous !== !depth || previous?.packed !== depth?.packed) {
|
|
373
|
+
this.rebuildMaterial();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
823
376
|
const material = this.mesh.material;
|
|
824
|
-
if (
|
|
825
|
-
material.setTexture('depthTexture',
|
|
377
|
+
if (depth && material instanceof ShaderMaterial) {
|
|
378
|
+
material.setTexture('depthTexture', depth.texture);
|
|
826
379
|
}
|
|
827
380
|
}
|
|
828
381
|
|
|
829
|
-
/**
|
|
382
|
+
/**
|
|
383
|
+
* Sets the soft-particle and alpha-test values on a material, and decodes the depth for the
|
|
384
|
+
* camera of each draw.
|
|
385
|
+
*/
|
|
386
|
+
protected bindSoft(material: ShaderMaterial): void {
|
|
387
|
+
this.writeSoftParams(material);
|
|
388
|
+
if (this.drawsSoft()) {
|
|
389
|
+
material.setTexture('depthTexture', this.depth!.texture);
|
|
390
|
+
// Reused across binds: this runs on every draw call.
|
|
391
|
+
const projParams = new BVector4(1, 0, 0, 1);
|
|
392
|
+
material.onBindObservable.add(() => {
|
|
393
|
+
const camera = this.scene.activeCamera;
|
|
394
|
+
if (camera && this.depth) {
|
|
395
|
+
softDepthParams(camera, this.depth.encoding, projParams);
|
|
396
|
+
VFXBatch.writeBound(material, (target) => target.setVector4('projParams', projParams));
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Writes the soft-particle fade and the alpha cutoff, the live parameters every mode shares. */
|
|
403
|
+
private writeSoftParams(material: ShaderMaterial): void {
|
|
404
|
+
if (this.drawsSoft()) {
|
|
405
|
+
material.setVector2(
|
|
406
|
+
'softParams',
|
|
407
|
+
new BVector2(
|
|
408
|
+
this.settings.softNearFade,
|
|
409
|
+
1.0 / Math.max(this.settings.softFarFade - this.settings.softNearFade, 0.0001)
|
|
410
|
+
)
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
if (this.settings.materialAlphaTest > 0) {
|
|
414
|
+
material.setFloat('alphaTest', this.settings.materialAlphaTest);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** True when this batch draws soft particles: they are on and a depth texture is attached. */
|
|
419
|
+
protected drawsSoft(): boolean {
|
|
420
|
+
return this.settings.softParticles && this.depth !== null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Sets the light `'fixed'` mesh particles are lit by. Batches that draw no meshes ignore it.
|
|
425
|
+
* @internal Use {@link BatchedRenderer.setMeshLighting}.
|
|
426
|
+
*/
|
|
830
427
|
applyMeshLighting(_lighting: MeshLightingSettings): void {}
|
|
831
428
|
|
|
429
|
+
/** @internal */
|
|
832
430
|
abstract setupBuffers(): void;
|
|
431
|
+
/** @internal */
|
|
833
432
|
abstract expandBuffers(target: number): void;
|
|
433
|
+
|
|
434
|
+
/** Rebuilds the instance buffers at the current {@link maxParticles}, after a shrink. @internal */
|
|
435
|
+
protected abstract resizeBuffers(): void;
|
|
436
|
+
|
|
437
|
+
/** The capacity a batch starts with and never shrinks below. @internal */
|
|
438
|
+
protected abstract readonly initialCapacity: number;
|
|
439
|
+
|
|
440
|
+
/** Fills in a row that used under a quarter of the buffers. */
|
|
441
|
+
private underusedFills = 0;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* How many fills in a row a batch may use under a quarter of its buffers before it halves
|
|
445
|
+
* them: about five seconds at 60 frames per second. A burst that grew the buffers does not
|
|
446
|
+
* hold that memory for the rest of the session, and a batch whose count swings does not
|
|
447
|
+
* reallocate every few frames.
|
|
448
|
+
*/
|
|
449
|
+
static readonly SHRINK_AFTER_FILLS = 300;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Grows the buffers to hold `count`, or halves them once the batch has used under a quarter of
|
|
453
|
+
* them for {@link SHRINK_AFTER_FILLS} fills, never below {@link initialCapacity}. Called by each
|
|
454
|
+
* fill with what it is about to write. @internal
|
|
455
|
+
*/
|
|
456
|
+
protected fitBuffers(count: number): void {
|
|
457
|
+
if (count > this.maxParticles) {
|
|
458
|
+
this.underusedFills = 0;
|
|
459
|
+
this.expandBuffers(count);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (this.maxParticles > this.initialCapacity && count < this.maxParticles / 4) {
|
|
463
|
+
if (++this.underusedFills >= VFXBatch.SHRINK_AFTER_FILLS) {
|
|
464
|
+
this.underusedFills = 0;
|
|
465
|
+
this.maxParticles = Math.max(this.initialCapacity, this.maxParticles / 2);
|
|
466
|
+
this.resizeBuffers();
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
this.underusedFills = 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** @internal */
|
|
834
474
|
abstract rebuildMaterial(): void;
|
|
475
|
+
/** @internal */
|
|
835
476
|
abstract update(): void;
|
|
836
477
|
|
|
478
|
+
/** @internal */
|
|
837
479
|
dispose(): void {
|
|
838
480
|
this.mesh.dispose();
|
|
839
481
|
}
|