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/SpriteBatch.ts
CHANGED
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
import {VertexBuffer} from '@babylonjs/core/Buffers/buffer';
|
|
2
2
|
import {Constants} from '@babylonjs/core/Engines/constants';
|
|
3
|
+
import type {Effect} from '@babylonjs/core/Materials/effect';
|
|
3
4
|
import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
|
|
4
5
|
import {RawTexture} from '@babylonjs/core/Materials/Textures/rawTexture';
|
|
5
|
-
import {Vector2 as BVector2, Vector3 as BVector3
|
|
6
|
+
import {Vector2 as BVector2, Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
|
|
6
7
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
7
8
|
import {VertexData} from '@babylonjs/core/Meshes/mesh.vertexData';
|
|
8
9
|
import {Scene} from '@babylonjs/core/scene';
|
|
9
10
|
import {
|
|
10
11
|
IParticle,
|
|
11
|
-
IParticleSystem,
|
|
12
12
|
Matrix3,
|
|
13
13
|
ParticleStore,
|
|
14
14
|
Quaternion,
|
|
15
15
|
SpriteParticle,
|
|
16
16
|
StretchedBillBoardSettings,
|
|
17
17
|
Vector3,
|
|
18
|
+
Vector4,
|
|
18
19
|
} from 'quarks.core';
|
|
19
20
|
import {VFXBatchSettings} from './BatchedRenderer';
|
|
21
|
+
import {hasSizeLimits, takesPivotAndSizeLimits, tilesSheet} from './batchKey';
|
|
20
22
|
import {gather, sortByKey, SortScratch} from './instanceSort';
|
|
21
23
|
import type {ParticleSystem} from './ParticleSystem';
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import particle_frag_wgsl from './shaders/particle_frag.wgsl';
|
|
26
|
-
import particle_physics_frag from './shaders/particle_physics_frag.glsl';
|
|
27
|
-
import particle_physics_frag_wgsl from './shaders/particle_physics_frag.wgsl';
|
|
28
|
-
import particle_vert from './shaders/particle_vert.glsl';
|
|
29
|
-
import particle_vert_wgsl from './shaders/particle_vert.wgsl';
|
|
30
|
-
import {registerShaders, shaderLanguageFor, ShaderSources} from './shaders/shaderLanguageSupport';
|
|
31
|
-
import stretched_bb_particle_vert from './shaders/stretched_bb_particle_vert.glsl';
|
|
32
|
-
import stretched_bb_particle_vert_wgsl from './shaders/stretched_bb_particle_vert.wgsl';
|
|
24
|
+
import {drawsAlongVelocity, shaderProgram} from './shaderProgram';
|
|
25
|
+
import {registerShaders, shaderLanguageFor} from './shaders/shaderLanguageSupport';
|
|
26
|
+
import type {DepthBinding} from './softDepth';
|
|
33
27
|
import {defaultMeshLighting, MeshLightingSettings, RenderMode, VFXBatch} from './VFXBatch';
|
|
34
28
|
|
|
29
|
+
/** The batch of billboard, stretched, mesh and beam particles. @internal */
|
|
35
30
|
export class SpriteBatch extends VFXBatch {
|
|
36
31
|
private static whiteTextureByScene = new WeakMap<Scene, RawTexture>();
|
|
37
32
|
|
|
@@ -52,8 +47,11 @@ export class SpriteBatch extends VFXBatch {
|
|
|
52
47
|
private uvTileBuffer!: Float32Array;
|
|
53
48
|
private velocityBuffer?: Float32Array;
|
|
54
49
|
|
|
55
|
-
/** Concretely typed handle on the batch material,
|
|
50
|
+
/** Concretely typed handle on the batch material, for the lighting and live-parameter writes. */
|
|
56
51
|
private shaderMaterial: ShaderMaterial | null = null;
|
|
52
|
+
/** Cancels the pending rebuild for an env atlas that has not loaded yet. */
|
|
53
|
+
private cancelAtlasRebuild: (() => void) | null = null;
|
|
54
|
+
private disposed = false;
|
|
57
55
|
/** The `'fixed'` mesh light, as shader uniforms; see applyMeshLighting. */
|
|
58
56
|
private readonly fixedLightDirection = new BVector3();
|
|
59
57
|
private readonly fixedLightColor = new BVector3();
|
|
@@ -66,14 +64,15 @@ export class SpriteBatch extends VFXBatch {
|
|
|
66
64
|
private uvTileVB!: VertexBuffer;
|
|
67
65
|
private velocityVB?: VertexBuffer;
|
|
68
66
|
|
|
69
|
-
constructor(settings: VFXBatchSettings, scene: Scene) {
|
|
70
|
-
super(settings, scene);
|
|
67
|
+
constructor(settings: VFXBatchSettings, scene: Scene, depth: DepthBinding | null = null) {
|
|
68
|
+
super(settings, scene, depth);
|
|
71
69
|
this.storeFixedLighting(defaultMeshLighting());
|
|
72
70
|
this.maxParticles = 1000;
|
|
73
71
|
this.setupBuffers();
|
|
74
72
|
this.rebuildMaterial();
|
|
75
73
|
}
|
|
76
74
|
|
|
75
|
+
/** @internal */
|
|
77
76
|
setupBuffers(): void {
|
|
78
77
|
this.mesh.dispose();
|
|
79
78
|
this.mesh = new Mesh('spriteBatch', this.scene);
|
|
@@ -91,6 +90,9 @@ export class SpriteBatch extends VFXBatch {
|
|
|
91
90
|
} else {
|
|
92
91
|
vertexData.uvs = new Float32Array(vertexCount * 2);
|
|
93
92
|
}
|
|
93
|
+
if (this.meshUV2()) {
|
|
94
|
+
vertexData.uvs2 = this.settings.instancingUVs2!;
|
|
95
|
+
}
|
|
94
96
|
if (this.settings.instancingNormals) {
|
|
95
97
|
vertexData.normals = this.settings.instancingNormals;
|
|
96
98
|
}
|
|
@@ -99,6 +101,33 @@ export class SpriteBatch extends VFXBatch {
|
|
|
99
101
|
this.buildExpandableBuffers();
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
/**
|
|
105
|
+
* True when a mesh batch reads UV channel 1 and has a second UV set to
|
|
106
|
+
* read it from, two values per vertex. A set of any other length is left
|
|
107
|
+
* out with a warning, and channel 1 then reads the first set.
|
|
108
|
+
*/
|
|
109
|
+
private warnedUV2 = false;
|
|
110
|
+
|
|
111
|
+
private meshUV2(): boolean {
|
|
112
|
+
const uvs2 = this.settings.instancingUVs2;
|
|
113
|
+
if (this.settings.renderMode !== RenderMode.Mesh || !uvs2 || !this.usesUV1()) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const expected = (this.settings.instancingGeometry.length / 3) * 2;
|
|
117
|
+
if (uvs2.length !== expected) {
|
|
118
|
+
if (this.warnedUV2) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
this.warnedUV2 = true;
|
|
122
|
+
console.warn(
|
|
123
|
+
`[babylon.quarks] instancingUVs2 has ${uvs2.length} values for ${expected / 2} vertices ` +
|
|
124
|
+
'(two per vertex expected); UV channel 1 reads the first UV set instead.'
|
|
125
|
+
);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
102
131
|
/** Releases the instance vertex buffers so a resize does not leak GPU memory. */
|
|
103
132
|
private disposeExpandableBuffers(): void {
|
|
104
133
|
this.offsetVB?.dispose();
|
|
@@ -139,7 +168,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
139
168
|
}
|
|
140
169
|
this.mesh.setVerticesBuffer(this.rotationVB);
|
|
141
170
|
|
|
142
|
-
if (
|
|
171
|
+
if (drawsAlongVelocity(this.settings.renderMode)) {
|
|
143
172
|
this.velocityBuffer = new Float32Array(this.maxParticles * 4);
|
|
144
173
|
this.velocityVB = new VertexBuffer(engine, this.velocityBuffer, 'velocity', true, false, 4, true);
|
|
145
174
|
this.mesh.setVerticesBuffer(this.velocityVB);
|
|
@@ -149,6 +178,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
149
178
|
this.mesh.doNotSyncBoundingInfo = true;
|
|
150
179
|
}
|
|
151
180
|
|
|
181
|
+
/** @internal */
|
|
152
182
|
expandBuffers(target: number): void {
|
|
153
183
|
while (target >= this.maxParticles) {
|
|
154
184
|
this.maxParticles *= 2;
|
|
@@ -162,10 +192,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
162
192
|
/** The screen-size limits as a uniform value, or null when the batch has none that apply. */
|
|
163
193
|
private sizeLimits(): BVector2 | null {
|
|
164
194
|
const {minParticleSize, maxParticleSize} = this.settings;
|
|
165
|
-
if (!
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
if (minParticleSize <= 0 && maxParticleSize === Infinity) {
|
|
195
|
+
if (!takesPivotAndSizeLimits(this.settings.renderMode) || !hasSizeLimits(this.settings)) {
|
|
169
196
|
return null;
|
|
170
197
|
}
|
|
171
198
|
// The shader clamps; an unlimited maximum is just a very large one.
|
|
@@ -264,147 +291,47 @@ export class SpriteBatch extends VFXBatch {
|
|
|
264
291
|
return spare;
|
|
265
292
|
}
|
|
266
293
|
|
|
294
|
+
/** @internal */
|
|
267
295
|
rebuildMaterial(): void {
|
|
268
|
-
|
|
269
|
-
// registered once per mode. A unique name per rebuild would leak entries in
|
|
270
|
-
// Effect.ShadersStore and defeat Babylon's compiled-effect cache.
|
|
271
|
-
const shaderName = `quarksParticle_${this.settings.renderMode}`;
|
|
272
|
-
this.lastStretchedSpeedFactor = Number.NaN;
|
|
273
|
-
const defines: string[] = this.fogDefines();
|
|
274
|
-
|
|
275
|
-
let vertexShader: ShaderSources;
|
|
276
|
-
let fragmentShader: ShaderSources;
|
|
277
|
-
if (this.settings.renderMode === RenderMode.Mesh) {
|
|
278
|
-
vertexShader = {glsl: local_particle_physics_vert, wgsl: local_particle_physics_vert_wgsl};
|
|
279
|
-
fragmentShader = {glsl: particle_physics_frag, wgsl: particle_physics_frag_wgsl};
|
|
280
|
-
} else if (SpriteBatch.drawsAlongVelocity(this.settings.renderMode)) {
|
|
281
|
-
vertexShader = {glsl: stretched_bb_particle_vert, wgsl: stretched_bb_particle_vert_wgsl};
|
|
282
|
-
fragmentShader = {glsl: particle_frag, wgsl: particle_frag_wgsl};
|
|
283
|
-
} else {
|
|
284
|
-
vertexShader = {glsl: particle_vert, wgsl: particle_vert_wgsl};
|
|
285
|
-
fragmentShader = {glsl: particle_frag, wgsl: particle_frag_wgsl};
|
|
286
|
-
}
|
|
287
|
-
|
|
296
|
+
const isMesh = this.settings.renderMode === RenderMode.Mesh;
|
|
288
297
|
// Mesh batches without a diffuse map still need a sampler2D on iOS WebKit —
|
|
289
298
|
// a zero-sampler particle mesh effect often never becomes drawable there.
|
|
290
|
-
const mapTexture =
|
|
291
|
-
this.settings.texture ??
|
|
292
|
-
(this.settings.renderMode === RenderMode.Mesh ? SpriteBatch.whiteTexture(this.scene) : null);
|
|
293
|
-
if (mapTexture) {
|
|
294
|
-
defines.push('USE_MAP');
|
|
295
|
-
}
|
|
299
|
+
const mapTexture = this.settings.texture ?? (isMesh ? SpriteBatch.whiteTexture(this.scene) : null);
|
|
296
300
|
const atlas = this.settings.reflectionAtlas;
|
|
297
|
-
const atlasPending =
|
|
301
|
+
const atlasPending = isMesh && !!atlas && !atlas.isReady();
|
|
302
|
+
this.cancelAtlasRebuild?.();
|
|
303
|
+
this.cancelAtlasRebuild = null;
|
|
298
304
|
if (atlasPending && atlas) {
|
|
299
|
-
const onLoad = (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
if (this.settings.softParticles) {
|
|
315
|
-
defines.push('SOFT_PARTICLES');
|
|
316
|
-
}
|
|
317
|
-
if (this.settings.materialAlphaTest > 0) {
|
|
318
|
-
defines.push('USE_ALPHATEST');
|
|
319
|
-
}
|
|
320
|
-
if (this.settings.renderMode === RenderMode.VerticalBillBoard) {
|
|
321
|
-
defines.push('VERTICAL');
|
|
322
|
-
}
|
|
323
|
-
if (this.settings.renderMode === RenderMode.HorizontalBillBoard) {
|
|
324
|
-
defines.push('HORIZONTAL');
|
|
325
|
-
}
|
|
326
|
-
if (this.settings.renderMode === RenderMode.Beam) {
|
|
327
|
-
defines.push('BEAM');
|
|
328
|
-
}
|
|
329
|
-
const pivot = SpriteBatch.takesPivotAndFlip(this.settings.renderMode) ? this.settings.pivot : undefined;
|
|
330
|
-
if (pivot) {
|
|
331
|
-
defines.push('PIVOT');
|
|
332
|
-
}
|
|
333
|
-
const sizeLimits = this.sizeLimits();
|
|
334
|
-
if (sizeLimits) {
|
|
335
|
-
defines.push('SIZE_LIMITS');
|
|
305
|
+
const onLoad = (
|
|
306
|
+
atlas as {
|
|
307
|
+
onLoadObservable?: {addOnce: (cb: () => void) => unknown; remove: (observer: unknown) => boolean};
|
|
308
|
+
}
|
|
309
|
+
).onLoadObservable;
|
|
310
|
+
if (onLoad) {
|
|
311
|
+
const observer = onLoad.addOnce(() => {
|
|
312
|
+
this.cancelAtlasRebuild = null;
|
|
313
|
+
if (!this.disposed) {
|
|
314
|
+
this.rebuildMaterial();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
this.cancelAtlasRebuild = () => onLoad.remove(observer);
|
|
318
|
+
}
|
|
336
319
|
}
|
|
337
|
-
const
|
|
320
|
+
const useEnvAtlas = this.usesEnvAtlas();
|
|
321
|
+
const program = shaderProgram(this.settings, {
|
|
322
|
+
depth: this.depth,
|
|
323
|
+
envAtlasReady: useEnvAtlas,
|
|
324
|
+
meshUV2: this.meshUV2(),
|
|
325
|
+
});
|
|
326
|
+
const {defines, attributes, uniforms, samplers} = program;
|
|
327
|
+
const shaderName = program.name;
|
|
338
328
|
const lighting = this.settings.lighting ?? 'fixed';
|
|
339
329
|
const lit = isMesh && lighting !== 'unlit';
|
|
340
|
-
if (isMesh && !lit) {
|
|
341
|
-
defines.push('MESH_UNLIT');
|
|
342
|
-
}
|
|
343
330
|
const rim = isMesh ? this.settings.rim : undefined;
|
|
344
|
-
if (rim) {
|
|
345
|
-
defines.push('MESH_RIM');
|
|
346
|
-
if (rim.mode === 'mix') {
|
|
347
|
-
defines.push('MESH_RIM_MIX');
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
331
|
const needsEyePosition = useEnvAtlas || !!rim;
|
|
351
|
-
defines.push(...this.surfaceDefines());
|
|
352
332
|
|
|
353
333
|
const shaderLanguage = shaderLanguageFor(this.scene.getEngine());
|
|
354
|
-
registerShaders(shaderName,
|
|
355
|
-
|
|
356
|
-
const attributes = ['position', 'uv', 'offset', 'color', 'size', 'rotation', 'uvTile'];
|
|
357
|
-
if (this.settings.renderMode === RenderMode.Mesh) {
|
|
358
|
-
attributes.push('normal');
|
|
359
|
-
}
|
|
360
|
-
if (SpriteBatch.drawsAlongVelocity(this.settings.renderMode)) {
|
|
361
|
-
attributes.push('velocity');
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const uniforms = ['world', 'view', 'projection', 'worldView', 'worldViewProjection', ...VFXBatch.FOG_UNIFORMS];
|
|
365
|
-
const samplers: string[] = [];
|
|
366
|
-
|
|
367
|
-
if (this.settings.uTileCount > 1 || this.settings.vTileCount > 1) {
|
|
368
|
-
uniforms.push('tileCountX');
|
|
369
|
-
uniforms.push('tileCountY');
|
|
370
|
-
}
|
|
371
|
-
if (mapTexture) {
|
|
372
|
-
samplers.push('map');
|
|
373
|
-
}
|
|
374
|
-
if (useEnvAtlas) {
|
|
375
|
-
samplers.push('envAtlas');
|
|
376
|
-
uniforms.push('reflectionLevel');
|
|
377
|
-
}
|
|
378
|
-
if (needsEyePosition) {
|
|
379
|
-
uniforms.push('eyePosition');
|
|
380
|
-
}
|
|
381
|
-
if (rim) {
|
|
382
|
-
uniforms.push('rimColor');
|
|
383
|
-
uniforms.push('rimParams');
|
|
384
|
-
}
|
|
385
|
-
this.surfaceUniforms(uniforms, samplers);
|
|
386
|
-
if (SpriteBatch.drawsAlongVelocity(this.settings.renderMode)) {
|
|
387
|
-
uniforms.push('speedFactor');
|
|
388
|
-
}
|
|
389
|
-
if (this.settings.softParticles) {
|
|
390
|
-
uniforms.push('softParams');
|
|
391
|
-
uniforms.push('projParams');
|
|
392
|
-
samplers.push('depthTexture');
|
|
393
|
-
}
|
|
394
|
-
if (this.settings.materialAlphaTest > 0) {
|
|
395
|
-
uniforms.push('alphaTest');
|
|
396
|
-
}
|
|
397
|
-
if (pivot) {
|
|
398
|
-
uniforms.push('pivot');
|
|
399
|
-
}
|
|
400
|
-
if (sizeLimits) {
|
|
401
|
-
uniforms.push('sizeLimits');
|
|
402
|
-
}
|
|
403
|
-
if (lit) {
|
|
404
|
-
uniforms.push('lightDirection');
|
|
405
|
-
uniforms.push('lightColor');
|
|
406
|
-
uniforms.push('ambientColor');
|
|
407
|
-
}
|
|
334
|
+
registerShaders(shaderName, program.vertex, program.fragment, shaderLanguage);
|
|
408
335
|
|
|
409
336
|
const mat = new ShaderMaterial(
|
|
410
337
|
shaderName,
|
|
@@ -425,11 +352,6 @@ export class SpriteBatch extends VFXBatch {
|
|
|
425
352
|
}
|
|
426
353
|
if (useEnvAtlas && atlas) {
|
|
427
354
|
mat.setTexture('envAtlas', atlas);
|
|
428
|
-
mat.setFloat('reflectionLevel', this.settings.reflectionLevel);
|
|
429
|
-
}
|
|
430
|
-
if (rim) {
|
|
431
|
-
mat.setVector3('rimColor', new BVector3(rim.color[0], rim.color[1], rim.color[2]));
|
|
432
|
-
mat.setVector2('rimParams', new BVector2(rim.power, rim.strength));
|
|
433
355
|
}
|
|
434
356
|
if (needsEyePosition) {
|
|
435
357
|
const eyePosition = new BVector3();
|
|
@@ -440,44 +362,15 @@ export class SpriteBatch extends VFXBatch {
|
|
|
440
362
|
return;
|
|
441
363
|
}
|
|
442
364
|
eyePosition.copyFrom(camera.globalPosition);
|
|
443
|
-
mat.setVector3('eyePosition', eyePosition);
|
|
365
|
+
VFXBatch.writeBound(mat, (target) => target.setVector3('eyePosition', eyePosition));
|
|
444
366
|
});
|
|
445
367
|
}
|
|
446
|
-
if (this.settings
|
|
368
|
+
if (tilesSheet(this.settings)) {
|
|
447
369
|
mat.setFloat('tileCountX', this.settings.uTileCount);
|
|
448
370
|
mat.setFloat('tileCountY', this.settings.vTileCount);
|
|
449
371
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
if (this.settings.softParticles) {
|
|
454
|
-
mat.setVector2(
|
|
455
|
-
'softParams',
|
|
456
|
-
new BVector2(
|
|
457
|
-
this.settings.softNearFade,
|
|
458
|
-
1.0 / Math.max(this.settings.softFarFade - this.settings.softNearFade, 0.0001)
|
|
459
|
-
)
|
|
460
|
-
);
|
|
461
|
-
// Reused across binds: this runs on every draw call.
|
|
462
|
-
const projParams = new BVector4(0, 0, 0, 0);
|
|
463
|
-
mat.onBindObservable.add(() => {
|
|
464
|
-
const camera = this.scene.activeCamera;
|
|
465
|
-
if (camera) {
|
|
466
|
-
projParams.x = camera.minZ;
|
|
467
|
-
projParams.y = camera.maxZ;
|
|
468
|
-
mat.setVector4('projParams', projParams);
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
if (this.settings.materialAlphaTest > 0) {
|
|
473
|
-
mat.setFloat('alphaTest', this.settings.materialAlphaTest);
|
|
474
|
-
}
|
|
475
|
-
if (pivot) {
|
|
476
|
-
mat.setVector3('pivot', new BVector3(pivot.x, pivot.y, pivot.z));
|
|
477
|
-
}
|
|
478
|
-
if (sizeLimits) {
|
|
479
|
-
mat.setVector2('sizeLimits', sizeLimits);
|
|
480
|
-
}
|
|
372
|
+
this.bindSoft(mat);
|
|
373
|
+
this.writeSpriteParams(mat, useEnvAtlas);
|
|
481
374
|
if (lit) {
|
|
482
375
|
this.writeFixedLighting(mat);
|
|
483
376
|
if (lighting === 'scene') {
|
|
@@ -485,13 +378,15 @@ export class SpriteBatch extends VFXBatch {
|
|
|
485
378
|
const color = new BVector3();
|
|
486
379
|
const ambient = new BVector3();
|
|
487
380
|
mat.onBindObservable.add(() => {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
381
|
+
const found = SpriteBatch.readSceneLight(this.scene, direction, color, ambient);
|
|
382
|
+
VFXBatch.writeBound(mat, (target) =>
|
|
383
|
+
found
|
|
384
|
+
? target
|
|
385
|
+
.setVector3('lightDirection', direction)
|
|
386
|
+
.setVector3('lightColor', color)
|
|
387
|
+
.setVector3('ambientColor', ambient)
|
|
388
|
+
: this.writeFixedLighting(target)
|
|
389
|
+
);
|
|
495
390
|
});
|
|
496
391
|
}
|
|
497
392
|
}
|
|
@@ -505,14 +400,52 @@ export class SpriteBatch extends VFXBatch {
|
|
|
505
400
|
} else {
|
|
506
401
|
mat.alphaMode = Constants.ALPHA_DISABLE;
|
|
507
402
|
}
|
|
508
|
-
|
|
403
|
+
// A transparent surface that writes depth is drawn into the depth buffer first, so its own
|
|
404
|
+
// overlapping pieces do not fight; an opaque one writes depth as it draws.
|
|
405
|
+
mat.needDepthPrePass = this.settings.materialDepthWrite && this.settings.materialTransparent;
|
|
509
406
|
mat.forceDepthWrite = this.settings.materialDepthWrite;
|
|
510
407
|
mat.disableDepthWrite = !this.settings.materialDepthWrite;
|
|
408
|
+
// 0 keeps the engine's depth function (LEQUAL); ALWAYS draws over everything, like ZTest Always.
|
|
409
|
+
mat.depthFunction = this.settings.materialDepthTest ? 0 : Constants.ALWAYS;
|
|
511
410
|
|
|
411
|
+
// A rebuild (an env atlas that finished loading) replaces the material; the old one and its
|
|
412
|
+
// bind observers go with it. Textures belong to the settings, so they stay.
|
|
413
|
+
this.shaderMaterial?.dispose(false, false);
|
|
512
414
|
this.mesh.material = mat;
|
|
513
415
|
this.shaderMaterial = mat;
|
|
514
416
|
}
|
|
515
417
|
|
|
418
|
+
/** Writes the live parameters only sprite and mesh batches have: reflection level, rim, pivot and size limits. */
|
|
419
|
+
private writeSpriteParams(material: ShaderMaterial, envAtlas: boolean): void {
|
|
420
|
+
if (envAtlas) {
|
|
421
|
+
material.setFloat('reflectionLevel', this.settings.reflectionLevel);
|
|
422
|
+
}
|
|
423
|
+
const rim = this.settings.renderMode === RenderMode.Mesh ? this.settings.rim : undefined;
|
|
424
|
+
if (rim) {
|
|
425
|
+
material.setVector3('rimColor', new BVector3(rim.color[0], rim.color[1], rim.color[2]));
|
|
426
|
+
material.setVector2('rimParams', new BVector2(rim.power, rim.strength));
|
|
427
|
+
}
|
|
428
|
+
const pivot = takesPivotAndSizeLimits(this.settings.renderMode) ? this.settings.pivot : undefined;
|
|
429
|
+
if (pivot) {
|
|
430
|
+
material.setVector3('pivot', new BVector3(pivot.x, pivot.y, pivot.z));
|
|
431
|
+
}
|
|
432
|
+
const sizeLimits = this.sizeLimits();
|
|
433
|
+
if (sizeLimits) {
|
|
434
|
+
material.setVector2('sizeLimits', sizeLimits);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
protected override writeLiveUniforms(material: ShaderMaterial): void {
|
|
439
|
+
super.writeLiveUniforms(material);
|
|
440
|
+
this.writeSpriteParams(material, this.usesEnvAtlas());
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** True when the material samples the env atlas: a mesh batch whose atlas has loaded. */
|
|
444
|
+
private usesEnvAtlas(): boolean {
|
|
445
|
+
const atlas = this.settings.reflectionAtlas;
|
|
446
|
+
return this.settings.renderMode === RenderMode.Mesh && !!atlas && atlas.isReady();
|
|
447
|
+
}
|
|
448
|
+
|
|
516
449
|
applyMeshLighting(lighting: MeshLightingSettings): void {
|
|
517
450
|
this.storeFixedLighting(lighting);
|
|
518
451
|
const material = this.shaderMaterial;
|
|
@@ -527,7 +460,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
527
460
|
this.fixedAmbientColor.set(lighting.ambient.r, lighting.ambient.g, lighting.ambient.b);
|
|
528
461
|
}
|
|
529
462
|
|
|
530
|
-
private writeFixedLighting(material: ShaderMaterial): void {
|
|
463
|
+
private writeFixedLighting(material: ShaderMaterial | Effect): void {
|
|
531
464
|
material.setVector3('lightDirection', this.fixedLightDirection);
|
|
532
465
|
material.setVector3('lightColor', this.fixedLightColor);
|
|
533
466
|
material.setVector3('ambientColor', this.fixedAmbientColor);
|
|
@@ -581,10 +514,10 @@ export class SpriteBatch extends VFXBatch {
|
|
|
581
514
|
private quaternion5_ = new Quaternion();
|
|
582
515
|
private rotationMat_ = new Matrix3();
|
|
583
516
|
private rotationMat2_ = new Matrix3();
|
|
584
|
-
private lastStretchedSpeedFactor = Number.NaN;
|
|
585
517
|
/** Slot 0 holds the zero-alpha instance since the batch last went empty. */
|
|
586
518
|
private clearedInstanceUploaded = false;
|
|
587
519
|
|
|
520
|
+
/** @internal */
|
|
588
521
|
update(): void {
|
|
589
522
|
let index = 0;
|
|
590
523
|
let particleCount = 0;
|
|
@@ -593,7 +526,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
593
526
|
const isStretchedRender = renderMode === RenderMode.StretchedBillBoard;
|
|
594
527
|
const isBeamRender = renderMode === RenderMode.Beam;
|
|
595
528
|
const usesVelocity = isStretchedRender || isBeamRender;
|
|
596
|
-
const flipsSprites =
|
|
529
|
+
const flipsSprites = takesPivotAndSizeLimits(renderMode);
|
|
597
530
|
|
|
598
531
|
const visibleSystems = this.getVisibleSystems();
|
|
599
532
|
for (let i = 0; i < visibleSystems.length; i++) {
|
|
@@ -894,7 +827,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
894
827
|
const vx = velocity.x + (velocity.x - previousVelocity.x) * stepFraction;
|
|
895
828
|
const vy = velocity.y + (velocity.y - previousVelocity.y) * stepFraction;
|
|
896
829
|
const vz = velocity.z + (velocity.z - previousVelocity.z) * stepFraction;
|
|
897
|
-
|
|
830
|
+
const vel: Vector3 = this.vector_;
|
|
898
831
|
vel.set(vx, vy, vz);
|
|
899
832
|
if (!systemWorldSpace) {
|
|
900
833
|
if (particle.parentMatrix) {
|
|
@@ -913,6 +846,10 @@ export class SpriteBatch extends VFXBatch {
|
|
|
913
846
|
}
|
|
914
847
|
}
|
|
915
848
|
|
|
849
|
+
const tint = (system as ParticleSystem).tint;
|
|
850
|
+
if (tint !== undefined && index > firstIndex) {
|
|
851
|
+
SpriteBatch.applyTint(this.colorBuffer, firstIndex * 4, index - firstIndex, tint);
|
|
852
|
+
}
|
|
916
853
|
if (flipsSprites && system.flip !== undefined) {
|
|
917
854
|
if (store !== undefined) {
|
|
918
855
|
SpriteBatch.applyFlip(this.sizeBuffer, firstIndex * 3, store.flip, particleNum);
|
|
@@ -925,10 +862,6 @@ export class SpriteBatch extends VFXBatch {
|
|
|
925
862
|
}
|
|
926
863
|
}
|
|
927
864
|
|
|
928
|
-
if (isStretchedRender && visibleSystems.length > 0) {
|
|
929
|
-
this.syncStretchedSpeedFactor(visibleSystems[0]);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
865
|
// Keep the mesh enabled even when count is 0 — toggling visibility off for a
|
|
933
866
|
// single frame (common with short lifetimes + low emission overlap) causes flicker.
|
|
934
867
|
this.mesh.forcedInstanceCount = index;
|
|
@@ -961,32 +894,6 @@ export class SpriteBatch extends VFXBatch {
|
|
|
961
894
|
}
|
|
962
895
|
}
|
|
963
896
|
|
|
964
|
-
/**
|
|
965
|
-
* Pushes the stretched-billboard speed factor to the shader when it changed.
|
|
966
|
-
*
|
|
967
|
-
* Deliberately kept out of `update`: touching the loosely typed
|
|
968
|
-
* `mesh.material` from inside that function gives V8 a generic property
|
|
969
|
-
* access it has no feedback for, which knocks the whole particle loop out of
|
|
970
|
-
* optimised code on every frame.
|
|
971
|
-
*/
|
|
972
|
-
private syncStretchedSpeedFactor(system: IParticleSystem): void {
|
|
973
|
-
const material = this.shaderMaterial;
|
|
974
|
-
if (material === null) {
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
const speedFactor = (system.rendererEmitterSettings as StretchedBillBoardSettings).speedFactor ?? 1.0;
|
|
978
|
-
const clampedSpeedFactor = speedFactor === 0 ? 0.001 : speedFactor;
|
|
979
|
-
if (clampedSpeedFactor !== this.lastStretchedSpeedFactor) {
|
|
980
|
-
material.setFloat('speedFactor', clampedSpeedFactor);
|
|
981
|
-
this.lastStretchedSpeedFactor = clampedSpeedFactor;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/** Stretched billboards and beams: drawn along a per-instance vector, by the stretched shader. */
|
|
986
|
-
private static drawsAlongVelocity(renderMode: RenderMode): boolean {
|
|
987
|
-
return renderMode === RenderMode.StretchedBillBoard || renderMode === RenderMode.Beam;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
897
|
private readonly beamEnd_ = new Vector3();
|
|
991
898
|
|
|
992
899
|
/**
|
|
@@ -1010,14 +917,14 @@ export class SpriteBatch extends VFXBatch {
|
|
|
1010
917
|
}
|
|
1011
918
|
}
|
|
1012
919
|
|
|
1013
|
-
/**
|
|
1014
|
-
private static
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
920
|
+
/** Multiplies `count` RGBA colours starting at `base` by a system's {@link ParticleSystem.tint}. */
|
|
921
|
+
private static applyTint(colors: Float32Array, base: number, count: number, tint: Vector4): void {
|
|
922
|
+
for (let j = 0, o = base; j < count; j++, o += 4) {
|
|
923
|
+
colors[o] *= tint.x;
|
|
924
|
+
colors[o + 1] *= tint.y;
|
|
925
|
+
colors[o + 2] *= tint.z;
|
|
926
|
+
colors[o + 3] *= tint.w;
|
|
927
|
+
}
|
|
1021
928
|
}
|
|
1022
929
|
|
|
1023
930
|
/**
|
|
@@ -1281,7 +1188,11 @@ export class SpriteBatch extends VFXBatch {
|
|
|
1281
1188
|
}
|
|
1282
1189
|
}
|
|
1283
1190
|
|
|
1191
|
+
/** @internal */
|
|
1284
1192
|
dispose(): void {
|
|
1193
|
+
this.disposed = true;
|
|
1194
|
+
this.cancelAtlasRebuild?.();
|
|
1195
|
+
this.cancelAtlasRebuild = null;
|
|
1285
1196
|
if (this.mesh.material) {
|
|
1286
1197
|
this.mesh.material.dispose();
|
|
1287
1198
|
}
|