babylon.quarks 0.27.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 +425 -0
- package/README.md +3 -1
- 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 +4256 -831
- package/dist/types/index.d.ts +4256 -831
- package/package.json +7 -2
- package/src/BatchedRenderer.ts +93 -78
- package/src/EffectPool.ts +91 -12
- package/src/MeshSurfaceEmitter.ts +24 -13
- package/src/ParticleEmitter.ts +31 -3
- package/src/ParticleSystem.ts +92 -25
- package/src/QuarksLoader.ts +289 -171
- package/src/QuarksPrefab.ts +301 -94
- package/src/QuarksSerializer.ts +43 -15
- package/src/QuarksUtil.ts +64 -9
- package/src/SpriteBatch.ts +21 -6
- package/src/TrailBatch.ts +12 -6
- package/src/VFXBatch.ts +70 -528
- package/src/batchKey.ts +4 -2
- package/src/batchSettings.ts +546 -0
- package/src/effectDocumentJSON.ts +261 -0
- package/src/effectResources.ts +21 -24
- package/src/envAtlas.ts +8 -2
- package/src/geometryUtil.ts +1 -1
- package/src/index.ts +39 -4
- package/src/nodeId.ts +25 -2
- package/src/shaderProgram.ts +110 -16
- package/src/shaderVariants.ts +24 -4
- package/src/shaders/chunks/blend.ts +31 -0
- package/src/shaders/chunks/fog.ts +8 -3
- package/src/shaders/chunks/soft.ts +6 -5
- package/src/shaders/chunks/surface.ts +2 -2
- package/src/shaders/particle_frag.glsl.ts +3 -1
- package/src/shaders/particle_frag.wgsl.ts +2 -0
- package/src/shaders/particle_physics_frag.glsl.ts +5 -2
- package/src/shaders/particle_physics_frag.wgsl.ts +2 -0
- package/src/shaders/trail_frag.glsl.ts +4 -1
- package/src/shaders/trail_frag.wgsl.ts +2 -0
- package/src/system/EmissionController.ts +2 -4
- package/src/system/EventDispatcher.ts +26 -10
- package/src/system/geometry.ts +22 -29
- package/src/system/materialResolve.ts +56 -7
- package/src/system/normalize.ts +6 -1
- package/src/system/serialization.ts +54 -33
- package/src/system/settingsSchema.ts +32 -10
- package/src/system/types.ts +67 -28
- package/src/trailSettings.ts +9 -6
package/src/shaderProgram.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {Constants} from '@babylonjs/core/Engines/constants';
|
|
2
2
|
import {hasSizeLimits, takesPivotAndSizeLimits, tilesSheet} from './batchKey';
|
|
3
|
+
import {readsUV1, RenderMode, StoredBatchSettings} from './batchSettings';
|
|
3
4
|
import local_particle_physics_vert from './shaders/local_particle_physics_vert.glsl';
|
|
4
5
|
import local_particle_physics_vert_wgsl from './shaders/local_particle_physics_vert.wgsl';
|
|
5
6
|
import particle_frag from './shaders/particle_frag.glsl';
|
|
@@ -15,7 +16,6 @@ import trail_frag from './shaders/trail_frag.glsl';
|
|
|
15
16
|
import trail_frag_wgsl from './shaders/trail_frag.wgsl';
|
|
16
17
|
import trail_vert from './shaders/trail_vert.glsl';
|
|
17
18
|
import trail_vert_wgsl from './shaders/trail_vert.wgsl';
|
|
18
|
-
import {readsUV1, RenderMode, StoredBatchSettings} from './VFXBatch';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* The part of a batch's settings that decides its shader: the render mode, which textures and
|
|
@@ -111,12 +111,94 @@ export function samplesMap(settings: {renderMode: RenderMode; texture: unknown})
|
|
|
111
111
|
return settings.texture !== null || settings.renderMode === RenderMode.Mesh;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
/**
|
|
115
|
+
* How a particle's colour has to leave the fragment shader for its blend mode, so that its alpha
|
|
116
|
+
* means coverage: alpha 0 leaves the frame as it was, alpha 1 draws the colour in full, and the
|
|
117
|
+
* fades (soft particles, erosion, a rim fade, alpha over life) only lower alpha. Unity's particle
|
|
118
|
+
* shaders do the same for their Alpha, Premultiply, Additive and Multiply modes. Alpha has no
|
|
119
|
+
* such meaning for an opaque material, nor under `ALPHA_INTERPOLATE` (constant blend colour),
|
|
120
|
+
* `ALPHA_REPLACE_COLOR` (replaces the frame) and `ALPHA_DUAL_SRC0_ADD_SRC1xDST` (reads a second
|
|
121
|
+
* shader output the particle shaders do not write); these leave the colour as it is.
|
|
122
|
+
*
|
|
123
|
+
* - `'straight'`: the blend weighs the colour by its alpha itself (Babylon's `ALPHA_COMBINE`,
|
|
124
|
+
* `ALPHA_ADD`, `ALPHA_LAYER_ACCUMULATE`, …) or does not blend; the colour leaves as it is.
|
|
125
|
+
* - `'premultiply'`: the blend takes the colour at full weight, or the frame by one minus the
|
|
126
|
+
* colour (`ALPHA_PREMULTIPLIED`, `ALPHA_ONEONE`, `ALPHA_SCREENMODE`, `ALPHA_SUBTRACT`,
|
|
127
|
+
* `ALPHA_MAXIMIZED`, `ALPHA_MAX`, …), so the shader multiplies it by its alpha: black, which
|
|
128
|
+
* these modes leave the frame alone with, at alpha 0. Textures stay straight alpha, as in
|
|
129
|
+
* Unity's Premultiply mode. The fade is linear in alpha except under `ALPHA_MAXIMIZED`, whose
|
|
130
|
+
* blend weighs the colour by alpha once more, and `ALPHA_MAX`.
|
|
131
|
+
* - `'multiply'`: the blend multiplies the frame by the colour (`ALPHA_MULTIPLY`, `ALPHA_MIN`), so
|
|
132
|
+
* the shader moves it toward white, which leaves the frame alone, as alpha falls. `ALPHA_MIN`
|
|
133
|
+
* clamps a frame brighter than 1 (an HDR target) to 1 where a particle draws, even at alpha 0.
|
|
134
|
+
*/
|
|
135
|
+
export type BlendOutput = 'straight' | 'premultiply' | 'multiply';
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* What the scene's fog turns a particle's colour into with distance: the fog colour where the
|
|
139
|
+
* particle covers the frame (`'color'`), black where it adds to it (`'black'`, or the fog would
|
|
140
|
+
* add itself to the whole screen), white where it multiplies it (`'white'`). Unity fogs its
|
|
141
|
+
* additive and multiply particles toward black and white for the same reason.
|
|
142
|
+
*/
|
|
143
|
+
export type FogTarget = 'color' | 'black' | 'white';
|
|
144
|
+
|
|
145
|
+
/** The {@link BlendOutput} and {@link FogTarget} of a blend mode. */
|
|
146
|
+
export interface BlendCategory {
|
|
147
|
+
/** How the colour leaves the shader. */
|
|
148
|
+
output: BlendOutput;
|
|
149
|
+
/** Where the fog takes the colour. */
|
|
150
|
+
fogTarget: FogTarget;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const PREMULTIPLY_MODES = new Set<number>([
|
|
154
|
+
Constants.ALPHA_SUBTRACT,
|
|
155
|
+
Constants.ALPHA_MAXIMIZED,
|
|
156
|
+
Constants.ALPHA_ONEONE,
|
|
157
|
+
Constants.ALPHA_PREMULTIPLIED,
|
|
158
|
+
Constants.ALPHA_PREMULTIPLIED_PORTERDUFF,
|
|
159
|
+
Constants.ALPHA_SCREENMODE,
|
|
160
|
+
Constants.ALPHA_ONEONE_ONEONE,
|
|
161
|
+
Constants.ALPHA_ALPHATOCOLOR,
|
|
162
|
+
Constants.ALPHA_REVERSEONEMINUS,
|
|
163
|
+
Constants.ALPHA_SRC_DSTONEMINUSSRCALPHA,
|
|
164
|
+
Constants.ALPHA_ONEONE_ONEZERO,
|
|
165
|
+
Constants.ALPHA_EXCLUSION,
|
|
166
|
+
Constants.ALPHA_MAX,
|
|
167
|
+
]);
|
|
168
|
+
const MULTIPLY_MODES = new Set<number>([Constants.ALPHA_MULTIPLY, Constants.ALPHA_MIN]);
|
|
169
|
+
/** The modes whose fog is the fog colour: those that lay the colour over the frame or replace it. */
|
|
170
|
+
const OVER_MODES = new Set<number>([
|
|
171
|
+
Constants.ALPHA_COMBINE,
|
|
172
|
+
Constants.ALPHA_PREMULTIPLIED,
|
|
173
|
+
Constants.ALPHA_PREMULTIPLIED_PORTERDUFF,
|
|
174
|
+
Constants.ALPHA_INTERPOLATE,
|
|
175
|
+
Constants.ALPHA_SRC_DSTONEMINUSSRCALPHA,
|
|
176
|
+
Constants.ALPHA_LAYER_ACCUMULATE,
|
|
177
|
+
Constants.ALPHA_REPLACE_COLOR,
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The {@link BlendCategory} of a material: an opaque one (`materialTransparent` false, or
|
|
182
|
+
* `ALPHA_DISABLE`) is straight and fogs toward the fog colour; a transparent one by its Babylon
|
|
183
|
+
* alpha mode. A mode this table does not know is straight and fogs toward the fog colour.
|
|
184
|
+
*/
|
|
185
|
+
export function blendCategory(settings: {materialTransparent: boolean; materialBlendMode: number}): BlendCategory {
|
|
186
|
+
const mode = settings.materialBlendMode;
|
|
187
|
+
if (!settings.materialTransparent || mode === Constants.ALPHA_DISABLE) {
|
|
188
|
+
return {output: 'straight', fogTarget: 'color'};
|
|
189
|
+
}
|
|
190
|
+
const output: BlendOutput = PREMULTIPLY_MODES.has(mode)
|
|
191
|
+
? 'premultiply'
|
|
192
|
+
: MULTIPLY_MODES.has(mode)
|
|
193
|
+
? 'multiply'
|
|
194
|
+
: 'straight';
|
|
195
|
+
const fogTarget: FogTarget =
|
|
196
|
+
output === 'multiply'
|
|
197
|
+
? 'white'
|
|
198
|
+
: OVER_MODES.has(mode) || (output === 'straight' && mode !== Constants.ALPHA_ADD)
|
|
199
|
+
? 'color'
|
|
200
|
+
: 'black';
|
|
201
|
+
return {output, fogTarget};
|
|
120
202
|
}
|
|
121
203
|
|
|
122
204
|
/** The shader program a batch with `settings` compiles, given what it knows about its textures. */
|
|
@@ -146,7 +228,7 @@ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext):
|
|
|
146
228
|
fragment = {glsl: particle_frag, wgsl: particle_frag_wgsl};
|
|
147
229
|
}
|
|
148
230
|
|
|
149
|
-
const defines =
|
|
231
|
+
const defines = blendDefines(settings);
|
|
150
232
|
const attributes = [...SPRITE_ATTRIBUTES];
|
|
151
233
|
const uniforms = ['world', 'view', 'projection', ...FOG_UNIFORMS];
|
|
152
234
|
const samplers: string[] = [];
|
|
@@ -225,7 +307,7 @@ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext):
|
|
|
225
307
|
|
|
226
308
|
/** The program of a trail batch. */
|
|
227
309
|
function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): ShaderProgram {
|
|
228
|
-
const defines =
|
|
310
|
+
const defines = blendDefines(settings);
|
|
229
311
|
const attributes = [...TRAIL_ATTRIBUTES];
|
|
230
312
|
const uniforms = ['view', 'projection', ...FOG_UNIFORMS];
|
|
231
313
|
const samplers: string[] = [];
|
|
@@ -251,15 +333,27 @@ function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): S
|
|
|
251
333
|
}
|
|
252
334
|
|
|
253
335
|
/**
|
|
254
|
-
* The
|
|
255
|
-
*
|
|
256
|
-
*
|
|
336
|
+
* The blend defines: `BLEND_PREMULTIPLY` or `BLEND_MULTIPLY` for how the colour leaves the shader,
|
|
337
|
+
* and with `FOG` (when the settings apply the scene's fog) `FOG_ADDITIVE` or `FOG_MULTIPLY` for a
|
|
338
|
+
* fog toward black or white. See {@link blendCategory}.
|
|
257
339
|
*/
|
|
258
|
-
function
|
|
259
|
-
|
|
260
|
-
|
|
340
|
+
function blendDefines(settings: ShaderSettings): string[] {
|
|
341
|
+
const {output, fogTarget} = blendCategory(settings);
|
|
342
|
+
const defines: string[] = [];
|
|
343
|
+
if (output === 'premultiply') {
|
|
344
|
+
defines.push('BLEND_PREMULTIPLY');
|
|
345
|
+
} else if (output === 'multiply') {
|
|
346
|
+
defines.push('BLEND_MULTIPLY');
|
|
347
|
+
}
|
|
348
|
+
if (settings.applyFog) {
|
|
349
|
+
defines.push('FOG');
|
|
350
|
+
if (fogTarget === 'black') {
|
|
351
|
+
defines.push('FOG_ADDITIVE');
|
|
352
|
+
} else if (fogTarget === 'white') {
|
|
353
|
+
defines.push('FOG_MULTIPLY');
|
|
354
|
+
}
|
|
261
355
|
}
|
|
262
|
-
return
|
|
356
|
+
return defines;
|
|
263
357
|
}
|
|
264
358
|
|
|
265
359
|
/** Soft particles, when on and a depth texture is there to read, and the alpha test. */
|
package/src/shaderVariants.ts
CHANGED
|
@@ -123,6 +123,21 @@ const FOG: Axis[] = [
|
|
|
123
123
|
{name: 'fog additive', settings: {applyFog: true, materialBlendMode: Constants.ALPHA_ADD}},
|
|
124
124
|
];
|
|
125
125
|
|
|
126
|
+
/** The blend resolves (see `blendCategory`), with and without the fog each one takes. */
|
|
127
|
+
const BLEND: Axis[] = [
|
|
128
|
+
{name: 'premultiplied', settings: {materialBlendMode: Constants.ALPHA_PREMULTIPLIED}},
|
|
129
|
+
{name: 'premultiplied fog', settings: {materialBlendMode: Constants.ALPHA_PREMULTIPLIED, applyFog: true}},
|
|
130
|
+
{name: 'one-one', settings: {materialBlendMode: Constants.ALPHA_ONEONE}},
|
|
131
|
+
{name: 'one-one fog', settings: {materialBlendMode: Constants.ALPHA_ONEONE, applyFog: true}},
|
|
132
|
+
{name: 'multiply', settings: {materialBlendMode: Constants.ALPHA_MULTIPLY}},
|
|
133
|
+
{name: 'multiply fog', settings: {materialBlendMode: Constants.ALPHA_MULTIPLY, applyFog: true}},
|
|
134
|
+
{
|
|
135
|
+
name: 'multiply fog alpha test',
|
|
136
|
+
settings: {materialBlendMode: Constants.ALPHA_MULTIPLY, applyFog: true, materialAlphaTest: 0.5},
|
|
137
|
+
},
|
|
138
|
+
{name: 'opaque', settings: {materialTransparent: false}},
|
|
139
|
+
];
|
|
140
|
+
|
|
126
141
|
/** Texture effects (`chunks/surface.ts`), alone and together, on both UV channels. */
|
|
127
142
|
const SURFACE: Axis[] = (() => {
|
|
128
143
|
const uv0 = normalizeMapUV({tiling: [2, 2]});
|
|
@@ -174,15 +189,16 @@ const MESH_UV2: Axis = {name: 'uv2', context: {meshUV2: true}};
|
|
|
174
189
|
|
|
175
190
|
/** Soft, alpha-tested and fogged at once, to try the effects with the rest of the fragment. */
|
|
176
191
|
const EVERYTHING_ELSE: Axis = {
|
|
177
|
-
name: 'soft packed alpha test fog
|
|
178
|
-
settings: {softParticles: true, materialAlphaTest: 0.5, applyFog: true, materialBlendMode: Constants.
|
|
192
|
+
name: 'soft packed alpha test fog one-one',
|
|
193
|
+
settings: {softParticles: true, materialAlphaTest: 0.5, applyFog: true, materialBlendMode: Constants.ALPHA_ONEONE},
|
|
179
194
|
context: {depth: {packed: true}},
|
|
180
195
|
};
|
|
181
196
|
|
|
182
197
|
/**
|
|
183
198
|
* Every shader program the library can compile, one per distinct set of defines: every render
|
|
184
|
-
* mode with every combination of sheet, map, soft particles, alpha test and fog,
|
|
185
|
-
*
|
|
199
|
+
* mode with every combination of sheet, map, soft particles, alpha test and fog, every blend
|
|
200
|
+
* resolve with and without fog and soft particles, and the texture effects alone and together on
|
|
201
|
+
* each shader family. What a shader validator compiles, and what
|
|
186
202
|
* an application can warm the shader cache with before its first frame (Unity's
|
|
187
203
|
* ShaderVariantCollection). Built by {@link shaderProgram}, so a variant here is a material a
|
|
188
204
|
* batch would build.
|
|
@@ -215,6 +231,10 @@ export function shaderVariants(): ShaderVariant[] {
|
|
|
215
231
|
for (const alphaTest of ALPHA_TEST)
|
|
216
232
|
for (const fog of FOG) add([target, quad, tile, map, soft, alphaTest, fog]);
|
|
217
233
|
|
|
234
|
+
for (const target of TARGETS)
|
|
235
|
+
for (const blend of BLEND)
|
|
236
|
+
for (const soft of SOFT.slice(0, 2)) for (const map of MAP) add([target, blend, soft, map]);
|
|
237
|
+
|
|
218
238
|
for (const target of TARGETS.filter((t) => SURFACE_TARGETS.includes(t.name)))
|
|
219
239
|
for (const uv2 of target.settings?.renderMode === RenderMode.Mesh ? [NONE, MESH_UV2] : [NONE])
|
|
220
240
|
for (const surface of SURFACE)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The blend resolve for the particle shaders, in both shader languages.
|
|
3
|
+
*
|
|
4
|
+
* Every fade in the shaders (soft particles, erosion, a rim fade, alpha over life) lowers alpha
|
|
5
|
+
* only, so that alpha means coverage. Blend modes that take the colour at full weight
|
|
6
|
+
* (`BLEND_PREMULTIPLY`: one-one, premultiplied, screen, subtract, …) would ignore it, so the
|
|
7
|
+
* resolve multiplies the colour by its alpha; modes that multiply the frame (`BLEND_MULTIPLY`)
|
|
8
|
+
* move it toward white instead. It runs last, after fog and the soft fade. See `blendCategory`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Resolves the vec4 lvalue `color` for the blend mode, GLSL. */
|
|
12
|
+
export function blendFragmentGLSL(color: string): string {
|
|
13
|
+
return /* glsl */ `
|
|
14
|
+
#if defined(BLEND_PREMULTIPLY)
|
|
15
|
+
${color}.rgb *= ${color}.a;
|
|
16
|
+
#elif defined(BLEND_MULTIPLY)
|
|
17
|
+
${color}.rgb = mix(vec3(1.0), ${color}.rgb, ${color}.a);
|
|
18
|
+
#endif
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Resolves the `var` vec4 `color` for the blend mode, WGSL. */
|
|
23
|
+
export function blendFragmentWGSL(color: string): string {
|
|
24
|
+
return /* wgsl */ `
|
|
25
|
+
#if defined(BLEND_PREMULTIPLY)
|
|
26
|
+
${color} = vec4f(${color}.rgb * ${color}.a, ${color}.a);
|
|
27
|
+
#elif defined(BLEND_MULTIPLY)
|
|
28
|
+
${color} = vec4f(mix(vec3f(1.0), ${color}.rgb, ${color}.a), ${color}.a);
|
|
29
|
+
#endif
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Additive particles cannot fade toward a fog colour (that would add fog to
|
|
13
13
|
* the whole screen), so under `FOG_ADDITIVE` they fade toward black instead,
|
|
14
|
-
*
|
|
14
|
+
* and multiplying particles (`FOG_MULTIPLY`) toward white, which leaves the
|
|
15
|
+
* frame as it is: what Unity does for additive and multiply materials.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
/** Vertex-side declaration, GLSL. */
|
|
@@ -62,8 +63,10 @@ export function fogFragmentGLSL(rgb: string): string {
|
|
|
62
63
|
#ifdef FOG
|
|
63
64
|
{
|
|
64
65
|
float fogFactor = quarksFogFactor();
|
|
65
|
-
#
|
|
66
|
+
#if defined(FOG_ADDITIVE)
|
|
66
67
|
${rgb} *= fogFactor;
|
|
68
|
+
#elif defined(FOG_MULTIPLY)
|
|
69
|
+
${rgb} = mix(vec3(1.0), ${rgb}, fogFactor);
|
|
67
70
|
#else
|
|
68
71
|
${rgb} = mix(vFogColor, ${rgb}, fogFactor);
|
|
69
72
|
#endif
|
|
@@ -110,8 +113,10 @@ fn quarksFog(rgb: vec3f, fogDistanceVector: vec3f) -> vec3f {
|
|
|
110
113
|
fogCoeff = exp(-fogDistance * fogDistance * fogDensity * fogDensity);
|
|
111
114
|
}
|
|
112
115
|
let fogFactor = clamp(fogCoeff, 0.0, 1.0);
|
|
113
|
-
#
|
|
116
|
+
#if defined(FOG_ADDITIVE)
|
|
114
117
|
return rgb * fogFactor;
|
|
118
|
+
#elif defined(FOG_MULTIPLY)
|
|
119
|
+
return mix(vec3f(1.0), rgb, fogFactor);
|
|
115
120
|
#else
|
|
116
121
|
return mix(uniforms.vFogColor, rgb, fogFactor);
|
|
117
122
|
#endif
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* The fragment reads the scene depth behind it from `depthTexture`, turns it into view depth with
|
|
5
5
|
* the coefficients in `projParams` (see `softDepthParams`), and fades out over the distance in
|
|
6
6
|
* `softParams` as it nears the scene, as Unity's soft particles and Unreal's Depth Fade do. The
|
|
7
|
-
* fade runs
|
|
7
|
+
* fade lowers alpha only and runs after fog and before the blend resolve (`blend.ts`), so it reads
|
|
8
|
+
* as coverage in every blend mode and both languages produce the same colour.
|
|
8
9
|
*
|
|
9
10
|
* `SOFT_DEPTH_PACKED` reads a depth packed into four 8-bit channels, as a Babylon
|
|
10
11
|
* `DepthRenderer` of type `UNSIGNED_BYTE` writes it.
|
|
@@ -33,14 +34,14 @@ float quarksSceneDepth(vec4 texel) {
|
|
|
33
34
|
#endif
|
|
34
35
|
`;
|
|
35
36
|
|
|
36
|
-
/** Fades `target` as the fragment nears the scene behind it, GLSL. */
|
|
37
|
+
/** Fades the alpha of the vec4 lvalue `target` as the fragment nears the scene behind it, GLSL. */
|
|
37
38
|
export function softFragmentGLSL(target: string): string {
|
|
38
39
|
return /* glsl */ `
|
|
39
40
|
#ifdef SOFT_PARTICLES
|
|
40
41
|
{
|
|
41
42
|
vec2 softUV = 0.5 * (projPosition.xy / projPosition.w) + 0.5;
|
|
42
43
|
float sceneDepth = quarksSceneDepth(texture2D(depthTexture, softUV));
|
|
43
|
-
${target} *= clamp(softParams.y * ((sceneDepth - softParams.x) - linearDepth), 0.0, 1.0);
|
|
44
|
+
${target}.a *= clamp(softParams.y * ((sceneDepth - softParams.x) - linearDepth), 0.0, 1.0);
|
|
44
45
|
}
|
|
45
46
|
#endif
|
|
46
47
|
`;
|
|
@@ -70,14 +71,14 @@ fn quarksSceneDepth(texel: vec4f) -> f32 {
|
|
|
70
71
|
#endif
|
|
71
72
|
`;
|
|
72
73
|
|
|
73
|
-
/** Fades `target` as the fragment nears the scene behind it, WGSL. */
|
|
74
|
+
/** Fades the alpha of the `var` vec4 `target` as the fragment nears the scene behind it, WGSL. */
|
|
74
75
|
export function softFragmentWGSL(target: string): string {
|
|
75
76
|
return /* wgsl */ `
|
|
76
77
|
#ifdef SOFT_PARTICLES
|
|
77
78
|
{
|
|
78
79
|
let softUV = 0.5 * (fragmentInputs.projPosition.xy / fragmentInputs.projPosition.w) + 0.5;
|
|
79
80
|
let sceneDepth = quarksSceneDepth(textureSample(depthTexture, depthTextureSampler, softUV));
|
|
80
|
-
${target} *= clamp(uniforms.softParams.y * ((sceneDepth - uniforms.softParams.x) - fragmentInputs.linearDepth), 0.0, 1.0);
|
|
81
|
+
${target}.a *= clamp(uniforms.softParams.y * ((sceneDepth - uniforms.softParams.x) - fragmentInputs.linearDepth), 0.0, 1.0);
|
|
81
82
|
}
|
|
82
83
|
#endif
|
|
83
84
|
`;
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
* on the way down the shape eats away from its lowest mask values first — a
|
|
36
36
|
* noise mask dissolves it, a gradient mask wipes it. `erosionParams` is
|
|
37
37
|
* (softness, edge width); the edge band just above the cut glows in
|
|
38
|
-
* `erosionEdgeColor`. Erosion only changes alpha,
|
|
39
|
-
*
|
|
38
|
+
* `erosionEdgeColor`. Erosion only changes alpha, which the blend resolve
|
|
39
|
+
* (`blend.ts`) turns into coverage in every blend mode.
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
/** Fragment-side declarations, GLSL. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {blendFragmentGLSL} from './chunks/blend';
|
|
1
2
|
import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
|
|
2
3
|
import {softFragmentDeclarationGLSL, softFragmentGLSL} from './chunks/soft';
|
|
3
4
|
import {surfaceColorGLSL, surfaceFragmentDeclarationGLSL} from './chunks/surface';
|
|
@@ -35,7 +36,8 @@ ${surfaceColorGLSL(true)}
|
|
|
35
36
|
#endif
|
|
36
37
|
|
|
37
38
|
${fogFragmentGLSL('baseColor.rgb')}
|
|
39
|
+
${softFragmentGLSL('baseColor')}
|
|
40
|
+
${blendFragmentGLSL('baseColor')}
|
|
38
41
|
gl_FragColor = baseColor;
|
|
39
|
-
${softFragmentGLSL('gl_FragColor')}
|
|
40
42
|
}
|
|
41
43
|
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {blendFragmentWGSL} from './chunks/blend';
|
|
1
2
|
import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
|
|
2
3
|
import {softFragmentDeclarationWGSL, softFragmentWGSL} from './chunks/soft';
|
|
3
4
|
import {surfaceColorWGSL, surfaceFragmentDeclarationWGSL} from './chunks/surface';
|
|
@@ -38,6 +39,7 @@ ${surfaceColorWGSL(true)}
|
|
|
38
39
|
|
|
39
40
|
${fogFragmentWGSL('baseColor')}
|
|
40
41
|
${softFragmentWGSL('baseColor')}
|
|
42
|
+
${blendFragmentWGSL('baseColor')}
|
|
41
43
|
fragmentOutputs.color = baseColor;
|
|
42
44
|
}
|
|
43
45
|
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {blendFragmentGLSL} from './chunks/blend';
|
|
1
2
|
import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
|
|
2
3
|
import {softFragmentDeclarationGLSL, softFragmentGLSL} from './chunks/soft';
|
|
3
4
|
import {surfaceColorGLSL, surfaceFragmentDeclarationGLSL} from './chunks/surface';
|
|
@@ -130,7 +131,9 @@ ${surfaceColorGLSL(true)}
|
|
|
130
131
|
#endif
|
|
131
132
|
|
|
132
133
|
${fogFragmentGLSL('color')}
|
|
133
|
-
|
|
134
|
-
${softFragmentGLSL('
|
|
134
|
+
vec4 finalColor = vec4(color, baseColor.a);
|
|
135
|
+
${softFragmentGLSL('finalColor')}
|
|
136
|
+
${blendFragmentGLSL('finalColor')}
|
|
137
|
+
gl_FragColor = finalColor;
|
|
135
138
|
}
|
|
136
139
|
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {blendFragmentWGSL} from './chunks/blend';
|
|
1
2
|
import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
|
|
2
3
|
import {softFragmentDeclarationWGSL, softFragmentWGSL} from './chunks/soft';
|
|
3
4
|
import {surfaceColorWGSL, surfaceFragmentDeclarationWGSL} from './chunks/surface';
|
|
@@ -133,6 +134,7 @@ ${surfaceColorWGSL(true)}
|
|
|
133
134
|
|
|
134
135
|
${fogFragmentWGSL('finalColor')}
|
|
135
136
|
${softFragmentWGSL('finalColor')}
|
|
137
|
+
${blendFragmentWGSL('finalColor')}
|
|
136
138
|
fragmentOutputs.color = finalColor;
|
|
137
139
|
}
|
|
138
140
|
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {blendFragmentGLSL} from './chunks/blend';
|
|
1
2
|
import {fogFragmentDeclarationGLSL, fogFragmentGLSL} from './chunks/fog';
|
|
2
3
|
import {softFragmentDeclarationGLSL, softFragmentGLSL} from './chunks/soft';
|
|
3
4
|
import {surfaceColorGLSL, surfaceFragmentDeclarationGLSL} from './chunks/surface';
|
|
@@ -29,6 +30,8 @@ ${surfaceColorGLSL(false)}
|
|
|
29
30
|
#endif
|
|
30
31
|
|
|
31
32
|
${fogFragmentGLSL('baseColor.rgb')}
|
|
33
|
+
${softFragmentGLSL('baseColor')}
|
|
34
|
+
${blendFragmentGLSL('baseColor')}
|
|
32
35
|
gl_FragColor = baseColor;
|
|
33
|
-
|
|
36
|
+
}
|
|
34
37
|
`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {blendFragmentWGSL} from './chunks/blend';
|
|
1
2
|
import {fogFragmentDeclarationWGSL, fogFragmentWGSL} from './chunks/fog';
|
|
2
3
|
import {softFragmentDeclarationWGSL, softFragmentWGSL} from './chunks/soft';
|
|
3
4
|
import {surfaceColorWGSL, surfaceFragmentDeclarationWGSL} from './chunks/surface';
|
|
@@ -32,6 +33,7 @@ ${surfaceColorWGSL(false)}
|
|
|
32
33
|
|
|
33
34
|
${fogFragmentWGSL('baseColor')}
|
|
34
35
|
${softFragmentWGSL('baseColor')}
|
|
36
|
+
${blendFragmentWGSL('baseColor')}
|
|
35
37
|
fragmentOutputs.color = baseColor;
|
|
36
38
|
}
|
|
37
39
|
`;
|
|
@@ -167,10 +167,8 @@ export class EmissionController {
|
|
|
167
167
|
if (emissionState.burstWaves !== undefined) {
|
|
168
168
|
emissionState.burstWaves.length = 0;
|
|
169
169
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
behaviors[i].reset();
|
|
173
|
-
}
|
|
170
|
+
// Behaviors carry on across the loop (a noise field keeps scrolling, sub emissions
|
|
171
|
+
// of live particles keep going); they reset only when the system restarts.
|
|
174
172
|
} else {
|
|
175
173
|
if (!system.isEmitEnded && !system.onlyUsedByOther) {
|
|
176
174
|
system.endEmit();
|
|
@@ -2,26 +2,41 @@ import type {ParticleSystemEvent, ParticleSystemEventType} from 'quarks.core';
|
|
|
2
2
|
|
|
3
3
|
type Listener = (event: ParticleSystemEvent) => void;
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The callbacks registered on a {@link ParticleSystem}, by event type.
|
|
7
|
+
*
|
|
8
|
+
* The lists are copied on write, not on fire: adding or removing a callback while an event is
|
|
9
|
+
* being fired (a listener removing itself, say) changes the next firing and never the one in
|
|
10
|
+
* progress, so every callback registered when the event fired is called once.
|
|
11
|
+
*/
|
|
6
12
|
export class EventDispatcher {
|
|
7
|
-
private readonly listeners: {[event: string]: Listener[]} = {};
|
|
13
|
+
private readonly listeners: {[event: string]: readonly Listener[]} = {};
|
|
8
14
|
|
|
9
15
|
/** Registers `callback` for `event`. */
|
|
10
16
|
add(event: ParticleSystemEventType, callback: Listener): void {
|
|
11
|
-
|
|
12
|
-
this.listeners[event].push(callback);
|
|
17
|
+
this.listeners[event] = [...(this.listeners[event] ?? []), callback];
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
/** Drops every callback for `event
|
|
16
|
-
removeAll(event
|
|
17
|
-
if (
|
|
20
|
+
/** Drops every callback for `event`, or for every event when it is left out. */
|
|
21
|
+
removeAll(event?: ParticleSystemEventType): void {
|
|
22
|
+
if (event === undefined) {
|
|
23
|
+
for (const key of Object.keys(this.listeners)) {
|
|
24
|
+
delete this.listeners[key];
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
delete this.listeners[event];
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
/** Drops one callback for `event`. */
|
|
21
32
|
remove(event: ParticleSystemEventType, callback: Listener): void {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
33
|
+
const callbacks = this.listeners[event];
|
|
34
|
+
if (callbacks === undefined) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const index = callbacks.indexOf(callback);
|
|
38
|
+
if (index !== -1) {
|
|
39
|
+
this.listeners[event] = callbacks.filter((_, i) => i !== index);
|
|
25
40
|
}
|
|
26
41
|
}
|
|
27
42
|
|
|
@@ -33,6 +48,7 @@ export class EventDispatcher {
|
|
|
33
48
|
|
|
34
49
|
/** Calls the callbacks registered for the event's type, in the order they were added. */
|
|
35
50
|
fire(event: ParticleSystemEvent): void {
|
|
51
|
+
// The list as it is now: a callback added or removed by a listener does not shift it.
|
|
36
52
|
const callbacks = this.listeners[event.type];
|
|
37
53
|
if (callbacks === undefined) {
|
|
38
54
|
return;
|
package/src/system/geometry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {BufferGeometryDataJSON, GeometryArrays, GeometryEntry} from '../effectDocumentJSON';
|
|
1
2
|
import {ensureTriangleIndices} from '../geometryUtil';
|
|
2
3
|
|
|
3
4
|
/** Positions of the unit quad billboards are drawn with, and the default mesh. */
|
|
@@ -47,19 +48,19 @@ export function isFallbackNormals(normals: Float32Array | undefined, positions:
|
|
|
47
48
|
return normals !== undefined && normals === fallbackNormals.get(positions);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/** `values` as a Float32Array: itself when it is one, else a copy. */
|
|
52
|
+
function floats(values: number[] | Float32Array | undefined): Float32Array | undefined {
|
|
53
|
+
return values ? (values instanceof Float32Array ? values : new Float32Array(values)) : undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
/**
|
|
51
57
|
* Typed arrays from a geometry entry of an effect document: quarks' own `{positions, indices, uvs,
|
|
52
|
-
* uvs2, normals}`, or a three.js `BufferGeometry` JSON
|
|
58
|
+
* uvs2, normals}`, or a three.js `BufferGeometry` JSON or its `data`. Anything else gives the
|
|
59
|
+
* default quad.
|
|
53
60
|
*/
|
|
54
|
-
export function resolveGeometryData(entry:
|
|
55
|
-
positions
|
|
56
|
-
|
|
57
|
-
uvs?: Float32Array;
|
|
58
|
-
uvs2?: Float32Array;
|
|
59
|
-
normals?: Float32Array;
|
|
60
|
-
} {
|
|
61
|
-
if (entry?.positions) {
|
|
62
|
-
const positions = entry.positions instanceof Float32Array ? entry.positions : new Float32Array(entry.positions);
|
|
61
|
+
export function resolveGeometryData(entry: GeometryEntry | BufferGeometryDataJSON | undefined): GeometryArrays {
|
|
62
|
+
if (entry && 'positions' in entry && entry.positions) {
|
|
63
|
+
const positions = floats(entry.positions)!;
|
|
63
64
|
const rawIndices =
|
|
64
65
|
entry.indices instanceof Uint16Array || entry.indices instanceof Uint32Array
|
|
65
66
|
? entry.indices
|
|
@@ -69,23 +70,17 @@ export function resolveGeometryData(entry: any): {
|
|
|
69
70
|
return {
|
|
70
71
|
positions,
|
|
71
72
|
indices: ensureTriangleIndices(positions, rawIndices),
|
|
72
|
-
uvs:
|
|
73
|
-
uvs2: entry.uvs2
|
|
74
|
-
|
|
75
|
-
? entry.uvs2
|
|
76
|
-
: new Float32Array(entry.uvs2)
|
|
77
|
-
: undefined,
|
|
78
|
-
normals: entry.normals
|
|
79
|
-
? entry.normals instanceof Float32Array
|
|
80
|
-
? entry.normals
|
|
81
|
-
: new Float32Array(entry.normals)
|
|
82
|
-
: undefined,
|
|
73
|
+
uvs: floats(entry.uvs),
|
|
74
|
+
uvs2: floats(entry.uvs2),
|
|
75
|
+
normals: floats(entry.normals),
|
|
83
76
|
};
|
|
84
77
|
}
|
|
85
78
|
|
|
86
|
-
const data = entry
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
const data = entry && 'data' in entry ? entry.data : entry;
|
|
80
|
+
const attributes = data && 'attributes' in data ? data.attributes : undefined;
|
|
81
|
+
const position = attributes?.position?.array;
|
|
82
|
+
if (data && 'attributes' in data && attributes && position) {
|
|
83
|
+
const positions = new Float32Array(position);
|
|
89
84
|
let indices: Uint32Array | Uint16Array | undefined;
|
|
90
85
|
if (data.index?.array) {
|
|
91
86
|
const indexType = data.index.type === 'Uint16Array' ? Uint16Array : Uint32Array;
|
|
@@ -94,12 +89,10 @@ export function resolveGeometryData(entry: any): {
|
|
|
94
89
|
return {
|
|
95
90
|
positions,
|
|
96
91
|
indices: ensureTriangleIndices(positions, indices),
|
|
97
|
-
uvs:
|
|
92
|
+
uvs: floats(attributes.uv?.array),
|
|
98
93
|
// three.js names the second set `uv1` (r151 on), `uv2` before.
|
|
99
|
-
uvs2: (
|
|
100
|
-
|
|
101
|
-
: undefined,
|
|
102
|
-
normals: data.attributes.normal?.array ? new Float32Array(data.attributes.normal.array) : undefined,
|
|
94
|
+
uvs2: floats((attributes.uv1 ?? attributes.uv2)?.array),
|
|
95
|
+
normals: floats(attributes.normal?.array),
|
|
103
96
|
};
|
|
104
97
|
}
|
|
105
98
|
|