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.
Files changed (79) hide show
  1. package/CHANGELOG.md +854 -8
  2. package/README.md +10 -8
  3. package/dist/babylon.quarks.cjs +1 -1
  4. package/dist/babylon.quarks.cjs.map +1 -1
  5. package/dist/babylon.quarks.esm.js +1 -1
  6. package/dist/babylon.quarks.esm.js.map +1 -1
  7. package/dist/babylon.quarks.umd.min.js +1 -1
  8. package/dist/babylon.quarks.umd.min.js.map +1 -1
  9. package/dist/types/index.d.cts +6683 -0
  10. package/dist/types/index.d.ts +2210 -613
  11. package/package.json +12 -6
  12. package/src/BatchedRenderer.ts +215 -106
  13. package/src/EffectPool.ts +4 -1
  14. package/src/MeshSurfaceEmitter.ts +29 -4
  15. package/src/ParticleEmitter.ts +45 -15
  16. package/src/ParticleSystem.ts +1012 -1962
  17. package/src/QuarksLoader.ts +463 -75
  18. package/src/QuarksPrefab.ts +91 -4
  19. package/src/QuarksSerializer.ts +484 -0
  20. package/src/QuarksUtil.ts +15 -0
  21. package/src/SpriteBatch.ts +149 -238
  22. package/src/TrailBatch.ts +586 -286
  23. package/src/VFXBatch.ts +517 -153
  24. package/src/batchKey.ts +237 -0
  25. package/src/effectResources.ts +79 -0
  26. package/src/envAtlas.ts +119 -17
  27. package/src/index.ts +51 -35
  28. package/src/nodeId.ts +13 -0
  29. package/src/shaderProgram.ts +325 -0
  30. package/src/shaderVariants.ts +225 -0
  31. package/src/shaders/chunks/fog.ts +4 -2
  32. package/src/shaders/chunks/sheet.ts +111 -0
  33. package/src/shaders/chunks/soft.ts +84 -0
  34. package/src/shaders/chunks/surface.ts +132 -42
  35. package/src/shaders/local_particle_physics_vert.glsl.ts +28 -27
  36. package/src/shaders/local_particle_physics_vert.wgsl.ts +30 -28
  37. package/src/shaders/particle_frag.glsl.ts +6 -22
  38. package/src/shaders/particle_frag.wgsl.ts +6 -23
  39. package/src/shaders/particle_physics_frag.glsl.ts +22 -29
  40. package/src/shaders/particle_physics_frag.wgsl.ts +22 -30
  41. package/src/shaders/particle_vert.glsl.ts +11 -26
  42. package/src/shaders/particle_vert.wgsl.ts +11 -25
  43. package/src/shaders/stretched_bb_particle_vert.glsl.ts +21 -39
  44. package/src/shaders/stretched_bb_particle_vert.wgsl.ts +22 -40
  45. package/src/shaders/trail_frag.glsl.ts +14 -1
  46. package/src/shaders/trail_frag.wgsl.ts +14 -0
  47. package/src/shaders/trail_vert.glsl.ts +53 -36
  48. package/src/shaders/trail_vert.wgsl.ts +54 -40
  49. package/src/softDepth.ts +136 -0
  50. package/src/system/BehaviorPlan.ts +85 -0
  51. package/src/system/BoundsTracker.ts +181 -0
  52. package/src/system/EmissionController.ts +360 -0
  53. package/src/system/EventDispatcher.ts +44 -0
  54. package/src/system/ParticleSpawner.ts +274 -0
  55. package/src/system/SimulationClock.ts +130 -0
  56. package/src/system/geometry.ts +111 -0
  57. package/src/system/integrate.ts +428 -0
  58. package/src/system/materialResolve.ts +116 -0
  59. package/src/system/normalize.ts +95 -0
  60. package/src/system/serialization.ts +401 -0
  61. package/src/system/settingsSchema.ts +317 -0
  62. package/src/system/types.ts +410 -0
  63. package/src/textureSource.ts +30 -0
  64. package/src/trailSettings.ts +51 -0
  65. package/src/BatchedParticleRenderer.ts +0 -3
  66. package/src/materials/ParticleMaterials.ts +0 -23
  67. package/src/materials/index.ts +0 -1
  68. package/src/shaders/chunks/index.ts +0 -1
  69. package/src/shaders/chunks/register-shader-chunks.ts +0 -32
  70. package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
  71. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
  72. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
  73. package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
  74. package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
  75. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
  76. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
  77. package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
  78. package/src/shaders/index.ts +0 -9
  79. package/src/shaders/local_particle_vert.glsl.ts +0 -50
@@ -0,0 +1,325 @@
1
+ import {Constants} from '@babylonjs/core/Engines/constants';
2
+ import {hasSizeLimits, takesPivotAndSizeLimits, tilesSheet} from './batchKey';
3
+ import local_particle_physics_vert from './shaders/local_particle_physics_vert.glsl';
4
+ import local_particle_physics_vert_wgsl from './shaders/local_particle_physics_vert.wgsl';
5
+ import particle_frag from './shaders/particle_frag.glsl';
6
+ import particle_frag_wgsl from './shaders/particle_frag.wgsl';
7
+ import particle_physics_frag from './shaders/particle_physics_frag.glsl';
8
+ import particle_physics_frag_wgsl from './shaders/particle_physics_frag.wgsl';
9
+ import particle_vert from './shaders/particle_vert.glsl';
10
+ import particle_vert_wgsl from './shaders/particle_vert.wgsl';
11
+ import type {ShaderSources} from './shaders/shaderLanguageSupport';
12
+ import stretched_bb_particle_vert from './shaders/stretched_bb_particle_vert.glsl';
13
+ import stretched_bb_particle_vert_wgsl from './shaders/stretched_bb_particle_vert.wgsl';
14
+ import trail_frag from './shaders/trail_frag.glsl';
15
+ import trail_frag_wgsl from './shaders/trail_frag.wgsl';
16
+ import trail_vert from './shaders/trail_vert.glsl';
17
+ import trail_vert_wgsl from './shaders/trail_vert.wgsl';
18
+ import {readsUV1, RenderMode, StoredBatchSettings} from './VFXBatch';
19
+
20
+ /**
21
+ * The part of a batch's settings that decides its shader: the render mode, which textures and
22
+ * effects are compiled in, and the blend state the fog and the fade read. Everything else a
23
+ * batch holds (geometry, draw order, the effects' parameters) is a buffer or a uniform value.
24
+ */
25
+ export type ShaderSettings = Pick<
26
+ StoredBatchSettings,
27
+ | 'renderMode'
28
+ | 'texture'
29
+ | 'reflectionAtlas'
30
+ | 'uTileCount'
31
+ | 'vTileCount'
32
+ | 'blendTiles'
33
+ | 'softParticles'
34
+ | 'materialAlphaTest'
35
+ | 'materialTransparent'
36
+ | 'materialBlendMode'
37
+ | 'applyFog'
38
+ | 'pivot'
39
+ | 'minParticleSize'
40
+ | 'maxParticleSize'
41
+ | 'lighting'
42
+ | 'rim'
43
+ | 'mapUV'
44
+ | 'uvDistortion'
45
+ | 'gradientMap'
46
+ | 'erosion'
47
+ >;
48
+
49
+ /** What the material needs to know about the depth texture soft particles read. */
50
+ export interface ShaderDepth {
51
+ /** Whether the depth is packed into four 8-bit channels, as a `DepthRenderer` of type `UNSIGNED_BYTE` writes it. */
52
+ packed: boolean;
53
+ }
54
+
55
+ /**
56
+ * What a batch knows at compile time that its settings do not say: whether the textures it
57
+ * depends on are there to be sampled.
58
+ */
59
+ export interface ShaderContext {
60
+ /**
61
+ * The depth texture soft particles read, when one is attached to the renderer. Soft particles
62
+ * compile only with a depth texture.
63
+ */
64
+ depth: ShaderDepth | null;
65
+ /** Whether the mesh batch's reflection atlas has finished loading; it is sampled only then. */
66
+ envAtlasReady: boolean;
67
+ /** Whether the mesh batch has a second UV set to read for {@link UVChannel} 1. */
68
+ meshUV2: boolean;
69
+ }
70
+
71
+ /**
72
+ * One compiled shader of the library: the sources in both languages and the defines, attributes,
73
+ * uniforms and samplers the material is built with. Built by {@link shaderProgram} from a
74
+ * batch's settings, so a batch and a shader validator describe the same material.
75
+ */
76
+ export interface ShaderProgram {
77
+ /** The name the sources are registered under in Babylon's shader store, one per render mode. */
78
+ name: string;
79
+ /** The vertex shader in both languages. */
80
+ vertex: ShaderSources;
81
+ /** The fragment shader in both languages. */
82
+ fragment: ShaderSources;
83
+ /** The preprocessor defines, without the `#define`. */
84
+ defines: string[];
85
+ /** The vertex attributes the material binds. */
86
+ attributes: string[];
87
+ /** The uniforms the material declares. */
88
+ uniforms: string[];
89
+ /** The texture samplers the material declares. */
90
+ samplers: string[];
91
+ }
92
+
93
+ /** The vertex attributes every sprite shader reads. */
94
+ const SPRITE_ATTRIBUTES = ['position', 'uv', 'offset', 'color', 'size', 'rotation', 'uvTile'];
95
+ /** The vertex attributes the trail shader reads. */
96
+ const TRAIL_ATTRIBUTES = ['position', 'previous', 'next', 'side', 'width', 'uv', 'color'];
97
+ /** The fog uniforms, declared on every material so a scene can turn its fog on without a recompile. */
98
+ export const FOG_UNIFORMS = ['vFogInfos', 'vFogColor'];
99
+
100
+ /** Whether `renderMode` is drawn by the stretched shader: along a per-instance vector. */
101
+ export function drawsAlongVelocity(renderMode: RenderMode): boolean {
102
+ return renderMode === RenderMode.StretchedBillBoard || renderMode === RenderMode.Beam;
103
+ }
104
+
105
+ /**
106
+ * Whether the material samples a map: the settings' texture, or the white placeholder a mesh
107
+ * batch gets, since a mesh effect with no sampler at all often never becomes drawable on iOS
108
+ * WebKit.
109
+ */
110
+ export function samplesMap(settings: {renderMode: RenderMode; texture: unknown}): boolean {
111
+ return settings.texture !== null || settings.renderMode === RenderMode.Mesh;
112
+ }
113
+
114
+ /** Whether the blend mode adds to the frame, in which case fog and fade darken instead of mixing. */
115
+ export function blendsAdditively(settings: {materialTransparent: boolean; materialBlendMode: number}): boolean {
116
+ return (
117
+ settings.materialTransparent &&
118
+ (settings.materialBlendMode === Constants.ALPHA_ADD || settings.materialBlendMode === Constants.ALPHA_ONEONE)
119
+ );
120
+ }
121
+
122
+ /** The shader program a batch with `settings` compiles, given what it knows about its textures. */
123
+ export function shaderProgram(settings: ShaderSettings, context: ShaderContext): ShaderProgram {
124
+ return settings.renderMode === RenderMode.Trail
125
+ ? trailShaderProgram(settings, context)
126
+ : spriteShaderProgram(settings, context);
127
+ }
128
+
129
+ /** The program of a sprite, stretched or mesh batch. */
130
+ function spriteShaderProgram(settings: ShaderSettings, context: ShaderContext): ShaderProgram {
131
+ const renderMode = settings.renderMode;
132
+ const isMesh = renderMode === RenderMode.Mesh;
133
+ // The sources only depend on the render mode, so the store entry is registered once per
134
+ // mode: a unique name per material would leak store entries and defeat the effect cache.
135
+ const name = `quarksParticle_${renderMode}`;
136
+ let vertex: ShaderSources;
137
+ let fragment: ShaderSources;
138
+ if (isMesh) {
139
+ vertex = {glsl: local_particle_physics_vert, wgsl: local_particle_physics_vert_wgsl};
140
+ fragment = {glsl: particle_physics_frag, wgsl: particle_physics_frag_wgsl};
141
+ } else if (drawsAlongVelocity(renderMode)) {
142
+ vertex = {glsl: stretched_bb_particle_vert, wgsl: stretched_bb_particle_vert_wgsl};
143
+ fragment = {glsl: particle_frag, wgsl: particle_frag_wgsl};
144
+ } else {
145
+ vertex = {glsl: particle_vert, wgsl: particle_vert_wgsl};
146
+ fragment = {glsl: particle_frag, wgsl: particle_frag_wgsl};
147
+ }
148
+
149
+ const defines = fogDefines(settings);
150
+ const attributes = [...SPRITE_ATTRIBUTES];
151
+ const uniforms = ['world', 'view', 'projection', ...FOG_UNIFORMS];
152
+ const samplers: string[] = [];
153
+
154
+ if (samplesMap(settings)) {
155
+ defines.push('USE_MAP');
156
+ samplers.push('map');
157
+ }
158
+ const envAtlas = isMesh && settings.reflectionAtlas !== null && context.envAtlasReady;
159
+ if (envAtlas) {
160
+ defines.push('USE_ENVMAP_ATLAS');
161
+ samplers.push('envAtlas');
162
+ uniforms.push('reflectionLevel');
163
+ }
164
+ if (tilesSheet(settings)) {
165
+ defines.push('UV_TILE');
166
+ uniforms.push('tileCountX', 'tileCountY');
167
+ // The second cell only exists on a sheet; a 1×1 sheet has nothing to blend into.
168
+ if (settings.blendTiles) {
169
+ defines.push('TILE_BLEND');
170
+ }
171
+ }
172
+ softDefines(settings, context, defines, uniforms, samplers);
173
+ if (renderMode === RenderMode.VerticalBillBoard) {
174
+ defines.push('VERTICAL');
175
+ }
176
+ if (renderMode === RenderMode.HorizontalBillBoard) {
177
+ defines.push('HORIZONTAL');
178
+ }
179
+ if (renderMode === RenderMode.Beam) {
180
+ defines.push('BEAM');
181
+ }
182
+ if (takesPivotAndSizeLimits(renderMode)) {
183
+ if (settings.pivot) {
184
+ defines.push('PIVOT');
185
+ uniforms.push('pivot');
186
+ }
187
+ if (hasSizeLimits(settings)) {
188
+ defines.push('SIZE_LIMITS');
189
+ uniforms.push('sizeLimits');
190
+ }
191
+ }
192
+ const lit = isMesh && (settings.lighting ?? 'fixed') !== 'unlit';
193
+ if (isMesh && !lit) {
194
+ defines.push('MESH_UNLIT');
195
+ }
196
+ if (lit) {
197
+ uniforms.push('lightDirection', 'lightColor', 'ambientColor');
198
+ }
199
+ const rim = isMesh ? settings.rim : undefined;
200
+ if (rim) {
201
+ defines.push('MESH_RIM');
202
+ if (rim.mode === 'fade') {
203
+ defines.push('MESH_RIM_FADE');
204
+ } else if (rim.mode === 'mix') {
205
+ defines.push('MESH_RIM_MIX');
206
+ }
207
+ uniforms.push('rimColor', 'rimParams');
208
+ }
209
+ if (envAtlas || rim) {
210
+ uniforms.push('eyePosition');
211
+ }
212
+ surfaceDefines(settings, defines, uniforms, samplers);
213
+ if (isMesh) {
214
+ attributes.push('normal');
215
+ if (readsUV1(settings) && context.meshUV2) {
216
+ defines.push('MESH_UV2');
217
+ attributes.push('uv2');
218
+ }
219
+ }
220
+ if (drawsAlongVelocity(renderMode)) {
221
+ attributes.push('velocity');
222
+ }
223
+ return {name, vertex, fragment, defines, attributes, uniforms, samplers};
224
+ }
225
+
226
+ /** The program of a trail batch. */
227
+ function trailShaderProgram(settings: ShaderSettings, context: ShaderContext): ShaderProgram {
228
+ const defines = fogDefines(settings);
229
+ const attributes = [...TRAIL_ATTRIBUTES];
230
+ const uniforms = ['view', 'projection', ...FOG_UNIFORMS];
231
+ const samplers: string[] = [];
232
+ if (settings.texture !== null) {
233
+ defines.push('USE_MAP');
234
+ samplers.push('map');
235
+ }
236
+ surfaceDefines(settings, defines, uniforms, samplers);
237
+ softDefines(settings, context, defines, uniforms, samplers);
238
+ if (readsUV1(settings)) {
239
+ // A trail's channel 1 arrives as its own vertex buffer.
240
+ attributes.push('uv2');
241
+ }
242
+ return {
243
+ name: 'quarksTrail',
244
+ vertex: {glsl: trail_vert, wgsl: trail_vert_wgsl},
245
+ fragment: {glsl: trail_frag, wgsl: trail_frag_wgsl},
246
+ defines,
247
+ attributes,
248
+ uniforms,
249
+ samplers,
250
+ };
251
+ }
252
+
253
+ /**
254
+ * The fog defines: `FOG` when the settings apply the scene's fog, and `FOG_ADDITIVE` when the
255
+ * particles add to the frame, so that they fade toward black instead of the fog colour, which
256
+ * would fog the whole screen (what Unity does for additive materials).
257
+ */
258
+ function fogDefines(settings: ShaderSettings): string[] {
259
+ if (!settings.applyFog) {
260
+ return [];
261
+ }
262
+ return blendsAdditively(settings) ? ['FOG', 'FOG_ADDITIVE'] : ['FOG'];
263
+ }
264
+
265
+ /** Soft particles, when on and a depth texture is there to read, and the alpha test. */
266
+ function softDefines(
267
+ settings: ShaderSettings,
268
+ context: ShaderContext,
269
+ defines: string[],
270
+ uniforms: string[],
271
+ samplers: string[]
272
+ ): void {
273
+ if (settings.softParticles && context.depth !== null) {
274
+ defines.push('SOFT_PARTICLES');
275
+ if (context.depth.packed) {
276
+ defines.push('SOFT_DEPTH_PACKED');
277
+ }
278
+ uniforms.push('softParams', 'projParams');
279
+ samplers.push('depthTexture');
280
+ }
281
+ if (settings.materialAlphaTest > 0) {
282
+ defines.push('USE_ALPHATEST');
283
+ uniforms.push('alphaTest');
284
+ }
285
+ }
286
+
287
+ /** The texture effects of `chunks/surface.ts`: the map's layout, distortion, gradient map and erosion. */
288
+ function surfaceDefines(settings: ShaderSettings, defines: string[], uniforms: string[], samplers: string[]): void {
289
+ const {mapUV, uvDistortion, gradientMap, erosion} = settings;
290
+ if (readsUV1(settings)) {
291
+ defines.push('UV1');
292
+ }
293
+ if (mapUV) {
294
+ defines.push('MAP_TRANSFORM');
295
+ if (mapUV.channel === 1) {
296
+ defines.push('MAP_UV1');
297
+ }
298
+ uniforms.push('mapTransform');
299
+ }
300
+ if (uvDistortion) {
301
+ defines.push('UV_DISTORTION');
302
+ if (uvDistortion.mapUV.channel === 1) {
303
+ defines.push('UV_DISTORTION_UV1');
304
+ }
305
+ samplers.push('distortionMap');
306
+ uniforms.push('distortionTransform', 'distortionStrength');
307
+ }
308
+ if (gradientMap) {
309
+ defines.push('GRADIENT_MAP');
310
+ samplers.push('gradientMap');
311
+ uniforms.push('gradientMapRange', 'gradientMapChannel');
312
+ }
313
+ if (erosion) {
314
+ defines.push('EROSION');
315
+ uniforms.push('erosionParams', 'erosionEdgeColor');
316
+ if (erosion.mask) {
317
+ defines.push('EROSION_MASK');
318
+ if (erosion.maskUV.channel === 1) {
319
+ defines.push('EROSION_MASK_UV1');
320
+ }
321
+ samplers.push('erosionMask');
322
+ uniforms.push('erosionMaskTransform');
323
+ }
324
+ }
325
+ }
@@ -0,0 +1,225 @@
1
+ import {Constants} from '@babylonjs/core/Engines/constants';
2
+ import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
3
+ import {Vector3} from 'quarks.core';
4
+ import {ShaderContext, shaderProgram, ShaderProgram, ShaderSettings} from './shaderProgram';
5
+ import {
6
+ normalizeErosion,
7
+ normalizeGradientMap,
8
+ normalizeMapUV,
9
+ normalizeMeshRim,
10
+ normalizeUVDistortion,
11
+ RenderMode,
12
+ } from './VFXBatch';
13
+
14
+ /**
15
+ * One shader the library can compile: a {@link ShaderProgram} with the settings that produce it,
16
+ * named for the renderer's benefit. See {@link shaderVariants}.
17
+ */
18
+ export interface ShaderVariant {
19
+ /** The render mode and the features compiled in, for a report. */
20
+ name: string;
21
+ /** The program a batch with these settings builds. */
22
+ program: ShaderProgram;
23
+ }
24
+
25
+ /** A texture reference for settings that only ask whether one is set; it is never sampled. */
26
+ const TEXTURE = {} as BaseTexture;
27
+
28
+ /** The settings of a bare batch in `renderMode`: no texture, no effects, fog off, 1×1 sheet. */
29
+ function bare(renderMode: RenderMode): ShaderSettings {
30
+ return {
31
+ renderMode,
32
+ texture: null,
33
+ reflectionAtlas: null,
34
+ uTileCount: 1,
35
+ vTileCount: 1,
36
+ blendTiles: false,
37
+ softParticles: false,
38
+ materialAlphaTest: 0,
39
+ materialTransparent: true,
40
+ materialBlendMode: Constants.ALPHA_COMBINE,
41
+ applyFog: false,
42
+ pivot: undefined,
43
+ minParticleSize: 0,
44
+ maxParticleSize: Infinity,
45
+ lighting: renderMode === RenderMode.Mesh ? 'fixed' : undefined,
46
+ rim: undefined,
47
+ mapUV: undefined,
48
+ uvDistortion: undefined,
49
+ gradientMap: undefined,
50
+ erosion: undefined,
51
+ };
52
+ }
53
+
54
+ /** A settings change with the name it adds to the variant. */
55
+ interface Axis {
56
+ name: string;
57
+ settings?: Partial<ShaderSettings>;
58
+ context?: Partial<ShaderContext>;
59
+ }
60
+
61
+ const NONE: Axis = {name: ''};
62
+
63
+ /** The render modes and the mode-specific features: quad pivot and size limits, mesh lighting, rim and reflection. */
64
+ const TARGETS: Axis[] = [
65
+ {name: 'billboard', settings: {renderMode: RenderMode.BillBoard}},
66
+ {name: 'vertical billboard', settings: {renderMode: RenderMode.VerticalBillBoard}},
67
+ {name: 'horizontal billboard', settings: {renderMode: RenderMode.HorizontalBillBoard}},
68
+ {name: 'stretched', settings: {renderMode: RenderMode.StretchedBillBoard}},
69
+ {name: 'beam', settings: {renderMode: RenderMode.Beam}},
70
+ {name: 'mesh', settings: {renderMode: RenderMode.Mesh}},
71
+ {name: 'mesh scene lit', settings: {renderMode: RenderMode.Mesh, lighting: 'scene'}},
72
+ {name: 'mesh unlit', settings: {renderMode: RenderMode.Mesh, lighting: 'unlit'}},
73
+ {name: 'mesh rim', settings: {renderMode: RenderMode.Mesh, rim: normalizeMeshRim({mode: 'add'})}},
74
+ {
75
+ name: 'mesh unlit rim mix',
76
+ settings: {renderMode: RenderMode.Mesh, lighting: 'unlit', rim: normalizeMeshRim({mode: 'mix'})},
77
+ },
78
+ {
79
+ name: 'mesh unlit rim fade',
80
+ settings: {renderMode: RenderMode.Mesh, lighting: 'unlit', rim: normalizeMeshRim({mode: 'fade'})},
81
+ },
82
+ {
83
+ name: 'mesh reflecting',
84
+ settings: {renderMode: RenderMode.Mesh, reflectionAtlas: TEXTURE},
85
+ context: {envAtlasReady: true},
86
+ },
87
+ {
88
+ name: 'mesh unlit reflecting rim fade',
89
+ settings: {
90
+ renderMode: RenderMode.Mesh,
91
+ lighting: 'unlit',
92
+ reflectionAtlas: TEXTURE,
93
+ rim: normalizeMeshRim({mode: 'fade'}),
94
+ },
95
+ context: {envAtlasReady: true},
96
+ },
97
+ {name: 'trail', settings: {renderMode: RenderMode.Trail}},
98
+ ];
99
+
100
+ /** Pivot and screen-size limits, which only the quad modes read. */
101
+ const QUAD: Axis[] = [
102
+ NONE,
103
+ {name: 'pivot', settings: {pivot: new Vector3(0.5, 0, 0)}},
104
+ {name: 'size limits', settings: {minParticleSize: 0.01}},
105
+ {name: 'pivot size limits', settings: {pivot: new Vector3(0, 0.5, 0.5), maxParticleSize: 0.5}},
106
+ ];
107
+
108
+ const TILE: Axis[] = [
109
+ NONE,
110
+ {name: 'sheet', settings: {uTileCount: 4, vTileCount: 2}},
111
+ {name: 'sheet blend', settings: {uTileCount: 4, vTileCount: 2, blendTiles: true}},
112
+ ];
113
+ const MAP: Axis[] = [NONE, {name: 'map', settings: {texture: TEXTURE}}];
114
+ const SOFT: Axis[] = [
115
+ NONE,
116
+ {name: 'soft', settings: {softParticles: true}, context: {depth: {packed: false}}},
117
+ {name: 'soft packed', settings: {softParticles: true}, context: {depth: {packed: true}}},
118
+ ];
119
+ const ALPHA_TEST: Axis[] = [NONE, {name: 'alpha test', settings: {materialAlphaTest: 0.5}}];
120
+ const FOG: Axis[] = [
121
+ NONE,
122
+ {name: 'fog', settings: {applyFog: true}},
123
+ {name: 'fog additive', settings: {applyFog: true, materialBlendMode: Constants.ALPHA_ADD}},
124
+ ];
125
+
126
+ /** Texture effects (`chunks/surface.ts`), alone and together, on both UV channels. */
127
+ const SURFACE: Axis[] = (() => {
128
+ const uv0 = normalizeMapUV({tiling: [2, 2]});
129
+ const uv1 = normalizeMapUV({channel: 1, tiling: [2, 2]});
130
+ const distortion0 = normalizeUVDistortion({map: TEXTURE});
131
+ const distortion1 = normalizeUVDistortion({map: TEXTURE, mapUV: {channel: 1}});
132
+ const gradient = normalizeGradientMap({ramp: TEXTURE});
133
+ const erosion = normalizeErosion({});
134
+ const erosionMask0 = normalizeErosion({mask: TEXTURE});
135
+ const erosionMask1 = normalizeErosion({mask: TEXTURE, maskUV: {channel: 1}});
136
+ return [
137
+ {name: 'map transform', settings: {mapUV: uv0}},
138
+ {name: 'map transform uv1', settings: {mapUV: uv1}},
139
+ {name: 'gradient map', settings: {gradientMap: gradient}},
140
+ {name: 'erosion', settings: {erosion}},
141
+ {name: 'erosion mask', settings: {erosion: erosionMask0}},
142
+ {name: 'erosion mask uv1', settings: {erosion: erosionMask1}},
143
+ {name: 'distortion', settings: {uvDistortion: distortion0}},
144
+ {name: 'distortion uv1', settings: {uvDistortion: distortion1}},
145
+ {
146
+ name: 'map transform distortion gradient erosion mask',
147
+ settings: {mapUV: uv0, uvDistortion: distortion0, gradientMap: gradient, erosion: erosionMask0},
148
+ },
149
+ {
150
+ name: 'map transform uv1 distortion gradient erosion',
151
+ settings: {mapUV: uv1, uvDistortion: distortion0, gradientMap: gradient, erosion},
152
+ },
153
+ {
154
+ name: 'every effect on uv1',
155
+ settings: {mapUV: uv1, uvDistortion: distortion1, gradientMap: gradient, erosion: erosionMask1},
156
+ },
157
+ ];
158
+ })();
159
+
160
+ /** The targets the texture effects are tried on: one per vertex shader and fragment family. */
161
+ const SURFACE_TARGETS = [
162
+ 'billboard',
163
+ 'stretched',
164
+ 'beam',
165
+ 'mesh',
166
+ 'mesh unlit',
167
+ 'mesh unlit rim mix',
168
+ 'mesh reflecting',
169
+ 'trail',
170
+ ];
171
+
172
+ /** A mesh batch whose geometry has a second UV set. */
173
+ const MESH_UV2: Axis = {name: 'uv2', context: {meshUV2: true}};
174
+
175
+ /** Soft, alpha-tested and fogged at once, to try the effects with the rest of the fragment. */
176
+ const EVERYTHING_ELSE: Axis = {
177
+ name: 'soft packed alpha test fog additive',
178
+ settings: {softParticles: true, materialAlphaTest: 0.5, applyFog: true, materialBlendMode: Constants.ALPHA_ADD},
179
+ context: {depth: {packed: true}},
180
+ };
181
+
182
+ /**
183
+ * 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, and the texture
185
+ * effects alone and together on each shader family. What a shader validator compiles, and what
186
+ * an application can warm the shader cache with before its first frame (Unity's
187
+ * ShaderVariantCollection). Built by {@link shaderProgram}, so a variant here is a material a
188
+ * batch would build.
189
+ */
190
+ export function shaderVariants(): ShaderVariant[] {
191
+ const seen = new Set<string>();
192
+ const out: ShaderVariant[] = [];
193
+ const add = (axes: Axis[]) => {
194
+ const settings = bare(RenderMode.BillBoard);
195
+ const context: ShaderContext = {depth: null, envAtlasReady: false, meshUV2: false};
196
+ for (const axis of axes) {
197
+ Object.assign(settings, axis.settings);
198
+ Object.assign(context, axis.context);
199
+ }
200
+ const program = shaderProgram(settings, context);
201
+ const key = program.name + ' ' + program.defines.join(',');
202
+ if (seen.has(key)) {
203
+ return;
204
+ }
205
+ seen.add(key);
206
+ const features = axes.map((axis) => axis.name).filter((name) => name !== '');
207
+ out.push({name: `${features.join(', ')} [${program.defines.join(',') || 'none'}]`, program});
208
+ };
209
+
210
+ for (const target of TARGETS)
211
+ for (const quad of QUAD)
212
+ for (const tile of TILE)
213
+ for (const map of MAP)
214
+ for (const soft of SOFT)
215
+ for (const alphaTest of ALPHA_TEST)
216
+ for (const fog of FOG) add([target, quad, tile, map, soft, alphaTest, fog]);
217
+
218
+ for (const target of TARGETS.filter((t) => SURFACE_TARGETS.includes(t.name)))
219
+ for (const uv2 of target.settings?.renderMode === RenderMode.Mesh ? [NONE, MESH_UV2] : [NONE])
220
+ for (const surface of SURFACE)
221
+ for (const map of MAP)
222
+ for (const tile of [NONE, TILE[2]])
223
+ for (const rest of [NONE, EVERYTHING_ELSE]) add([target, uv2, surface, map, tile, rest]);
224
+ return out;
225
+ }
@@ -44,7 +44,8 @@ float quarksFogFactor() {
44
44
  float fogDensity = vFogInfos.w;
45
45
  float fogDistance = length(vFogDistance);
46
46
  if (vFogInfos.x == 3.0) {
47
- fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);
47
+ // A zero-width band is a cut at its distance, not a division by zero.
48
+ fogCoeff = (fogEnd - fogDistance) / max(fogEnd - fogStart, 1e-6);
48
49
  } else if (vFogInfos.x == 1.0) {
49
50
  fogCoeff = exp(-fogDistance * fogDensity);
50
51
  } else if (vFogInfos.x == 2.0) {
@@ -101,7 +102,8 @@ fn quarksFog(rgb: vec3f, fogDistanceVector: vec3f) -> vec3f {
101
102
  let fogDensity = uniforms.vFogInfos.w;
102
103
  let fogDistance = length(fogDistanceVector);
103
104
  if (uniforms.vFogInfos.x == 3.0) {
104
- fogCoeff = (fogEnd - fogDistance) / (fogEnd - fogStart);
105
+ // A zero-width band is a cut at its distance, not a division by zero.
106
+ fogCoeff = (fogEnd - fogDistance) / max(fogEnd - fogStart, 1e-6);
105
107
  } else if (uniforms.vFogInfos.x == 1.0) {
106
108
  fogCoeff = exp(-fogDistance * fogDensity);
107
109
  } else if (uniforms.vFogInfos.x == 2.0) {
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The flipbook (sprite sheet) cell for the sprite, stretched and mesh vertex
3
+ * shaders, in both shader languages: Unity's Texture Sheet Animation, Unreal's
4
+ * SubUV.
5
+ *
6
+ * `uvTile` is the particle's frame, counted along the rows from the top-left
7
+ * cell; `tileCountX` and `tileCountY` are the sheet's columns and rows. The
8
+ * frame is wrapped into the sheet, so a frame past the last cell starts over
9
+ * and a negative one counts back from the end, in both languages alike (the
10
+ * WGSL `%` truncates where GLSL's `mod` floors, so the wrap is spelled out).
11
+ * Under `TILE_BLEND` the next frame, wrapped the same way, is passed with the
12
+ * fraction between the two, for the fragment to cross-fade (Unity's flipbook
13
+ * blending).
14
+ */
15
+
16
+ /**
17
+ * The cell of `frame` on a sheet of `columns` × `rows`, counted along the rows
18
+ * from the top-left cell and wrapped into the sheet, as the shaders count it.
19
+ * For a host that lays its cells out on the CPU, such as the trail.
20
+ */
21
+ export function sheetCell(frame: number, columns: number, rows: number): {column: number; row: number} {
22
+ const total = columns * rows;
23
+ const tile = wrap(Math.floor(frame), total);
24
+ return {column: wrap(tile, columns), row: Math.floor(tile / columns)};
25
+ }
26
+
27
+ /** A floored modulo, as GLSL's `mod`: never negative for a positive divisor. */
28
+ function wrap(value: number, divisor: number): number {
29
+ return value - divisor * Math.floor(value / divisor);
30
+ }
31
+
32
+ /** Vertex-side declarations, GLSL: the sheet's size and the cell varyings. */
33
+ export const sheetVertexDeclarationGLSL = /* glsl */ `
34
+ #ifdef UV_TILE
35
+ uniform float tileCountX;
36
+ uniform float tileCountY;
37
+ #endif
38
+ #ifdef TILE_BLEND
39
+ varying vec2 vUV2;
40
+ varying float vTileBlend;
41
+ #endif
42
+ `;
43
+
44
+ /**
45
+ * Writes `vUV` (and the blend varyings) from the quad UV expression `uv` and the
46
+ * frame `uvTile`, GLSL.
47
+ */
48
+ export function sheetVertexGLSL(uv: string): string {
49
+ return /* glsl */ `
50
+ #ifdef UV_TILE
51
+ {
52
+ vec2 tc = vec2(tileCountX, tileCountY);
53
+ float tileTotal = tc.x * tc.y;
54
+ float baseTile = mod(floor(uvTile), tileTotal);
55
+ vec2 tileUV = vec2(mod(baseTile, tc.x), tc.y - 1.0 - floor(baseTile / tc.x)) / tc;
56
+ vUV = ${uv} / tc + tileUV;
57
+ #ifdef TILE_BLEND
58
+ float nextTile = mod(baseTile + 1.0, tileTotal);
59
+ vec2 nextUV = vec2(mod(nextTile, tc.x), tc.y - 1.0 - floor(nextTile / tc.x)) / tc;
60
+ vUV2 = ${uv} / tc + nextUV;
61
+ vTileBlend = fract(uvTile);
62
+ #endif
63
+ }
64
+ #else
65
+ vUV = ${uv};
66
+ #endif
67
+ `;
68
+ }
69
+
70
+ /** Vertex-side declarations, WGSL: the sheet's size and the cell varyings. */
71
+ export const sheetVertexDeclarationWGSL = /* wgsl */ `
72
+ #ifdef UV_TILE
73
+ uniform tileCountX: f32;
74
+ uniform tileCountY: f32;
75
+
76
+ // A floored modulo, as GLSL's mod: never negative for a positive divisor.
77
+ fn quarksWrap(value: f32, divisor: f32) -> f32 {
78
+ return value - divisor * floor(value / divisor);
79
+ }
80
+ #endif
81
+ #ifdef TILE_BLEND
82
+ varying vUV2: vec2f;
83
+ varying vTileBlend: f32;
84
+ #endif
85
+ `;
86
+
87
+ /**
88
+ * Writes `vertexOutputs.vUV` (and the blend varyings) from the quad UV
89
+ * expression `uv` and the frame `vertexInputs.uvTile`, WGSL.
90
+ */
91
+ export function sheetVertexWGSL(uv: string): string {
92
+ return /* wgsl */ `
93
+ #ifdef UV_TILE
94
+ {
95
+ let tc = vec2f(uniforms.tileCountX, uniforms.tileCountY);
96
+ let tileTotal = tc.x * tc.y;
97
+ let baseTile = quarksWrap(floor(vertexInputs.uvTile), tileTotal);
98
+ let tileUV = vec2f(quarksWrap(baseTile, tc.x), tc.y - 1.0 - floor(baseTile / tc.x)) / tc;
99
+ vertexOutputs.vUV = ${uv} / tc + tileUV;
100
+ #ifdef TILE_BLEND
101
+ let nextTile = quarksWrap(baseTile + 1.0, tileTotal);
102
+ let nextUV = vec2f(quarksWrap(nextTile, tc.x), tc.y - 1.0 - floor(nextTile / tc.x)) / tc;
103
+ vertexOutputs.vUV2 = ${uv} / tc + nextUV;
104
+ vertexOutputs.vTileBlend = fract(vertexInputs.uvTile);
105
+ #endif
106
+ }
107
+ #else
108
+ vertexOutputs.vUV = ${uv};
109
+ #endif
110
+ `;
111
+ }