babylon.quarks 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1194 -8
- package/README.md +13 -9
- package/dist/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.cts +10108 -0
- package/dist/types/index.d.ts +5794 -998
- package/package.json +18 -7
- package/src/BatchedRenderer.ts +274 -156
- package/src/EffectPool.ts +94 -12
- package/src/MeshSurfaceEmitter.ts +50 -14
- package/src/ParticleEmitter.ts +75 -17
- package/src/ParticleSystem.ts +981 -2066
- package/src/QuarksLoader.ts +666 -194
- package/src/QuarksPrefab.ts +367 -73
- package/src/QuarksSerializer.ts +512 -0
- package/src/QuarksUtil.ts +77 -7
- package/src/SpriteBatch.ts +127 -252
- package/src/TrailBatch.ts +545 -348
- package/src/VFXBatch.ts +238 -596
- package/src/batchKey.ts +239 -0
- package/src/batchSettings.ts +546 -0
- package/src/effectDocumentJSON.ts +261 -0
- package/src/effectResources.ts +76 -0
- package/src/envAtlas.ts +126 -18
- package/src/geometryUtil.ts +1 -1
- package/src/index.ts +69 -35
- package/src/nodeId.ts +36 -0
- package/src/shaderProgram.ts +419 -0
- package/src/shaderVariants.ts +245 -0
- package/src/shaders/chunks/blend.ts +31 -0
- package/src/shaders/chunks/fog.ts +12 -5
- package/src/shaders/chunks/sheet.ts +111 -0
- package/src/shaders/chunks/soft.ts +85 -0
- package/src/shaders/chunks/surface.ts +12 -2
- package/src/shaders/local_particle_physics_vert.glsl.ts +14 -28
- package/src/shaders/local_particle_physics_vert.wgsl.ts +16 -29
- package/src/shaders/particle_frag.glsl.ts +5 -22
- package/src/shaders/particle_frag.wgsl.ts +5 -23
- package/src/shaders/particle_physics_frag.glsl.ts +16 -26
- package/src/shaders/particle_physics_frag.wgsl.ts +14 -26
- package/src/shaders/particle_vert.glsl.ts +5 -27
- package/src/shaders/particle_vert.wgsl.ts +5 -26
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +7 -33
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +7 -33
- package/src/shaders/trail_frag.glsl.ts +13 -0
- package/src/shaders/trail_frag.wgsl.ts +13 -0
- package/src/shaders/trail_vert.glsl.ts +45 -36
- package/src/shaders/trail_vert.wgsl.ts +46 -40
- package/src/softDepth.ts +136 -0
- package/src/system/BehaviorPlan.ts +85 -0
- package/src/system/BoundsTracker.ts +181 -0
- package/src/system/EmissionController.ts +358 -0
- package/src/system/EventDispatcher.ts +60 -0
- package/src/system/ParticleSpawner.ts +274 -0
- package/src/system/SimulationClock.ts +130 -0
- package/src/system/geometry.ts +104 -0
- package/src/system/integrate.ts +428 -0
- package/src/system/materialResolve.ts +165 -0
- package/src/system/normalize.ts +100 -0
- package/src/system/serialization.ts +422 -0
- package/src/system/settingsSchema.ts +339 -0
- package/src/system/types.ts +449 -0
- package/src/textureSource.ts +30 -0
- package/src/trailSettings.ts +54 -0
- package/src/BatchedParticleRenderer.ts +0 -3
- package/src/materials/ParticleMaterials.ts +0 -23
- package/src/materials/index.ts +0 -1
- package/src/shaders/chunks/index.ts +0 -1
- package/src/shaders/chunks/register-shader-chunks.ts +0 -32
- package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
- package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
- package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
- package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
- package/src/shaders/index.ts +0 -9
- package/src/shaders/local_particle_vert.glsl.ts +0 -50
|
@@ -0,0 +1,245 @@
|
|
|
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
|
+
/** 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
|
+
|
|
141
|
+
/** Texture effects (`chunks/surface.ts`), alone and together, on both UV channels. */
|
|
142
|
+
const SURFACE: Axis[] = (() => {
|
|
143
|
+
const uv0 = normalizeMapUV({tiling: [2, 2]});
|
|
144
|
+
const uv1 = normalizeMapUV({channel: 1, tiling: [2, 2]});
|
|
145
|
+
const distortion0 = normalizeUVDistortion({map: TEXTURE});
|
|
146
|
+
const distortion1 = normalizeUVDistortion({map: TEXTURE, mapUV: {channel: 1}});
|
|
147
|
+
const gradient = normalizeGradientMap({ramp: TEXTURE});
|
|
148
|
+
const erosion = normalizeErosion({});
|
|
149
|
+
const erosionMask0 = normalizeErosion({mask: TEXTURE});
|
|
150
|
+
const erosionMask1 = normalizeErosion({mask: TEXTURE, maskUV: {channel: 1}});
|
|
151
|
+
return [
|
|
152
|
+
{name: 'map transform', settings: {mapUV: uv0}},
|
|
153
|
+
{name: 'map transform uv1', settings: {mapUV: uv1}},
|
|
154
|
+
{name: 'gradient map', settings: {gradientMap: gradient}},
|
|
155
|
+
{name: 'erosion', settings: {erosion}},
|
|
156
|
+
{name: 'erosion mask', settings: {erosion: erosionMask0}},
|
|
157
|
+
{name: 'erosion mask uv1', settings: {erosion: erosionMask1}},
|
|
158
|
+
{name: 'distortion', settings: {uvDistortion: distortion0}},
|
|
159
|
+
{name: 'distortion uv1', settings: {uvDistortion: distortion1}},
|
|
160
|
+
{
|
|
161
|
+
name: 'map transform distortion gradient erosion mask',
|
|
162
|
+
settings: {mapUV: uv0, uvDistortion: distortion0, gradientMap: gradient, erosion: erosionMask0},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'map transform uv1 distortion gradient erosion',
|
|
166
|
+
settings: {mapUV: uv1, uvDistortion: distortion0, gradientMap: gradient, erosion},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'every effect on uv1',
|
|
170
|
+
settings: {mapUV: uv1, uvDistortion: distortion1, gradientMap: gradient, erosion: erosionMask1},
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
})();
|
|
174
|
+
|
|
175
|
+
/** The targets the texture effects are tried on: one per vertex shader and fragment family. */
|
|
176
|
+
const SURFACE_TARGETS = [
|
|
177
|
+
'billboard',
|
|
178
|
+
'stretched',
|
|
179
|
+
'beam',
|
|
180
|
+
'mesh',
|
|
181
|
+
'mesh unlit',
|
|
182
|
+
'mesh unlit rim mix',
|
|
183
|
+
'mesh reflecting',
|
|
184
|
+
'trail',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
/** A mesh batch whose geometry has a second UV set. */
|
|
188
|
+
const MESH_UV2: Axis = {name: 'uv2', context: {meshUV2: true}};
|
|
189
|
+
|
|
190
|
+
/** Soft, alpha-tested and fogged at once, to try the effects with the rest of the fragment. */
|
|
191
|
+
const EVERYTHING_ELSE: Axis = {
|
|
192
|
+
name: 'soft packed alpha test fog one-one',
|
|
193
|
+
settings: {softParticles: true, materialAlphaTest: 0.5, applyFog: true, materialBlendMode: Constants.ALPHA_ONEONE},
|
|
194
|
+
context: {depth: {packed: true}},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Every shader program the library can compile, one per distinct set of defines: every render
|
|
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
|
|
202
|
+
* an application can warm the shader cache with before its first frame (Unity's
|
|
203
|
+
* ShaderVariantCollection). Built by {@link shaderProgram}, so a variant here is a material a
|
|
204
|
+
* batch would build.
|
|
205
|
+
*/
|
|
206
|
+
export function shaderVariants(): ShaderVariant[] {
|
|
207
|
+
const seen = new Set<string>();
|
|
208
|
+
const out: ShaderVariant[] = [];
|
|
209
|
+
const add = (axes: Axis[]) => {
|
|
210
|
+
const settings = bare(RenderMode.BillBoard);
|
|
211
|
+
const context: ShaderContext = {depth: null, envAtlasReady: false, meshUV2: false};
|
|
212
|
+
for (const axis of axes) {
|
|
213
|
+
Object.assign(settings, axis.settings);
|
|
214
|
+
Object.assign(context, axis.context);
|
|
215
|
+
}
|
|
216
|
+
const program = shaderProgram(settings, context);
|
|
217
|
+
const key = program.name + ' ' + program.defines.join(',');
|
|
218
|
+
if (seen.has(key)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
seen.add(key);
|
|
222
|
+
const features = axes.map((axis) => axis.name).filter((name) => name !== '');
|
|
223
|
+
out.push({name: `${features.join(', ')} [${program.defines.join(',') || 'none'}]`, program});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
for (const target of TARGETS)
|
|
227
|
+
for (const quad of QUAD)
|
|
228
|
+
for (const tile of TILE)
|
|
229
|
+
for (const map of MAP)
|
|
230
|
+
for (const soft of SOFT)
|
|
231
|
+
for (const alphaTest of ALPHA_TEST)
|
|
232
|
+
for (const fog of FOG) add([target, quad, tile, map, soft, alphaTest, fog]);
|
|
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
|
+
|
|
238
|
+
for (const target of TARGETS.filter((t) => SURFACE_TARGETS.includes(t.name)))
|
|
239
|
+
for (const uv2 of target.settings?.renderMode === RenderMode.Mesh ? [NONE, MESH_UV2] : [NONE])
|
|
240
|
+
for (const surface of SURFACE)
|
|
241
|
+
for (const map of MAP)
|
|
242
|
+
for (const tile of [NONE, TILE[2]])
|
|
243
|
+
for (const rest of [NONE, EVERYTHING_ELSE]) add([target, uv2, surface, map, tile, rest]);
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
@@ -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. */
|
|
@@ -44,7 +45,8 @@ float quarksFogFactor() {
|
|
|
44
45
|
float fogDensity = vFogInfos.w;
|
|
45
46
|
float fogDistance = length(vFogDistance);
|
|
46
47
|
if (vFogInfos.x == 3.0) {
|
|
47
|
-
|
|
48
|
+
// A zero-width band is a cut at its distance, not a division by zero.
|
|
49
|
+
fogCoeff = (fogEnd - fogDistance) / max(fogEnd - fogStart, 1e-6);
|
|
48
50
|
} else if (vFogInfos.x == 1.0) {
|
|
49
51
|
fogCoeff = exp(-fogDistance * fogDensity);
|
|
50
52
|
} else if (vFogInfos.x == 2.0) {
|
|
@@ -61,8 +63,10 @@ export function fogFragmentGLSL(rgb: string): string {
|
|
|
61
63
|
#ifdef FOG
|
|
62
64
|
{
|
|
63
65
|
float fogFactor = quarksFogFactor();
|
|
64
|
-
#
|
|
66
|
+
#if defined(FOG_ADDITIVE)
|
|
65
67
|
${rgb} *= fogFactor;
|
|
68
|
+
#elif defined(FOG_MULTIPLY)
|
|
69
|
+
${rgb} = mix(vec3(1.0), ${rgb}, fogFactor);
|
|
66
70
|
#else
|
|
67
71
|
${rgb} = mix(vFogColor, ${rgb}, fogFactor);
|
|
68
72
|
#endif
|
|
@@ -101,15 +105,18 @@ fn quarksFog(rgb: vec3f, fogDistanceVector: vec3f) -> vec3f {
|
|
|
101
105
|
let fogDensity = uniforms.vFogInfos.w;
|
|
102
106
|
let fogDistance = length(fogDistanceVector);
|
|
103
107
|
if (uniforms.vFogInfos.x == 3.0) {
|
|
104
|
-
|
|
108
|
+
// A zero-width band is a cut at its distance, not a division by zero.
|
|
109
|
+
fogCoeff = (fogEnd - fogDistance) / max(fogEnd - fogStart, 1e-6);
|
|
105
110
|
} else if (uniforms.vFogInfos.x == 1.0) {
|
|
106
111
|
fogCoeff = exp(-fogDistance * fogDensity);
|
|
107
112
|
} else if (uniforms.vFogInfos.x == 2.0) {
|
|
108
113
|
fogCoeff = exp(-fogDistance * fogDistance * fogDensity * fogDensity);
|
|
109
114
|
}
|
|
110
115
|
let fogFactor = clamp(fogCoeff, 0.0, 1.0);
|
|
111
|
-
#
|
|
116
|
+
#if defined(FOG_ADDITIVE)
|
|
112
117
|
return rgb * fogFactor;
|
|
118
|
+
#elif defined(FOG_MULTIPLY)
|
|
119
|
+
return mix(vec3f(1.0), rgb, fogFactor);
|
|
113
120
|
#else
|
|
114
121
|
return mix(uniforms.vFogColor, rgb, fogFactor);
|
|
115
122
|
#endif
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft particles for the particle shaders, in both shader languages.
|
|
3
|
+
*
|
|
4
|
+
* The fragment reads the scene depth behind it from `depthTexture`, turns it into view depth with
|
|
5
|
+
* the coefficients in `projParams` (see `softDepthParams`), and fades out over the distance in
|
|
6
|
+
* `softParams` as it nears the scene, as Unity's soft particles and Unreal's Depth Fade do. The
|
|
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.
|
|
9
|
+
*
|
|
10
|
+
* `SOFT_DEPTH_PACKED` reads a depth packed into four 8-bit channels, as a Babylon
|
|
11
|
+
* `DepthRenderer` of type `UNSIGNED_BYTE` writes it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Fragment-side declaration and depth decode, GLSL. */
|
|
15
|
+
export const softFragmentDeclarationGLSL = /* glsl */ `
|
|
16
|
+
#ifdef SOFT_PARTICLES
|
|
17
|
+
uniform sampler2D depthTexture;
|
|
18
|
+
uniform vec2 softParams;
|
|
19
|
+
uniform vec4 projParams;
|
|
20
|
+
varying vec4 projPosition;
|
|
21
|
+
varying float linearDepth;
|
|
22
|
+
|
|
23
|
+
float quarksSceneDepth(vec4 texel) {
|
|
24
|
+
#ifdef SOFT_DEPTH_PACKED
|
|
25
|
+
float d = dot(texel, vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0));
|
|
26
|
+
#else
|
|
27
|
+
float d = texel.r;
|
|
28
|
+
#endif
|
|
29
|
+
float denominator = projParams.z * d + projParams.w;
|
|
30
|
+
float depth = abs(denominator) > 1e-8 ? abs((projParams.x * d + projParams.y) / denominator) : 0.0;
|
|
31
|
+
// Nothing drawn there reads as infinitely far.
|
|
32
|
+
return depth > 0.0 ? depth : 1e30;
|
|
33
|
+
}
|
|
34
|
+
#endif
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
/** Fades the alpha of the vec4 lvalue `target` as the fragment nears the scene behind it, GLSL. */
|
|
38
|
+
export function softFragmentGLSL(target: string): string {
|
|
39
|
+
return /* glsl */ `
|
|
40
|
+
#ifdef SOFT_PARTICLES
|
|
41
|
+
{
|
|
42
|
+
vec2 softUV = 0.5 * (projPosition.xy / projPosition.w) + 0.5;
|
|
43
|
+
float sceneDepth = quarksSceneDepth(texture2D(depthTexture, softUV));
|
|
44
|
+
${target}.a *= clamp(softParams.y * ((sceneDepth - softParams.x) - linearDepth), 0.0, 1.0);
|
|
45
|
+
}
|
|
46
|
+
#endif
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Fragment-side declaration and depth decode, WGSL. */
|
|
51
|
+
export const softFragmentDeclarationWGSL = /* wgsl */ `
|
|
52
|
+
#ifdef SOFT_PARTICLES
|
|
53
|
+
var depthTextureSampler: sampler;
|
|
54
|
+
var depthTexture: texture_2d<f32>;
|
|
55
|
+
uniform softParams: vec2f;
|
|
56
|
+
uniform projParams: vec4f;
|
|
57
|
+
varying projPosition: vec4f;
|
|
58
|
+
varying linearDepth: f32;
|
|
59
|
+
|
|
60
|
+
fn quarksSceneDepth(texel: vec4f) -> f32 {
|
|
61
|
+
#ifdef SOFT_DEPTH_PACKED
|
|
62
|
+
let d = dot(texel, vec4f(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0));
|
|
63
|
+
#else
|
|
64
|
+
let d = texel.r;
|
|
65
|
+
#endif
|
|
66
|
+
let denominator = uniforms.projParams.z * d + uniforms.projParams.w;
|
|
67
|
+
let depth = select(0.0, abs((uniforms.projParams.x * d + uniforms.projParams.y) / denominator), abs(denominator) > 1e-8);
|
|
68
|
+
// Nothing drawn there reads as infinitely far.
|
|
69
|
+
return select(1e30, depth, depth > 0.0);
|
|
70
|
+
}
|
|
71
|
+
#endif
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
/** Fades the alpha of the `var` vec4 `target` as the fragment nears the scene behind it, WGSL. */
|
|
75
|
+
export function softFragmentWGSL(target: string): string {
|
|
76
|
+
return /* wgsl */ `
|
|
77
|
+
#ifdef SOFT_PARTICLES
|
|
78
|
+
{
|
|
79
|
+
let softUV = 0.5 * (fragmentInputs.projPosition.xy / fragmentInputs.projPosition.w) + 0.5;
|
|
80
|
+
let sceneDepth = quarksSceneDepth(textureSample(depthTexture, depthTextureSampler, softUV));
|
|
81
|
+
${target}.a *= clamp(uniforms.softParams.y * ((sceneDepth - uniforms.softParams.x) - fragmentInputs.linearDepth), 0.0, 1.0);
|
|
82
|
+
}
|
|
83
|
+
#endif
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
@@ -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. */
|
|
@@ -108,6 +108,7 @@ export function surfaceColorGLSL(tileBlend: boolean): string {
|
|
|
108
108
|
baseColor.a = 1.0;
|
|
109
109
|
float erosionMaskValue = 1.0;
|
|
110
110
|
#endif
|
|
111
|
+
#if defined(USE_MAP) || defined(GRADIENT_MAP)
|
|
111
112
|
#ifdef USE_MAP
|
|
112
113
|
vec4 texColor = texture2D(map, mapUV);
|
|
113
114
|
${
|
|
@@ -130,6 +131,10 @@ export function surfaceColorGLSL(tileBlend: boolean): string {
|
|
|
130
131
|
#endif`
|
|
131
132
|
: ''
|
|
132
133
|
}
|
|
134
|
+
#else
|
|
135
|
+
// No map: the ramp is looked up at white, its far end.
|
|
136
|
+
vec4 texColor = vec4(1.0);
|
|
137
|
+
#endif
|
|
133
138
|
#ifdef GRADIENT_MAP
|
|
134
139
|
float rampAt = clamp(dot(texColor, gradientMapChannel), 0.0, 1.0);
|
|
135
140
|
vec4 ramp = texture2D(gradientMap, vec2(mix(gradientMapRange.x, gradientMapRange.y, rampAt), 0.5));
|
|
@@ -221,6 +226,7 @@ export function surfaceColorWGSL(tileBlend: boolean): string {
|
|
|
221
226
|
baseColor.a = 1.0;
|
|
222
227
|
var erosionMaskValue = 1.0;
|
|
223
228
|
#endif
|
|
229
|
+
#if defined(USE_MAP) || defined(GRADIENT_MAP)
|
|
224
230
|
#ifdef USE_MAP
|
|
225
231
|
var texColor = textureSample(map, mapSampler, mapUV);
|
|
226
232
|
${
|
|
@@ -243,6 +249,10 @@ export function surfaceColorWGSL(tileBlend: boolean): string {
|
|
|
243
249
|
#endif`
|
|
244
250
|
: ''
|
|
245
251
|
}
|
|
252
|
+
#else
|
|
253
|
+
// No map: the ramp is looked up at white, its far end.
|
|
254
|
+
var texColor = vec4f(1.0);
|
|
255
|
+
#endif
|
|
246
256
|
#ifdef GRADIENT_MAP
|
|
247
257
|
let rampAt = clamp(dot(texColor, uniforms.gradientMapChannel), 0.0, 1.0);
|
|
248
258
|
let ramp = textureSample(gradientMap, gradientMapSampler, vec2f(mix(uniforms.gradientMapRange.x, uniforms.gradientMapRange.y, rampAt), 0.5));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {fogVertexDeclarationGLSL, fogVertexGLSL} from './chunks/fog';
|
|
2
|
+
import {sheetVertexDeclarationGLSL, sheetVertexGLSL} from './chunks/sheet';
|
|
2
3
|
|
|
3
4
|
export default /* glsl */ `
|
|
4
5
|
attribute vec3 position;
|
|
@@ -13,11 +14,7 @@ attribute float uvTile;
|
|
|
13
14
|
uniform mat4 world;
|
|
14
15
|
uniform mat4 view;
|
|
15
16
|
uniform mat4 projection;
|
|
16
|
-
|
|
17
|
-
#ifdef UV_TILE
|
|
18
|
-
uniform float tileCountX;
|
|
19
|
-
uniform float tileCountY;
|
|
20
|
-
#endif
|
|
17
|
+
${sheetVertexDeclarationGLSL}
|
|
21
18
|
|
|
22
19
|
varying vec2 vUV;
|
|
23
20
|
varying vec4 vColor;
|
|
@@ -32,11 +29,6 @@ varying vec2 vUV1;
|
|
|
32
29
|
varying vec3 vNormal;
|
|
33
30
|
varying vec3 vWorldPos;
|
|
34
31
|
|
|
35
|
-
#ifdef TILE_BLEND
|
|
36
|
-
varying vec2 vUV2;
|
|
37
|
-
varying float vTileBlend;
|
|
38
|
-
#endif
|
|
39
|
-
|
|
40
32
|
#ifdef SOFT_PARTICLES
|
|
41
33
|
varying vec4 projPosition;
|
|
42
34
|
varying float linearDepth;
|
|
@@ -53,7 +45,15 @@ ${fogVertexDeclarationGLSL}
|
|
|
53
45
|
void main() {
|
|
54
46
|
vec3 scaledPosition = position * size;
|
|
55
47
|
vec3 rotatedPosition = applyQuaternion(scaledPosition, rotation);
|
|
56
|
-
|
|
48
|
+
// A normal scales by the inverse of the size, so a stretched mesh keeps its normals
|
|
49
|
+
// perpendicular; a flat size counts as a sliver rather than dividing by zero.
|
|
50
|
+
vec3 safeSize = vec3(
|
|
51
|
+
abs(size.x) > 1e-6 ? size.x : 1e-6,
|
|
52
|
+
abs(size.y) > 1e-6 ? size.y : 1e-6,
|
|
53
|
+
abs(size.z) > 1e-6 ? size.z : 1e-6);
|
|
54
|
+
// A mirrored size (an odd number of negative components) turns the winding inside out, so the
|
|
55
|
+
// normal is turned with it and the fragment's front-face test agrees with the geometry.
|
|
56
|
+
vec3 rotatedNormal = normalize(applyQuaternion(normal / safeSize, rotation)) * sign(safeSize.x * safeSize.y * safeSize.z);
|
|
57
57
|
vec3 localPos = rotatedPosition + offset;
|
|
58
58
|
|
|
59
59
|
vec4 worldPos = world * vec4(localPos, 1.0);
|
|
@@ -66,25 +66,11 @@ ${fogVertexGLSL('viewPos.xyz')}
|
|
|
66
66
|
|
|
67
67
|
#ifdef SOFT_PARTICLES
|
|
68
68
|
projPosition = gl_Position;
|
|
69
|
-
|
|
69
|
+
// View depth is +z ahead of a left-handed camera and -z ahead of a right-handed one.
|
|
70
|
+
linearDepth = abs(viewPos.z);
|
|
70
71
|
#endif
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
vec2 tc = vec2(tileCountX, tileCountY);
|
|
74
|
-
float baseTile = floor(uvTile);
|
|
75
|
-
float tileU = mod(baseTile, tc.x) / tc.x;
|
|
76
|
-
float tileV = 1.0 - floor(baseTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
77
|
-
vUV = uv / tc + vec2(tileU, tileV);
|
|
78
|
-
#ifdef TILE_BLEND
|
|
79
|
-
float nextTile = ceil(uvTile);
|
|
80
|
-
float nextU = mod(nextTile, tc.x) / tc.x;
|
|
81
|
-
float nextV = 1.0 - floor(nextTile / tc.x) / tc.y - 1.0 / tc.y;
|
|
82
|
-
vUV2 = uv / tc + vec2(nextU, nextV);
|
|
83
|
-
vTileBlend = fract(uvTile);
|
|
84
|
-
#endif
|
|
85
|
-
#else
|
|
86
|
-
vUV = uv;
|
|
87
|
-
#endif
|
|
73
|
+
${sheetVertexGLSL('uv')}
|
|
88
74
|
|
|
89
75
|
#ifdef UV1
|
|
90
76
|
#ifdef MESH_UV2
|